From 80a5dc036278162e336453d758245f106ab21528 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Wed, 30 Apr 2025 21:28:20 -0700 Subject: [PATCH] allow backspace stagingselections --- .../contrib/void/browser/chatThreadService.ts | 26 +++++++++++++++ .../void/browser/react/src/util/inputs.tsx | 33 ++++++++++++++----- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index a2d5dbd7..70362db7 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -235,6 +235,7 @@ export interface IChatThreadService { isCurrentlyFocusingMessage(): boolean; setCurrentlyFocusedMessageIdx(messageIdx: number | undefined): void; + popStagingSelections(numPops?: number): void; addNewStagingSelection(newSelection: StagingSelectionItem): void; dangerousSetState: (newState: ThreadsState) => void; @@ -1612,6 +1613,31 @@ We only need to do it for files that were edited since `from`, ie files between } + // Pops the staging selections from the current thread's state + popStagingSelections(numPops: number): void { + + numPops = numPops ?? 1; + + const focusedMessageIdx = this.getCurrentFocusedMessageIdx() + + // set the selections to the proper value + let selections: StagingSelectionItem[] = [] + let setSelections = (s: StagingSelectionItem[]) => { } + + if (focusedMessageIdx === undefined) { + selections = this.getCurrentThreadState().stagingSelections + setSelections = (s: StagingSelectionItem[]) => this.setCurrentThreadState({ stagingSelections: s }) + } else { + selections = this.getCurrentMessageState(focusedMessageIdx).stagingSelections + setSelections = (s) => this.setCurrentMessageState(focusedMessageIdx, { stagingSelections: s }) + } + + setSelections([ + ...selections.slice(0, selections.length - numPops) + ]) + + } + // set message.state private _setCurrentMessageState(state: Partial, messageIdx: number): void { diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx index 32ff1cf7..f4bc418f 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx @@ -17,7 +17,7 @@ import { asCssVariable } from '../../../../../../../platform/theme/common/colorU import { inputBackground, inputForeground } from '../../../../../../../platform/theme/common/colorRegistry.js'; import { useFloating, autoUpdate, offset, flip, shift, size, autoPlacement } from '@floating-ui/react'; import { URI } from '../../../../../../../base/common/uri.js'; -import { getBasename } from '../sidebar-tsx/SidebarChat.js'; +import { getBasename, getFolderName } from '../sidebar-tsx/SidebarChat.js'; import { ChevronRight, File, Folder, FolderClosed, LucideProps } from 'lucide-react'; import { StagingSelectionItem } from '../../../../common/chatThreadServiceTypes.js'; @@ -56,11 +56,12 @@ type GenerateNextOptions = (optionText: string) => Promise type Option = { fullName: string, + abbreviatedName: string, iconInMenu: ForwardRefExoticComponent & RefAttributes>, // type for lucide-react components } & ( - | { nextOptions: Option[], generateNextOptions?: undefined, abbreviatedName?: undefined } - | { nextOptions?: undefined, generateNextOptions: GenerateNextOptions, abbreviatedName?: undefined } - | { leafNodeType: 'File' | 'Folder', abbreviatedName: string, uri: URI, nextOptions?: undefined, generateNextOptions?: undefined, } + | { leafNodeType?: undefined, nextOptions: Option[], generateNextOptions?: undefined, } + | { leafNodeType?: undefined, nextOptions?: undefined, generateNextOptions: GenerateNextOptions, } + | { leafNodeType: 'File' | 'Folder', uri: URI, nextOptions?: undefined, generateNextOptions?: undefined, } ) @@ -177,7 +178,7 @@ const numOptionsToShow = 100 // TODO make this unique based on other options const getAbbreviatedName = (relativePath: string) => { - return getBasename(relativePath, 2) + return getBasename(relativePath, 1) } const getOptionsAtPath = async (accessor: ReturnType, path: string[], optionText: string): Promise => { @@ -279,11 +280,13 @@ const getOptionsAtPath = async (accessor: ReturnType, path: const allOptions: Option[] = [ { fullName: 'files', + abbreviatedName: 'files', iconInMenu: File, generateNextOptions: async (t) => (await searchForFilesOrFolders(t, 'files')) || [], }, { fullName: 'folders', + abbreviatedName: 'folders', iconInMenu: Folder, generateNextOptions: async (t) => (await searchForFilesOrFolders(t, 'folders')) || [], }, @@ -744,6 +747,16 @@ export const VoidInputBox2 = forwardRef(fun return; } + if (e.key === 'Backspace') { // TODO allow user to undo this. + if (!e.currentTarget.value) { // if there is no text, remove a selection + if (e.metaKey || e.ctrlKey) { // Ctrl+Backspace = remove all + chatThreadService.popStagingSelections(Number.MAX_SAFE_INTEGER) + } else { // Backspace = pop 1 selection + chatThreadService.popStagingSelections(1) + } + return; + } + } if (e.key === 'Enter') { // Shift + Enter when multiline = newline const shouldAddNewline = e.shiftKey && multiline @@ -769,7 +782,7 @@ export const VoidInputBox2 = forwardRef(fun onWheel={(e) => e.stopPropagation()} > {/* Breadcrumbs Header */} - {areBreadcrumbsShowing &&
+ {areBreadcrumbsShowing &&
{optionText ?
{/* {optionPath.map((path, index) => ( @@ -787,7 +800,7 @@ export const VoidInputBox2 = forwardRef(fun {/* Options list */}
-
+
{options.length === 0 ?
No results found
: options.map((o, oIdx) => { @@ -799,14 +812,16 @@ export const VoidInputBox2 = forwardRef(fun key={o.fullName} className={` flex items-center gap-2 - px-3 py-0.5 cursor-pointer bg-void-bg-2-alt + px-3 py-1 cursor-pointer bg-void-bg-2-alt ${oIdx === optionIdx ? 'bg-void-bg-2-hover' : ''} `} onClick={() => { onSelectOption(); }} onMouseMove={() => { setOptionIdx(oIdx) }} > {} - {o.fullName} + {o.abbreviatedName} + + {o.fullName && o.fullName !== o.abbreviatedName && {o.fullName}} {o.nextOptions || o.generateNextOptions ? ( ) : null}