diff --git a/src/vs/workbench/contrib/void/browser/editCodeService.ts b/src/vs/workbench/contrib/void/browser/editCodeService.ts index 6bb52d7e..96e3bf6d 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeService.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeService.ts @@ -1124,8 +1124,8 @@ class EditCodeService extends Disposable implements IEditCodeService { return } - public async callBeforeStartApplying(opts: CallBeforeStartApplyingOpts) { - const uri = this._getURIBeforeStartApplying(opts) + public async callBeforeApplyOrEdit(givenURI: URI | 'current') { + const uri = this._uriOfGivenURI(givenURI) if (!uri) return await this._voidModelService.initializeModel(uri) await this._voidModelService.saveModel(uri) // save the URI @@ -1200,7 +1200,7 @@ class EditCodeService extends Disposable implements IEditCodeService { } - public instantlyApplyNewContent({ uri, newContent }: { uri: URI, newContent: string }) { + public instantlyRewriteFile({ uri, newContent }: { uri: URI, newContent: string }) { // start diffzone const res = this._startStreamingDiffZone({ uri, diff --git a/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts b/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts index f0712349..9e33fbd2 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts @@ -44,10 +44,10 @@ export interface IEditCodeService { processRawKeybindingText(keybindingStr: string): string; - callBeforeStartApplying(opts: CallBeforeStartApplyingOpts): Promise; + callBeforeApplyOrEdit(uri: URI | 'current'): Promise; startApplying(opts: StartApplyingOpts): [URI, Promise] | null; instantlyApplySearchReplaceBlocks(opts: { uri: URI; searchReplaceBlocks: string }): void; - instantlyApplyNewContent(opts: { uri: URI; newContent: string }): void; + instantlyRewriteFile(opts: { uri: URI; newContent: string }): void; addCtrlKZone(opts: AddCtrlKOpts): number | undefined; removeCtrlKZone(opts: { diffareaid: number }): void; diff --git a/src/vs/workbench/contrib/void/browser/extensionTransferService.ts b/src/vs/workbench/contrib/void/browser/extensionTransferService.ts index 08152c2d..05b7b47a 100644 --- a/src/vs/workbench/contrib/void/browser/extensionTransferService.ts +++ b/src/vs/workbench/contrib/void/browser/extensionTransferService.ts @@ -35,6 +35,7 @@ const extensionBlacklist = [ 'codeium.codeium', 'saoudrizwan.claude-dev', // cline 'rooveterinaryinc.roo-cline', // roo + 'supermaven.supermaven' // supermaven // 'github.copilot', ]; diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx index 2d397ecf..9e54b85d 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx @@ -3,14 +3,14 @@ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. *--------------------------------------------------------------------------------------*/ -import { useState, useEffect, useCallback } from 'react' +import { useState, useEffect, useCallback, useRef, Fragment } from 'react' import { useAccessor, useCommandBarState, useCommandBarURIListener, useSettingsState } from '../util/services.js' import { usePromise, useRefState } from '../util/helpers.js' import { isFeatureNameDisabled } from '../../../../common/voidSettingsTypes.js' import { URI } from '../../../../../../../base/common/uri.js' import { FileSymlink, LucideIcon, RotateCw, Terminal } from 'lucide-react' import { Check, X, Square, Copy, Play, } from 'lucide-react' -import { getBasename, ListableToolItem, ToolChildrenWrapper } from '../sidebar-tsx/SidebarChat.js' +import { getBasename, ListableToolItem, voidOpenFileFn, ToolChildrenWrapper } from '../sidebar-tsx/SidebarChat.js' import { PlacesType, VariantType } from 'react-tooltip' enum CopyButtonText { @@ -24,8 +24,9 @@ type IconButtonProps = { Icon: LucideIcon } -export const IconShell1 = ({ onClick, Icon, disabled, className, ...props }: IconButtonProps & React.ButtonHTMLAttributes) => ( - -) +} // export const IconShell2 = ({ onClick, title, Icon, disabled, className }: IconButtonProps) => ( @@ -108,7 +109,7 @@ export const JumpToFileButton = ({ uri, ...props }: { uri: URI | 'current' } & R { - commandService.executeCommand('vscode.open', uri, { preview: true }) + voidOpenFileFn(uri, accessor) }} {...tooltipPropsForApplyBlock({ tooltipName: 'Go to file' })} {...props} @@ -131,48 +132,41 @@ export const JumpToTerminalButton = ({ onClick }: { onClick: () => void }) => { // state persisted for duration of react only // TODO change this to use type `ChatThreads.applyBoxState[applyBoxId]` -const applyingURIOfApplyBoxIdRef: { current: { [applyBoxId: string]: URI | undefined } } = { current: {} } +const _applyingURIOfApplyBoxIdRef: { current: { [applyBoxId: string]: URI | undefined } } = { current: {} } const getUriBeingApplied = (applyBoxId: string) => { - return applyingURIOfApplyBoxIdRef.current[applyBoxId] ?? null + return _applyingURIOfApplyBoxIdRef.current[applyBoxId] ?? null } -export const useApplyButtonState = ({ applyBoxId, uri }: { applyBoxId: string, uri: URI | 'current' }) => { - - const settingsState = useSettingsState() - const isDisabled = !!isFeatureNameDisabled('Apply', settingsState) || !applyBoxId - +export const useApplyStreamState = ({ applyBoxId }: { applyBoxId: string }) => { const accessor = useAccessor() const voidCommandBarService = accessor.get('IVoidCommandBarService') - const [_, rerender] = useState(0) - const getStreamState = useCallback(() => { const uri = getUriBeingApplied(applyBoxId) if (!uri) return 'idle-no-changes' return voidCommandBarService.getStreamState(uri) }, [voidCommandBarService, applyBoxId]) + + const [currStreamStateRef, setStreamState] = useRefState(getStreamState()) + + const setApplying = useCallback((uri: URI | undefined) => { + _applyingURIOfApplyBoxIdRef.current[applyBoxId] = uri ?? undefined + setStreamState(getStreamState()) + }, [setStreamState, getStreamState, applyBoxId]) + // listen for stream updates on this box useCommandBarURIListener(useCallback((uri_) => { - const shouldUpdate = ( - getUriBeingApplied(applyBoxId)?.fsPath === uri_.fsPath - || (uri !== 'current' && uri.fsPath === uri_.fsPath) - ) - if (shouldUpdate) { - rerender(c => c + 1) + const uri = getUriBeingApplied(applyBoxId) + if (uri?.fsPath === uri_.fsPath) { + setStreamState(getStreamState()) } - }, [applyBoxId, uri])) - - const currStreamState = getStreamState() + }, [setStreamState, applyBoxId, getStreamState])) - return { - getStreamState, - isDisabled, - currStreamState, - } + return { currStreamStateRef, setApplying } } @@ -202,10 +196,24 @@ const tooltipPropsForApplyBlock = ({ tooltipName, color = undefined, position = 'data-tooltip-offset': offset, }) +export const useEditToolStreamState = ({ applyBoxId, uri }: { applyBoxId: string, uri: URI }) => { + const accessor = useAccessor() + const voidCommandBarService = accessor.get('IVoidCommandBarService') + const [streamState, setStreamState] = useState(voidCommandBarService.getStreamState(uri)) + // listen for stream updates on this box + useCommandBarURIListener(useCallback((uri_) => { + const shouldUpdate = uri.fsPath === uri_.fsPath + if (shouldUpdate) { setStreamState(voidCommandBarService.getStreamState(uri)) } + }, [voidCommandBarService, applyBoxId, uri])) + + return { streamState, } +} export const StatusIndicatorForApplyButton = ({ applyBoxId, uri }: { applyBoxId: string, uri: URI | 'current' } & React.HTMLAttributes) => { - const { currStreamState } = useApplyButtonState({ applyBoxId, uri }) + const { currStreamStateRef } = useApplyStreamState({ applyBoxId }) + const currStreamState = currStreamStateRef.current + const color = ( currStreamState === 'idle-no-changes' ? 'dark' : @@ -231,74 +239,88 @@ export const StatusIndicatorForApplyButton = ({ applyBoxId, uri }: { applyBoxId: } -export const ApplyButtonsHTML = ({ codeStr, applyBoxId, uri }: { codeStr: string, applyBoxId: string, uri: URI | 'current' }) => { +export const ApplyButtonsHTML = ({ + codeStr, + applyBoxId, + uri, +}: { + codeStr: string, + applyBoxId: string, +} & ({ + uri: URI | 'current'; +}) +) => { const accessor = useAccessor() const editCodeService = accessor.get('IEditCodeService') const metricsService = accessor.get('IMetricsService') - const { - currStreamState, - isDisabled, - getStreamState, - } = useApplyButtonState({ applyBoxId, uri }) + const settingsState = useSettingsState() + const isDisabled = !!isFeatureNameDisabled('Apply', settingsState) || !applyBoxId + + const { currStreamStateRef, setApplying } = useApplyStreamState({ applyBoxId }) + const onClickSubmit = useCallback(async () => { - if (isDisabled) return - if (getStreamState() === 'streaming') return - const opts = { + if (currStreamStateRef.current === 'streaming') return + + await editCodeService.callBeforeApplyOrEdit(uri) + + const [newApplyingUri, applyDonePromise] = editCodeService.startApplying({ from: 'ClickApply', applyStr: codeStr, uri: uri, startBehavior: 'reject-conflicts', - } as const - - await editCodeService.callBeforeStartApplying(opts) - const [newApplyingUri, applyDonePromise] = editCodeService.startApplying(opts) ?? [] + }) ?? [] + console.log('setting!!!', newApplyingUri) + setApplying(newApplyingUri) // catch any errors by interrupting the stream applyDonePromise?.catch(e => { const uri = getUriBeingApplied(applyBoxId) if (uri) editCodeService.interruptURIStreaming({ uri: uri }) }) - - applyingURIOfApplyBoxIdRef.current[applyBoxId] = newApplyingUri ?? undefined - - // rerender(c => c + 1) metricsService.capture('Apply Code', { length: codeStr.length }) // capture the length only - }, [isDisabled, getStreamState, editCodeService, codeStr, uri, applyBoxId, metricsService]) + + }, [setApplying, currStreamStateRef, editCodeService, codeStr, uri, applyBoxId, metricsService]) - const onInterrupt = useCallback(() => { - if (getStreamState() !== 'streaming') return + const onClickStop = useCallback(() => { + if (currStreamStateRef.current !== 'streaming') return const uri = getUriBeingApplied(applyBoxId) if (!uri) return editCodeService.interruptURIStreaming({ uri }) metricsService.capture('Stop Apply', {}) - }, [getStreamState, applyBoxId, editCodeService, metricsService]) + }, [currStreamStateRef, applyBoxId, editCodeService, metricsService]) const onAccept = useCallback(() => { const uri = getUriBeingApplied(applyBoxId) - if (uri) editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'accept', removeCtrlKs: false }) - }, [applyBoxId, editCodeService]) + if (uri) editCodeService.acceptOrRejectAllDiffAreas({ uri: uri, behavior: 'accept', removeCtrlKs: false }) + }, [uri, applyBoxId, editCodeService]) const onReject = useCallback(() => { const uri = getUriBeingApplied(applyBoxId) - if (uri) editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'reject', removeCtrlKs: false }) - }, [applyBoxId, editCodeService]) + if (uri) editCodeService.acceptOrRejectAllDiffAreas({ uri: uri, behavior: 'reject', removeCtrlKs: false }) + }, [uri, applyBoxId, editCodeService]) + + + const currStreamState = currStreamStateRef.current + console.log('currStreamState...', currStreamState) if (currStreamState === 'streaming') { return } - if (currStreamState === 'idle-no-changes') { + if (isDisabled) { + return null + } + + if (currStreamState === 'idle-no-changes') { return + + + + } +} + + + + + +export const EditToolButtonsHTML = ({ + codeStr, + applyBoxId, + uri, + type, +}: { + codeStr: string, + applyBoxId: string, +} & ({ + uri: URI, + type: 'edit_file' | 'rewrite_file' +}) +) => { + const accessor = useAccessor() + const editCodeService = accessor.get('IEditCodeService') + const metricsService = accessor.get('IMetricsService') + + const { streamState } = useEditToolStreamState({ applyBoxId, uri }) + const settingsState = useSettingsState() + + const isDisabled = !!isFeatureNameDisabled('Chat', settingsState) || !applyBoxId + + const onClickSubmit = useCallback(async () => { + await editCodeService.callBeforeApplyOrEdit(uri) + if (type === 'edit_file') { + editCodeService.instantlyApplySearchReplaceBlocks({ uri, searchReplaceBlocks: codeStr }) + } + else if (type === 'rewrite_file') { + editCodeService.instantlyRewriteFile({ uri, newContent: codeStr }) + } + }, [type, editCodeService, codeStr, uri, applyBoxId, metricsService]) + + const onAccept = useCallback(() => { + editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'accept', removeCtrlKs: false }) + }, [uri, applyBoxId, editCodeService]) + + const onReject = useCallback(() => { + editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'reject', removeCtrlKs: false }) + }, [uri, applyBoxId, editCodeService]) + + if (isDisabled) return null + + if (streamState === 'idle-no-changes') { + return + } + + if (streamState === 'idle-has-changes') { return <> { const accessor = useAccessor() const commandService = accessor.get('ICommandService') - const { currStreamState } = useApplyButtonState({ applyBoxId, uri }) + const { currStreamStateRef } = useApplyStreamState({ applyBoxId }) + const currStreamState = currStreamStateRef.current const name = uri !== 'current' ? @@ -348,7 +441,7 @@ export const BlockCodeApplyWrapper = ({ name={{getBasename(uri.fsPath)}} isSmall={true} showDot={false} - onClick={() => { commandService.executeCommand('vscode.open', uri, { preview: true }) }} + onClick={() => { voidOpenFileFn(uri, accessor) }} /> : {language} diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx index 3f6a9882..d2714d08 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx @@ -9,12 +9,12 @@ import { marked, MarkedToken, Token } from 'marked' import { convertToVscodeLang, detectLanguage } from '../../../../common/helpers/languageHelpers.js' import { BlockCodeApplyWrapper } from './ApplyBlockHoverButtons.js' import { useAccessor } from '../util/services.js' -import { ScrollType } from '../../../../../../../editor/common/editorCommon.js' import { URI } from '../../../../../../../base/common/uri.js' import { isAbsolute } from '../../../../../../../base/common/path.js' import { separateOutFirstLine } from '../../../../common/helpers/util.js' import { BlockCode } from '../util/inputs.js' import { CodespanLocationLink } from '../../../../common/chatThreadServiceTypes.js' +import { voidOpenFileFn } from '../sidebar-tsx/SidebarChat.js' export type ChatMessageLocation = { @@ -134,27 +134,10 @@ const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string const onClick = () => { + if (!link || !link.selection) return; - if (!link) return; - const selection = link.selection - - // open the file - commandService.executeCommand('vscode.open', link.uri).then(() => { - - // select the text - setTimeout(() => { - if (!selection) return; - - const editor = editorService.getActiveCodeEditor() - if (!editor) return; - - editor.setSelection(selection) - editor.revealRange(selection, ScrollType.Immediate) - - }, 50) // needed when document was just opened and needs to initialize - - }) - + // Use the updated voidOpenFileFn to open the file and handle selection + voidOpenFileFn(link.uri, accessor, [link.selection.startLineNumber, link.selection.endLineNumber]); } return { if (newApplyingUri) editCodeService.interruptCtrlKStreaming({ diffareaid }) }) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 47ea83c0..2b685a00 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -7,6 +7,7 @@ import React, { ButtonHTMLAttributes, FormEvent, FormHTMLAttributes, Fragment, K import { useAccessor, useChatThreadsState, useChatThreadsStreamState, useSettingsState, useActiveURI, useCommandBarState, useFullChatThreadsStreamState } from '../util/services.js'; +import { ScrollType } from '../../../../../../../editor/common/editorCommon.js'; import { ChatMarkdownRender, ChatMessageLocation, getApplyBoxId } from '../markdown/ChatMarkdownRender.js'; import { URI } from '../../../../../../../base/common/uri.js'; @@ -18,12 +19,13 @@ import { PastThreadsList } from './SidebarThreadSelector.js'; import { VOID_CTRL_L_ACTION_ID } from '../../../actionIDs.js'; import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js'; import { ChatMode, displayInfoOfProviderName, FeatureName, isFeatureNameDisabled } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js'; +import { ICommandService } from '../../../../../../../platform/commands/common/commands.js'; import { WarningBox } from '../void-settings-tsx/WarningBox.js'; import { getModelCapabilities, getIsReasoningEnabledState } from '../../../../common/modelCapabilities.js'; import { AlertTriangle, File, Ban, Check, ChevronRight, Dot, FileIcon, Pencil, Undo, Undo2, X, Flag, Copy as CopyIcon, Info, CirclePlus, Ellipsis, CircleEllipsis, Folder, ALargeSmall, TypeOutline, Text } from 'lucide-react'; import { ChatMessage, CheckpointEntry, StagingSelectionItem, ToolMessage } from '../../../../common/chatThreadServiceTypes.js'; import { approvalTypeOfToolName, LintErrorItem, ToolApprovalType, toolApprovalTypes, ToolCallParams } from '../../../../common/toolsServiceTypes.js'; -import { ApplyButtonsHTML, CopyButton, IconShell1, JumpToFileButton, JumpToTerminalButton, StatusIndicator, StatusIndicatorForApplyButton, useApplyButtonState } from '../markdown/ApplyBlockHoverButtons.js'; +import { CopyButton, EditToolButtonsHTML, IconShell1, JumpToFileButton, JumpToTerminalButton, StatusIndicator, StatusIndicatorForApplyButton, useApplyStreamState, useEditToolStreamState } from '../markdown/ApplyBlockHoverButtons.js'; import { IsRunningType } from '../../../chatThreadService.js'; import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectAllBg, rejectBg, rejectBorder } from '../../../../common/helpers/colors.js'; import { MAX_FILE_CHARS_PAGE, MAX_TERMINAL_INACTIVE_TIME, ToolName, toolNames } from '../../../../common/prompt/prompts.js'; @@ -530,6 +532,49 @@ export const getBasename = (pathStr: string, parts: number = 1) => { } + +// Open file utility function +export const voidOpenFileFn = ( + uri: URI, + accessor: ReturnType, + range?: [number, number] +) => { + const commandService = accessor.get('ICommandService') + const editorService = accessor.get('ICodeEditorService') + + // Get editor selection from CodeSelection range + let editorSelection = undefined; + + // If we have a selection, create an editor selection from the range + if (range) { + editorSelection = { + startLineNumber: range[0], + startColumn: 1, + endLineNumber: range[1], + endColumn: Number.MAX_SAFE_INTEGER, + }; + } + + // open the file + commandService.executeCommand('vscode.open', uri).then(() => { + + // select the text + setTimeout(() => { + if (!editorSelection) return; + + const editor = editorService.getActiveCodeEditor() + if (!editor) return; + + editor.setSelection(editorSelection) + editor.revealRange(editorSelection, ScrollType.Immediate) + + }, 50) // needed when document was just opened and needs to initialize + + }) + +}; + + export const SelectedFiles = ( { type, selections, setSelections, showProspectiveSelections, messageIdx, }: | { type: 'past', selections: StagingSelectionItem[]; setSelections?: undefined, showProspectiveSelections?: undefined, messageIdx: number, } @@ -640,11 +685,7 @@ export const SelectedFiles = ( setSelections([...selections, selection]) } else if (selection.type === 'File') { // open files - - commandService.executeCommand('vscode.open', selection.uri, { - preview: true, - // preserveFocus: false, - }); + voidOpenFileFn(selection.uri, accessor); const wasAddedAsCurrentFile = selection.state.wasAddedAsCurrentFile if (wasAddedAsCurrentFile) { @@ -658,10 +699,7 @@ export const SelectedFiles = ( } } else if (selection.type === 'CodeSelection') { - commandService.executeCommand('vscode.open', selection.uri, { - preview: true, - // TODO!!! open in range - }); + voidOpenFileFn(selection.uri, accessor, selection.range); } else if (selection.type === 'Folder') { // TODO!!! reveal in tree @@ -714,6 +752,7 @@ type ToolHeaderParams = { icon?: React.ReactNode; title: React.ReactNode; desc1: React.ReactNode; + desc1OnClick?: () => void; desc2?: React.ReactNode; isError?: boolean; info?: string; @@ -733,6 +772,7 @@ const ToolHeaderWrapper = ({ icon, title, desc1, + desc1OnClick, desc1Info, desc2, numResults, @@ -754,37 +794,51 @@ const ToolHeaderWrapper = ({ const isDropdown = children !== undefined // null ALLOWS dropdown const isClickable = !!(isDropdown || onClick) + const isDesc1Clickable = !!desc1OnClick + + const desc1HTML = {desc1} + return (
{/* header */}
{/* left */} -
+ {/* title eg "> Edited File" */} +
{ - if (isDropdown) { setIsOpen(v => !v); } - if (onClick) { onClick(); } - }} - > - {isDropdown && ( { + if (isDropdown) { setIsOpen(v => !v); } + if (onClick) { onClick(); } + }} + > + {isDropdown && ()} - {title} - {desc1} + />)} + {title} + + {!isDesc1Clickable && desc1HTML} +
+ {isDesc1Clickable && desc1HTML}
{/* right */} @@ -849,8 +903,9 @@ const EditTool = ({ toolMessage, threadId, messageIdx, content }: Parameters voidOpenFileFn(params.uri, accessor) + const componentParams: ToolHeaderParams = { title, desc1, desc1OnClick, desc1Info, isError, icon, isRejected, } if (toolMessage.type === 'running_now' || toolMessage.type === 'tool_request') { componentParams.children = @@ -859,7 +914,7 @@ const EditTool = ({ toolMessage, threadId, messageIdx, content }: Parameters - componentParams.desc2 = + // JumpToFileButton removed in favor of FileLinkText } else if (toolMessage.type === 'success' || toolMessage.type === 'rejected' || toolMessage.type === 'tool_error') { // add apply box @@ -872,6 +927,7 @@ const EditTool = ({ toolMessage, threadId, messageIdx, content }: Parameters // add children @@ -1364,12 +1420,12 @@ const getTitle = (toolMessage: Pick): { - desc1: string, + desc1: React.ReactNode, desc1Info?: string, } => { if (!_toolParams) { - return { desc1: '' }; + return { desc1: '', }; } const x = { @@ -1620,14 +1676,13 @@ const BottomChildren = ({ children, title }: { children: React.ReactNode, title: } -const EditToolHeaderButtons = ({ applyBoxId, uri, codeStr }: { applyBoxId: string, uri: URI, codeStr: string }) => { - const { currStreamState } = useApplyButtonState({ applyBoxId, uri }) +const EditToolHeaderButtons = ({ applyBoxId, uri, codeStr, toolName }: { applyBoxId: string, uri: URI, codeStr: string, toolName: 'edit_file' | 'rewrite_file' }) => { + const { streamState } = useEditToolStreamState({ applyBoxId, uri }) return
- - - {currStreamState === 'idle-no-changes' && } + {streamState === 'idle-no-changes' && } +
} @@ -1781,16 +1836,18 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper, const { rawParams, params } = toolMessage const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, } + let range: [number, number] | undefined = undefined if (toolMessage.params.startLine !== null || toolMessage.params.endLine !== null) { const start = toolMessage.params.startLine === null ? `1` : `${toolMessage.params.startLine}` const end = toolMessage.params.endLine === null ? `` : `${toolMessage.params.endLine}` const addStr = `(${start}-${end})` componentParams.desc1 += ` ${addStr}` + range = [params.startLine || 1, params.endLine || 1] } if (toolMessage.type === 'success') { const { result } = toolMessage - componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) } + componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor, range) } if (result.hasNextPage && params.pageNumber === 1) // first page componentParams.desc2 = `(truncated after ${Math.round(MAX_FILE_CHARS_PAGE) / 1000}k)` else if (params.pageNumber > 1) // subsequent pages @@ -1798,7 +1855,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper, } else if (toolMessage.type === 'tool_error') { const { result } = toolMessage - componentParams.desc2 = + // JumpToFileButton removed in favor of FileLinkText componentParams.bottomChildren = {result} @@ -1889,7 +1946,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper, name={`${child.name}${child.isDirectory ? '/' : ''}`} className='w-full overflow-auto' onClick={() => { - commandService.executeCommand('vscode.open', child.uri, { preview: true }) + voidOpenFileFn(child.uri, accessor) // commandService.executeCommand('workbench.view.explorer'); // open in explorer folders view instead // explorerService.select(child.uri, true); }} @@ -1940,7 +1997,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper, {result.uris.map((uri, i) => ( { commandService.executeCommand('vscode.open', uri, { preview: true }) }} + onClick={() => { voidOpenFileFn(uri, accessor) }} />))} {result.hasNextPage && @@ -1995,7 +2052,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper, {result.uris.map((uri, i) => ( { commandService.executeCommand('vscode.open', uri, { preview: true }) }} + onClick={() => { voidOpenFileFn(uri, accessor) }} />))} {result.hasNextPage && @@ -2085,7 +2142,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper, if (toolMessage.type === 'success') { const { result } = toolMessage - componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) } + componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) } if (result.lintErrors) componentParams.children = else @@ -2094,7 +2151,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper, } else if (toolMessage.type === 'tool_error') { const { result } = toolMessage - if (params) componentParams.desc2 = + // JumpToFileButton removed in favor of FileLinkText componentParams.bottomChildren = {result} @@ -2126,14 +2183,14 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper, if (toolMessage.type === 'success') { const { result } = toolMessage - componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) } + componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) } } else if (toolMessage.type === 'rejected') { - componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) } + componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) } } else if (toolMessage.type === 'tool_error') { const { result } = toolMessage - if (params) { componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) } } + if (params) { componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) } } componentParams.bottomChildren = {result} @@ -2168,14 +2225,14 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper, if (toolMessage.type === 'success') { const { result } = toolMessage - componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) } + componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) } } else if (toolMessage.type === 'rejected') { - componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) } + componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) } } else if (toolMessage.type === 'tool_error') { const { result } = toolMessage - if (params) { componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) } } + if (params) { componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) } } componentParams.bottomChildren = {result} @@ -2184,11 +2241,11 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper, } else if (toolMessage.type === 'running_now') { const { result } = toolMessage - componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) } + componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) } } else if (toolMessage.type === 'tool_request') { const { result } = toolMessage - componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) } + componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) } } return @@ -2558,7 +2615,7 @@ const CommandBarInChat = () => { const fileNameHTML =
commandService.executeCommand('vscode.open', uri, { preview: true })} + onClick={() => voidOpenFileFn(uri, accessor)} > {/* */} {basename} @@ -2683,6 +2740,9 @@ const CommandBarInChat = () => { const EditToolSoFar = ({ toolCallSoFar, }: { toolCallSoFar: RawToolCallObj }) => { + + const accessor = useAccessor() + const uri = toolCallSoFar.rawParams.uri ? URI.file(toolCallSoFar.rawParams.uri) : undefined const title = titleOfToolName[toolCallSoFar.name].proposed @@ -2695,11 +2755,13 @@ const EditToolSoFar = ({ toolCallSoFar, }: { toolCallSoFar: RawToolCallObj }) => + const desc1OnClick = () => { uri && voidOpenFileFn(uri, accessor) } + // If URI has not been specified return } + desc1OnClick={desc1OnClick} > msg.role === 'assistant' || msg.role === 'user').length; const detailsHTML = - {`(${numMessages})`} + {numMessages} + {` `} {formatDate(new Date(pastThread.lastModified))} + {/* {` messages `} */} return
{firstMsg} + {firstMsg} + {/* {`(${numMessages})`} */}
diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/helpers.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/helpers.tsx index d76f131a..75dcea8d 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/helpers.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/helpers.tsx @@ -9,10 +9,13 @@ type ReturnType = [ // use this if state might be too slow to catch export const useRefState = (initVal: T): ReturnType => { - const [_, _setState] = useState(false) + // this actually makes a difference being an int, not a boolean. + // if it's a boolean and changes happen to fast, it goes with old values and leads to *very* weird bugs (like returning JSX, but not actually rendering it) + const [_s, _setState] = useState(0) + const ref = useRef(initVal) const setState = useCallback((newVal: T) => { - _setState(n => !n) // call rerender + _setState(n => n + 1) // call rerender ref.current = newVal }, []) return [ref, setState] diff --git a/src/vs/workbench/contrib/void/browser/toolsService.ts b/src/vs/workbench/contrib/void/browser/toolsService.ts index ad6944d9..bba085d8 100644 --- a/src/vs/workbench/contrib/void/browser/toolsService.ts +++ b/src/vs/workbench/contrib/void/browser/toolsService.ts @@ -400,7 +400,8 @@ export class ToolsService implements IToolsService { if (this.commandBarService.getStreamState(uri) === 'streaming') { throw new Error(`Another LLM is currently making changes to this file. Please stop streaming for now and ask the user to resume later.`) } - editCodeService.instantlyApplyNewContent({ uri, newContent }) + await editCodeService.callBeforeApplyOrEdit(uri) + editCodeService.instantlyRewriteFile({ uri, newContent }) // at end, get lint errors const lintErrorsPromise = Promise.resolve().then(async () => { await timeout(2000) @@ -415,6 +416,7 @@ export class ToolsService implements IToolsService { if (this.commandBarService.getStreamState(uri) === 'streaming') { throw new Error(`Another LLM is currently making changes to this file. Please stop streaming for now and ask the user to resume later.`) } + await editCodeService.callBeforeApplyOrEdit(uri) editCodeService.instantlyApplySearchReplaceBlocks({ uri, searchReplaceBlocks }) // at end, get lint errors diff --git a/src/vs/workbench/contrib/void/common/prompt/prompts.ts b/src/vs/workbench/contrib/void/common/prompt/prompts.ts index 858e69c6..613142b8 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -463,7 +463,7 @@ ${directoryStr} details.push(`Many tools only work if the user has a workspace open.`) } else { - details.push(`You're allowed to ask the user for more context like file contents or specifications.`) + details.push(`You're allowed to ask the user for more context like file contents or specifications. If this comes up, tell them to reference files and folders by typing @.`) } if (mode === 'agent') { diff --git a/src/vs/workbench/contrib/void/common/toolsServiceTypes.ts b/src/vs/workbench/contrib/void/common/toolsServiceTypes.ts index d97372fa..2eb3a784 100644 --- a/src/vs/workbench/contrib/void/common/toolsServiceTypes.ts +++ b/src/vs/workbench/contrib/void/common/toolsServiceTypes.ts @@ -23,6 +23,8 @@ export const approvalTypeOfToolName: Partial<{ [T in ToolName]?: 'edits' | 'term 'edit_file': 'edits', 'run_command': 'terminal', 'run_persistent_command': 'terminal', + 'open_persistent_terminal': 'terminal', + 'kill_persistent_terminal': 'terminal', }