From 4618442c11597403bec1e21145678c5e69071dac Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Wed, 7 May 2025 20:12:49 -0700 Subject: [PATCH 1/7] num messages better --- .../browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx index 5bb32e92..44709933 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx @@ -220,13 +220,13 @@ const PastThreadElement = ({ pastThread, idx, hoveredIdx, setHoveredIdx, isRunni const numMessages = pastThread.messages.filter((msg) => msg.role === 'assistant' || msg.role === 'user').length; const detailsHTML = - {`(${numMessages})`} + {numMessages} {formatDate(new Date(pastThread.lastModified))} + {/* {` messages `} */} return
{firstMsg} + {/* {`(${numMessages})`} */}
From 42391a33c81658e4d203a0d071649c60bd63e1d5 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Thu, 8 May 2025 01:48:41 -0700 Subject: [PATCH 2/7] tooltip --- .../react/src/sidebar-tsx/SidebarThreadSelector.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx index 44709933..a6147461 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx @@ -225,6 +225,7 @@ const PastThreadElement = ({ pastThread, idx, hoveredIdx, setHoveredIdx, isRunni // data-tooltip-place='top' > {numMessages} + {` `} {formatDate(new Date(pastThread.lastModified))} {/* {` messages `} */} @@ -249,7 +250,11 @@ const PastThreadElement = ({ pastThread, idx, hoveredIdx, setHoveredIdx, isRunni : null} {/* name */} - {firstMsg} + {firstMsg} {/* {`(${numMessages})`} */} From d00c478c5bd254970fd62c83a8bd882da25a5a1e Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Thu, 8 May 2025 02:21:55 -0700 Subject: [PATCH 3/7] add accept|reject on edit tool! --- .../contrib/void/browser/editCodeService.ts | 6 +- .../void/browser/editCodeServiceInterface.ts | 4 +- .../src/markdown/ApplyBlockHoverButtons.tsx | 223 +++++++++++++----- .../src/quick-edit-tsx/QuickEditChat.tsx | 2 +- .../react/src/sidebar-tsx/SidebarChat.tsx | 14 +- .../void/browser/react/src/util/helpers.tsx | 7 +- .../contrib/void/browser/toolsService.ts | 4 +- 7 files changed, 179 insertions(+), 81 deletions(-) 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/react/src/markdown/ApplyBlockHoverButtons.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx index 2d397ecf..f412c1af 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,7 +3,7 @@ * 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' @@ -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) => ( @@ -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' ? diff --git a/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx index 191b7212..30762d89 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx @@ -71,7 +71,7 @@ export const QuickEditChat = ({ startBehavior: 'keep-conflicts', } as const - await editCodeService.callBeforeStartApplying(opts) + await editCodeService.callBeforeApplyOrEdit(opts) const [newApplyingUri, applyDonePromise] = editCodeService.startApplying(opts) ?? [] // catch any errors by interrupting the stream applyDonePromise?.catch(e => { 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..10c44be3 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 @@ -23,7 +23,7 @@ import { getModelCapabilities, getIsReasoningEnabledState } from '../../../../co 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'; @@ -849,7 +849,7 @@ const EditTool = ({ toolMessage, threadId, messageIdx, content }: Parameters // add children @@ -1620,14 +1621,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' && } +
} 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 From 84ddf2b88c74be5e05ac7f575a8417271f088c6b Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Thu, 8 May 2025 02:40:13 -0700 Subject: [PATCH 4/7] prompt --- src/vs/workbench/contrib/void/common/prompt/prompts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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') { From 7a3cf4953d51811143d51a654208630b757c29b1 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Thu, 8 May 2025 03:41:02 -0700 Subject: [PATCH 5/7] misc --- .../workbench/contrib/void/browser/extensionTransferService.ts | 1 + src/vs/workbench/contrib/void/common/toolsServiceTypes.ts | 2 ++ 2 files changed, 3 insertions(+) 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/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', } From 42f295895920f8c9b05fdb01a6677cdfb5a1aca3 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Thu, 8 May 2025 03:51:42 -0700 Subject: [PATCH 6/7] desc1 link --- .../src/markdown/ApplyBlockHoverButtons.tsx | 6 +- .../react/src/markdown/ChatMarkdownRender.tsx | 23 +-- .../react/src/sidebar-tsx/SidebarChat.tsx | 146 ++++++++++++------ 3 files changed, 104 insertions(+), 71 deletions(-) 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 f412c1af..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 @@ -10,7 +10,7 @@ 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 { @@ -109,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} @@ -441,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..ede1708b 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) 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, { selection: link.selection, }); } return { } + +// Open file utility function +export const voidOpenFileFn = ( + uri: URI, + accessor: ReturnType, + options?: { selection?: { startLineNumber: number; startColumn: number; endLineNumber: number; endColumn: number }, } +) => { + const commandService = accessor.get('ICommandService') + const editorService = accessor.get('ICodeEditorService') + + const selection = options?.selection + + // open the file + commandService.executeCommand('vscode.open', 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 + + }) + +}; + + export const SelectedFiles = ( { type, selections, setSelections, showProspectiveSelections, messageIdx, }: | { type: 'past', selections: StagingSelectionItem[]; setSelections?: undefined, showProspectiveSelections?: undefined, messageIdx: number, } @@ -641,10 +675,7 @@ export const SelectedFiles = ( } 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 +689,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); } else if (selection.type === 'Folder') { // TODO!!! reveal in tree @@ -714,6 +742,7 @@ type ToolHeaderParams = { icon?: React.ReactNode; title: React.ReactNode; desc1: React.ReactNode; + desc1OnClick?: () => void; desc2?: React.ReactNode; isError?: boolean; info?: string; @@ -733,6 +762,7 @@ const ToolHeaderWrapper = ({ icon, title, desc1, + desc1OnClick, desc1Info, desc2, numResults, @@ -754,37 +784,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 */} @@ -850,7 +894,8 @@ 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 +904,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 @@ -1365,12 +1410,12 @@ const getTitle = (toolMessage: Pick): { - desc1: string, + desc1: React.ReactNode, desc1Info?: string, } => { if (!_toolParams) { - return { desc1: '' }; + return { desc1: '', }; } const x = { @@ -1790,7 +1835,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.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 +1843,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 +1934,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 +1985,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 +2040,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 +2130,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 +2139,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 +2171,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 +2213,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 +2229,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 +2603,7 @@ const CommandBarInChat = () => { const fileNameHTML =
commandService.executeCommand('vscode.open', uri, { preview: true })} + onClick={() => voidOpenFileFn(uri, accessor)} > {/* */} {basename} @@ -2683,6 +2728,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 +2743,13 @@ const EditToolSoFar = ({ toolCallSoFar, }: { toolCallSoFar: RawToolCallObj }) => + const desc1OnClick = () => { uri && voidOpenFileFn(uri, accessor) } + // If URI has not been specified return } + desc1OnClick={desc1OnClick} > Date: Thu, 8 May 2025 04:22:50 -0700 Subject: [PATCH 7/7] selecitons work with link --- .../react/src/markdown/ChatMarkdownRender.tsx | 4 +-- .../react/src/sidebar-tsx/SidebarChat.tsx | 28 +++++++++++++------ 2 files changed, 22 insertions(+), 10 deletions(-) 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 ede1708b..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 @@ -134,10 +134,10 @@ const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string const onClick = () => { - if (!link) return; + if (!link || !link.selection) return; // Use the updated voidOpenFileFn to open the file and handle selection - voidOpenFileFn(link.uri, accessor, { selection: link.selection, }); + voidOpenFileFn(link.uri, accessor, [link.selection.startLineNumber, link.selection.endLineNumber]); } return { export const voidOpenFileFn = ( uri: URI, accessor: ReturnType, - options?: { selection?: { startLineNumber: number; startColumn: number; endLineNumber: number; endColumn: number }, } + range?: [number, number] ) => { const commandService = accessor.get('ICommandService') const editorService = accessor.get('ICodeEditorService') - const selection = options?.selection + // 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 (!selection) return; + if (!editorSelection) return; const editor = editorService.getActiveCodeEditor() if (!editor) return; - editor.setSelection(selection) - editor.revealRange(selection, ScrollType.Immediate) + editor.setSelection(editorSelection) + editor.revealRange(editorSelection, ScrollType.Immediate) }, 50) // needed when document was just opened and needs to initialize @@ -674,7 +685,6 @@ export const SelectedFiles = ( setSelections([...selections, selection]) } else if (selection.type === 'File') { // open files - voidOpenFileFn(selection.uri, accessor); const wasAddedAsCurrentFile = selection.state.wasAddedAsCurrentFile @@ -689,7 +699,7 @@ export const SelectedFiles = ( } } else if (selection.type === 'CodeSelection') { - voidOpenFileFn(selection.uri, accessor); + voidOpenFileFn(selection.uri, accessor, selection.range); } else if (selection.type === 'Folder') { // TODO!!! reveal in tree @@ -1826,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 = () => { voidOpenFileFn(params.uri, accessor) } + 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