From 3f7f2fb40cc487237258551b02924425a8a9da81 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Sat, 15 Mar 2025 01:19:52 -0700 Subject: [PATCH] commandbar draft --- .../contrib/void/browser/editCodeService.ts | 64 ++++++- .../void/browser/editCodeServiceInterface.ts | 12 ++ .../react/src/markdown/ChatMarkdownRender.tsx | 4 +- .../react/src/sidebar-tsx/SidebarChat.tsx | 171 ++++++++++++++++++ 4 files changed, 244 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/editCodeService.ts b/src/vs/workbench/contrib/void/browser/editCodeService.ts index 978016f7..78acce8e 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeService.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeService.ts @@ -177,7 +177,7 @@ type CtrlKZone = { } & CommonZoneProps -type DiffZone = { +export type DiffZone = { type: 'DiffZone', originalCode: string; _diffOfId: Record; // diffid -> diff in this DiffArea @@ -207,7 +207,7 @@ type TrackingZone = { // called DiffArea for historical purposes, we can rename to something like TextRegion if we want -type DiffArea = CtrlKZone | DiffZone | TrackingZone +export type DiffArea = CtrlKZone | DiffZone | TrackingZone const diffAreaSnapshotKeys = [ 'type', @@ -240,16 +240,22 @@ class EditCodeService extends Disposable implements IEditCodeService { // URI <--> model - diffAreasOfURI: Record | undefined> = {} + diffAreasOfURI: Record | undefined> = {}; // uri -> diffareaId - diffAreaOfId: Record = {}; - diffOfId: Record = {}; // redundant with diffArea._diffs + diffAreaOfId: Record = {}; // diffareaId -> diffArea + diffOfId: Record = {}; // diffid -> diff (redundant with diffArea._diffOfId) + _sortedUrisWithDiffs: URI[] = [] // derivative of diffAreaOfId + _sortedDiffsOfFspath: { [uriString: string]: Diff[] | undefined } = {} // derivative of diffAreaOfId // only applies to diffZones // streamingDiffZones: Set = new Set() private readonly _onDidChangeDiffZoneStreaming = new Emitter<{ uri: URI; diffareaid: number }>(); private readonly _onDidAddOrDeleteDiffZones = new Emitter<{ uri: URI }>(); + onDidAddOrDeleteDiffZones = this._onDidAddOrDeleteDiffZones.event; + + private readonly _onDidFinishAddOrDeleteDiffInDiffZone = new Emitter<{ uri: URI }>(); + onDidAddOrDeleteDiffInDiffZone = this._onDidFinishAddOrDeleteDiffInDiffZone.event; private readonly _onDidChangeCtrlKZoneStreaming = new Emitter<{ uri: URI; diffareaid: number }>(); onDidChangeCtrlKZoneStreaming = this._onDidChangeCtrlKZoneStreaming.event @@ -344,6 +350,46 @@ class EditCodeService extends Disposable implements IEditCodeService { for (let editor of this._codeEditorService.listCodeEditors()) { initializeEditor(editor) } this._register(this._codeEditorService.onCodeEditorAdd(editor => { initializeEditor(editor) })) + + // update `_sortedUrisWithDiffs` and `_sortedDiffsOfUri` on all changes to diffzones + this._register(this._onDidFinishAddOrDeleteDiffInDiffZone.event(({ uri }) => { + + + // 1. Update _sortedUrisWithDiffs + const hasDiffZones = Array.from(this.diffAreasOfURI[uri.fsPath] || []) + .some(diffAreaId => this.diffAreaOfId[diffAreaId]?.type === 'DiffZone'); + + // Add or remove this URI from _sortedUrisWithDiffs + const currentIndex = this._sortedUrisWithDiffs.findIndex(u => u.fsPath === uri.fsPath); + if (hasDiffZones && currentIndex === -1) { + // Add URI and maintain sort + this._sortedUrisWithDiffs.push(uri); + this._sortedUrisWithDiffs.sort((a, b) => a.fsPath.localeCompare(b.fsPath)); + } else if (!hasDiffZones && currentIndex !== -1) { + // Remove URI + this._sortedUrisWithDiffs.splice(currentIndex, 1); + } + + // 2. Update _sortedDiffsOfUri only for this URI + const diffsInUri: Diff[] = []; + + // Collect all diffs from DiffZones in this URI + for (const diffAreaId of this.diffAreasOfURI[uri.fsPath] || []) { + const diffArea = this.diffAreaOfId[diffAreaId]; + if (diffArea?.type === 'DiffZone') { + diffsInUri.push(...Object.values(diffArea._diffOfId)); + } + } + + // Update or remove the entry for this URI + if (diffsInUri.length > 0) { + this._sortedDiffsOfFspath[uri.fsPath] = diffsInUri.sort((a, b) => a.startLine - b.startLine); + console.log('_sortedDiffsOfFspath', this._sortedDiffsOfFspath) + } else { + delete this._sortedDiffsOfFspath[uri.fsPath]; + } + })); + } @@ -901,6 +947,10 @@ class EditCodeService extends Disposable implements IEditCodeService { if (diffArea.type !== 'DiffZone') return delete diffArea._diffOfId[diff.diffid] delete this.diffOfId[diff.diffid] + + if (!diffArea._streamState.isStreaming) { + this._onDidFinishAddOrDeleteDiffInDiffZone.fire({ uri: diffArea._URI }) + } } private _deleteDiffs(diffZone: DiffZone) { @@ -993,6 +1043,10 @@ class EditCodeService extends Disposable implements IEditCodeService { this.diffOfId[diffid] = newDiff diffZone._diffOfId[diffid] = newDiff + if (!diffZone._streamState.isStreaming) { + this._onDidFinishAddOrDeleteDiffInDiffZone.fire({ uri }) + } + return newDiff } diff --git a/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts b/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts index e9b1f49e..7fa25ce5 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts @@ -7,6 +7,7 @@ import { Event } from '../../../../base/common/event.js'; import { URI } from '../../../../base/common/uri.js'; import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; +import { Diff, DiffArea } from './editCodeService.js'; @@ -36,13 +37,24 @@ export const IEditCodeService = createDecorator('editCodeServi export interface IEditCodeService { readonly _serviceBrand: undefined; + // main entrypoints (initialize things for the functions below to be called): startApplying(opts: StartApplyingOpts): Promise<[URI, Promise] | null>; + _sortedUrisWithDiffs: URI[]; + _sortedDiffsOfFspath: { [fsPath: string]: Diff[] | undefined }; + + diffAreaOfId: Record; + diffOfId: Record; + + addCtrlKZone(opts: AddCtrlKOpts): number | undefined; removeCtrlKZone(opts: { diffareaid: number }): void; removeDiffAreas(opts: { uri: URI, removeCtrlKs: boolean, behavior: 'reject' | 'accept' }): void; + onDidAddOrDeleteDiffZones: Event<{ uri: URI }>; + onDidAddOrDeleteDiffInDiffZone: Event<{ uri: URI }>; + // CtrlKZone streaming state isCtrlKZoneStreaming(opts: { diffareaid: number }): boolean; interruptCtrlKStreaming(opts: { diffareaid: number }): void; 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 b09b163f..31ad9784 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 @@ -41,7 +41,7 @@ const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string const accessor = useAccessor() const chatThreadService = accessor.get('IChatThreadService') - const commandSerivce = accessor.get('ICommandService') + const commandService = accessor.get('ICommandService') const editorService = accessor.get('ICodeEditorService') const { messageIdx, threadId } = chatMessageLocation @@ -73,7 +73,7 @@ const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string const selection = link.selection // open the file - commandSerivce.executeCommand('vscode.open', link.uri).then(() => { + commandService.executeCommand('vscode.open', link.uri).then(() => { // select the text setTimeout(() => { 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 433378ea..2d011a36 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 @@ -32,6 +32,8 @@ import { ResolveReason, ToolCallParams, ToolName, ToolNameWithApproval } from '. import { getLanguageFromModel } from '../../../../common/helpers/getLanguage.js'; import { dirname } from '../../../../../../../base/common/resources.js'; import { useApplyButtonHTML } from '../markdown/ApplyBlockHoverButtons.js'; +import { DiffZone } from '../../../editCodeService.js'; +import { ScrollType } from '../../../../../../../editor/common/editorCommon.js'; @@ -1609,6 +1611,173 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx, isLast }: ChatBubblePr } +const VoidCommandBar = () => { + const accessor = useAccessor() + const editCodeService = accessor.get('IEditCodeService') + const editorService = accessor.get('ICodeEditorService') + const commandService = accessor.get('ICommandService') + + const [_, rerender] = useState(0) + console.log('rerender count: ', _) + + // state for what the user is currently focused on (both URI and diff) + const [diffIdxOfFspath, setDiffIdxOfFspath] = useState>({}) + // const [currentUriIdx, setCurrentUriIdx] = useState(-1) // we are doing O(n) search for this + + const getCurrentUri = useCallback(() => { + const editor = editorService.getActiveCodeEditor() + if (!editor) return null + const uri = editor.getModel()?.uri + if (!uri) return null + return uri + }, [editorService]) + + const diffZones: DiffZone[] = [] + + + // trigger rerender when diffzone is created (TODO need to also update when diff is accepted/rejected) + useEffect(() => { + const disposable = editCodeService.onDidAddOrDeleteDiffInDiffZone(() => { + rerender(c => c + 1) // rerender + }) + return () => disposable.dispose() + }, [editCodeService, rerender]) + + + const getNextDiff = useCallback(({ step }: { step: 1 | -1 }) => { + + const currentUri = getCurrentUri() + + if (!currentUri) { + return; + } + + const sortedDiffs = editCodeService._sortedDiffsOfFspath[currentUri.fsPath] + + if (!sortedDiffs || sortedDiffs.length === 0) { + return; + } + + const currentDiffIdx = diffIdxOfFspath[currentUri.fsPath] || 0 + const nextDiffIdx = (currentDiffIdx + step) % sortedDiffs.length + + const nextDiff = sortedDiffs[nextDiffIdx] + + return { nextDiff, nextDiffIdx, } + + }, [getCurrentUri, editCodeService._sortedDiffsOfFspath, diffIdxOfFspath]) + + const getNextUri = useCallback(({ step }: { step: 1 | -1 }) => { + + const sortedUris = editCodeService._sortedUrisWithDiffs + if (sortedUris.length === 0) { + return; + } + + const currentUri = getCurrentUri() + + const defaultUriIdx = step === 1 ? -1 : 0 // defaults: if next, currentIdx = -1; if prev, currentIdx = 0 + let currentUriIdx = -1 + if (currentUri) { + currentUriIdx = sortedUris.findIndex(u => u.fsPath === currentUri.fsPath) + } + + if (currentUriIdx === -1) { // not found + currentUriIdx = defaultUriIdx // set to default + } + + const nextUriIdx = (currentUriIdx + step) % sortedUris.length + const nextUri = sortedUris[nextUriIdx] + + return { nextUri, nextUriIdx, } + + }, [getCurrentUri, editCodeService._sortedUrisWithDiffs]) + + + + return
+ + + + + +
+
+
numUris: {Object.keys(editCodeService._sortedDiffsOfFspath).length}
+
numDiffs: {Object.values(editCodeService._sortedDiffsOfFspath).reduce((acc, diffs) => acc + (diffs?.length || 0), 0)}
+
+
+ {diffZones.map((area, index) => ( + <> +
{getBasename(area?._URI?.toString())}
+ + ))} +
+} + export const SidebarChat = () => { const textAreaRef = useRef(null) @@ -1805,6 +1974,8 @@ export const SidebarChat = () => { fnsRef={textAreaFnsRef} multiline={true} /> + +