From 80a87ff3c623a1a82dade8726f615684262e4b25 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 23 Dec 2024 02:43:19 -0800 Subject: [PATCH] improvements but might still be buggy --- .../helperServices/consistentItemService.ts | 176 +++++++++++++++++ .../helperServices/zoneStyleService.ts | 178 ------------------ .../void/browser/inlineDiffsService.ts | 122 ++++++------ 3 files changed, 245 insertions(+), 231 deletions(-) create mode 100644 src/vs/workbench/contrib/void/browser/helperServices/consistentItemService.ts delete mode 100644 src/vs/workbench/contrib/void/browser/helperServices/zoneStyleService.ts diff --git a/src/vs/workbench/contrib/void/browser/helperServices/consistentItemService.ts b/src/vs/workbench/contrib/void/browser/helperServices/consistentItemService.ts new file mode 100644 index 00000000..09dea57b --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/helperServices/consistentItemService.ts @@ -0,0 +1,176 @@ +import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { generateUuid } from '../../../../../base/common/uuid.js'; +import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js'; +import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js'; +import { InstantiationType, registerSingleton } from '../../../../../platform/instantiation/common/extensions.js'; +import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js'; + + +// lets you add a "consistent" item to a Model (aka URI), +// instead of just to a single editor + + +type AddItemInputs = { uri: URI; fn: (editor: ICodeEditor) => (() => void); } + +export interface IConsistentItemService { + readonly _serviceBrand: undefined; + addConsistentItemToURI(inputs: AddItemInputs): string; + removeConsistentItemFromURI(consistentItemId: string): void; +} + +export const IConsistentItemService = createDecorator('ConsistentItemService'); + +export class ConsistentItemService extends Disposable { + + readonly _serviceBrand: undefined + + // the items that are attached to each URI, completely independent from current state of editors + private readonly consistentItemIdsOfURI: Record | undefined> = {} + private readonly infoOfConsistentItemId: Record = {} + + + // current state of items on each editor, and the fns to call to remove them + private readonly itemIdsOfEditorId: Record | undefined> = {} + private readonly consistentItemIdOfItemId: Record = {} + private readonly disposeFnOfItemId: Record void> = {} + + + constructor( + @ICodeEditorService private readonly _editorService: ICodeEditorService, + ) { + super() + + + const removeItemsFromEditor = (editor: ICodeEditor) => { + const editorId = editor.getId() + for (const itemId of this.itemIdsOfEditorId[editorId] ?? []) + this._removeItemFromEditor(editor, itemId) + } + + // put items on the editor, based on the consistent items for that URI + const putItemsOnEditor = (editor: ICodeEditor, uri: URI | null) => { + if (!uri) return + for (const consistentItemId of this.consistentItemIdsOfURI[uri.fsPath] ?? []) + this._putItemOnEditor(editor, consistentItemId) + } + + + const addTabSwitchListeners = (editor: ICodeEditor) => { + this._register( + editor.onDidChangeModel(e => { + removeItemsFromEditor(editor) + putItemsOnEditor(editor, e.newModelUrl) + }) + ) + } + + const addDisposeListener = (editor: ICodeEditor) => { + this._register(editor.onDidDispose(() => { + // anything on the editor has been disposed already + for (const itemId of this.itemIdsOfEditorId[editor.getId()] ?? []) + delete this.disposeFnOfItemId[itemId] + })) + } + + const initializeEditor = (editor: ICodeEditor) => { + addTabSwitchListeners(editor) + addDisposeListener(editor) + putItemsOnEditor(editor, editor.getModel()?.uri ?? null) + } + + // initialize current editors + any new editors + for (let editor of this._editorService.listCodeEditors()) initializeEditor(editor) + this._register(this._editorService.onCodeEditorAdd(editor => { initializeEditor(editor) })) + + // when an editor is deleted, remove its items + this._register(this._editorService.onCodeEditorRemove(editor => { + removeItemsFromEditor(editor) + })) + + } + + + + _putItemOnEditor(editor: ICodeEditor, consistentItemId: string) { + const { fn } = this.infoOfConsistentItemId[consistentItemId] + + // add item + const dispose = fn(editor) + + const itemId = generateUuid() + const editorId = editor.getId() + + if (!(editorId in this.itemIdsOfEditorId)) + this.itemIdsOfEditorId[editorId] = new Set() + this.itemIdsOfEditorId[editorId]!.add(itemId) + + + this.consistentItemIdOfItemId[itemId] = consistentItemId + + this.disposeFnOfItemId[itemId] = () => { + // console.log('calling remove for', itemId) + dispose?.() + } + + } + + + _removeItemFromEditor(editor: ICodeEditor, itemId: string) { + + const editorId = editor.getId() + this.itemIdsOfEditorId[editorId]?.delete(itemId) + + this.disposeFnOfItemId[itemId]?.() + delete this.disposeFnOfItemId[itemId] + + delete this.consistentItemIdOfItemId[itemId] + } + + + consistentItemIdPool = 0 + addConsistentItemToURI({ uri, fn }: AddItemInputs) { + const consistentItemId = (this.consistentItemIdPool++) + '' + + if (!(uri.fsPath in this.consistentItemIdsOfURI)) + this.consistentItemIdsOfURI[uri.fsPath] = new Set() + this.consistentItemIdsOfURI[uri.fsPath]!.add(consistentItemId) + + this.infoOfConsistentItemId[consistentItemId] = { fn, uri } + + const editors = this._editorService.listCodeEditors().filter(editor => editor.getModel()?.uri.fsPath === uri.fsPath) + for (const editor of editors) + this._putItemOnEditor(editor, consistentItemId) + + return consistentItemId + } + + + removeConsistentItemFromURI(consistentItemId: string) { + + if (!(consistentItemId in this.infoOfConsistentItemId)) + return + + const { uri } = this.infoOfConsistentItemId[consistentItemId] + const editors = this._editorService.listCodeEditors().filter(e => e.getModel()?.uri.fsPath === uri.fsPath) + + for (const editor of editors) { + for (const itemId of this.itemIdsOfEditorId[editor.getId()] ?? []) { + if (this.consistentItemIdOfItemId[itemId] === consistentItemId) + this._removeItemFromEditor(editor, itemId) + } + } + + // clear + this.consistentItemIdsOfURI[uri.fsPath]?.delete(consistentItemId) + delete this.infoOfConsistentItemId[consistentItemId] + + } + + + +} + +registerSingleton(IConsistentItemService, ConsistentItemService, InstantiationType.Eager); + + diff --git a/src/vs/workbench/contrib/void/browser/helperServices/zoneStyleService.ts b/src/vs/workbench/contrib/void/browser/helperServices/zoneStyleService.ts deleted file mode 100644 index b57b7d8a..00000000 --- a/src/vs/workbench/contrib/void/browser/helperServices/zoneStyleService.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js'; -import { URI } from '../../../../../base/common/uri.js'; -import { generateUuid } from '../../../../../base/common/uuid.js'; -import { ICodeEditor, IViewZone } from '../../../../../editor/browser/editorBrowser.js'; -import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js'; -import { InstantiationType, registerSingleton } from '../../../../../platform/instantiation/common/extensions.js'; -import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js'; - - -// lets you add a zone to a Model (aka URI), instead of just to a single editor - - -export interface IZoneStyleService { - readonly _serviceBrand: undefined; - addConsistentZoneToURI(uri: URI, iZoneFn: (editor: ICodeEditor) => IViewZone, iOther?: (editor: ICodeEditor) => (() => void)): string; - removeConsistentZoneFromURI(consistentZoneId: string): void; -} - -export const IZoneStyleService = createDecorator('zoneStyleService'); - -export class ZoneStyleService extends Disposable { - - readonly _serviceBrand: undefined - - // the zones that are attached to each URI, completely independent from current state of editors - private readonly consistentZoneIdsOfURI: Record | undefined> = {} - private readonly infoOfConsistentZoneId: Record IViewZone, - iOther?: (editor: ICodeEditor) => (() => void), - }> = {} - // listener disposables - private readonly disposablesOfEditorId: Record | undefined> = {} - - - // current state of zones on each editor, and the fns to call to remove them. A zone is the actual zone plus whatever iOther you put on it. - private readonly zoneIdsOfEditorId: Record | undefined> = {} - private readonly removeFnOfZoneId: Record void> = {} - private readonly consistentZoneIdOfZoneId: Record = {} - - - constructor( - @ICodeEditorService private readonly _editorService: ICodeEditorService, - ) { - super() - - - const removeZonesFromEditor = (editor: ICodeEditor) => { - const editorId = editor.getId() - for (const zoneId of this.zoneIdsOfEditorId[editorId] ?? []) - this._removeZoneIdFromEditor(editor, zoneId) - } - - // put zones on the editor, based on the consistentZones for that URI - const putZonesOnEditor = (editor: ICodeEditor, uri: URI | null) => { - if (!uri) return - for (const consistentZoneId of this.consistentZoneIdsOfURI[uri.fsPath] ?? []) - this._putZoneOnEditor(editor, consistentZoneId) - } - - - - const addTabSwitchListeners = (editor: ICodeEditor) => { - const editorId = editor.getId() - if (!(editorId in this.disposablesOfEditorId)) - this.disposablesOfEditorId[editorId] = new Set() - - this.disposablesOfEditorId[editorId]!.add( - editor.onDidChangeModel(e => { - removeZonesFromEditor(editor) - putZonesOnEditor(editor, e.newModelUrl) - }) - ) - } - - const initializeEditor = (editor: ICodeEditor) => { - addTabSwitchListeners(editor) - putZonesOnEditor(editor, editor.getModel()?.uri ?? null) - } - - // initialize current editors + any new editors - for (let editor of this._editorService.listCodeEditors()) initializeEditor(editor) - this._register(this._editorService.onCodeEditorAdd(editor => { initializeEditor(editor) })) - - // when an editor is deleted, remove its zones and call any disposables it has - this._register(this._editorService.onCodeEditorRemove(editor => { - const editorId = editor.getId() - - removeZonesFromEditor(editor) - for (const d of this.disposablesOfEditorId[editorId] ?? []) - d.dispose() - delete this.disposablesOfEditorId[editorId] - - })) - - } - - - _putZoneOnEditor(editor: ICodeEditor, consistentZoneId: string) { - const { iZoneFn, iOther } = this.infoOfConsistentZoneId[consistentZoneId] - - editor.changeViewZones(accessor => { - // add zone + other - const zoneId = accessor.addZone(iZoneFn(editor)) - const rmFn = iOther?.(editor) - - const editorId = editor.getId() - if (!(editorId in this.zoneIdsOfEditorId)) - this.zoneIdsOfEditorId[editorId] = new Set() - this.zoneIdsOfEditorId[editorId]!.add(zoneId) - - // fn that describes how to remove zone + other - this.removeFnOfZoneId[zoneId] = () => { - editor.changeViewZones(accessor => accessor.removeZone(zoneId)) - rmFn?.() - } - - this.consistentZoneIdOfZoneId[zoneId] = consistentZoneId - }) - } - - - _removeZoneIdFromEditor(editor: ICodeEditor, zoneId: string) { - - const editorId = editor.getId() - this.zoneIdsOfEditorId[editorId]?.delete(zoneId) - - this.removeFnOfZoneId[zoneId]?.() - delete this.removeFnOfZoneId[zoneId] - - delete this.consistentZoneIdOfZoneId[zoneId] - } - - - addConsistentZoneToURI(uri: URI, iZoneFn: (editor: ICodeEditor) => IViewZone, iOther?: (editor: ICodeEditor) => (() => void)) { - const consistentZoneId = generateUuid() - this.infoOfConsistentZoneId[consistentZoneId] = { iZoneFn, iOther, uri } - - if (!(uri.fsPath in this.consistentZoneIdsOfURI)) - this.consistentZoneIdsOfURI[uri.fsPath] = new Set() - this.consistentZoneIdsOfURI[uri.fsPath]!.add(consistentZoneId) - - const editors = this._editorService.listCodeEditors().filter(editor => editor.getModel()?.uri.fsPath === uri.fsPath) - for (const editor of editors) - this._putZoneOnEditor(editor, consistentZoneId) - - return consistentZoneId - } - - - removeConsistentZoneFromURI(consistentZoneId: string) { - - if (!(consistentZoneId in this.infoOfConsistentZoneId)) - return - - const { uri } = this.infoOfConsistentZoneId[consistentZoneId] - const editors = this._editorService.listCodeEditors().filter(e => e.getModel()?.uri.fsPath === uri.fsPath) - - for (const editor of editors) { - for (const zoneId of this.zoneIdsOfEditorId[editor.getId()] ?? []) { - if (this.consistentZoneIdOfZoneId[zoneId] === consistentZoneId) - this._removeZoneIdFromEditor(editor, zoneId) - } - } - - // clear - this.consistentZoneIdsOfURI[uri.fsPath]?.delete(consistentZoneId) - delete this.infoOfConsistentZoneId[consistentZoneId] - - } - - - -} - -registerSingleton(IZoneStyleService, ZoneStyleService, InstantiationType.Eager); - - diff --git a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts index 50a51301..48091d81 100644 --- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts +++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts @@ -11,7 +11,6 @@ import { ICodeEditor, IOverlayWidget, IViewZone } from '../../../../editor/brows // import { IUndoRedoService } from '../../../../platform/undoRedo/common/undoRedo.js'; import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; // import { throttle } from '../../../../base/common/decorators.js'; -import { inlineDiff_systemMessage } from './prompt/prompts.js'; import { ComputedDiff, findDiffs } from './helpers/findDiffs.js'; import { EndOfLinePreference, IModelDecorationOptions, ITextModel } from '../../../../editor/common/model.js'; import { IRange } from '../../../../editor/common/core/range.js'; @@ -28,6 +27,8 @@ import * as dom from '../../../../base/browser/dom.js'; import { Widget } from '../../../../base/browser/ui/widget.js'; import { URI } from '../../../../base/common/uri.js'; import { LLMFeatureSelection, ServiceSendLLMMessageParams } from '../../../../platform/void/common/llmMessageTypes.js'; +import { IConsistentItemService } from './helperServices/consistentItemService.js'; +import { inlineDiff_systemMessage } from './prompt/prompts.js'; import { ILLMMessageService } from '../../../../platform/void/common/llmMessageService.js'; @@ -132,6 +133,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, // undoRedo service is the history of pressing ctrl+z @ILanguageService private readonly _langService: ILanguageService, @ILLMMessageService private readonly _llmMessageService: ILLMMessageService, + @IConsistentItemService private readonly _zoneStyleService: IConsistentItemService, ) { super(); @@ -150,36 +152,33 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { // it's as if we just called _write, now all we need to do is realign and refresh if (this._weAreWriting) return const uri = model.uri - // realign for (const change of e.changes) { this._realignAllDiffAreasLines(uri, change.text, change.range) } - // refresh this._refreshDiffsInURI(uri) }) ) } - // initialize all existing models + // initialize all existing models + initialize when a new model mounts for (let model of this._modelService.getModels()) { initializeModel(model) } - // initialize whenever a new model mounts this._register(this._modelService.onModelAdded(model => initializeModel(model))); - // this function adds listeners to refresh styles when editor changes tab let initializeEditor = (editor: ICodeEditor) => { const uri = editor.getModel()?.uri ?? null if (uri) this._refreshDiffsInURI(uri) + + // this isn't relevant anymore because consistentItemService takes care of it // called when the user switches tabs (typically there's only 1 editor on the screen, it switches between models, make sure you understand this) - this._register(editor.onDidChangeModel((e) => { - if (e.oldModelUrl) this._refreshDiffsInURI(e.oldModelUrl) - if (e.newModelUrl) this._refreshDiffsInURI(e.newModelUrl) - })) + // this._register(editor.onDidChangeModel((e) => { + // if (e.newModelUrl) this._refreshDiffsInURI(e.newModelUrl) + // if (e.oldModelUrl) this._clearAllDiffsAndStyles(e.oldModelUrl) + // })) } - // add listeners for all existing editors + // add listeners for all existing editors + listen for editor being added for (let editor of this._editorService.listCodeEditors()) { initializeEditor(editor) } - // add listeners when an editor is created - this._register(this._editorService.onCodeEditorAdd(editor => { console.log('ADD EDITOR'); initializeEditor(editor) })) - this._register(this._editorService.onCodeEditorRemove(editor => { console.log('REMOVE EDITOR'); initializeEditor(editor) })) + this._register(this._editorService.onCodeEditorAdd(editor => { initializeEditor(editor) })) + // this._register(this._editorService.onCodeEditorRemove(editor => { console.log('REMOVE EDITOR'); initializeEditor(editor) })) } @@ -198,7 +197,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { ...options })) const disposeHighlight = () => { - if (id) model.changeDecorations(accessor => accessor.removeDecoration(id)) + if (id && !model.isDisposed()) model.changeDecorations(accessor => accessor.removeDecoration(id)) } return disposeHighlight } @@ -227,14 +226,16 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { } - private _addDiffStylesToEditor = (editor: ICodeEditor, diff: Diff) => { + private _addDiffStylesToURI = (uri: URI, diff: Diff) => { const { type, diffid } = diff const disposeInThisEditorFns: (() => void)[] = [] + const model = this._modelService.getModel(uri) + // green decoration and minimap decoration if (type !== 'deletion') { - const fn = this._addLineDecoration(editor.getModel(), diff.startLine, diff.endLine, 'void-greenBG', { + const fn = this._addLineDecoration(model, diff.startLine, diff.endLine, 'void-greenBG', { minimap: { color: { id: 'minimapGutter.addedBackground' }, position: 2 }, overviewRuler: { color: { id: 'editorOverviewRuler.addedForeground' }, position: 7 } }) @@ -244,47 +245,65 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { // red in a view zone if (type !== 'insertion') { - editor.changeViewZones(accessor => { + const consistentZoneId = this._zoneStyleService.addConsistentItemToURI({ + uri, + fn: (editor) => { - const domNode = document.createElement('div'); - domNode.className = 'void-redBG' + const domNode = document.createElement('div'); + domNode.className = 'void-redBG' - const renderOptions = RenderOptions.fromEditor(editor); - // applyFontInfo(domNode, renderOptions.fontInfo) + const renderOptions = RenderOptions.fromEditor(editor); + // applyFontInfo(domNode, renderOptions.fontInfo) - // Compute view-lines based on redText - const redText = diff.originalCode - const lines = redText.split('\n'); - const lineTokens = lines.map(line => LineTokens.createFromTextAndMetadata([{ text: line, metadata: 0 }], this._langService.languageIdCodec)); - const source = new LineSource(lineTokens, lines.map(() => null), false, false) - const result = renderLines(source, renderOptions, [], domNode); + // Compute view-lines based on redText + const redText = diff.originalCode + const lines = redText.split('\n'); + const lineTokens = lines.map(line => LineTokens.createFromTextAndMetadata([{ text: line, metadata: 0 }], this._langService.languageIdCodec)); + const source = new LineSource(lineTokens, lines.map(() => null), false, false) + const result = renderLines(source, renderOptions, [], domNode); - const viewZone: IViewZone = { - // afterLineNumber: computedDiff.startLine - 1, - afterLineNumber: type === 'edit' ? diff.endLine : diff.startLine - 1, - heightInLines: result.heightInLines, - minWidthInPx: result.minWidthInPx, - domNode: domNode, - marginDomNode: document.createElement('div'), // displayed to left - suppressMouseDown: true, - }; + const viewZone: IViewZone = { + // afterLineNumber: computedDiff.startLine - 1, + afterLineNumber: type === 'edit' ? diff.endLine : diff.startLine - 1, + heightInLines: result.heightInLines, + minWidthInPx: result.minWidthInPx, + domNode: domNode, + marginDomNode: document.createElement('div'), // displayed to left + suppressMouseDown: true, + }; - const zoneId = accessor.addZone(viewZone) - disposeInThisEditorFns.push(() => { editor.changeViewZones(accessor => { if (zoneId) accessor.removeZone(zoneId) }) }) + let zoneId: string | null = null + editor.changeViewZones(accessor => { zoneId = accessor.addZone(viewZone) }) + return () => editor.changeViewZones(accessor => { if (zoneId) accessor.removeZone(zoneId) }) + }, + }) + + console.log('added (Z)', consistentZoneId) + + disposeInThisEditorFns.push(() => { console.log('removing (Z)', consistentZoneId); this._zoneStyleService.removeConsistentItemFromURI(consistentZoneId) }) - }); } + // Accept | Reject widget - const buttonsWidget = new AcceptRejectWidget({ - editor, - onAccept: () => { this.acceptDiff({ diffid }) }, - onReject: () => { this.rejectDiff({ diffid }) }, - diffid: diffid.toString(), - startLine: diff.startLine, + const consistentWidgetId = this._zoneStyleService.addConsistentItemToURI({ + uri, + fn: (editor) => { + const buttonsWidget = new AcceptRejectWidget({ + editor, + onAccept: () => { this.acceptDiff({ diffid }) }, + onReject: () => { this.rejectDiff({ diffid }) }, + diffid: diffid.toString(), + startLine: diff.startLine, + }) + return () => { buttonsWidget.dispose() } + } }) - disposeInThisEditorFns.push(() => { buttonsWidget.dispose() }) + console.log('added (O)', consistentWidgetId) + disposeInThisEditorFns.push(() => { console.log('removing (O)', consistentWidgetId); this._zoneStyleService.removeConsistentItemFromURI(consistentWidgetId) }) + + const disposeInEditor = () => { disposeInThisEditorFns.forEach(f => f()) } return disposeInEditor; @@ -499,7 +518,6 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { this._clearAllDiffsAndStyles(uri) // 2. recompute all diffs on each editor with this URI - const editors = this._editorService.listCodeEditors().filter(editor => editor.getModel()?.uri.fsPath === uri.fsPath) const fullFileText = this._readURI(uri) ?? '' @@ -520,10 +538,8 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { diffareaid: diffArea.diffareaid, } - for (let editor of editors) { - const fn = this._addDiffStylesToEditor(editor, newDiff) - this.removeStylesFnsOfURI[uri.fsPath].add(() => fn()) - } + const fn = this._addDiffStylesToURI(uri, newDiff) + this.removeStylesFnsOfURI[uri.fsPath].add(fn) this.diffOfId[diffid] = newDiff diffArea._diffOfId[diffid] = newDiff @@ -651,7 +667,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { _diffOfId: {}, // added later } - console.log('adding uri.fspath', uri.fsPath, diffArea.diffareaid.toString()) + // console.log('adding uri.fspath', uri.fsPath, diffArea.diffareaid.toString()) this.diffAreasOfURI[uri.fsPath].add(diffArea.diffareaid.toString()) this.diffAreaOfId[diffArea.diffareaid] = diffArea