From a1ab8a57963d5db6d725c6c7183417895cc53e3a Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Sun, 22 Dec 2024 23:52:21 -0800 Subject: [PATCH 01/25] misc --- .../inlineDiffService/inlineDiffService.ts | 2 + .../helperServices/abstractAreaService.ts | 186 ++++ .../helperServices/abstractAreaService2.ts | 185 ++++ .../inlineDiffsServiceBackup.ts | 996 ++++++++++++++++++ .../helperServices/zoneStyleService.ts | 186 ++++ .../void/browser/inlineDiffsService.ts | 13 +- 6 files changed, 1559 insertions(+), 9 deletions(-) create mode 100644 src/vs/workbench/contrib/void/browser/helperServices/abstractAreaService.ts create mode 100644 src/vs/workbench/contrib/void/browser/helperServices/abstractAreaService2.ts create mode 100644 src/vs/workbench/contrib/void/browser/helperServices/inlineDiffsServiceBackup.ts create mode 100644 src/vs/workbench/contrib/void/browser/helperServices/zoneStyleService.ts diff --git a/src/vs/editor/browser/services/inlineDiffService/inlineDiffService.ts b/src/vs/editor/browser/services/inlineDiffService/inlineDiffService.ts index 7c2d1324..56637a06 100644 --- a/src/vs/editor/browser/services/inlineDiffService/inlineDiffService.ts +++ b/src/vs/editor/browser/services/inlineDiffService/inlineDiffService.ts @@ -6,6 +6,8 @@ import { ICodeEditor, IViewZone } from '../../editorBrowser.js'; import { IRange } from '../../../common/core/range.js'; import { EditorOption } from '../../../common/config/editorOptions.js'; + +// THIS FILE IS OLD!!! export interface IInlineDiffService { readonly _serviceBrand: undefined; addDiff(editor: ICodeEditor, originalText: string, modifiedRange: IRange): void; diff --git a/src/vs/workbench/contrib/void/browser/helperServices/abstractAreaService.ts b/src/vs/workbench/contrib/void/browser/helperServices/abstractAreaService.ts new file mode 100644 index 00000000..f06bbee4 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/helperServices/abstractAreaService.ts @@ -0,0 +1,186 @@ +// import { Disposable } from '../../../../../base/common/lifecycle.js'; +// import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js'; +// import { IModelService } from '../../../../../editor/common/services/model.js'; +// import { ITextModel, EndOfLinePreference } from '../../../../../editor/common/model.js'; +// import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js'; +// import { URI } from '../../../../../base/common/uri.js'; +// import { IRange } from '../../../../../editor/common/core/range.js'; + + +// // DiffArea +// export interface BaseArea { +// areaId: number; +// uri: URI; +// startLine: number; +// endLine: number; +// } + + +// export abstract class AbstractAreaService extends Disposable { + +// protected _areasOfURI: Record> = {}; +// protected _areaOfId: Record = {}; +// protected _areaIdPool = 0; // for generating unique IDs + +// protected _removeStylesFnsOfURI: Record void>> = {}; + +// private _weAreWriting = false; + +// constructor( +// protected readonly _editorService: ICodeEditorService, +// protected readonly _modelService: IModelService, +// ) { +// super(); + + +// const initializeModel = (model: ITextModel) => { +// const fsPath = model.uri.fsPath; +// if (!this._areasOfURI[fsPath]) { +// this._areasOfURI[fsPath] = new Set(); +// } +// if (!this._removeStylesFnsOfURI[fsPath]) { +// this._removeStylesFnsOfURI[fsPath] = new Set(); +// } + +// // when the user types, realign diff areas and re-render them +// this._register( +// model.onDidChangeContent(e => { +// const uri = model.uri +// // it's as if we just called _write, now all we need to do is realign and refresh +// if (this._weAreWriting) return; +// for (const change of e.changes) this._realignAreasInURI(uri, change.text, change.range); +// this._renderAreaInEditor(uri); +// }) +// ); +// } + +// const initializeEditor = (editor: ICodeEditor) => { +// this._register(editor.onDidChangeModel(e => { +// if (e.oldModelUrl) this._renderAreaInEditor(e.oldModelUrl); +// if (e.newModelUrl) this._renderAreaInEditor(e.newModelUrl); +// })); +// const uri = editor.getModel()?.uri; +// if (uri) this._renderAreaInEditor(uri); +// } + +// // initialize all current models + listen for new models to appear +// for (const model of this._modelService.getModels()) initializeModel(model); +// this._register(this._modelService.onModelAdded(model => initializeModel(model))); + +// // initialize all current editors + listen for new editors to appear +// for (const editor of this._editorService.listCodeEditors()) initializeEditor(editor); +// this._register(this._editorService.onCodeEditorAdd(editor => initializeEditor(editor))); +// } + + +// //-------------------------------------- +// // Realignment + refresh +// //-------------------------------------- + +// // changes the start/line locations of all DiffAreas on the page (adjust their start/end based on the change) based on the change that was recently made +// private _realignAreasInURI(uri: URI, text: string, recentChange: { startLineNumber: number; endLineNumber: number }) { + +// const model = this._getModel(uri) +// if (!model) return + +// // compute net number of newlines lines that were added/removed +// const startLine = recentChange.startLineNumber +// const endLine = recentChange.endLineNumber +// const changeRangeHeight = endLine - startLine + 1 + +// const newTextHeight = (text.match(/\n/g) || []).length + 1 // number of newlines is number of \n's + 1, e.g. "ab\ncd" + +// const deltaNewlines = newTextHeight - changeRangeHeight + +// // compute overlap with each diffArea and shrink/elongate each diffArea accordingly +// for (const diffareaid of this._areasOfURI[model.uri.fsPath] || []) { +// const diffArea = this._areaOfId[diffareaid] + +// // if the diffArea is above the range, it is not affected +// if (diffArea.endLine < startLine) { +// console.log('A') +// continue +// } + +// // console.log('Changing DiffArea:', diffArea.startLine, diffArea.endLine) + +// // if the diffArea fully contains the change, elongate it by the delta amount of newlines +// if (startLine >= diffArea.startLine && endLine <= diffArea.endLine) { +// diffArea.endLine += deltaNewlines +// } +// // if the change fully contains the diffArea, make the diffArea have the same range as the change +// else if (diffArea.startLine > startLine && diffArea.endLine < endLine) { + +// diffArea.startLine = startLine +// diffArea.endLine = startLine + newTextHeight +// console.log('B', diffArea.startLine, diffArea.endLine) +// } +// // if the change contains only the diffArea's top +// else if (diffArea.startLine > startLine) { +// // TODO fill in this case +// console.log('C', diffArea.startLine, diffArea.endLine) +// } +// // if the change contains only the diffArea's bottom +// else if (diffArea.endLine < endLine) { +// const numOverlappingLines = diffArea.endLine - startLine + 1 +// diffArea.endLine += newTextHeight - numOverlappingLines // TODO double check this +// console.log('D', diffArea.startLine, diffArea.endLine) +// } +// // if a diffArea is below the last character of the change, shift the diffArea up/down by the delta amount of newlines +// else if (diffArea.startLine > endLine) { +// diffArea.startLine += deltaNewlines +// diffArea.endLine += deltaNewlines +// console.log('E', diffArea.startLine, diffArea.endLine) +// } + +// // console.log('To:', diffArea.startLine, diffArea.endLine) +// } + +// } + +// //-------------------------------------- +// // Reading + Writing the text +// //-------------------------------------- + +// protected _readURI(uri: URI): string | null { +// const m = this._modelService.getModel(uri); +// if (!m || m.isDisposed()) { +// return null; +// } +// return m.getValue(EndOfLinePreference.LF); +// } + +// protected _getModel(uri: URI): ITextModel | null { +// const m = this._modelService.getModel(uri); +// return (m && !m.isDisposed()) ? m : null; +// } + + +// protected _writeText(uri: URI, text: string, range: IRange) { +// const model = this._getModel(uri); +// if (!model) return; + +// const finalRange = { +// startLineNumber: range.startLineNumber, +// startColumn: range.startColumn ?? 1, +// endLineNumber: range.endLineNumber, +// endColumn: range.endColumn ?? Number.MAX_SAFE_INTEGER +// }; + +// this._weAreWriting = true; +// model.applyEdits([{ range: finalRange, text }]); +// this._weAreWriting = false; +// } + +// //-------------------------------------- +// // Abstract: how to render an area +// //-------------------------------------- +// /** +// * Subclasses override this to define how an area gets +// * painted in a particular editor: decorations, zones, widgets, etc. +// * +// * Return an array of functions that will remove those +// * decorations/zones when needed. +// */ +// protected abstract _renderAreaInEditor(editor: ICodeEditor, area: A): Array<() => void>; +// } diff --git a/src/vs/workbench/contrib/void/browser/helperServices/abstractAreaService2.ts b/src/vs/workbench/contrib/void/browser/helperServices/abstractAreaService2.ts new file mode 100644 index 00000000..9e4a0416 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/helperServices/abstractAreaService2.ts @@ -0,0 +1,185 @@ +// import { Disposable } from '../../../../../base/common/lifecycle.js'; +// import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js'; +// import { IModelService } from '../../../../../editor/common/services/model.js'; +// import { ITextModel, EndOfLinePreference } from '../../../../../editor/common/model.js'; +// import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js'; +// import { URI } from '../../../../../base/common/uri.js'; +// import { IRange } from '../../../../../editor/common/core/range.js'; + + +// // DiffArea +// export interface BaseArea { +// areaId: number; +// uri: URI; +// startLine: number; +// endLine: number; +// } + + +// export abstract class AbstractAreaService extends Disposable { + +// protected _areasOfURI: Record> = {}; +// protected _areaOfId: Record = {}; + +// protected _removeStylesFnsOfURI: Record void>> = {}; + +// private _weAreWriting = false; + +// constructor( +// protected readonly _editorService: ICodeEditorService, +// protected readonly _modelService: IModelService, +// ) { +// super(); + + +// const initializeModel = (model: ITextModel) => { +// const fsPath = model.uri.fsPath; +// if (!this._areasOfURI[fsPath]) { +// this._areasOfURI[fsPath] = new Set(); +// } +// if (!this._removeStylesFnsOfURI[fsPath]) { +// this._removeStylesFnsOfURI[fsPath] = new Set(); +// } + +// // when the user types, realign diff areas and re-render them +// this._register( +// model.onDidChangeContent(e => { +// const uri = model.uri +// // it's as if we just called _write, now all we need to do is realign and refresh +// if (this._weAreWriting) return; +// for (const change of e.changes) this._realignAreasInURI(uri, change.text, change.range); +// this._renderAreaInEditor(uri); +// }) +// ); +// } + +// const initializeEditor = (editor: ICodeEditor) => { +// this._register(editor.onDidChangeModel(e => { +// if (e.oldModelUrl) this._renderAreaInEditor(e.oldModelUrl); +// if (e.newModelUrl) this._renderAreaInEditor(e.newModelUrl); +// })); +// const uri = editor.getModel()?.uri; +// if (uri) this._renderAreaInEditor(uri); +// } + +// // initialize all current models + listen for new models to appear +// for (const model of this._modelService.getModels()) initializeModel(model); +// this._register(this._modelService.onModelAdded(model => initializeModel(model))); + +// // initialize all current editors + listen for new editors to appear +// for (const editor of this._editorService.listCodeEditors()) initializeEditor(editor); +// this._register(this._editorService.onCodeEditorAdd(editor => initializeEditor(editor))); +// } + + +// //-------------------------------------- +// // Realignment + refresh +// //-------------------------------------- + +// // changes the start/line locations of all DiffAreas on the page (adjust their start/end based on the change) based on the change that was recently made +// private _realignAreasInURI(uri: URI, text: string, recentChange: { startLineNumber: number; endLineNumber: number }) { + +// const model = this._getModel(uri) +// if (!model) return + +// // compute net number of newlines lines that were added/removed +// const startLine = recentChange.startLineNumber +// const endLine = recentChange.endLineNumber +// const changeRangeHeight = endLine - startLine + 1 + +// const newTextHeight = (text.match(/\n/g) || []).length + 1 // number of newlines is number of \n's + 1, e.g. "ab\ncd" + +// const deltaNewlines = newTextHeight - changeRangeHeight + +// // compute overlap with each diffArea and shrink/elongate each diffArea accordingly +// for (const diffareaid of this._areasOfURI[model.uri.fsPath] || []) { +// const diffArea = this._areaOfId[diffareaid] + +// // if the diffArea is above the range, it is not affected +// if (diffArea.endLine < startLine) { +// console.log('A') +// continue +// } + +// // console.log('Changing DiffArea:', diffArea.startLine, diffArea.endLine) + +// // if the diffArea fully contains the change, elongate it by the delta amount of newlines +// if (startLine >= diffArea.startLine && endLine <= diffArea.endLine) { +// diffArea.endLine += deltaNewlines +// } +// // if the change fully contains the diffArea, make the diffArea have the same range as the change +// else if (diffArea.startLine > startLine && diffArea.endLine < endLine) { + +// diffArea.startLine = startLine +// diffArea.endLine = startLine + newTextHeight +// console.log('B', diffArea.startLine, diffArea.endLine) +// } +// // if the change contains only the diffArea's top +// else if (diffArea.startLine > startLine) { +// // TODO fill in this case +// console.log('C', diffArea.startLine, diffArea.endLine) +// } +// // if the change contains only the diffArea's bottom +// else if (diffArea.endLine < endLine) { +// const numOverlappingLines = diffArea.endLine - startLine + 1 +// diffArea.endLine += newTextHeight - numOverlappingLines // TODO double check this +// console.log('D', diffArea.startLine, diffArea.endLine) +// } +// // if a diffArea is below the last character of the change, shift the diffArea up/down by the delta amount of newlines +// else if (diffArea.startLine > endLine) { +// diffArea.startLine += deltaNewlines +// diffArea.endLine += deltaNewlines +// console.log('E', diffArea.startLine, diffArea.endLine) +// } + +// // console.log('To:', diffArea.startLine, diffArea.endLine) +// } + +// } + +// //-------------------------------------- +// // Reading + Writing the text +// //-------------------------------------- + +// protected _readURI(uri: URI): string | null { +// const m = this._modelService.getModel(uri); +// if (!m || m.isDisposed()) { +// return null; +// } +// return m.getValue(EndOfLinePreference.LF); +// } + +// protected _getModel(uri: URI): ITextModel | null { +// const m = this._modelService.getModel(uri); +// return (m && !m.isDisposed()) ? m : null; +// } + + +// protected _writeText(uri: URI, text: string, range: IRange) { +// const model = this._getModel(uri); +// if (!model) return; + +// const finalRange = { +// startLineNumber: range.startLineNumber, +// startColumn: range.startColumn ?? 1, +// endLineNumber: range.endLineNumber, +// endColumn: range.endColumn ?? Number.MAX_SAFE_INTEGER +// }; + +// this._weAreWriting = true; +// model.applyEdits([{ range: finalRange, text }]); +// this._weAreWriting = false; +// } + +// //-------------------------------------- +// // Abstract: how to render an area +// //-------------------------------------- +// /** +// * Subclasses override this to define how an area gets +// * painted in a particular editor: decorations, zones, widgets, etc. +// * +// * Return an array of functions that will remove those +// * decorations/zones when needed. +// */ +// protected abstract _renderAreaInEditor(editor: ICodeEditor, area: A): Array<() => void>; +// } diff --git a/src/vs/workbench/contrib/void/browser/helperServices/inlineDiffsServiceBackup.ts b/src/vs/workbench/contrib/void/browser/helperServices/inlineDiffsServiceBackup.ts new file mode 100644 index 00000000..169f7d50 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/helperServices/inlineDiffsServiceBackup.ts @@ -0,0 +1,996 @@ +// /*--------------------------------------------------------------------------------------------- +// * Copyright (c) Glass Devtools, Inc. All rights reserved. +// * Void Editor additions licensed under the AGPL 3.0 License. +// *--------------------------------------------------------------------------------------------*/ + +// import { Disposable } from '../../../../base/common/lifecycle.js'; +// import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; +// import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; +// import { ICodeEditor, IOverlayWidget, IViewZone } from '../../../../editor/browser/editorBrowser.js'; + +// // 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'; +// import { registerColor } from '../../../../platform/theme/common/colorUtils.js'; +// import { Color, RGBA } from '../../../../base/common/color.js'; +// import { IModelService } from '../../../../editor/common/services/model.js'; +// import { IUndoRedoElement, IUndoRedoService, UndoRedoElementType } from '../../../../platform/undoRedo/common/undoRedo.js'; +// import { LineSource, renderLines, RenderOptions } from '../../../../editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js'; +// import { LineTokens } from '../../../../editor/common/tokens/lineTokens.js'; +// import { ILanguageService } from '../../../../editor/common/languages/language.js'; +// // import { IModelService } from '../../../../editor/common/services/model.js'; + +// 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 { ILLMMessageService } from '../../../../platform/void/common/llmMessageService.js'; + + +// const configOfBG = (color: Color) => { +// return { dark: color, light: color, hcDark: color, hcLight: color, } +// } +// // gets converted to --vscode-void-greenBG, see void.css +// const greenBG = new Color(new RGBA(155, 185, 85, .3)); // default is RGBA(155, 185, 85, .2) +// registerColor('void.greenBG', configOfBG(greenBG), '', true); + +// const redBG = new Color(new RGBA(255, 0, 0, .3)); // default is RGBA(255, 0, 0, .2) +// registerColor('void.redBG', configOfBG(redBG), '', true); + +// const sweepBG = new Color(new RGBA(100, 100, 100, .2)); +// registerColor('void.sweepBG', configOfBG(sweepBG), '', true); + +// const highlightBG = new Color(new RGBA(100, 100, 100, .1)); +// registerColor('void.highlightBG', configOfBG(highlightBG), '', true); + +// const sweepIdxBG = new Color(new RGBA(100, 100, 100, .5)); +// registerColor('void.sweepIdxBG', configOfBG(sweepIdxBG), '', true); + + +// export type Diff = { +// diffid: number; +// diffareaid: number; // the diff area this diff belongs to, "computed" +// } & ComputedDiff + + +// // _ means anything we don't include if we clone it +// // DiffArea.originalStartLine is the line in originalCode (not the file) +// type DiffArea = { +// diffareaid: number; +// originalCode: string; +// startLine: number; +// endLine: number; +// shouldHighlight: boolean; // should visually highlight this DiffArea + +// _URI: URI; // typically we get the URI from model +// _diffOfId: Record; // diffid -> diff in this DiffArea + +// } & ({ +// _sweepState: { +// isStreaming: true; +// line: number; +// } | { +// isStreaming: false; +// line: null; +// }; +// }) + +// const diffAreaSnapshotKeys = [ +// 'diffareaid', +// 'originalCode', +// 'startLine', +// 'endLine', +// 'shouldHighlight', +// ] as const satisfies (keyof DiffArea)[] + +// type DiffAreaSnapshot = Pick + + + +// type HistorySnapshot = { +// snapshottedDiffAreaOfId: Record; +// entireFileCode: string; +// } & +// ({ +// type: 'Ctrl+K'; +// ctrlKText: string; +// } | { +// type: 'Ctrl+L'; +// }) + + + +// export interface IInlineDiffsService { +// readonly _serviceBrand: undefined; +// startStreaming(params: LLMFeatureSelection, str: string): void; +// } + +// export const IInlineDiffsService = createDecorator('inlineDiffAreasService'); + +// class InlineDiffsService extends Disposable implements IInlineDiffsService { +// _serviceBrand: undefined; + + +// // URI <--> model +// removeStylesFnsOfURI: Record> = {} // functions that remove the styles of this uri +// diffAreasOfURI: Record> = {} + +// diffAreaOfId: Record = {}; +// diffOfId: Record = {}; // redundant with diffArea._diffs + +// _diffareaidPool = 0 // each diffarea has an id +// _diffidPool = 0 // each diff has an id + +// constructor( +// // @IHistoryService private readonly _historyService: IHistoryService, // history service is the history of pressing alt left/right +// @ICodeEditorService private readonly _editorService: ICodeEditorService, +// @IModelService private readonly _modelService: IModelService, +// @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, // undoRedo service is the history of pressing ctrl+z +// @ILanguageService private readonly _langService: ILanguageService, +// @ILLMMessageService private readonly _llmMessageService: ILLMMessageService, +// ) { +// super(); + +// // this function initializes data structures and listens for changes +// const initializeModel = (model: ITextModel) => { +// if (!(model.uri.fsPath in this.diffAreasOfURI)) { +// this.diffAreasOfURI[model.uri.fsPath] = new Set(); +// } +// if (!(model.uri.fsPath in this.removeStylesFnsOfURI)) { +// this.removeStylesFnsOfURI[model.uri.fsPath] = new Set(); +// } + +// // when the user types, realign diff areas and re-render them +// this._register( +// model.onDidChangeContent(e => { +// // 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 +// 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) + +// // 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) +// })) +// } +// // add listeners for all existing editors +// 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) })) + +// } + + + + +// // highlight the region +// private _addLineDecoration = (model: ITextModel | null, startLine: number, endLine: number, className: string, options?: Partial) => { +// if (model === null) return +// const id = model.changeDecorations(accessor => accessor.addDecoration( +// { startLineNumber: startLine, startColumn: 1, endLineNumber: endLine, endColumn: Number.MAX_SAFE_INTEGER }, +// { +// className: className, +// description: className, +// isWholeLine: true, +// ...options +// })) +// const disposeHighlight = () => { +// if (id) model.changeDecorations(accessor => accessor.removeDecoration(id)) +// } +// return disposeHighlight +// } + + + +// private _addDiffAreaStylesToURI = (uri: URI) => { +// const model = this._getModel(uri) + +// for (const diffareaid of this.diffAreasOfURI[uri.fsPath]) { +// const diffArea = this.diffAreaOfId[diffareaid] +// // add sweep styles to the diffArea +// if (diffArea._sweepState.isStreaming) { +// // sweepLine ... sweepLine +// const fn1 = this._addLineDecoration(model, diffArea._sweepState.line, diffArea._sweepState.line, 'void-sweepIdxBG') +// // sweepLine+1 ... endLine +// const fn2 = this._addLineDecoration(model, diffArea._sweepState.line + 1, diffArea.endLine, 'void-sweepBG') +// this.removeStylesFnsOfURI[uri.fsPath].add(() => { fn1?.(); fn2?.(); }) +// } +// // highlight the diffArea +// if (diffArea.shouldHighlight) { +// const fn = this._addLineDecoration(model, diffArea.startLine, diffArea.endLine, 'void-highlightBG') +// this.removeStylesFnsOfURI[uri.fsPath].add(() => fn?.()); +// } +// } +// } + + +// private _addDiffStylesToEditor = (editor: ICodeEditor, diff: Diff) => { +// const { type, diffid } = diff + +// const disposeInThisEditorFns: (() => void)[] = [] + +// // green decoration and minimap decoration +// if (type !== 'deletion') { +// const fn = this._addLineDecoration(editor.getModel(), diff.startLine, diff.endLine, 'void-greenBG', { +// minimap: { color: { id: 'minimapGutter.addedBackground' }, position: 2 }, +// overviewRuler: { color: { id: 'editorOverviewRuler.addedForeground' }, position: 7 } +// }) +// disposeInThisEditorFns.push(() => { fn?.() }) +// } + + +// // red in a view zone +// if (type !== 'insertion') { +// editor.changeViewZones(accessor => { + +// const domNode = document.createElement('div'); +// domNode.className = 'void-redBG' + +// 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); + +// 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) }) }) + +// }); +// } + + +// // Accept | Reject widget +// const buttonsWidget = new AcceptRejectWidget({ +// editor, +// onAccept: () => { this.acceptDiff({ diffid }) }, +// onReject: () => { this.rejectDiff({ diffid }) }, +// diffid: diffid.toString(), +// startLine: diff.startLine, +// }) +// disposeInThisEditorFns.push(() => { buttonsWidget.dispose() }) + +// const disposeInEditor = () => { disposeInThisEditorFns.forEach(f => f()) } +// return disposeInEditor; + +// } + + + +// private _getModel(uri: URI) { +// const model = this._modelService.getModel(uri) +// if (!model || model.isDisposed()) { +// return null +// } +// return model +// } +// private _readURI(uri: URI): string | null { +// return this._getModel(uri)?.getValue(EndOfLinePreference.LF) ?? null +// } +// private _getNumLines(uri: URI): number | null { +// return this._getModel(uri)?.getLineCount() ?? null +// } + + +// _weAreWriting = false +// private _writeText(uri: URI, text: string, range: IRange) { +// const model = this._getModel(uri) +// if (!model) return + +// this._weAreWriting = true +// model.applyEdits([{ range, text }]) // applies edits without adding them to undo/redo stack +// this._weAreWriting = false + +// this._realignAllDiffAreasLines(uri, text, range) +// } + + + + +// private _addToHistory(uri: URI) { + +// const getCurrentSnapshot = (): HistorySnapshot => { +// const diffAreaOfId = this.diffAreaOfId + +// const snapshottedDiffAreaOfId: Record = {} +// for (const diffareaid in diffAreaOfId) { +// const diffArea = diffAreaOfId[diffareaid] +// snapshottedDiffAreaOfId[diffareaid] = structuredClone( // a structured clone must be on a JSON object +// Object.fromEntries(diffAreaSnapshotKeys.map(key => [key, diffArea[key]])) +// ) as DiffAreaSnapshot +// } +// return { +// snapshottedDiffAreaOfId, +// entireFileCode: this._readURI(uri) ?? '', // the whole file's code +// type: 'Ctrl+L', +// } +// } + +// const restoreDiffAreas = (snapshot: HistorySnapshot) => { +// const { snapshottedDiffAreaOfId, entireFileCode: entireModelCode } = structuredClone(snapshot) // don't want to destroy the snapshot + +// // delete all current decorations (diffs, sweep styles) so we don't have any unwanted leftover decorations +// this._clearAllDiffsAndStyles(uri) + +// // restore diffAreaOfId and diffAreasOfModelId +// this.diffAreaOfId = {} +// this.diffAreasOfURI[uri.fsPath].clear() +// for (const diffareaid in snapshottedDiffAreaOfId) { +// this.diffAreaOfId[diffareaid] = { +// ...snapshottedDiffAreaOfId[diffareaid], +// _diffOfId: {}, +// _URI: uri, +// _sweepState: { +// isStreaming: false, +// line: null, +// }, +// } +// this.diffAreasOfURI[uri.fsPath].add(diffareaid) +// } + +// // restore file content +// const numLines = this._getNumLines(uri) +// if (numLines === null) return +// this._writeText(uri, entireModelCode, { startColumn: 1, startLineNumber: 1, endLineNumber: numLines, endColumn: Number.MAX_SAFE_INTEGER }) + +// // restore all the decorations +// this._refreshDiffsInURI(uri) +// } + +// const beforeSnapshot: HistorySnapshot = getCurrentSnapshot() +// let afterSnapshot: HistorySnapshot | null = null + +// const elt: IUndoRedoElement = { +// type: UndoRedoElementType.Resource, +// resource: uri, +// label: 'Void Changes', +// code: 'undoredo.inlineDiffs', +// undo: () => { restoreDiffAreas(beforeSnapshot) }, +// redo: () => { if (afterSnapshot) restoreDiffAreas(afterSnapshot) } +// } +// this._undoRedoService.pushElement(elt) + +// const onFinishEdit = () => { afterSnapshot = getCurrentSnapshot() } +// return { onFinishEdit } +// } + + +// // delete diffOfId and diffArea._diffOfId +// private _deleteDiff(diff: Diff) { +// const diffArea = this.diffAreaOfId[diff.diffareaid] +// delete diffArea._diffOfId[diff.diffid] +// delete this.diffOfId[diff.diffid] +// } + +// private _deleteDiffs(diffArea: DiffArea) { +// for (const diffid in diffArea._diffOfId) { +// const diff = diffArea._diffOfId[diffid] +// this._deleteDiff(diff) +// } +// } + +// private _clearAllDiffsAndStyles(uri: URI) { +// for (let diffareaid of this.diffAreasOfURI[uri.fsPath]) { +// const diffArea = this.diffAreaOfId[diffareaid] +// this._deleteDiffs(diffArea) +// } +// for (const removeStyleFn of this.removeStylesFnsOfURI[uri.fsPath]) { +// removeStyleFn() +// } +// this.removeStylesFnsOfURI[uri.fsPath].clear() +// } + + + +// // delete all diffs, update diffAreaOfId, update diffAreasOfModelId +// private _deleteDiffArea(diffArea: DiffArea) { +// this._deleteDiffs(diffArea) +// delete this.diffAreaOfId[diffArea.diffareaid] +// this.diffAreasOfURI[diffArea._URI.fsPath].delete(diffArea.diffareaid.toString()) +// } + + + + + +// // changes the start/line locations of all DiffAreas on the page (adjust their start/end based on the change) based on the change that was recently made +// private _realignAllDiffAreasLines(uri: URI, text: string, recentChange: { startLineNumber: number; endLineNumber: number }) { + +// const model = this._getModel(uri) +// if (!model) return + +// // compute net number of newlines lines that were added/removed +// const startLine = recentChange.startLineNumber +// const endLine = recentChange.endLineNumber +// const changeRangeHeight = endLine - startLine + 1 + +// const newTextHeight = (text.match(/\n/g) || []).length + 1 // number of newlines is number of \n's + 1, e.g. "ab\ncd" + +// const deltaNewlines = newTextHeight - changeRangeHeight + +// // compute overlap with each diffArea and shrink/elongate each diffArea accordingly +// for (const diffareaid of this.diffAreasOfURI[model.uri.fsPath] || []) { +// const diffArea = this.diffAreaOfId[diffareaid] + +// // if the diffArea is above the range, it is not affected +// if (diffArea.endLine < startLine) { +// console.log('A') +// continue +// } + +// // console.log('Changing DiffArea:', diffArea.startLine, diffArea.endLine) + +// // if the diffArea fully contains the change, elongate it by the delta amount of newlines +// if (startLine >= diffArea.startLine && endLine <= diffArea.endLine) { +// diffArea.endLine += deltaNewlines +// } +// // if the change fully contains the diffArea, make the diffArea have the same range as the change +// else if (diffArea.startLine > startLine && diffArea.endLine < endLine) { + +// diffArea.startLine = startLine +// diffArea.endLine = startLine + newTextHeight +// console.log('B', diffArea.startLine, diffArea.endLine) +// } +// // if the change contains only the diffArea's top +// else if (diffArea.startLine > startLine) { +// // TODO fill in this case +// console.log('C', diffArea.startLine, diffArea.endLine) +// } +// // if the change contains only the diffArea's bottom +// else if (diffArea.endLine < endLine) { +// const numOverlappingLines = diffArea.endLine - startLine + 1 +// diffArea.endLine += newTextHeight - numOverlappingLines // TODO double check this +// console.log('D', diffArea.startLine, diffArea.endLine) +// } +// // if a diffArea is below the last character of the change, shift the diffArea up/down by the delta amount of newlines +// else if (diffArea.startLine > endLine) { +// diffArea.startLine += deltaNewlines +// diffArea.endLine += deltaNewlines +// console.log('E', diffArea.startLine, diffArea.endLine) +// } + +// // console.log('To:', diffArea.startLine, diffArea.endLine) +// } + +// } + + +// private _refreshDiffsInURI(uri: URI) { +// const content = this._readURI(uri) +// if (content === null) return + +// // 1. clear Diffs and styles +// 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) ?? '' + + +// // go thru all diffareas in this URI, creating diffs and adding styles to it +// for (let diffareaid of this.diffAreasOfURI[uri.fsPath]) { +// const diffArea = this.diffAreaOfId[diffareaid] + +// const newDiffAreaCode = fullFileText.split('\n').slice((diffArea.startLine - 1), (diffArea.endLine - 1) + 1).join('\n') +// const computedDiffs = findDiffs(diffArea.originalCode, newDiffAreaCode) + +// for (let computedDiff of computedDiffs) { +// const diffid = this._diffidPool++ + +// // create a Diff of it +// const newDiff: Diff = { +// ...computedDiff, +// diffid: diffid, +// diffareaid: diffArea.diffareaid, +// } + +// for (let editor of editors) { +// const fn = this._addDiffStylesToEditor(editor, newDiff) +// this.removeStylesFnsOfURI[uri.fsPath].add(() => fn()) +// } + +// this.diffOfId[diffid] = newDiff +// diffArea._diffOfId[diffid] = newDiff +// } + +// // update styles on this DiffArea +// this._addDiffAreaStylesToURI(uri) +// } + + +// } + + +// // @throttle(100) +// private _writeDiffAreaLLMText(diffArea: DiffArea, newCodeSoFar: string) { + +// // ----------- 1. Write the new code to the document ----------- +// // figure out where to highlight based on where the AI is in the stream right now, use the last diff to figure that out +// const uri = diffArea._URI +// const computedDiffs = findDiffs(diffArea.originalCode, newCodeSoFar) + +// // if not streaming, just write the new code +// if (!diffArea._sweepState.isStreaming) { +// this._writeText(uri, newCodeSoFar, +// { startLineNumber: diffArea.startLine, startColumn: 1, endLineNumber: diffArea.endLine, endColumn: Number.MAX_SAFE_INTEGER, } // 1-indexed +// ) +// } +// // if streaming, use diffs to figure out where to write new code +// else { +// // these are two different coordinate systems - new and old line number +// let newFileEndLine: number // get new[0...newStoppingPoint] with line=newStoppingPoint highlighted +// let oldFileStartLine: number // get original[oldStartingPoint...] + +// const lastDiff = computedDiffs.pop() + +// if (!lastDiff) { +// // if the writing is identical so far, display no changes +// newFileEndLine = 1 +// oldFileStartLine = 1 +// } +// else { +// if (lastDiff.type === 'insertion') { +// newFileEndLine = lastDiff.endLine +// oldFileStartLine = lastDiff.originalStartLine +// } +// else if (lastDiff.type === 'deletion') { +// newFileEndLine = lastDiff.startLine +// oldFileStartLine = lastDiff.originalStartLine +// } +// else if (lastDiff.type === 'edit') { +// newFileEndLine = lastDiff.endLine +// oldFileStartLine = lastDiff.originalStartLine +// } +// else { +// throw new Error(`Void: diff.type not recognized on: ${lastDiff}`) +// } +// } + +// diffArea._sweepState.line = newFileEndLine + +// // lines are 1-indexed +// const newFileTop = newCodeSoFar.split('\n').slice(0, (newFileEndLine - 1)).join('\n') +// const oldFileBottom = diffArea.originalCode.split('\n').slice((oldFileStartLine - 1), Infinity).join('\n') + +// const newCode = `${newFileTop}\n${oldFileBottom}` + +// this._writeText(uri, newCode, +// { startLineNumber: diffArea.startLine, startColumn: 1, endLineNumber: diffArea.endLine, endColumn: Number.MAX_SAFE_INTEGER, } // 1-indexed +// ) + +// } + +// return computedDiffs + +// } + + + + +// private async _initializeStream(opts: LLMFeatureSelection, diffRepr: string, uri: URI,) { + +// // diff area begin and end line +// const numLines = this._getNumLines(uri) +// if (numLines === null) return + +// const beginLine = 1 +// const endLine = numLines + +// // check if there's overlap with any other diffAreas and return early if there is +// for (const diffareaid of this.diffAreasOfURI[uri.fsPath]) { +// const da2 = this.diffAreaOfId[diffareaid] +// if (!da2) continue +// const noOverlap = da2.startLine > endLine || da2.endLine < beginLine +// if (!noOverlap) { +// // TODO add a message here that says this to the user too +// console.error('Not diffing because found overlap:', this.diffAreasOfURI[uri.fsPath], beginLine, endLine) +// return +// } +// } + +// const currentFileStr = this._readURI(uri) +// if (currentFileStr === null) return +// const originalCode = currentFileStr.split('\n').slice((beginLine - 1), (endLine - 1) + 1).join('\n') + +// // add to history +// const { onFinishEdit } = this._addToHistory(uri) + +// // create a diffArea for the stream +// const diffareaid = this._diffareaidPool++ + +// // in ctrl+L the start and end lines are the full document +// const diffArea: DiffArea = { +// diffareaid: diffareaid, +// // originalStartLine: beginLine, +// // originalEndLine: endLine, +// originalCode: originalCode, +// startLine: beginLine, +// endLine: endLine, // starts out the same as the current file +// shouldHighlight: false, +// _URI: uri, +// _sweepState: { +// isStreaming: true, +// line: 1, +// }, +// _diffOfId: {}, // added later +// } + +// console.log('adding uri.fspath', uri.fsPath, diffArea.diffareaid.toString()) +// this.diffAreasOfURI[uri.fsPath].add(diffArea.diffareaid.toString()) +// this.diffAreaOfId[diffArea.diffareaid] = diffArea + +// // actually call the LLM +// const promptContent = `\ +// ORIGINAL_CODE +// \`\`\` +// ${originalCode} +// \`\`\` + +// DIFF +// \`\`\` +// ${diffRepr} +// \`\`\` + +// INSTRUCTIONS +// Please finish writing the new file by applying the diff to the original file. Return ONLY the completion of the file, without any explanation. +// ` + + +// await new Promise((resolve, reject) => { + +// let streamRequestId: string | null = null + +// const object: ServiceSendLLMMessageParams = { +// logging: { loggingName: 'streamChunk' }, +// messages: [ +// { role: 'system', content: inlineDiff_systemMessage, }, +// // TODO include more context too +// { role: 'user', content: promptContent, } +// ], +// onText: ({ newText, fullText }) => { +// this._writeDiffAreaLLMText(diffArea, fullText) +// this._refreshDiffsInURI(uri) +// }, +// onFinalMessage: ({ fullText }) => { +// this._writeText(uri, fullText, +// { startLineNumber: diffArea.startLine, startColumn: 1, endLineNumber: diffArea.endLine, endColumn: Number.MAX_SAFE_INTEGER }, // 1-indexed +// ) +// diffArea._sweepState = { isStreaming: false, line: null } +// this._refreshDiffsInURI(uri) +// resolve(); +// }, +// onError: (e: any) => { +// console.error('Error rewriting file with diff', e); +// // TODO indicate there was an error +// if (streamRequestId) +// this._llmMessageService.abort(streamRequestId) + +// diffArea._sweepState = { isStreaming: false, line: null } +// resolve(); +// }, +// ...opts +// } + +// streamRequestId = this._llmMessageService.sendLLMMessage(object) +// }) + +// onFinishEdit() + +// } + + + + + + +// async startStreaming(opts: LLMFeatureSelection, userMessage: string) { + +// const editor = this._editorService.getActiveCodeEditor() +// if (!editor) return + +// const uri = editor.getModel()?.uri +// if (!uri) return + +// // TODO reject all diffs in the diff area + +// // TODO deselect user's cursor + +// this._initializeStream(opts, userMessage, uri) +// } + + +// interruptStreaming() { +// // TODO add abort +// } + + + +// addDiffArea({ uri, startLine, endLine, originalCode }: { uri: URI, startLine: number, endLine: number, originalCode: string }) { +// const diffareaid = this._diffareaidPool++ + +// const diffArea: DiffArea = { +// diffareaid: diffareaid, +// originalCode, +// startLine, +// endLine, +// shouldHighlight: true, +// _URI: uri, +// _sweepState: { +// isStreaming: false, +// line: null, +// }, +// _diffOfId: {}, +// } + +// this.diffAreasOfURI[uri.fsPath].add(diffArea.diffareaid.toString()) +// this.diffAreaOfId[diffArea.diffareaid] = diffArea + +// this._refreshDiffsInURI(uri) +// } + + + + + +// // called on void.acceptDiff +// public async acceptDiff({ diffid }: { diffid: number }) { + +// const diff = this.diffOfId[diffid] +// if (!diff) return + +// const { diffareaid } = diff +// const diffArea = this.diffAreaOfId[diffareaid] +// if (!diffArea) return + +// const uri = diffArea._URI + +// // add to history +// const { onFinishEdit } = this._addToHistory(uri) + +// const originalLines = diffArea.originalCode.split('\n') +// let newOriginalCode: string + +// if (diff.type === 'deletion') { +// newOriginalCode = [ +// ...originalLines.slice(0, (diff.originalStartLine - 1)), // everything before startLine +// // <-- deletion has nothing here +// ...originalLines.slice((diff.originalEndLine - 1) + 1, Infinity) // everything after endLine +// ].join('\n') +// } +// else if (diff.type === 'insertion') { +// newOriginalCode = [ +// ...originalLines.slice(0, (diff.originalStartLine - 1)), // everything before startLine +// diff.code, // code +// ...originalLines.slice((diff.originalStartLine - 1), Infinity) // startLine (inclusive) and on (no +1) +// ].join('\n') +// } +// else if (diff.type === 'edit') { +// newOriginalCode = [ +// ...originalLines.slice(0, (diff.originalStartLine - 1)), // everything before startLine +// diff.code, // code +// ...originalLines.slice((diff.originalEndLine - 1) + 1, Infinity) // everything after endLine +// ].join('\n') +// } +// else { +// throw new Error(`Void error: ${diff}.type not recognized`) +// } + +// // console.log('DIFF', diff) +// // console.log('DIFFAREA', diffArea) +// // console.log('ORIGINAL', diffArea.originalCode) +// // console.log('new original Code', newOriginalCode) + +// // update code now accepted as original +// diffArea.originalCode = newOriginalCode + +// // delete the diff +// this._deleteDiff(diff) + +// // diffArea should be removed if it has no more diffs in it +// if (Object.keys(diffArea._diffOfId).length === 0) { +// this._deleteDiffArea(diffArea) +// } + +// this._refreshDiffsInURI(uri) + +// onFinishEdit() + +// } + + + +// // called on void.rejectDiff +// public async rejectDiff({ diffid }: { diffid: number }) { + +// const diff = this.diffOfId[diffid] +// if (!diff) return + +// const { diffareaid } = diff +// const diffArea = this.diffAreaOfId[diffareaid] +// if (!diffArea) return + +// const uri = diffArea._URI + +// // add to history +// const { onFinishEdit } = this._addToHistory(uri) + +// let writeText: string +// let toRange: IRange + +// // if it was a deletion, need to re-insert +// // (this image applies to writeText and toRange, not newOriginalCode) +// // A +// // |B <-- deleted here, diff.startLine == diff.endLine +// // C +// if (diff.type === 'deletion') { +// writeText = diff.originalCode + '\n' +// toRange = { startLineNumber: diff.startLine, startColumn: 1, endLineNumber: diff.startLine, endColumn: 1 } +// } +// // if it was an insertion, need to delete all the lines +// // (this image applies to writeText and toRange, not newOriginalCode) +// // |A <-- startLine +// // B| <-- endLine (we want to delete this whole line) +// // C +// else if (diff.type === 'insertion') { +// writeText = '' +// toRange = { startLineNumber: diff.startLine, startColumn: 1, endLineNumber: diff.endLine + 1, endColumn: 1 } // 1-indexed +// } +// // if it was an edit, just edit the range +// // (this image applies to writeText and toRange, not newOriginalCode) +// // |A <-- startLine +// // B| <-- endLine (just swap out these lines for the originalCode) +// // C +// else if (diff.type === 'edit') { +// writeText = diff.originalCode +// toRange = { startLineNumber: diff.startLine, startColumn: 1, endLineNumber: diff.endLine, endColumn: Number.MAX_SAFE_INTEGER } // 1-indexed +// } +// else { +// throw new Error(`Void error: ${diff}.type not recognized`) +// } + +// // update the file +// this._writeText(uri, writeText, toRange) + +// // originalCode does not change! + +// // delete the diff +// this._deleteDiff(diff) + +// // diffArea should be removed if it has no more diffs in it +// if (Object.keys(diffArea._diffOfId).length === 0) { +// this._deleteDiffArea(diffArea) +// } + +// this._refreshDiffsInURI(uri) + +// onFinishEdit() + +// } + +// } + +// registerSingleton(IInlineDiffsService, InlineDiffsService, InstantiationType.Eager); + + + + +// class AcceptRejectWidget extends Widget implements IOverlayWidget { + +// public getId() { return this.ID } +// public getDomNode() { return this._domNode; } +// public getPosition() { return null } + +// private readonly _domNode: HTMLElement; +// private readonly editor +// private readonly ID +// private readonly startLine + +// constructor({ editor, onAccept, onReject, diffid, startLine }: { editor: ICodeEditor; onAccept: () => void; onReject: () => void; diffid: string, startLine: number }) { +// super() + +// this.ID = editor.getModel()?.uri.fsPath + diffid; +// this.editor = editor; +// this.startLine = startLine; + +// // Create container div with buttons +// const { acceptButton, rejectButton, buttons } = dom.h('div@buttons', [ +// dom.h('button@acceptButton', []), +// dom.h('button@rejectButton', []) +// ]); + +// // Style the container +// buttons.style.display = 'flex'; +// buttons.style.position = 'absolute'; +// buttons.style.gap = '4px'; +// buttons.style.padding = '4px'; +// buttons.style.zIndex = '1000'; + + +// // Style accept button +// acceptButton.onclick = onAccept; +// acceptButton.textContent = 'Accept'; +// acceptButton.style.backgroundColor = '#28a745'; +// acceptButton.style.color = 'white'; +// acceptButton.style.border = 'none'; +// acceptButton.style.padding = '4px 8px'; +// acceptButton.style.borderRadius = '3px'; +// acceptButton.style.cursor = 'pointer'; + +// // Style reject button +// rejectButton.onclick = onReject; +// rejectButton.textContent = 'Reject'; +// rejectButton.style.backgroundColor = '#dc3545'; +// rejectButton.style.color = 'white'; +// rejectButton.style.border = 'none'; +// rejectButton.style.padding = '4px 8px'; +// rejectButton.style.borderRadius = '3px'; +// rejectButton.style.cursor = 'pointer'; + +// this._domNode = buttons; + +// const updateTop = () => { +// const topPx = editor.getTopForLineNumber(this.startLine) - editor.getScrollTop() +// this._domNode.style.top = `${topPx}px` +// } +// const updateLeft = () => { +// const leftPx = 0//editor.getScrollLeft() - editor.getScrollWidth() +// this._domNode.style.left = `${leftPx}px` +// } + +// updateTop() +// updateLeft() + +// this._register(editor.onDidScrollChange(e => { updateTop() })) +// this._register(editor.onDidChangeModelContent(e => { updateTop() })) +// this._register(editor.onDidLayoutChange(e => { updateTop(); updateLeft() })) + +// // mount this widget + +// editor.addOverlayWidget(this); +// // console.log('created elt', this._domNode) +// } + +// public override dispose(): void { +// this.editor.removeOverlayWidget(this) +// super.dispose() +// } + +// } + + diff --git a/src/vs/workbench/contrib/void/browser/helperServices/zoneStyleService.ts b/src/vs/workbench/contrib/void/browser/helperServices/zoneStyleService.ts new file mode 100644 index 00000000..8ccf1374 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/helperServices/zoneStyleService.ts @@ -0,0 +1,186 @@ +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), + uri: URI + }> = {} + + // 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 = {} + + // current state on each consistent zone + + // listener disposables + private readonly disposablesOfEditorId: Record | undefined> = {} + + constructor( + @ICodeEditorService private readonly _editorService: ICodeEditorService, + ) { + super() + + const addTabSwitchListeners = (editor: ICodeEditor) => { + const editorId = editor.getId() + + const d = editor.onDidChangeModel(e => { + const newURI = e.newModelUrl + // clear all the zones off of this editor + for (const zoneId of this.zoneIdsOfEditorId[editorId] ?? []) + this._removeZoneIdFromEditor(editor, zoneId) + + // add all the zones it should have, judging by the new URI + if (newURI) + for (const consistentZoneId of this.consistentZoneIdsOfURI[newURI.fsPath] ?? []) + this._putZoneOnEditor(editor, consistentZoneId) + }) + + if (!(editorId in this.disposablesOfEditorId)) + this.disposablesOfEditorId[editorId] = new Set() + this.disposablesOfEditorId[editorId]!.add(d) + } + + // initialize current editors + const initialEditors = this._editorService.listCodeEditors() + for (let editor of initialEditors) + addTabSwitchListeners(editor) + + // initialize any new editors - add tab switch listeners and add all zones it should have + this._register(this._editorService.onCodeEditorAdd(editor => { + addTabSwitchListeners(editor) + + const uri = editor.getModel()?.uri + if (uri) + for (const consistentZoneId of this.consistentZoneIdsOfURI[uri.fsPath] ?? []) + this._putZoneOnEditor(editor, consistentZoneId) + })) + + // when an editor is deleted, remove its zones and call any disposables it has + this._register(this._editorService.onCodeEditorRemove(editor => { + const editorId = editor.getId() + + for (const zoneId of this.zoneIdsOfEditorId[editorId] ?? []) + this._removeZoneIdFromEditor(editor, zoneId) + + for (const d of this.disposablesOfEditorId[editorId] ?? []) { + d.dispose() + this.disposablesOfEditorId[editorId]?.delete(d) + } + + })) + + } + + + _putZoneOnEditor(editor: ICodeEditor, consistentZoneId: string) { + const { iZoneFn, iOther } = this.infoOfConsistentZoneId[consistentZoneId] + + const iZone = iZoneFn(editor) + + const editorId = editor.getId() + + editor.changeViewZones(accessor => { + // add zone + other + const zoneId = accessor.addZone(iZone) + const rmFn = iOther?.(editor) + + if (!(editorId in this.zoneIdsOfEditorId)) + this.zoneIdsOfEditorId[editorId] = new Set() + this.zoneIdsOfEditorId[editorId]!.add(zoneId) + + this.consistentZoneIdOfZoneId[zoneId] = consistentZoneId + + // fn that describes how to remove zone + other + this.removeFnOfZoneId[zoneId] = () => { + editor.changeViewZones(accessor => accessor.removeZone(zoneId)) + rmFn?.() + } + }) + } + + + _removeZoneIdFromEditor(editor: ICodeEditor, zoneId: string) { + + this.removeFnOfZoneId[zoneId]?.() + delete this.removeFnOfZoneId[zoneId] + + + const editorId = editor.getId() + if (editorId in this.zoneIdsOfEditorId) { + this.zoneIdsOfEditorId[editorId]?.delete(zoneId) + } + + if (zoneId in this.consistentZoneIdOfZoneId) + 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..cc6277e4 100644 --- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts +++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts @@ -150,20 +150,16 @@ 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 @@ -175,17 +171,16 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { if (e.newModelUrl) this._refreshDiffsInURI(e.newModelUrl) })) } - // 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) })) } + // highlight the region private _addLineDecoration = (model: ITextModel | null, startLine: number, endLine: number, className: string, options?: Partial) => { if (model === null) return From ffa31bc2897af707cc9be6cb2da304845002d3bf Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 23 Dec 2024 00:14:54 -0800 Subject: [PATCH 02/25] update zoneStyleService --- .../helperServices/zoneStyleService.ts | 102 ++++++++---------- 1 file changed, 47 insertions(+), 55 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/helperServices/zoneStyleService.ts b/src/vs/workbench/contrib/void/browser/helperServices/zoneStyleService.ts index 8ccf1374..b57b7d8a 100644 --- a/src/vs/workbench/contrib/void/browser/helperServices/zoneStyleService.ts +++ b/src/vs/workbench/contrib/void/browser/helperServices/zoneStyleService.ts @@ -25,72 +25,71 @@ export class ZoneStyleService extends Disposable { // 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), - uri: URI }> = {} + // 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 = {} - // current state on each consistent zone - - // listener disposables - private readonly disposablesOfEditorId: Record | undefined> = {} constructor( @ICodeEditorService private readonly _editorService: ICodeEditorService, ) { super() - const addTabSwitchListeners = (editor: ICodeEditor) => { + + const removeZonesFromEditor = (editor: ICodeEditor) => { const editorId = editor.getId() - - const d = editor.onDidChangeModel(e => { - const newURI = e.newModelUrl - // clear all the zones off of this editor - for (const zoneId of this.zoneIdsOfEditorId[editorId] ?? []) - this._removeZoneIdFromEditor(editor, zoneId) - - // add all the zones it should have, judging by the new URI - if (newURI) - for (const consistentZoneId of this.consistentZoneIdsOfURI[newURI.fsPath] ?? []) - this._putZoneOnEditor(editor, consistentZoneId) - }) - - if (!(editorId in this.disposablesOfEditorId)) - this.disposablesOfEditorId[editorId] = new Set() - this.disposablesOfEditorId[editorId]!.add(d) + for (const zoneId of this.zoneIdsOfEditorId[editorId] ?? []) + this._removeZoneIdFromEditor(editor, zoneId) } - // initialize current editors - const initialEditors = this._editorService.listCodeEditors() - for (let editor of initialEditors) - addTabSwitchListeners(editor) + // 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) + } - // initialize any new editors - add tab switch listeners and add all zones it should have - this._register(this._editorService.onCodeEditorAdd(editor => { - addTabSwitchListeners(editor) - const uri = editor.getModel()?.uri - if (uri) - 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() - for (const zoneId of this.zoneIdsOfEditorId[editorId] ?? []) - this._removeZoneIdFromEditor(editor, zoneId) - - for (const d of this.disposablesOfEditorId[editorId] ?? []) { + removeZonesFromEditor(editor) + for (const d of this.disposablesOfEditorId[editorId] ?? []) d.dispose() - this.disposablesOfEditorId[editorId]?.delete(d) - } + delete this.disposablesOfEditorId[editorId] })) @@ -100,43 +99,36 @@ export class ZoneStyleService extends Disposable { _putZoneOnEditor(editor: ICodeEditor, consistentZoneId: string) { const { iZoneFn, iOther } = this.infoOfConsistentZoneId[consistentZoneId] - const iZone = iZoneFn(editor) - - const editorId = editor.getId() - editor.changeViewZones(accessor => { // add zone + other - const zoneId = accessor.addZone(iZone) + 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) - this.consistentZoneIdOfZoneId[zoneId] = consistentZoneId - // 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] - - const editorId = editor.getId() - if (editorId in this.zoneIdsOfEditorId) { - this.zoneIdsOfEditorId[editorId]?.delete(zoneId) - } - - if (zoneId in this.consistentZoneIdOfZoneId) - delete this.consistentZoneIdOfZoneId[zoneId] + delete this.consistentZoneIdOfZoneId[zoneId] } From 98a22d2eb6e798de171184f9bb872f61eb19f165 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 23 Dec 2024 00:16:37 -0800 Subject: [PATCH 03/25] minor changes --- .../inlineDiffsServiceBackup.ts | 99 ++++++++++--------- .../void/browser/inlineDiffsService.ts | 13 ++- 2 files changed, 60 insertions(+), 52 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/helperServices/inlineDiffsServiceBackup.ts b/src/vs/workbench/contrib/void/browser/helperServices/inlineDiffsServiceBackup.ts index 169f7d50..e870c9b5 100644 --- a/src/vs/workbench/contrib/void/browser/helperServices/inlineDiffsServiceBackup.ts +++ b/src/vs/workbench/contrib/void/browser/helperServices/inlineDiffsServiceBackup.ts @@ -29,6 +29,7 @@ // import { URI } from '../../../../base/common/uri.js'; // import { LLMFeatureSelection, ServiceSendLLMMessageParams } from '../../../../platform/void/common/llmMessageTypes.js'; // import { ILLMMessageService } from '../../../../platform/void/common/llmMessageService.js'; +// import { IZoneStyleService } from './helperServices/zoneStyleService.js'; // const configOfBG = (color: Color) => { @@ -132,6 +133,7 @@ // @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, // undoRedo service is the history of pressing ctrl+z // @ILanguageService private readonly _langService: ILanguageService, // @ILLMMessageService private readonly _llmMessageService: ILLMMessageService, +// @IZoneStyleService private readonly _zoneStyleService: IZoneStyleService, // ) { // super(); @@ -150,20 +152,16 @@ // // 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 @@ -175,11 +173,9 @@ // if (e.newModelUrl) this._refreshDiffsInURI(e.newModelUrl) // })) // } -// // 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) })) // } @@ -227,14 +223,16 @@ // } -// 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 +242,55 @@ // // red in a view zone // if (type !== 'insertion') { -// editor.changeViewZones(accessor => { +// const consistentZoneId = this._zoneStyleService.addConsistentZoneToURI( -// const domNode = document.createElement('div'); -// domNode.className = 'void-redBG' +// uri, -// const renderOptions = RenderOptions.fromEditor(editor); -// // applyFontInfo(domNode, renderOptions.fontInfo) +// (editor) => { +// const domNode = document.createElement('div'); +// domNode.className = 'void-redBG' -// // 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 renderOptions = RenderOptions.fromEditor(editor); +// // applyFontInfo(domNode, renderOptions.fontInfo) -// 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, -// }; +// // 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, +// }; +// return viewZone +// }, + +// (editor) => { +// // Accept | Reject widget +// const buttonsWidget = new AcceptRejectWidget({ +// editor, +// onAccept: () => { this.acceptDiff({ diffid }) }, +// onReject: () => { this.rejectDiff({ diffid }) }, +// diffid: diffid.toString(), +// startLine: diff.startLine, +// }) +// return () => buttonsWidget.dispose() +// } +// ) + +// disposeInThisEditorFns.push(() => { this._zoneStyleService.removeConsistentZoneFromURI(consistentZoneId) }) -// const zoneId = accessor.addZone(viewZone) -// disposeInThisEditorFns.push(() => { editor.changeViewZones(accessor => { if (zoneId) accessor.removeZone(zoneId) }) }) -// }); // } -// // Accept | Reject widget -// const buttonsWidget = new AcceptRejectWidget({ -// editor, -// onAccept: () => { this.acceptDiff({ diffid }) }, -// onReject: () => { this.rejectDiff({ diffid }) }, -// diffid: diffid.toString(), -// startLine: diff.startLine, -// }) -// disposeInThisEditorFns.push(() => { buttonsWidget.dispose() }) // const disposeInEditor = () => { disposeInThisEditorFns.forEach(f => f()) } // return disposeInEditor; @@ -499,7 +505,6 @@ // 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 +525,8 @@ // 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 diff --git a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts index cc6277e4..50a51301 100644 --- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts +++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts @@ -150,16 +150,20 @@ 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 when a new model mounts + // initialize all existing models 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 @@ -171,16 +175,17 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { if (e.newModelUrl) this._refreshDiffsInURI(e.newModelUrl) })) } - // add listeners for all existing editors + listen for editor being added + // add listeners for all existing editors for (let editor of this._editorService.listCodeEditors()) { initializeEditor(editor) } - this._register(this._editorService.onCodeEditorAdd(editor => { 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) })) } - // highlight the region private _addLineDecoration = (model: ITextModel | null, startLine: number, endLine: number, className: string, options?: Partial) => { if (model === null) return From 80a87ff3c623a1a82dade8726f615684262e4b25 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 23 Dec 2024 02:43:19 -0800 Subject: [PATCH 04/25] 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 From 84bfc26dce68c40c2496c015a3f00c35167f8371 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 23 Dec 2024 03:00:10 -0800 Subject: [PATCH 05/25] debugging --- src/vs/workbench/contrib/void/browser/inlineDiffsService.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts index 48091d81..fff12d8a 100644 --- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts +++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts @@ -451,6 +451,8 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { // changes the start/line locations of all DiffAreas on the page (adjust their start/end based on the change) based on the change that was recently made private _realignAllDiffAreasLines(uri: URI, text: string, recentChange: { startLineNumber: number; endLineNumber: number }) { + console.log('recent change', recentChange) + const model = this._getModel(uri) if (!model) return @@ -525,6 +527,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { for (let diffareaid of this.diffAreasOfURI[uri.fsPath]) { const diffArea = this.diffAreaOfId[diffareaid] + console.log('DA start and end:', diffArea.startLine, diffArea.endLine) const newDiffAreaCode = fullFileText.split('\n').slice((diffArea.startLine - 1), (diffArea.endLine - 1) + 1).join('\n') const computedDiffs = findDiffs(diffArea.originalCode, newDiffAreaCode) @@ -900,9 +903,12 @@ Please finish writing the new file by applying the diff to the original file. Re throw new Error(`Void error: ${diff}.type not recognized`) } + console.log('REJECTION start, end:', diffArea.startLine, diffArea.endLine) // update the file this._writeText(uri, writeText, toRange) + console.log('2REJECTION start, end:', diffArea.startLine, diffArea.endLine) + // originalCode does not change! // delete the diff From 406f29bb5af56f9931494ae708c946ed3b2f77f4 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 23 Dec 2024 03:29:49 -0800 Subject: [PATCH 06/25] + --- .../contrib/void/browser/inlineDiffsService.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts index fff12d8a..e6feecdc 100644 --- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts +++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts @@ -154,6 +154,18 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { const uri = model.uri for (const change of e.changes) { this._realignAllDiffAreasLines(uri, change.text, change.range) } this._refreshDiffsInURI(uri) + + // // diffArea should be removed if we just discovered it has no more diffs in it + // for (const diffareaid of this.diffAreasOfURI[uri.fsPath]) { + // const diffArea = this.diffAreaOfId[diffareaid] + // if (Object.keys(diffArea._diffOfId).length === 0 && !diffArea._sweepState.isStreaming) { + // const { onFinishEdit } = this._addToHistory(uri) + // this._deleteDiffArea(diffArea) + // onFinishEdit() + // } + // } + + }) ) } @@ -470,7 +482,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { const diffArea = this.diffAreaOfId[diffareaid] // if the diffArea is above the range, it is not affected - if (diffArea.endLine < startLine) { + if (startLine > diffArea.endLine + 1) { console.log('A') continue } From 372beb7a2d7cb796010ecd03f282f4a55eace22b Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 23 Dec 2024 03:30:05 -0800 Subject: [PATCH 07/25] rm temp --- .../inlineDiffsServiceBackup.ts | 999 ------------------ 1 file changed, 999 deletions(-) delete mode 100644 src/vs/workbench/contrib/void/browser/helperServices/inlineDiffsServiceBackup.ts diff --git a/src/vs/workbench/contrib/void/browser/helperServices/inlineDiffsServiceBackup.ts b/src/vs/workbench/contrib/void/browser/helperServices/inlineDiffsServiceBackup.ts deleted file mode 100644 index e870c9b5..00000000 --- a/src/vs/workbench/contrib/void/browser/helperServices/inlineDiffsServiceBackup.ts +++ /dev/null @@ -1,999 +0,0 @@ -// /*--------------------------------------------------------------------------------------------- -// * Copyright (c) Glass Devtools, Inc. All rights reserved. -// * Void Editor additions licensed under the AGPL 3.0 License. -// *--------------------------------------------------------------------------------------------*/ - -// import { Disposable } from '../../../../base/common/lifecycle.js'; -// import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; -// import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; -// import { ICodeEditor, IOverlayWidget, IViewZone } from '../../../../editor/browser/editorBrowser.js'; - -// // 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'; -// import { registerColor } from '../../../../platform/theme/common/colorUtils.js'; -// import { Color, RGBA } from '../../../../base/common/color.js'; -// import { IModelService } from '../../../../editor/common/services/model.js'; -// import { IUndoRedoElement, IUndoRedoService, UndoRedoElementType } from '../../../../platform/undoRedo/common/undoRedo.js'; -// import { LineSource, renderLines, RenderOptions } from '../../../../editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js'; -// import { LineTokens } from '../../../../editor/common/tokens/lineTokens.js'; -// import { ILanguageService } from '../../../../editor/common/languages/language.js'; -// // import { IModelService } from '../../../../editor/common/services/model.js'; - -// 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 { ILLMMessageService } from '../../../../platform/void/common/llmMessageService.js'; -// import { IZoneStyleService } from './helperServices/zoneStyleService.js'; - - -// const configOfBG = (color: Color) => { -// return { dark: color, light: color, hcDark: color, hcLight: color, } -// } -// // gets converted to --vscode-void-greenBG, see void.css -// const greenBG = new Color(new RGBA(155, 185, 85, .3)); // default is RGBA(155, 185, 85, .2) -// registerColor('void.greenBG', configOfBG(greenBG), '', true); - -// const redBG = new Color(new RGBA(255, 0, 0, .3)); // default is RGBA(255, 0, 0, .2) -// registerColor('void.redBG', configOfBG(redBG), '', true); - -// const sweepBG = new Color(new RGBA(100, 100, 100, .2)); -// registerColor('void.sweepBG', configOfBG(sweepBG), '', true); - -// const highlightBG = new Color(new RGBA(100, 100, 100, .1)); -// registerColor('void.highlightBG', configOfBG(highlightBG), '', true); - -// const sweepIdxBG = new Color(new RGBA(100, 100, 100, .5)); -// registerColor('void.sweepIdxBG', configOfBG(sweepIdxBG), '', true); - - -// export type Diff = { -// diffid: number; -// diffareaid: number; // the diff area this diff belongs to, "computed" -// } & ComputedDiff - - -// // _ means anything we don't include if we clone it -// // DiffArea.originalStartLine is the line in originalCode (not the file) -// type DiffArea = { -// diffareaid: number; -// originalCode: string; -// startLine: number; -// endLine: number; -// shouldHighlight: boolean; // should visually highlight this DiffArea - -// _URI: URI; // typically we get the URI from model -// _diffOfId: Record; // diffid -> diff in this DiffArea - -// } & ({ -// _sweepState: { -// isStreaming: true; -// line: number; -// } | { -// isStreaming: false; -// line: null; -// }; -// }) - -// const diffAreaSnapshotKeys = [ -// 'diffareaid', -// 'originalCode', -// 'startLine', -// 'endLine', -// 'shouldHighlight', -// ] as const satisfies (keyof DiffArea)[] - -// type DiffAreaSnapshot = Pick - - - -// type HistorySnapshot = { -// snapshottedDiffAreaOfId: Record; -// entireFileCode: string; -// } & -// ({ -// type: 'Ctrl+K'; -// ctrlKText: string; -// } | { -// type: 'Ctrl+L'; -// }) - - - -// export interface IInlineDiffsService { -// readonly _serviceBrand: undefined; -// startStreaming(params: LLMFeatureSelection, str: string): void; -// } - -// export const IInlineDiffsService = createDecorator('inlineDiffAreasService'); - -// class InlineDiffsService extends Disposable implements IInlineDiffsService { -// _serviceBrand: undefined; - - -// // URI <--> model -// removeStylesFnsOfURI: Record> = {} // functions that remove the styles of this uri -// diffAreasOfURI: Record> = {} - -// diffAreaOfId: Record = {}; -// diffOfId: Record = {}; // redundant with diffArea._diffs - -// _diffareaidPool = 0 // each diffarea has an id -// _diffidPool = 0 // each diff has an id - -// constructor( -// // @IHistoryService private readonly _historyService: IHistoryService, // history service is the history of pressing alt left/right -// @ICodeEditorService private readonly _editorService: ICodeEditorService, -// @IModelService private readonly _modelService: IModelService, -// @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, // undoRedo service is the history of pressing ctrl+z -// @ILanguageService private readonly _langService: ILanguageService, -// @ILLMMessageService private readonly _llmMessageService: ILLMMessageService, -// @IZoneStyleService private readonly _zoneStyleService: IZoneStyleService, -// ) { -// super(); - -// // this function initializes data structures and listens for changes -// const initializeModel = (model: ITextModel) => { -// if (!(model.uri.fsPath in this.diffAreasOfURI)) { -// this.diffAreasOfURI[model.uri.fsPath] = new Set(); -// } -// if (!(model.uri.fsPath in this.removeStylesFnsOfURI)) { -// this.removeStylesFnsOfURI[model.uri.fsPath] = new Set(); -// } - -// // when the user types, realign diff areas and re-render them -// this._register( -// model.onDidChangeContent(e => { -// // 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 -// for (const change of e.changes) { this._realignAllDiffAreasLines(uri, change.text, change.range) } -// this._refreshDiffsInURI(uri) -// }) -// ) -// } -// // initialize all existing models + initialize when a new model mounts -// for (let model of this._modelService.getModels()) { initializeModel(model) } -// 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) - -// // 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) -// })) -// } -// // add listeners for all existing editors + listen for editor being added -// for (let editor of this._editorService.listCodeEditors()) { initializeEditor(editor) } -// this._register(this._editorService.onCodeEditorAdd(editor => { initializeEditor(editor) })) - -// } - - - - -// // highlight the region -// private _addLineDecoration = (model: ITextModel | null, startLine: number, endLine: number, className: string, options?: Partial) => { -// if (model === null) return -// const id = model.changeDecorations(accessor => accessor.addDecoration( -// { startLineNumber: startLine, startColumn: 1, endLineNumber: endLine, endColumn: Number.MAX_SAFE_INTEGER }, -// { -// className: className, -// description: className, -// isWholeLine: true, -// ...options -// })) -// const disposeHighlight = () => { -// if (id) model.changeDecorations(accessor => accessor.removeDecoration(id)) -// } -// return disposeHighlight -// } - - - -// private _addDiffAreaStylesToURI = (uri: URI) => { -// const model = this._getModel(uri) - -// for (const diffareaid of this.diffAreasOfURI[uri.fsPath]) { -// const diffArea = this.diffAreaOfId[diffareaid] -// // add sweep styles to the diffArea -// if (diffArea._sweepState.isStreaming) { -// // sweepLine ... sweepLine -// const fn1 = this._addLineDecoration(model, diffArea._sweepState.line, diffArea._sweepState.line, 'void-sweepIdxBG') -// // sweepLine+1 ... endLine -// const fn2 = this._addLineDecoration(model, diffArea._sweepState.line + 1, diffArea.endLine, 'void-sweepBG') -// this.removeStylesFnsOfURI[uri.fsPath].add(() => { fn1?.(); fn2?.(); }) -// } -// // highlight the diffArea -// if (diffArea.shouldHighlight) { -// const fn = this._addLineDecoration(model, diffArea.startLine, diffArea.endLine, 'void-highlightBG') -// this.removeStylesFnsOfURI[uri.fsPath].add(() => fn?.()); -// } -// } -// } - - -// 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(model, diff.startLine, diff.endLine, 'void-greenBG', { -// minimap: { color: { id: 'minimapGutter.addedBackground' }, position: 2 }, -// overviewRuler: { color: { id: 'editorOverviewRuler.addedForeground' }, position: 7 } -// }) -// disposeInThisEditorFns.push(() => { fn?.() }) -// } - - -// // red in a view zone -// if (type !== 'insertion') { -// const consistentZoneId = this._zoneStyleService.addConsistentZoneToURI( - -// uri, - -// (editor) => { -// const domNode = document.createElement('div'); -// domNode.className = 'void-redBG' - -// 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); - -// 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, -// }; -// return viewZone -// }, - -// (editor) => { -// // Accept | Reject widget -// const buttonsWidget = new AcceptRejectWidget({ -// editor, -// onAccept: () => { this.acceptDiff({ diffid }) }, -// onReject: () => { this.rejectDiff({ diffid }) }, -// diffid: diffid.toString(), -// startLine: diff.startLine, -// }) -// return () => buttonsWidget.dispose() -// } -// ) - -// disposeInThisEditorFns.push(() => { this._zoneStyleService.removeConsistentZoneFromURI(consistentZoneId) }) - - -// } - - - -// const disposeInEditor = () => { disposeInThisEditorFns.forEach(f => f()) } -// return disposeInEditor; - -// } - - - -// private _getModel(uri: URI) { -// const model = this._modelService.getModel(uri) -// if (!model || model.isDisposed()) { -// return null -// } -// return model -// } -// private _readURI(uri: URI): string | null { -// return this._getModel(uri)?.getValue(EndOfLinePreference.LF) ?? null -// } -// private _getNumLines(uri: URI): number | null { -// return this._getModel(uri)?.getLineCount() ?? null -// } - - -// _weAreWriting = false -// private _writeText(uri: URI, text: string, range: IRange) { -// const model = this._getModel(uri) -// if (!model) return - -// this._weAreWriting = true -// model.applyEdits([{ range, text }]) // applies edits without adding them to undo/redo stack -// this._weAreWriting = false - -// this._realignAllDiffAreasLines(uri, text, range) -// } - - - - -// private _addToHistory(uri: URI) { - -// const getCurrentSnapshot = (): HistorySnapshot => { -// const diffAreaOfId = this.diffAreaOfId - -// const snapshottedDiffAreaOfId: Record = {} -// for (const diffareaid in diffAreaOfId) { -// const diffArea = diffAreaOfId[diffareaid] -// snapshottedDiffAreaOfId[diffareaid] = structuredClone( // a structured clone must be on a JSON object -// Object.fromEntries(diffAreaSnapshotKeys.map(key => [key, diffArea[key]])) -// ) as DiffAreaSnapshot -// } -// return { -// snapshottedDiffAreaOfId, -// entireFileCode: this._readURI(uri) ?? '', // the whole file's code -// type: 'Ctrl+L', -// } -// } - -// const restoreDiffAreas = (snapshot: HistorySnapshot) => { -// const { snapshottedDiffAreaOfId, entireFileCode: entireModelCode } = structuredClone(snapshot) // don't want to destroy the snapshot - -// // delete all current decorations (diffs, sweep styles) so we don't have any unwanted leftover decorations -// this._clearAllDiffsAndStyles(uri) - -// // restore diffAreaOfId and diffAreasOfModelId -// this.diffAreaOfId = {} -// this.diffAreasOfURI[uri.fsPath].clear() -// for (const diffareaid in snapshottedDiffAreaOfId) { -// this.diffAreaOfId[diffareaid] = { -// ...snapshottedDiffAreaOfId[diffareaid], -// _diffOfId: {}, -// _URI: uri, -// _sweepState: { -// isStreaming: false, -// line: null, -// }, -// } -// this.diffAreasOfURI[uri.fsPath].add(diffareaid) -// } - -// // restore file content -// const numLines = this._getNumLines(uri) -// if (numLines === null) return -// this._writeText(uri, entireModelCode, { startColumn: 1, startLineNumber: 1, endLineNumber: numLines, endColumn: Number.MAX_SAFE_INTEGER }) - -// // restore all the decorations -// this._refreshDiffsInURI(uri) -// } - -// const beforeSnapshot: HistorySnapshot = getCurrentSnapshot() -// let afterSnapshot: HistorySnapshot | null = null - -// const elt: IUndoRedoElement = { -// type: UndoRedoElementType.Resource, -// resource: uri, -// label: 'Void Changes', -// code: 'undoredo.inlineDiffs', -// undo: () => { restoreDiffAreas(beforeSnapshot) }, -// redo: () => { if (afterSnapshot) restoreDiffAreas(afterSnapshot) } -// } -// this._undoRedoService.pushElement(elt) - -// const onFinishEdit = () => { afterSnapshot = getCurrentSnapshot() } -// return { onFinishEdit } -// } - - -// // delete diffOfId and diffArea._diffOfId -// private _deleteDiff(diff: Diff) { -// const diffArea = this.diffAreaOfId[diff.diffareaid] -// delete diffArea._diffOfId[diff.diffid] -// delete this.diffOfId[diff.diffid] -// } - -// private _deleteDiffs(diffArea: DiffArea) { -// for (const diffid in diffArea._diffOfId) { -// const diff = diffArea._diffOfId[diffid] -// this._deleteDiff(diff) -// } -// } - -// private _clearAllDiffsAndStyles(uri: URI) { -// for (let diffareaid of this.diffAreasOfURI[uri.fsPath]) { -// const diffArea = this.diffAreaOfId[diffareaid] -// this._deleteDiffs(diffArea) -// } -// for (const removeStyleFn of this.removeStylesFnsOfURI[uri.fsPath]) { -// removeStyleFn() -// } -// this.removeStylesFnsOfURI[uri.fsPath].clear() -// } - - - -// // delete all diffs, update diffAreaOfId, update diffAreasOfModelId -// private _deleteDiffArea(diffArea: DiffArea) { -// this._deleteDiffs(diffArea) -// delete this.diffAreaOfId[diffArea.diffareaid] -// this.diffAreasOfURI[diffArea._URI.fsPath].delete(diffArea.diffareaid.toString()) -// } - - - - - -// // changes the start/line locations of all DiffAreas on the page (adjust their start/end based on the change) based on the change that was recently made -// private _realignAllDiffAreasLines(uri: URI, text: string, recentChange: { startLineNumber: number; endLineNumber: number }) { - -// const model = this._getModel(uri) -// if (!model) return - -// // compute net number of newlines lines that were added/removed -// const startLine = recentChange.startLineNumber -// const endLine = recentChange.endLineNumber -// const changeRangeHeight = endLine - startLine + 1 - -// const newTextHeight = (text.match(/\n/g) || []).length + 1 // number of newlines is number of \n's + 1, e.g. "ab\ncd" - -// const deltaNewlines = newTextHeight - changeRangeHeight - -// // compute overlap with each diffArea and shrink/elongate each diffArea accordingly -// for (const diffareaid of this.diffAreasOfURI[model.uri.fsPath] || []) { -// const diffArea = this.diffAreaOfId[diffareaid] - -// // if the diffArea is above the range, it is not affected -// if (diffArea.endLine < startLine) { -// console.log('A') -// continue -// } - -// // console.log('Changing DiffArea:', diffArea.startLine, diffArea.endLine) - -// // if the diffArea fully contains the change, elongate it by the delta amount of newlines -// if (startLine >= diffArea.startLine && endLine <= diffArea.endLine) { -// diffArea.endLine += deltaNewlines -// } -// // if the change fully contains the diffArea, make the diffArea have the same range as the change -// else if (diffArea.startLine > startLine && diffArea.endLine < endLine) { - -// diffArea.startLine = startLine -// diffArea.endLine = startLine + newTextHeight -// console.log('B', diffArea.startLine, diffArea.endLine) -// } -// // if the change contains only the diffArea's top -// else if (diffArea.startLine > startLine) { -// // TODO fill in this case -// console.log('C', diffArea.startLine, diffArea.endLine) -// } -// // if the change contains only the diffArea's bottom -// else if (diffArea.endLine < endLine) { -// const numOverlappingLines = diffArea.endLine - startLine + 1 -// diffArea.endLine += newTextHeight - numOverlappingLines // TODO double check this -// console.log('D', diffArea.startLine, diffArea.endLine) -// } -// // if a diffArea is below the last character of the change, shift the diffArea up/down by the delta amount of newlines -// else if (diffArea.startLine > endLine) { -// diffArea.startLine += deltaNewlines -// diffArea.endLine += deltaNewlines -// console.log('E', diffArea.startLine, diffArea.endLine) -// } - -// // console.log('To:', diffArea.startLine, diffArea.endLine) -// } - -// } - - -// private _refreshDiffsInURI(uri: URI) { -// const content = this._readURI(uri) -// if (content === null) return - -// // 1. clear Diffs and styles -// this._clearAllDiffsAndStyles(uri) - -// // 2. recompute all diffs on each editor with this URI -// const fullFileText = this._readURI(uri) ?? '' - - -// // go thru all diffareas in this URI, creating diffs and adding styles to it -// for (let diffareaid of this.diffAreasOfURI[uri.fsPath]) { -// const diffArea = this.diffAreaOfId[diffareaid] - -// const newDiffAreaCode = fullFileText.split('\n').slice((diffArea.startLine - 1), (diffArea.endLine - 1) + 1).join('\n') -// const computedDiffs = findDiffs(diffArea.originalCode, newDiffAreaCode) - -// for (let computedDiff of computedDiffs) { -// const diffid = this._diffidPool++ - -// // create a Diff of it -// const newDiff: Diff = { -// ...computedDiff, -// diffid: diffid, -// diffareaid: diffArea.diffareaid, -// } - -// const fn = this._addDiffStylesToURI(uri, newDiff) -// this.removeStylesFnsOfURI[uri.fsPath].add(fn) - -// this.diffOfId[diffid] = newDiff -// diffArea._diffOfId[diffid] = newDiff -// } - -// // update styles on this DiffArea -// this._addDiffAreaStylesToURI(uri) -// } - - -// } - - -// // @throttle(100) -// private _writeDiffAreaLLMText(diffArea: DiffArea, newCodeSoFar: string) { - -// // ----------- 1. Write the new code to the document ----------- -// // figure out where to highlight based on where the AI is in the stream right now, use the last diff to figure that out -// const uri = diffArea._URI -// const computedDiffs = findDiffs(diffArea.originalCode, newCodeSoFar) - -// // if not streaming, just write the new code -// if (!diffArea._sweepState.isStreaming) { -// this._writeText(uri, newCodeSoFar, -// { startLineNumber: diffArea.startLine, startColumn: 1, endLineNumber: diffArea.endLine, endColumn: Number.MAX_SAFE_INTEGER, } // 1-indexed -// ) -// } -// // if streaming, use diffs to figure out where to write new code -// else { -// // these are two different coordinate systems - new and old line number -// let newFileEndLine: number // get new[0...newStoppingPoint] with line=newStoppingPoint highlighted -// let oldFileStartLine: number // get original[oldStartingPoint...] - -// const lastDiff = computedDiffs.pop() - -// if (!lastDiff) { -// // if the writing is identical so far, display no changes -// newFileEndLine = 1 -// oldFileStartLine = 1 -// } -// else { -// if (lastDiff.type === 'insertion') { -// newFileEndLine = lastDiff.endLine -// oldFileStartLine = lastDiff.originalStartLine -// } -// else if (lastDiff.type === 'deletion') { -// newFileEndLine = lastDiff.startLine -// oldFileStartLine = lastDiff.originalStartLine -// } -// else if (lastDiff.type === 'edit') { -// newFileEndLine = lastDiff.endLine -// oldFileStartLine = lastDiff.originalStartLine -// } -// else { -// throw new Error(`Void: diff.type not recognized on: ${lastDiff}`) -// } -// } - -// diffArea._sweepState.line = newFileEndLine - -// // lines are 1-indexed -// const newFileTop = newCodeSoFar.split('\n').slice(0, (newFileEndLine - 1)).join('\n') -// const oldFileBottom = diffArea.originalCode.split('\n').slice((oldFileStartLine - 1), Infinity).join('\n') - -// const newCode = `${newFileTop}\n${oldFileBottom}` - -// this._writeText(uri, newCode, -// { startLineNumber: diffArea.startLine, startColumn: 1, endLineNumber: diffArea.endLine, endColumn: Number.MAX_SAFE_INTEGER, } // 1-indexed -// ) - -// } - -// return computedDiffs - -// } - - - - -// private async _initializeStream(opts: LLMFeatureSelection, diffRepr: string, uri: URI,) { - -// // diff area begin and end line -// const numLines = this._getNumLines(uri) -// if (numLines === null) return - -// const beginLine = 1 -// const endLine = numLines - -// // check if there's overlap with any other diffAreas and return early if there is -// for (const diffareaid of this.diffAreasOfURI[uri.fsPath]) { -// const da2 = this.diffAreaOfId[diffareaid] -// if (!da2) continue -// const noOverlap = da2.startLine > endLine || da2.endLine < beginLine -// if (!noOverlap) { -// // TODO add a message here that says this to the user too -// console.error('Not diffing because found overlap:', this.diffAreasOfURI[uri.fsPath], beginLine, endLine) -// return -// } -// } - -// const currentFileStr = this._readURI(uri) -// if (currentFileStr === null) return -// const originalCode = currentFileStr.split('\n').slice((beginLine - 1), (endLine - 1) + 1).join('\n') - -// // add to history -// const { onFinishEdit } = this._addToHistory(uri) - -// // create a diffArea for the stream -// const diffareaid = this._diffareaidPool++ - -// // in ctrl+L the start and end lines are the full document -// const diffArea: DiffArea = { -// diffareaid: diffareaid, -// // originalStartLine: beginLine, -// // originalEndLine: endLine, -// originalCode: originalCode, -// startLine: beginLine, -// endLine: endLine, // starts out the same as the current file -// shouldHighlight: false, -// _URI: uri, -// _sweepState: { -// isStreaming: true, -// line: 1, -// }, -// _diffOfId: {}, // added later -// } - -// console.log('adding uri.fspath', uri.fsPath, diffArea.diffareaid.toString()) -// this.diffAreasOfURI[uri.fsPath].add(diffArea.diffareaid.toString()) -// this.diffAreaOfId[diffArea.diffareaid] = diffArea - -// // actually call the LLM -// const promptContent = `\ -// ORIGINAL_CODE -// \`\`\` -// ${originalCode} -// \`\`\` - -// DIFF -// \`\`\` -// ${diffRepr} -// \`\`\` - -// INSTRUCTIONS -// Please finish writing the new file by applying the diff to the original file. Return ONLY the completion of the file, without any explanation. -// ` - - -// await new Promise((resolve, reject) => { - -// let streamRequestId: string | null = null - -// const object: ServiceSendLLMMessageParams = { -// logging: { loggingName: 'streamChunk' }, -// messages: [ -// { role: 'system', content: inlineDiff_systemMessage, }, -// // TODO include more context too -// { role: 'user', content: promptContent, } -// ], -// onText: ({ newText, fullText }) => { -// this._writeDiffAreaLLMText(diffArea, fullText) -// this._refreshDiffsInURI(uri) -// }, -// onFinalMessage: ({ fullText }) => { -// this._writeText(uri, fullText, -// { startLineNumber: diffArea.startLine, startColumn: 1, endLineNumber: diffArea.endLine, endColumn: Number.MAX_SAFE_INTEGER }, // 1-indexed -// ) -// diffArea._sweepState = { isStreaming: false, line: null } -// this._refreshDiffsInURI(uri) -// resolve(); -// }, -// onError: (e: any) => { -// console.error('Error rewriting file with diff', e); -// // TODO indicate there was an error -// if (streamRequestId) -// this._llmMessageService.abort(streamRequestId) - -// diffArea._sweepState = { isStreaming: false, line: null } -// resolve(); -// }, -// ...opts -// } - -// streamRequestId = this._llmMessageService.sendLLMMessage(object) -// }) - -// onFinishEdit() - -// } - - - - - - -// async startStreaming(opts: LLMFeatureSelection, userMessage: string) { - -// const editor = this._editorService.getActiveCodeEditor() -// if (!editor) return - -// const uri = editor.getModel()?.uri -// if (!uri) return - -// // TODO reject all diffs in the diff area - -// // TODO deselect user's cursor - -// this._initializeStream(opts, userMessage, uri) -// } - - -// interruptStreaming() { -// // TODO add abort -// } - - - -// addDiffArea({ uri, startLine, endLine, originalCode }: { uri: URI, startLine: number, endLine: number, originalCode: string }) { -// const diffareaid = this._diffareaidPool++ - -// const diffArea: DiffArea = { -// diffareaid: diffareaid, -// originalCode, -// startLine, -// endLine, -// shouldHighlight: true, -// _URI: uri, -// _sweepState: { -// isStreaming: false, -// line: null, -// }, -// _diffOfId: {}, -// } - -// this.diffAreasOfURI[uri.fsPath].add(diffArea.diffareaid.toString()) -// this.diffAreaOfId[diffArea.diffareaid] = diffArea - -// this._refreshDiffsInURI(uri) -// } - - - - - -// // called on void.acceptDiff -// public async acceptDiff({ diffid }: { diffid: number }) { - -// const diff = this.diffOfId[diffid] -// if (!diff) return - -// const { diffareaid } = diff -// const diffArea = this.diffAreaOfId[diffareaid] -// if (!diffArea) return - -// const uri = diffArea._URI - -// // add to history -// const { onFinishEdit } = this._addToHistory(uri) - -// const originalLines = diffArea.originalCode.split('\n') -// let newOriginalCode: string - -// if (diff.type === 'deletion') { -// newOriginalCode = [ -// ...originalLines.slice(0, (diff.originalStartLine - 1)), // everything before startLine -// // <-- deletion has nothing here -// ...originalLines.slice((diff.originalEndLine - 1) + 1, Infinity) // everything after endLine -// ].join('\n') -// } -// else if (diff.type === 'insertion') { -// newOriginalCode = [ -// ...originalLines.slice(0, (diff.originalStartLine - 1)), // everything before startLine -// diff.code, // code -// ...originalLines.slice((diff.originalStartLine - 1), Infinity) // startLine (inclusive) and on (no +1) -// ].join('\n') -// } -// else if (diff.type === 'edit') { -// newOriginalCode = [ -// ...originalLines.slice(0, (diff.originalStartLine - 1)), // everything before startLine -// diff.code, // code -// ...originalLines.slice((diff.originalEndLine - 1) + 1, Infinity) // everything after endLine -// ].join('\n') -// } -// else { -// throw new Error(`Void error: ${diff}.type not recognized`) -// } - -// // console.log('DIFF', diff) -// // console.log('DIFFAREA', diffArea) -// // console.log('ORIGINAL', diffArea.originalCode) -// // console.log('new original Code', newOriginalCode) - -// // update code now accepted as original -// diffArea.originalCode = newOriginalCode - -// // delete the diff -// this._deleteDiff(diff) - -// // diffArea should be removed if it has no more diffs in it -// if (Object.keys(diffArea._diffOfId).length === 0) { -// this._deleteDiffArea(diffArea) -// } - -// this._refreshDiffsInURI(uri) - -// onFinishEdit() - -// } - - - -// // called on void.rejectDiff -// public async rejectDiff({ diffid }: { diffid: number }) { - -// const diff = this.diffOfId[diffid] -// if (!diff) return - -// const { diffareaid } = diff -// const diffArea = this.diffAreaOfId[diffareaid] -// if (!diffArea) return - -// const uri = diffArea._URI - -// // add to history -// const { onFinishEdit } = this._addToHistory(uri) - -// let writeText: string -// let toRange: IRange - -// // if it was a deletion, need to re-insert -// // (this image applies to writeText and toRange, not newOriginalCode) -// // A -// // |B <-- deleted here, diff.startLine == diff.endLine -// // C -// if (diff.type === 'deletion') { -// writeText = diff.originalCode + '\n' -// toRange = { startLineNumber: diff.startLine, startColumn: 1, endLineNumber: diff.startLine, endColumn: 1 } -// } -// // if it was an insertion, need to delete all the lines -// // (this image applies to writeText and toRange, not newOriginalCode) -// // |A <-- startLine -// // B| <-- endLine (we want to delete this whole line) -// // C -// else if (diff.type === 'insertion') { -// writeText = '' -// toRange = { startLineNumber: diff.startLine, startColumn: 1, endLineNumber: diff.endLine + 1, endColumn: 1 } // 1-indexed -// } -// // if it was an edit, just edit the range -// // (this image applies to writeText and toRange, not newOriginalCode) -// // |A <-- startLine -// // B| <-- endLine (just swap out these lines for the originalCode) -// // C -// else if (diff.type === 'edit') { -// writeText = diff.originalCode -// toRange = { startLineNumber: diff.startLine, startColumn: 1, endLineNumber: diff.endLine, endColumn: Number.MAX_SAFE_INTEGER } // 1-indexed -// } -// else { -// throw new Error(`Void error: ${diff}.type not recognized`) -// } - -// // update the file -// this._writeText(uri, writeText, toRange) - -// // originalCode does not change! - -// // delete the diff -// this._deleteDiff(diff) - -// // diffArea should be removed if it has no more diffs in it -// if (Object.keys(diffArea._diffOfId).length === 0) { -// this._deleteDiffArea(diffArea) -// } - -// this._refreshDiffsInURI(uri) - -// onFinishEdit() - -// } - -// } - -// registerSingleton(IInlineDiffsService, InlineDiffsService, InstantiationType.Eager); - - - - -// class AcceptRejectWidget extends Widget implements IOverlayWidget { - -// public getId() { return this.ID } -// public getDomNode() { return this._domNode; } -// public getPosition() { return null } - -// private readonly _domNode: HTMLElement; -// private readonly editor -// private readonly ID -// private readonly startLine - -// constructor({ editor, onAccept, onReject, diffid, startLine }: { editor: ICodeEditor; onAccept: () => void; onReject: () => void; diffid: string, startLine: number }) { -// super() - -// this.ID = editor.getModel()?.uri.fsPath + diffid; -// this.editor = editor; -// this.startLine = startLine; - -// // Create container div with buttons -// const { acceptButton, rejectButton, buttons } = dom.h('div@buttons', [ -// dom.h('button@acceptButton', []), -// dom.h('button@rejectButton', []) -// ]); - -// // Style the container -// buttons.style.display = 'flex'; -// buttons.style.position = 'absolute'; -// buttons.style.gap = '4px'; -// buttons.style.padding = '4px'; -// buttons.style.zIndex = '1000'; - - -// // Style accept button -// acceptButton.onclick = onAccept; -// acceptButton.textContent = 'Accept'; -// acceptButton.style.backgroundColor = '#28a745'; -// acceptButton.style.color = 'white'; -// acceptButton.style.border = 'none'; -// acceptButton.style.padding = '4px 8px'; -// acceptButton.style.borderRadius = '3px'; -// acceptButton.style.cursor = 'pointer'; - -// // Style reject button -// rejectButton.onclick = onReject; -// rejectButton.textContent = 'Reject'; -// rejectButton.style.backgroundColor = '#dc3545'; -// rejectButton.style.color = 'white'; -// rejectButton.style.border = 'none'; -// rejectButton.style.padding = '4px 8px'; -// rejectButton.style.borderRadius = '3px'; -// rejectButton.style.cursor = 'pointer'; - -// this._domNode = buttons; - -// const updateTop = () => { -// const topPx = editor.getTopForLineNumber(this.startLine) - editor.getScrollTop() -// this._domNode.style.top = `${topPx}px` -// } -// const updateLeft = () => { -// const leftPx = 0//editor.getScrollLeft() - editor.getScrollWidth() -// this._domNode.style.left = `${leftPx}px` -// } - -// updateTop() -// updateLeft() - -// this._register(editor.onDidScrollChange(e => { updateTop() })) -// this._register(editor.onDidChangeModelContent(e => { updateTop() })) -// this._register(editor.onDidLayoutChange(e => { updateTop(); updateLeft() })) - -// // mount this widget - -// editor.addOverlayWidget(this); -// // console.log('created elt', this._domNode) -// } - -// public override dispose(): void { -// this.editor.removeOverlayWidget(this) -// super.dispose() -// } - -// } - - From ffe5a41b0178370b2e1344524d7e279962df4832 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 23 Dec 2024 04:33:49 -0800 Subject: [PATCH 08/25] fix rounding --- .../contrib/void/browser/inlineDiffsService.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts index e6feecdc..ec499bab 100644 --- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts +++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts @@ -890,8 +890,15 @@ Please finish writing the new file by applying the diff to the original file. Re // |B <-- deleted here, diff.startLine == diff.endLine // C if (diff.type === 'deletion') { - writeText = diff.originalCode + '\n' - toRange = { startLineNumber: diff.startLine, startColumn: 1, endLineNumber: diff.startLine, endColumn: 1 } + // if startLine is out of bounds (deleted lines past the diffarea), applyEdit will do a weird rounding thing, to account for that we apply the edit the line before + if (diff.startLine - 1 === diffArea.endLine) { + writeText = '\n' + diff.originalCode + toRange = { startLineNumber: diff.startLine - 1, startColumn: Number.MAX_SAFE_INTEGER, endLineNumber: diff.startLine - 1, endColumn: Number.MAX_SAFE_INTEGER } + } + else { + writeText = diff.originalCode + '\n' + toRange = { startLineNumber: diff.startLine, startColumn: 1, endLineNumber: diff.startLine, endColumn: 1 } + } } // if it was an insertion, need to delete all the lines // (this image applies to writeText and toRange, not newOriginalCode) From ef9d0daef4bd1a6f12d3e1e5b30b9f675aabd407 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 23 Dec 2024 15:37:06 -0800 Subject: [PATCH 09/25] minor chat styles --- .../contrib/void/browser/inlineDiffsService.ts | 7 ++++--- .../void/browser/react/src/sidebar-tsx/Sidebar.tsx | 2 +- .../void/browser/react/src/sidebar-tsx/SidebarChat.tsx | 2 +- .../contrib/void/browser/react/src/util/inputs.tsx | 10 +++++++--- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts index ec499bab..87039031 100644 --- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts +++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts @@ -377,7 +377,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { const { snapshottedDiffAreaOfId, entireFileCode: entireModelCode } = structuredClone(snapshot) // don't want to destroy the snapshot // delete all current decorations (diffs, sweep styles) so we don't have any unwanted leftover decorations - this._clearAllDiffsAndStyles(uri) + this._clearAllDiffsAndAllStyles(uri) // restore diffAreaOfId and diffAreasOfModelId this.diffAreaOfId = {} @@ -436,7 +436,8 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { } } - private _clearAllDiffsAndStyles(uri: URI) { + // clears styles of DiffAreas too + private _clearAllDiffsAndAllStyles(uri: URI) { for (let diffareaid of this.diffAreasOfURI[uri.fsPath]) { const diffArea = this.diffAreaOfId[diffareaid] this._deleteDiffs(diffArea) @@ -529,7 +530,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { if (content === null) return // 1. clear Diffs and styles - this._clearAllDiffsAndStyles(uri) + this._clearAllDiffsAndAllStyles(uri) // 2. recompute all diffs on each editor with this URI const fullFileText = this._readURI(uri) ?? '' diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/Sidebar.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/Sidebar.tsx index 55fa83b7..3c2e5857 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/Sidebar.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/Sidebar.tsx @@ -31,7 +31,7 @@ export const Sidebar = ({ className }: { className: string }) => { sidebarStateService.setState({ currentTab: tabs[(index + 1) % tabs.length] as any }) }}>clickme {tab} */} -
+
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 d3c1ce8b..1194dbf8 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 @@ -300,7 +300,7 @@ const ChatBubble = ({ chatMessage }: { } return
-
+
{chatbubbleContents}
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 260f8c75..03cb9960 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 @@ -287,7 +287,7 @@ const normalizeIndentation = (code: string): string => { export const VoidCodeEditor = ({ initValue, language }: { initValue: string, language: string | undefined }) => { - const MAX_HEIGHT = 200; + const MAX_HEIGHT = Infinity; const divRef = useRef(null) @@ -310,8 +310,8 @@ export const VoidCodeEditor = ({ initValue, language }: { initValue: string, lan scrollbar: { alwaysConsumeMouseWheel: false, - vertical: 'auto', - horizontal: 'auto', + vertical: 'hidden', + horizontal: 'hidden', }, scrollBeyondLastLine: false, @@ -335,6 +335,10 @@ export const VoidCodeEditor = ({ initValue, language }: { initValue: string, lan hideCursorInOverviewRuler: true, overviewRulerBorder: false, glyphMargin: false, + + stickyScroll: { + enabled: false, + }, }, { isSimpleWidget: true, From dfbd729fa4a6bce83ed6915ba5c359a7a4f6dda9 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Tue, 24 Dec 2024 23:45:58 -0500 Subject: [PATCH 10/25] nightly progress --- .../platform/void/common/llmMessageTypes.ts | 14 +- .../void/browser/inlineDiffsService.ts | 407 +++++++++++------- .../contrib/void/browser/quickEditActions.ts | 97 +---- 3 files changed, 272 insertions(+), 246 deletions(-) diff --git a/src/vs/platform/void/common/llmMessageTypes.ts b/src/vs/platform/void/common/llmMessageTypes.ts index 5bc92e24..c1e46671 100644 --- a/src/vs/platform/void/common/llmMessageTypes.ts +++ b/src/vs/platform/void/common/llmMessageTypes.ts @@ -17,14 +17,14 @@ export type LLMMessage = { content: string; } -export type LLMFeatureSelection = { - featureName: 'Ctrl+K', - range: IRange +export type ServiceSendLLMFeatureParams = { + featureName: 'Ctrl+K'; + range: IRange; } | { - featureName: 'Ctrl+L', + featureName: 'Ctrl+L'; } | { - featureName: 'Autocomplete', - range: IRange + featureName: 'Autocomplete'; + range: IRange; } // params to the true sendLLMMessage function @@ -54,7 +54,7 @@ export type ServiceSendLLMMessageParams = { logging: { loggingName: string, }; -} & LLMFeatureSelection +} & ServiceSendLLMFeatureParams // can't send functions across a proxy, use listeners instead export type BlockedMainLLMMessageParams = 'onText' | 'onFinalMessage' | 'onError' | 'abortRef' diff --git a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts index 87039031..d20d770f 100644 --- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts +++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts @@ -5,7 +5,7 @@ import { Disposable } from '../../../../base/common/lifecycle.js'; import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; -import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; +import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ICodeEditor, IOverlayWidget, IViewZone } from '../../../../editor/browser/editorBrowser.js'; // import { IUndoRedoService } from '../../../../platform/undoRedo/common/undoRedo.js'; @@ -26,11 +26,14 @@ import { ILanguageService } from '../../../../editor/common/languages/language.j 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 { ServiceSendLLMFeatureParams } 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'; +import { IPosition } from '../../../../editor/common/core/position.js'; +import { mountCtrlK } from '../browser/react/out/ctrl-k-tsx/index.js' +import { QuickEditPropsType } from './quickEditActions.js'; const configOfBG = (color: Color) => { return { dark: color, light: color, hcDark: color, hcLight: color, } @@ -52,62 +55,99 @@ const sweepIdxBG = new Color(new RGBA(100, 100, 100, .5)); registerColor('void.sweepIdxBG', configOfBG(sweepIdxBG), '', true); + +// similar to ServiceLLM +export type StartStreamingOpts = { + featureName: 'Ctrl+K'; + diffareaid: string; // id of the CtrlK area +} | { + featureName: 'Ctrl+L'; +} | { + featureName: 'Autocomplete'; + range: IRange; +} + + + + export type Diff = { diffid: number; diffareaid: number; // the diff area this diff belongs to, "computed" } & ComputedDiff + + // _ means anything we don't include if we clone it // DiffArea.originalStartLine is the line in originalCode (not the file) -type DiffArea = { + +type CommonZoneProps = { diffareaid: number; - originalCode: string; startLine: number; endLine: number; - shouldHighlight: boolean; // should visually highlight this DiffArea _URI: URI; // typically we get the URI from model - _diffOfId: Record; // diffid -> diff in this DiffArea -} & ({ + _removeStylesFns: Set; // these don't remove diffs or this diffArea, only their styles + +} + +type CtrlKZone = { + type: 'CtrlKZone'; + originalCode?: undefined; + userText: string; +} & CommonZoneProps + + +type DiffZone = { + type: 'DiffZone', + originalCode: string; + _diffOfId: Record; // diffid -> diff in this DiffArea _sweepState: { isStreaming: true; + streamRequestIdRef: { current: string | null }; line: number; } | { isStreaming: false; + streamRequestIdRef?: undefined; line: null; }; -}) + userText?: undefined; +} & CommonZoneProps + + + +// called DiffArea for historical purposes, we can rename to something like TextRegion if we want +type DiffArea = CtrlKZone | DiffZone const diffAreaSnapshotKeys = [ + 'type', 'diffareaid', 'originalCode', 'startLine', 'endLine', - 'shouldHighlight', + 'userText', ] as const satisfies (keyof DiffArea)[] -type DiffAreaSnapshot = Pick +type DiffAreaSnapshot = Pick type HistorySnapshot = { snapshottedDiffAreaOfId: Record; entireFileCode: string; -} & - ({ - type: 'Ctrl+K'; - ctrlKText: string; - } | { - type: 'Ctrl+L'; - }) +} & ({ + type: 'Ctrl+K'; + ctrlKText: string; +} | { + type: 'Ctrl+L'; +}) export interface IInlineDiffsService { readonly _serviceBrand: undefined; - startStreaming(params: LLMFeatureSelection, str: string): void; + startStreaming(params: , str: string): void; } export const IInlineDiffsService = createDecorator('inlineDiffAreasService'); @@ -117,12 +157,13 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { // URI <--> model - removeStylesFnsOfURI: Record> = {} // functions that remove the styles of this uri diffAreasOfURI: Record> = {} diffAreaOfId: Record = {}; diffOfId: Record = {}; // redundant with diffArea._diffs + + _diffareaidPool = 0 // each diffarea has an id _diffidPool = 0 // each diff has an id @@ -134,6 +175,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { @ILanguageService private readonly _langService: ILanguageService, @ILLMMessageService private readonly _llmMessageService: ILLMMessageService, @IConsistentItemService private readonly _zoneStyleService: IConsistentItemService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { super(); @@ -142,9 +184,6 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { if (!(model.uri.fsPath in this.diffAreasOfURI)) { this.diffAreasOfURI[model.uri.fsPath] = new Set(); } - if (!(model.uri.fsPath in this.removeStylesFnsOfURI)) { - this.removeStylesFnsOfURI[model.uri.fsPath] = new Set(); - } // when the user types, realign diff areas and re-render them this._register( @@ -178,19 +217,10 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { 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.newModelUrl) this._refreshDiffsInURI(e.newModelUrl) - // if (e.oldModelUrl) this._clearAllDiffsAndStyles(e.oldModelUrl) - // })) } // add listeners for all existing editors + listen for editor being added for (let editor of this._editorService.listCodeEditors()) { initializeEditor(editor) } this._register(this._editorService.onCodeEditorAdd(editor => { initializeEditor(editor) })) - // this._register(this._editorService.onCodeEditorRemove(editor => { console.log('REMOVE EDITOR'); initializeEditor(editor) })) } @@ -221,18 +251,61 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { for (const diffareaid of this.diffAreasOfURI[uri.fsPath]) { const diffArea = this.diffAreaOfId[diffareaid] - // add sweep styles to the diffArea - if (diffArea._sweepState.isStreaming) { - // sweepLine ... sweepLine - const fn1 = this._addLineDecoration(model, diffArea._sweepState.line, diffArea._sweepState.line, 'void-sweepIdxBG') - // sweepLine+1 ... endLine - const fn2 = this._addLineDecoration(model, diffArea._sweepState.line + 1, diffArea.endLine, 'void-sweepBG') - this.removeStylesFnsOfURI[uri.fsPath].add(() => { fn1?.(); fn2?.(); }) + if (diffArea.type === 'DiffZone') { + // add sweep styles to the diffZone + if (diffArea._sweepState.isStreaming) { + // sweepLine ... sweepLine + const fn1 = this._addLineDecoration(model, diffArea._sweepState.line, diffArea._sweepState.line, 'void-sweepIdxBG') + // sweepLine+1 ... endLine + const fn2 = this._addLineDecoration(model, diffArea._sweepState.line + 1, diffArea.endLine, 'void-sweepBG') + diffArea._removeStylesFns.add(() => { fn1?.(); fn2?.(); }) + } } - // highlight the diffArea - if (diffArea.shouldHighlight) { + // highlight the ctrlK zone + if (diffArea.type === 'CtrlKZone') { + + const consistentZoneId = this._zoneStyleService.addConsistentItemToURI({ + uri, + fn: (editor) => { + const domNode = document.createElement('div'); + domNode.style.zIndex = '1' + + // domNode.className = 'void-redBG' + const viewZone: IViewZone = { + // afterLineNumber: computedDiff.startLine - 1, + afterLineNumber: 1, + heightInPx: 100, + // heightInLines: 1, + // minWidthInPx: 200, + domNode: domNode, + suppressMouseDown: false, + }; + + + let zoneId: string | null = null + editor.changeViewZones(accessor => { zoneId = accessor.addZone(viewZone) }) + const fn1 = () => editor.changeViewZones(accessor => { if (zoneId) accessor.removeZone(zoneId) }) + + // on resize + domNode.onresize = () => { + viewZone.heightInPx = domNode.clientHeight + editor.changeViewZones(accessor => { if (zoneId) accessor.layoutZone(zoneId) }) + } + + this._instantiationService.invokeFunction(accessor => { + const props: QuickEditPropsType = { + quickEditId: diffArea.diffareaid, + } + mountCtrlK(domNode, accessor, props) + }) + + return () => { fn1(); } + }, + }) + diffArea._removeStylesFns.add(() => this._zoneStyleService.removeConsistentItemFromURI(consistentZoneId)); + const fn = this._addLineDecoration(model, diffArea.startLine, diffArea.endLine, 'void-highlightBG') - this.removeStylesFnsOfURI[uri.fsPath].add(() => fn?.()); + diffArea._removeStylesFns.add(() => fn?.()); } } } @@ -377,20 +450,38 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { const { snapshottedDiffAreaOfId, entireFileCode: entireModelCode } = structuredClone(snapshot) // don't want to destroy the snapshot // delete all current decorations (diffs, sweep styles) so we don't have any unwanted leftover decorations - this._clearAllDiffsAndAllStyles(uri) + this._clearAllEffects(uri) + + // __TODO__ stop streaming if currently streaming + + // restore diffAreaOfId and diffAreasOfModelId this.diffAreaOfId = {} this.diffAreasOfURI[uri.fsPath].clear() for (const diffareaid in snapshottedDiffAreaOfId) { - this.diffAreaOfId[diffareaid] = { - ...snapshottedDiffAreaOfId[diffareaid], - _diffOfId: {}, - _URI: uri, - _sweepState: { - isStreaming: false, - line: null, - }, + + const snapshottedDiffArea = snapshottedDiffAreaOfId[diffareaid] + + if (snapshottedDiffArea.type === 'DiffZone') { + this.diffAreaOfId[diffareaid] = { + ...snapshottedDiffArea as DiffAreaSnapshot, + type: 'DiffZone', + _diffOfId: {}, + _URI: uri, + _sweepState: { + isStreaming: false, + line: null, + } as const, + _removeStylesFns: new Set(), + } + } + else if (snapshottedDiffArea.type === 'CtrlKZone') { + this.diffAreaOfId[diffareaid] = { + ...snapshottedDiffArea as DiffAreaSnapshot, + _URI: uri, + _removeStylesFns: new Set(), + } } this.diffAreasOfURI[uri.fsPath].add(diffareaid) } @@ -425,34 +516,43 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { // delete diffOfId and diffArea._diffOfId private _deleteDiff(diff: Diff) { const diffArea = this.diffAreaOfId[diff.diffareaid] + if (diffArea.type !== 'DiffZone') return delete diffArea._diffOfId[diff.diffid] delete this.diffOfId[diff.diffid] } - private _deleteDiffs(diffArea: DiffArea) { - for (const diffid in diffArea._diffOfId) { - const diff = diffArea._diffOfId[diffid] + private _deleteDiffs(diffZone: DiffZone) { + for (const diffid in diffZone._diffOfId) { + const diff = diffZone._diffOfId[diffid] this._deleteDiff(diff) } } - // clears styles of DiffAreas too - private _clearAllDiffsAndAllStyles(uri: URI) { + private _deleteEffects(diffArea: DiffArea) { + // clear diffZone effects (diffs) + if (diffArea.type === 'DiffZone') + this._deleteDiffs(diffArea) + else if (diffArea.type === 'CtrlKZone') { + + } + + diffArea._removeStylesFns.forEach(removeStyles => removeStyles()) + } + + // clears all Diffs (and their styles) and all styles of DiffAreas + private _clearAllEffects(uri: URI) { for (let diffareaid of this.diffAreasOfURI[uri.fsPath]) { const diffArea = this.diffAreaOfId[diffareaid] - this._deleteDiffs(diffArea) + this._deleteEffects(diffArea) + diffArea._removeStylesFns.clear() } - for (const removeStyleFn of this.removeStylesFnsOfURI[uri.fsPath]) { - removeStyleFn() - } - this.removeStylesFnsOfURI[uri.fsPath].clear() } // delete all diffs, update diffAreaOfId, update diffAreasOfModelId private _deleteDiffArea(diffArea: DiffArea) { - this._deleteDiffs(diffArea) + // we had clear all diffs, but not really needed delete this.diffAreaOfId[diffArea.diffareaid] this.diffAreasOfURI[diffArea._URI.fsPath].delete(diffArea.diffareaid.toString()) } @@ -530,9 +630,10 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { if (content === null) return // 1. clear Diffs and styles - this._clearAllDiffsAndAllStyles(uri) + this._clearAllEffects(uri) // 2. recompute all diffs on each editor with this URI + const fullFileText = this._readURI(uri) ?? '' @@ -540,25 +641,28 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { for (let diffareaid of this.diffAreasOfURI[uri.fsPath]) { const diffArea = this.diffAreaOfId[diffareaid] - console.log('DA start and end:', diffArea.startLine, diffArea.endLine) - const newDiffAreaCode = fullFileText.split('\n').slice((diffArea.startLine - 1), (diffArea.endLine - 1) + 1).join('\n') - const computedDiffs = findDiffs(diffArea.originalCode, newDiffAreaCode) + if (diffArea.type === 'DiffZone') { - for (let computedDiff of computedDiffs) { - const diffid = this._diffidPool++ + console.log('DA start and end:', diffArea.startLine, diffArea.endLine) + const newDiffAreaCode = fullFileText.split('\n').slice((diffArea.startLine - 1), (diffArea.endLine - 1) + 1).join('\n') + const computedDiffs = findDiffs(diffArea.originalCode, newDiffAreaCode) - // create a Diff of it - const newDiff: Diff = { - ...computedDiff, - diffid: diffid, - diffareaid: diffArea.diffareaid, + for (let computedDiff of computedDiffs) { + const diffid = this._diffidPool++ + + // create a Diff of it + const newDiff: Diff = { + ...computedDiff, + diffid: diffid, + diffareaid: diffArea.diffareaid, + } + + const fn = this._addDiffStylesToURI(uri, newDiff) + diffArea._removeStylesFns.add(fn) + + this.diffOfId[diffid] = newDiff + diffArea._diffOfId[diffid] = newDiff } - - const fn = this._addDiffStylesToURI(uri, newDiff) - this.removeStylesFnsOfURI[uri.fsPath].add(fn) - - this.diffOfId[diffid] = newDiff - diffArea._diffOfId[diffid] = newDiff } // update styles on this DiffArea @@ -570,16 +674,17 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { // @throttle(100) - private _writeDiffAreaLLMText(diffArea: DiffArea, newCodeSoFar: string) { + private _writeDiffZoneLLMText(diffArea: DiffArea, llmText: string, latestCurrentFileEnd: IPosition, newPosition: IPosition) { + if (diffArea.type !== 'DiffZone') return // ----------- 1. Write the new code to the document ----------- // figure out where to highlight based on where the AI is in the stream right now, use the last diff to figure that out const uri = diffArea._URI - const computedDiffs = findDiffs(diffArea.originalCode, newCodeSoFar) + const computedDiffs = findDiffs(diffArea.originalCode, llmText) // if not streaming, just write the new code if (!diffArea._sweepState.isStreaming) { - this._writeText(uri, newCodeSoFar, + this._writeText(uri, llmText, { startLineNumber: diffArea.startLine, startColumn: 1, endLineNumber: diffArea.endLine, endColumn: Number.MAX_SAFE_INTEGER, } // 1-indexed ) } @@ -617,7 +722,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { diffArea._sweepState.line = newFileEndLine // lines are 1-indexed - const newFileTop = newCodeSoFar.split('\n').slice(0, (newFileEndLine - 1)).join('\n') + const newFileTop = llmText.split('\n').slice(0, (newFileEndLine - 1)).join('\n') const oldFileBottom = diffArea.originalCode.split('\n').slice((oldFileStartLine - 1), Infinity).join('\n') const newCode = `${newFileTop}\n${oldFileBottom}` @@ -635,7 +740,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { - private async _initializeStream(opts: LLMFeatureSelection, diffRepr: string, uri: URI,) { + private _initializeStream(featureParams: ServiceSendLLMFeatureParams, diffRepr: string, uri: URI,): DiffZone | undefined { // diff area begin and end line const numLines = this._getNumLines(uri) @@ -660,6 +765,10 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { if (currentFileStr === null) return const originalCode = currentFileStr.split('\n').slice((beginLine - 1), (endLine - 1) + 1).join('\n') + + let streamRequestIdRef: { current: string | null } = { current: null } + + // add to history const { onFinishEdit } = this._addToHistory(uri) @@ -667,20 +776,22 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { const diffareaid = this._diffareaidPool++ // in ctrl+L the start and end lines are the full document - const diffArea: DiffArea = { + const diffArea: DiffZone = { + type: 'DiffZone', diffareaid: diffareaid, // originalStartLine: beginLine, // originalEndLine: endLine, originalCode: originalCode, startLine: beginLine, endLine: endLine, // starts out the same as the current file - shouldHighlight: false, _URI: uri, _sweepState: { isStreaming: true, + streamRequestIdRef, line: 1, }, _diffOfId: {}, // added later + _removeStylesFns: new Set(), } // console.log('adding uri.fspath', uri.fsPath, diffArea.diffareaid.toString()) @@ -704,45 +815,42 @@ Please finish writing the new file by applying the diff to the original file. Re ` - await new Promise((resolve, reject) => { + const latestCurrentFileEnd: IPosition = { lineNumber: 1, column: 1 } + const latestOriginalFileStart: IPosition = { lineNumber: 1, column: 1 } - let streamRequestId: string | null = null + streamRequestIdRef.current = this._llmMessageService.sendLLMMessage({ + logging: { loggingName: 'streamChunk' }, + messages: [ + { role: 'system', content: inlineDiff_systemMessage, }, + // TODO include more context too + { role: 'user', content: promptContent, } + ], + onText: ({ newText, fullText }) => { + this._writeDiffZoneLLMText(diffArea, fullText, latestCurrentFileEnd, latestOriginalFileStart) + this._refreshDiffsInURI(uri) + }, + onFinalMessage: ({ fullText }) => { + this._writeText(uri, fullText, + { startLineNumber: diffArea.startLine, startColumn: 1, endLineNumber: diffArea.endLine, endColumn: Number.MAX_SAFE_INTEGER }, // 1-indexed + ) + diffArea._sweepState = { isStreaming: false, line: null } + this._refreshDiffsInURI(uri) + onFinishEdit() + }, + onError: (e) => { + console.error('Error rewriting file with diff', e); + // TODO indicate there was an error + if (streamRequestIdRef.current) + this._llmMessageService.abort(streamRequestIdRef.current) - const object: ServiceSendLLMMessageParams = { - logging: { loggingName: 'streamChunk' }, - messages: [ - { role: 'system', content: inlineDiff_systemMessage, }, - // TODO include more context too - { role: 'user', content: promptContent, } - ], - onText: ({ newText, fullText }) => { - this._writeDiffAreaLLMText(diffArea, fullText) - this._refreshDiffsInURI(uri) - }, - onFinalMessage: ({ fullText }) => { - this._writeText(uri, fullText, - { startLineNumber: diffArea.startLine, startColumn: 1, endLineNumber: diffArea.endLine, endColumn: Number.MAX_SAFE_INTEGER }, // 1-indexed - ) - diffArea._sweepState = { isStreaming: false, line: null } - this._refreshDiffsInURI(uri) - resolve(); - }, - onError: (e: any) => { - console.error('Error rewriting file with diff', e); - // TODO indicate there was an error - if (streamRequestId) - this._llmMessageService.abort(streamRequestId) - - diffArea._sweepState = { isStreaming: false, line: null } - resolve(); - }, - ...opts - } - - streamRequestId = this._llmMessageService.sendLLMMessage(object) + diffArea._sweepState = { isStreaming: false, line: null } + onFinishEdit() + }, + ...featureParams }) - onFinishEdit() + + return diffArea } @@ -751,49 +859,50 @@ Please finish writing the new file by applying the diff to the original file. Re - async startStreaming(opts: LLMFeatureSelection, userMessage: string) { - + async startStreaming(opts: StartStreamingOpts, userMessage: string) { const editor = this._editorService.getActiveCodeEditor() if (!editor) return - const uri = editor.getModel()?.uri if (!uri) return - // TODO reject all diffs in the diff area - // TODO deselect user's cursor - - this._initializeStream(opts, userMessage, uri) + const addedDiffZone = this._initializeStream(opts, userMessage, uri) + return addedDiffZone?.diffareaid } - interruptStreaming() { - // TODO add abort + private _stopIfStreaming(diffZone: DiffZone) { + } - addDiffArea({ uri, startLine, endLine, originalCode }: { uri: URI, startLine: number, endLine: number, originalCode: string }) { - const diffareaid = this._diffareaidPool++ + interruptStreaming(diffareaid: string) { + const diffArea = this.diffAreaOfId[diffareaid] - const diffArea: DiffArea = { - diffareaid: diffareaid, - originalCode, - startLine, - endLine, - shouldHighlight: true, - _URI: uri, - _sweepState: { - isStreaming: false, - line: null, - }, - _diffOfId: {}, - } + if (!diffArea) return + if (diffArea.type !== 'DiffZone') return + if (!diffArea._sweepState.isStreaming) return - this.diffAreasOfURI[uri.fsPath].add(diffArea.diffareaid.toString()) - this.diffAreaOfId[diffArea.diffareaid] = diffArea + const streamRequestId = diffArea._sweepState.streamRequestIdRef.current + if (streamRequestId) + this._llmMessageService.abort(streamRequestId) + + // __TODO__ update diffArea streamState here + don't elsewhere + // call undo - __TODO__ make this get called in undo and redo too + + this._undoRedoService.undo(diffArea._URI) + + } + + + + + + addCtrlK({ uri, range }: { uri: URI, range: IRange, }) { + + // TODO check if intersects with a current ctrl K, if so focus it - this._refreshDiffsInURI(uri) } @@ -810,6 +919,8 @@ Please finish writing the new file by applying the diff to the original file. Re const diffArea = this.diffAreaOfId[diffareaid] if (!diffArea) return + if (diffArea.type !== 'DiffZone') return + const uri = diffArea._URI // add to history @@ -877,6 +988,8 @@ Please finish writing the new file by applying the diff to the original file. Re const diffArea = this.diffAreaOfId[diffareaid] if (!diffArea) return + if (diffArea.type !== 'DiffZone') return + const uri = diffArea._URI // add to history diff --git a/src/vs/workbench/contrib/void/browser/quickEditActions.ts b/src/vs/workbench/contrib/void/browser/quickEditActions.ts index 9d5314a8..e3ea47c3 100644 --- a/src/vs/workbench/contrib/void/browser/quickEditActions.ts +++ b/src/vs/workbench/contrib/void/browser/quickEditActions.ts @@ -1,20 +1,11 @@ import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; -import { ICodeEditor, IViewZone } from '../../../../editor/browser/editorBrowser.js'; import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js'; -import { createDecorator, IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { IMetricsService } from '../../../../platform/void/common/metricsService.js'; -import { Emitter, Event } from '../../../../base/common/event.js'; -// import { IInlineDiffService } from '../../../../editor/browser/services/inlineDiffService/inlineDiffService.js'; -import { Disposable } from '../../../../base/common/lifecycle.js'; -import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; -import { mountCtrlK } from './react/out/ctrl-k-tsx/index.js'; -import { URI } from '../../../../base/common/uri.js'; -type InitialZone = { uri: URI, startLine: number, selectedText: string, } - export type QuickEditPropsType = { quickEditId: number, } @@ -28,83 +19,6 @@ export type QuickEdit = { } -export interface IQuickEditService { - readonly _serviceBrand: undefined; - readonly onDidChangeState: Event; - addZone(zone: InitialZone): void; -} - -export const IQuickEditService = createDecorator('voidQuickEditService'); -class VoidQuickEditService extends Disposable implements IQuickEditService { - _serviceBrand: undefined; - - quickEditId: number = 0 - - private readonly _onDidChangeState = new Emitter(); - readonly onDidChangeState: Event = this._onDidChangeState.event; - - // state - // state: {} - - constructor( - // @IInlineDiffService private readonly _inlineDiffService: IInlineDiffService, - @ICodeEditorService private readonly _editorService: ICodeEditorService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - ) { - super(); - } - - addZone(zone: InitialZone) { - - const addZoneToEditor = (editor: ICodeEditor) => { - - const model = editor.getModel() - if (!model) return - - editor.changeViewZones(accessor => { - - const domNode = document.createElement('div'); - domNode.style.zIndex = '1' - - // domNode.className = 'void-redBG' - const viewZone: IViewZone = { - // afterLineNumber: computedDiff.startLine - 1, - afterLineNumber: 1, - heightInPx: 100, - // heightInLines: 1, - // minWidthInPx: 200, - domNode: domNode, - // marginDomNode: document.createElement('div'), // displayed to left - suppressMouseDown: false, - }; - - // const zoneId = - accessor.addZone(viewZone) - - this._instantiationService.invokeFunction(accessor => { - const props: QuickEditPropsType = { - quickEditId: this.quickEditId++, - } - mountCtrlK(domNode, accessor, props) - }) - - // disposeInThisEditorFns.push(() => { editor.changeViewZones(accessor => { if (zoneId) accessor.removeZone(zoneId) }) }) - }) - } - - - const editors = this._editorService.listCodeEditors().filter(editor => editor.getModel()?.uri.fsPath === zone.uri.fsPath) - for (const editor of editors) { - addZoneToEditor(editor) - } - } - -} - -registerSingleton(IQuickEditService, VoidQuickEditService, InstantiationType.Eager); - - - export const VOID_CTRL_K_ACTION_ID = 'void.ctrlKAction' registerAction2(class extends Action2 { constructor() { @@ -112,7 +26,6 @@ registerAction2(class extends Action2 { } async run(accessor: ServicesAccessor): Promise { - const quickEditService = accessor.get(IQuickEditService) const editorService = accessor.get(ICodeEditorService) const metricsService = accessor.get(IMetricsService) @@ -125,11 +38,11 @@ registerAction2(class extends Action2 { const selection = editor.getSelection() if (!selection) return; - const uri = model.uri - const startLine = selection.startLineNumber - const selectedText = model.getValueInRange(selection) + // const uri = model.uri + // const startLine = selection.startLineNumber + // const selectedText = model.getValueInRange(selection) - quickEditService.addZone({ uri, startLine, selectedText, }) + // quickEditService.addZone({ uri, startLine, selectedText, }) } }); From 1df044fb02f033c069a8fb4108fecfa275a35bb2 Mon Sep 17 00:00:00 2001 From: Andrew Pareles <43356051+andrewpareles@users.noreply.github.com> Date: Wed, 25 Dec 2024 22:23:01 -0500 Subject: [PATCH 11/25] Update product.json --- product.json | 51 +-------------------------------------------------- 1 file changed, 1 insertion(+), 50 deletions(-) diff --git a/product.json b/product.json index d892feaf..83cdc4e1 100644 --- a/product.json +++ b/product.json @@ -35,54 +35,5 @@ "serviceUrl": "https://open-vsx.org/vscode/gallery", "itemUrl": "https://open-vsx.org/vscode/item" }, - "builtInExtensions": [ - { - "name": "ms-vscode.js-debug-companion", - "version": "1.1.3", - "sha256": "7380a890787452f14b2db7835dfa94de538caf358ebc263f9d46dd68ac52de93", - "repo": "https://github.com/microsoft/vscode-js-debug-companion", - "metadata": { - "id": "99cb0b7f-7354-4278-b8da-6cc79972169d", - "publisherId": { - "publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee", - "publisherName": "ms-vscode", - "displayName": "Microsoft", - "flags": "verified" - }, - "publisherDisplayName": "Microsoft" - } - }, - { - "name": "ms-vscode.js-debug", - "version": "1.93.0", - "sha256": "9339cb8e6b77f554df54d79e71f533279cb76b0f9b04c207f633bfd507442b6a", - "repo": "https://github.com/microsoft/vscode-js-debug", - "metadata": { - "id": "25629058-ddac-4e17-abba-74678e126c5d", - "publisherId": { - "publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee", - "publisherName": "ms-vscode", - "displayName": "Microsoft", - "flags": "verified" - }, - "publisherDisplayName": "Microsoft" - } - }, - { - "name": "ms-vscode.vscode-js-profile-table", - "version": "1.0.9", - "sha256": "3b62ee4276a2bbea3fe230f94b1d5edd915b05966090ea56f882e1e0ab53e1a6", - "repo": "https://github.com/microsoft/vscode-js-profile-visualizer", - "metadata": { - "id": "7e52b41b-71ad-457b-ab7e-0620f1fc4feb", - "publisherId": { - "publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee", - "publisherName": "ms-vscode", - "displayName": "Microsoft", - "flags": "verified" - }, - "publisherDisplayName": "Microsoft" - } - } - ] + "builtInExtensions": [] } From 9a9d0da91e2b33b6df242f4f629dd6ae006aba31 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Fri, 27 Dec 2024 23:56:24 -0500 Subject: [PATCH 12/25] remove errors --- .../workbench/contrib/void/browser/inlineDiffsService.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts index d20d770f..51eaabdd 100644 --- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts +++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts @@ -147,7 +147,7 @@ type HistorySnapshot = { export interface IInlineDiffsService { readonly _serviceBrand: undefined; - startStreaming(params: , str: string): void; + startStreaming(opts: StartStreamingOpts, userMessage: string): Promise; } export const IInlineDiffsService = createDecorator('inlineDiffAreasService'); @@ -866,14 +866,15 @@ Please finish writing the new file by applying the diff to the original file. Re if (!uri) return // TODO reject all diffs in the diff area // TODO deselect user's cursor + // TODO convert opts to opts const addedDiffZone = this._initializeStream(opts, userMessage, uri) return addedDiffZone?.diffareaid } - private _stopIfStreaming(diffZone: DiffZone) { + // private _stopIfStreaming(diffZone: DiffZone) { - } + // } From 33909b31431c82df2cc2296521ec3fd3f87567bd Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Sat, 28 Dec 2024 20:03:47 -0500 Subject: [PATCH 13/25] add ctrl+K --- .../void/browser/inlineDiffsService.ts | 273 ++++++++++++------ .../contrib/void/browser/quickEditActions.ts | 26 +- .../react/src/ctrl-k-tsx/CtrlKChat.tsx | 35 ++- 3 files changed, 235 insertions(+), 99 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts index 51eaabdd..793e7f86 100644 --- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts +++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts @@ -26,7 +26,6 @@ import { ILanguageService } from '../../../../editor/common/languages/language.j import * as dom from '../../../../base/browser/dom.js'; import { Widget } from '../../../../base/browser/ui/widget.js'; import { URI } from '../../../../base/common/uri.js'; -import { ServiceSendLLMFeatureParams } 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'; @@ -59,14 +58,22 @@ registerColor('void.sweepIdxBG', configOfBG(sweepIdxBG), '', true); // similar to ServiceLLM export type StartStreamingOpts = { featureName: 'Ctrl+K'; - diffareaid: string; // id of the CtrlK area + diffareaid: number; // id of the CtrlK area + userMessage?: undefined; } | { featureName: 'Ctrl+L'; + userMessage: string; } | { featureName: 'Autocomplete'; range: IRange; + userMessage: string; } +export type AddCtrlKOpts = { + startLine: number, + endLine: number, + uri: URI, +} @@ -95,7 +102,7 @@ type CommonZoneProps = { type CtrlKZone = { type: 'CtrlKZone'; originalCode?: undefined; - userText: string; + userText: string | null; } & CommonZoneProps @@ -103,7 +110,7 @@ type DiffZone = { type: 'DiffZone', originalCode: string; _diffOfId: Record; // diffid -> diff in this DiffArea - _sweepState: { + _streamState: { isStreaming: true; streamRequestIdRef: { current: string | null }; line: number; @@ -147,7 +154,9 @@ type HistorySnapshot = { export interface IInlineDiffsService { readonly _serviceBrand: undefined; - startStreaming(opts: StartStreamingOpts, userMessage: string): Promise; + startStreaming(opts: StartStreamingOpts, userMessage: string): number | undefined; + interruptStreaming(diffareaid: number): void; + addCtrlKZone(opts: AddCtrlKOpts): number; } export const IInlineDiffsService = createDecorator('inlineDiffAreasService'); @@ -164,7 +173,6 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { - _diffareaidPool = 0 // each diffarea has an id _diffidPool = 0 // each diff has an id constructor( @@ -253,11 +261,11 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { const diffArea = this.diffAreaOfId[diffareaid] if (diffArea.type === 'DiffZone') { // add sweep styles to the diffZone - if (diffArea._sweepState.isStreaming) { + if (diffArea._streamState.isStreaming) { // sweepLine ... sweepLine - const fn1 = this._addLineDecoration(model, diffArea._sweepState.line, diffArea._sweepState.line, 'void-sweepIdxBG') + const fn1 = this._addLineDecoration(model, diffArea._streamState.line, diffArea._streamState.line, 'void-sweepIdxBG') // sweepLine+1 ... endLine - const fn2 = this._addLineDecoration(model, diffArea._sweepState.line + 1, diffArea.endLine, 'void-sweepBG') + const fn2 = this._addLineDecoration(model, diffArea._streamState.line + 1, diffArea.endLine, 'void-sweepBG') diffArea._removeStylesFns.add(() => { fn1?.(); fn2?.(); }) } } @@ -273,7 +281,8 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { // domNode.className = 'void-redBG' const viewZone: IViewZone = { // afterLineNumber: computedDiff.startLine - 1, - afterLineNumber: 1, + afterLineNumber: diffArea.startLine, + // __TODO__ heightInPx update dynamically heightInPx: 100, // heightInLines: 1, // minWidthInPx: 200, @@ -286,17 +295,22 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { editor.changeViewZones(accessor => { zoneId = accessor.addZone(viewZone) }) const fn1 = () => editor.changeViewZones(accessor => { if (zoneId) accessor.removeZone(zoneId) }) + + // __TODO fix this resize part // on resize domNode.onresize = () => { + console.log('RESIZING!!!!') viewZone.heightInPx = domNode.clientHeight editor.changeViewZones(accessor => { if (zoneId) accessor.layoutZone(zoneId) }) } this._instantiationService.invokeFunction(accessor => { const props: QuickEditPropsType = { - quickEditId: diffArea.diffareaid, + diffareaid: diffArea.diffareaid, + onUserUpdateText(text) { diffArea.userText = text; }, } mountCtrlK(domNode, accessor, props) + // __TODO__ dismount }) return () => { fn1(); } @@ -410,7 +424,13 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { private _getNumLines(uri: URI): number | null { return this._getModel(uri)?.getLineCount() ?? null } - + private _getActiveEditorURI(): URI | null { + const editor = this._editorService.getActiveCodeEditor() + if (!editor) return null + const uri = editor.getModel()?.uri + if (!uri) return null + return uri + } _weAreWriting = false private _writeText(uri: URI, text: string, range: IRange) { @@ -452,9 +472,12 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { // delete all current decorations (diffs, sweep styles) so we don't have any unwanted leftover decorations this._clearAllEffects(uri) - // __TODO__ stop streaming if currently streaming - - + // for each diffarea in this uri, stop streaming if currently streaming + for (const diffareaid in this.diffAreaOfId) { + const diffArea = this.diffAreaOfId[diffareaid] + if (diffArea.type === 'DiffZone') + this._stopIfStreaming(diffArea) + } // restore diffAreaOfId and diffAreasOfModelId this.diffAreaOfId = {} @@ -469,7 +492,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { type: 'DiffZone', _diffOfId: {}, _URI: uri, - _sweepState: { + _streamState: { isStreaming: false, line: null, } as const, @@ -560,6 +583,18 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { + private _diffareaidPool = 0 // each diffarea has an id + private _addDiffArea(diffArea: Omit): T { + const diffareaid = this._diffareaidPool++ + const diffArea2 = { ...diffArea, diffareaid } as T + this.diffAreasOfURI[diffArea2._URI.fsPath].add(diffareaid.toString()) + this.diffAreaOfId[diffareaid] = diffArea2 + return diffArea2 + } + + + + // changes the start/line locations of all DiffAreas on the page (adjust their start/end based on the change) based on the change that was recently made private _realignAllDiffAreasLines(uri: URI, text: string, recentChange: { startLineNumber: number; endLineNumber: number }) { @@ -683,7 +718,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { const computedDiffs = findDiffs(diffArea.originalCode, llmText) // if not streaming, just write the new code - if (!diffArea._sweepState.isStreaming) { + if (!diffArea._streamState.isStreaming) { this._writeText(uri, llmText, { startLineNumber: diffArea.startLine, startColumn: 1, endLineNumber: diffArea.endLine, endColumn: Number.MAX_SAFE_INTEGER, } // 1-indexed ) @@ -719,7 +754,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { } } - diffArea._sweepState.line = newFileEndLine + diffArea._streamState.line = newFileEndLine // lines are 1-indexed const newFileTop = llmText.split('\n').slice(0, (newFileEndLine - 1)).join('\n') @@ -739,31 +774,77 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { + private _initializeStream(opts: StartStreamingOpts): DiffZone | undefined { - private _initializeStream(featureParams: ServiceSendLLMFeatureParams, diffRepr: string, uri: URI,): DiffZone | undefined { + const { featureName } = opts - // diff area begin and end line - const numLines = this._getNumLines(uri) - if (numLines === null) return + let startLine: number + let endLine: number + let uri: URI + let userMessage: string - const beginLine = 1 - const endLine = numLines + if (featureName === 'Ctrl+L') { - // check if there's overlap with any other diffAreas and return early if there is - for (const diffareaid of this.diffAreasOfURI[uri.fsPath]) { - const da2 = this.diffAreaOfId[diffareaid] - if (!da2) continue - const noOverlap = da2.startLine > endLine || da2.endLine < beginLine - if (!noOverlap) { - // TODO add a message here that says this to the user too - console.error('Not diffing because found overlap:', this.diffAreasOfURI[uri.fsPath], beginLine, endLine) - return + const uri_ = this._getActiveEditorURI() + if (!uri_) return + uri = uri_ + + // __TODO__ reject all diffs in the diff area + // __TODO__ deselect user's cursor + + // in ctrl+L the start and end lines are the full document + const numLines = this._getNumLines(uri) + if (numLines === null) return + startLine = 1 + endLine = numLines + + // check if there's overlap with any other diffAreas and return early if there is + for (const diffareaid of this.diffAreasOfURI[uri.fsPath]) { + const da2 = this.diffAreaOfId[diffareaid] + if (!da2) continue + const noOverlap = da2.startLine > endLine || da2.endLine < startLine + if (!noOverlap) { + // TODO add a message here that says this to the user too + console.error('Not diffing because found overlap:', this.diffAreasOfURI[uri.fsPath], startLine, endLine) + return + } } + + userMessage = opts.userMessage } + else if (featureName === 'Ctrl+K') { + + const { diffareaid } = opts + + const ctrlKZone = this.diffAreaOfId[diffareaid] + const { startLine: startLine_, endLine: endLine_, _URI, userText } = ctrlKZone + uri = _URI + + startLine = startLine_ + endLine = endLine_ + + // check if there's overlap with any other ctrlKZones and if so, focus them + for (const diffareaid of this.diffAreasOfURI[uri.fsPath]) { + const da2 = this.diffAreaOfId[diffareaid] + if (!da2) continue + const noOverlap = da2.startLine > endLine || da2.endLine < startLine + if (!noOverlap) { + // __TODO__ focus it + return + } + } + + if (!userText) return + userMessage = userText + } + else { + throw new Error(`Void: diff.type not recognized on: ${featureName}`) + } + const currentFileStr = this._readURI(uri) if (currentFileStr === null) return - const originalCode = currentFileStr.split('\n').slice((beginLine - 1), (endLine - 1) + 1).join('\n') + const originalCode = currentFileStr.split('\n').slice((startLine - 1), (endLine - 1) + 1).join('\n') let streamRequestIdRef: { current: string | null } = { current: null } @@ -772,20 +853,24 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { // add to history const { onFinishEdit } = this._addToHistory(uri) - // create a diffArea for the stream - const diffareaid = this._diffareaidPool++ - // in ctrl+L the start and end lines are the full document - const diffArea: DiffZone = { + // for Ctrl+K, delete the current ctrlKZone, swapping it out for a diffZone + if (featureName === 'Ctrl+K') { + const { diffareaid } = opts + const ctrlKZone = this.diffAreaOfId[diffareaid] + this._deleteDiffArea(ctrlKZone) + } + + + + + const adding: Omit = { type: 'DiffZone', - diffareaid: diffareaid, - // originalStartLine: beginLine, - // originalEndLine: endLine, originalCode: originalCode, - startLine: beginLine, + startLine: startLine, endLine: endLine, // starts out the same as the current file _URI: uri, - _sweepState: { + _streamState: { isStreaming: true, streamRequestIdRef, line: 1, @@ -793,13 +878,10 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { _diffOfId: {}, // added later _removeStylesFns: new Set(), } - - // console.log('adding uri.fspath', uri.fsPath, diffArea.diffareaid.toString()) - this.diffAreasOfURI[uri.fsPath].add(diffArea.diffareaid.toString()) - this.diffAreaOfId[diffArea.diffareaid] = diffArea + const diffZone = this._addDiffArea(adding) // actually call the LLM - const promptContent = `\ + const userContent = featureName === 'Ctrl+L' ? `\ ORIGINAL_CODE \`\`\` ${originalCode} @@ -807,33 +889,40 @@ ${originalCode} DIFF \`\`\` -${diffRepr} +${userMessage} \`\`\` INSTRUCTIONS Please finish writing the new file by applying the diff to the original file. Return ONLY the completion of the file, without any explanation. +` + : `\ +CTRL K MESSAGE GOES HERE __TODO__! +${userMessage} ` + // __TODO__ make these only move forward + const latestCurrentFileEnd: IPosition = { lineNumber: 1, column: 1 } const latestOriginalFileStart: IPosition = { lineNumber: 1, column: 1 } streamRequestIdRef.current = this._llmMessageService.sendLLMMessage({ + featureName, logging: { loggingName: 'streamChunk' }, messages: [ { role: 'system', content: inlineDiff_systemMessage, }, // TODO include more context too - { role: 'user', content: promptContent, } + { role: 'user', content: userContent, } ], onText: ({ newText, fullText }) => { - this._writeDiffZoneLLMText(diffArea, fullText, latestCurrentFileEnd, latestOriginalFileStart) + this._writeDiffZoneLLMText(diffZone, fullText, latestCurrentFileEnd, latestOriginalFileStart) this._refreshDiffsInURI(uri) }, onFinalMessage: ({ fullText }) => { this._writeText(uri, fullText, - { startLineNumber: diffArea.startLine, startColumn: 1, endLineNumber: diffArea.endLine, endColumn: Number.MAX_SAFE_INTEGER }, // 1-indexed + { startLineNumber: diffZone.startLine, startColumn: 1, endLineNumber: diffZone.endLine, endColumn: Number.MAX_SAFE_INTEGER }, // 1-indexed ) - diffArea._sweepState = { isStreaming: false, line: null } + diffZone._streamState = { isStreaming: false, line: null } this._refreshDiffsInURI(uri) onFinishEdit() }, @@ -843,71 +932,81 @@ Please finish writing the new file by applying the diff to the original file. Re if (streamRequestIdRef.current) this._llmMessageService.abort(streamRequestIdRef.current) - diffArea._sweepState = { isStreaming: false, line: null } + diffZone._streamState = { isStreaming: false, line: null } onFinishEdit() }, - ...featureParams + + range: { startLineNumber: startLine, endLineNumber: endLine, startColumn: 1, endColumn: Number.MAX_SAFE_INTEGER }, }) - - return diffArea + return diffZone } + public addCtrlKZone({ startLine, endLine, uri }: AddCtrlKOpts) { + + const { onFinishEdit } = this._addToHistory(uri) + + const adding: Omit = { + type: 'CtrlKZone', + startLine: startLine, + endLine: endLine, + _URI: uri, + userText: null, + _removeStylesFns: new Set(), + } + const ctrlKZone = this._addDiffArea(adding) + + onFinishEdit() + + this._refreshDiffsInURI(uri) + return ctrlKZone.diffareaid + } - async startStreaming(opts: StartStreamingOpts, userMessage: string) { - const editor = this._editorService.getActiveCodeEditor() - if (!editor) return - const uri = editor.getModel()?.uri - if (!uri) return - // TODO reject all diffs in the diff area - // TODO deselect user's cursor - // TODO convert opts to opts - const addedDiffZone = this._initializeStream(opts, userMessage, uri) + + public startStreaming(opts: StartStreamingOpts) { + const addedDiffZone = this._initializeStream(opts) return addedDiffZone?.diffareaid } - // private _stopIfStreaming(diffZone: DiffZone) { + private _stopIfStreaming(diffZone: DiffZone) { - // } + const streamRequestId = diffZone._streamState.streamRequestIdRef?.current + if (!streamRequestId) + return + + this._llmMessageService.abort(streamRequestId) + + diffZone._streamState = { + isStreaming: false, + streamRequestIdRef: undefined, + line: null + } + + } - - interruptStreaming(diffareaid: string) { + // call this outside undo/redo (it calls undo) + interruptStreaming(diffareaid: number) { const diffArea = this.diffAreaOfId[diffareaid] if (!diffArea) return if (diffArea.type !== 'DiffZone') return - if (!diffArea._sweepState.isStreaming) return - - const streamRequestId = diffArea._sweepState.streamRequestIdRef.current - if (streamRequestId) - this._llmMessageService.abort(streamRequestId) - - // __TODO__ update diffArea streamState here + don't elsewhere - // call undo - __TODO__ make this get called in undo and redo too + if (!diffArea._streamState.isStreaming) return + this._stopIfStreaming(diffArea) this._undoRedoService.undo(diffArea._URI) - } - addCtrlK({ uri, range }: { uri: URI, range: IRange, }) { - - // TODO check if intersects with a current ctrl K, if so focus it - - } - - - // called on void.acceptDiff diff --git a/src/vs/workbench/contrib/void/browser/quickEditActions.ts b/src/vs/workbench/contrib/void/browser/quickEditActions.ts index e3ea47c3..76a1e194 100644 --- a/src/vs/workbench/contrib/void/browser/quickEditActions.ts +++ b/src/vs/workbench/contrib/void/browser/quickEditActions.ts @@ -4,10 +4,12 @@ import { ServicesAccessor } from '../../../../platform/instantiation/common/inst import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { IMetricsService } from '../../../../platform/void/common/metricsService.js'; import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; +import { IInlineDiffsService } from './inlineDiffsService.js'; export type QuickEditPropsType = { - quickEditId: number, + diffareaid: number, + onUserUpdateText: (text: string) => void; } export type QuickEdit = { @@ -21,13 +23,21 @@ export type QuickEdit = { export const VOID_CTRL_K_ACTION_ID = 'void.ctrlKAction' registerAction2(class extends Action2 { - constructor() { - super({ id: VOID_CTRL_K_ACTION_ID, title: 'Void: Quick Edit', keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyK, weight: KeybindingWeight.BuiltinExtension } }); + constructor( + ) { + super({ + id: VOID_CTRL_K_ACTION_ID, + title: 'Void: Quick Edit', + keybinding: { + primary: KeyMod.CtrlCmd | KeyCode.KeyK, + weight: KeybindingWeight.BuiltinExtension, + } + }); } + async run(accessor: ServicesAccessor): Promise { const editorService = accessor.get(ICodeEditorService) - const metricsService = accessor.get(IMetricsService) metricsService.capture('User Action', { type: 'Open Ctrl+K' }) @@ -38,11 +48,11 @@ registerAction2(class extends Action2 { const selection = editor.getSelection() if (!selection) return; - // const uri = model.uri - // const startLine = selection.startLineNumber - // const selectedText = model.getValueInRange(selection) - // quickEditService.addZone({ uri, startLine, selectedText, }) + const { startLineNumber: startLine, endLineNumber: endLine } = selection + const uri = model.uri + const inlineDiffsService = accessor.get(IInlineDiffsService) + inlineDiffsService.addCtrlKZone({ startLine, endLine, uri }) } }); diff --git a/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlKChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlKChat.tsx index e831ce6c..01a1a22b 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlKChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlKChat.tsx @@ -1,13 +1,18 @@ import React, { FormEvent, useCallback, useRef, useState } from 'react'; -import { useSettingsState, useSidebarState, useThreadsState, useQuickEditState } from '../util/services.js'; +import { useSettingsState, useSidebarState, useThreadsState, useQuickEditState, useAccessor } from '../util/services.js'; import { OnError } from '../../../../../../../platform/void/common/llmMessageTypes.js'; import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'; import { getCmdKey } from '../../../helpers/getCmdKey.js'; import { VoidInputBox } from '../util/inputs.js'; import { QuickEditPropsType } from '../../../quickEditActions.js'; -export const CtrlKChat = (props: QuickEditPropsType) => { +export const CtrlKChat = ({ diffareaid, onUserUpdateText }: QuickEditPropsType) => { + + const accessor = useAccessor() + + const inlineDiffsService = accessor.get('IInlineDiffsService') + const inputBoxRef: React.MutableRefObject = useRef(null); @@ -15,11 +20,24 @@ export const CtrlKChat = (props: QuickEditPropsType) => { // state of current message const [instructions, setInstructions] = useState('') // the user's instructions - const onChangeText = useCallback((newStr: string) => { setInstructions(newStr) }, [setInstructions]) + const onChangeText = useCallback((newStr: string) => { + setInstructions(newStr) + onUserUpdateText(newStr) + }, [setInstructions]) const isDisabled = !instructions.trim() + const currentlyStreamingRef = useRef(undefined) + const onSubmit = useCallback((e: FormEvent) => { - // TODO + currentlyStreamingRef.current = inlineDiffsService.startStreaming({ + featureName: 'Ctrl+K', + diffareaid: diffareaid, + }, instructions) + }, [inlineDiffsService, diffareaid, instructions]) + + const onInterrupt = useCallback(() => { + if (currentlyStreamingRef.current !== undefined) + inlineDiffsService.interruptStreaming(currentlyStreamingRef.current) }, []) return
{ onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) { onSubmit(e) + return } }} onSubmit={(e) => { + if (isDisabled) { + // __TODO__ show disabled + return + } console.log('submit!') onSubmit(e) }} @@ -63,6 +86,10 @@ export const CtrlKChat = (props: QuickEditPropsType) => {
+ + From 01a345a391735180bd5311f3f9f2e691b0ec2d08 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Sat, 28 Dec 2024 21:28:20 -0500 Subject: [PATCH 14/25] ctrlk double redundancy fix --- .../void/browser/inlineDiffsService.ts | 146 +++++++++--------- .../contrib/void/browser/quickEditActions.ts | 1 + .../react/src/ctrl-k-tsx/CtrlKChat.tsx | 15 +- 3 files changed, 85 insertions(+), 77 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts index 793e7f86..d17b263f 100644 --- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts +++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts @@ -172,9 +172,6 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { diffOfId: Record = {}; // redundant with diffArea._diffs - - _diffidPool = 0 // each diff has an id - constructor( // @IHistoryService private readonly _historyService: IHistoryService, // history service is the history of pressing alt left/right @ICodeEditorService private readonly _editorService: ICodeEditorService, @@ -200,7 +197,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { if (this._weAreWriting) return const uri = model.uri for (const change of e.changes) { this._realignAllDiffAreasLines(uri, change.text, change.range) } - this._refreshDiffsInURI(uri) + this._refreshStylesAndDiffsInURI(uri) // // diffArea should be removed if we just discovered it has no more diffs in it // for (const diffareaid of this.diffAreasOfURI[uri.fsPath]) { @@ -224,7 +221,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { // 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) + if (uri) this._refreshStylesAndDiffsInURI(uri) } // add listeners for all existing editors + listen for editor being added for (let editor of this._editorService.listCodeEditors()) { initializeEditor(editor) } @@ -257,8 +254,13 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { private _addDiffAreaStylesToURI = (uri: URI) => { const model = this._getModel(uri) + const fullFileText = this._readURI(uri) ?? '' + + for (const diffareaid of this.diffAreasOfURI[uri.fsPath]) { const diffArea = this.diffAreaOfId[diffareaid] + console.log('DA start and end:', diffArea.startLine, diffArea.endLine) + if (diffArea.type === 'DiffZone') { // add sweep styles to the diffZone if (diffArea._streamState.isStreaming) { @@ -268,52 +270,68 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { const fn2 = this._addLineDecoration(model, diffArea._streamState.line + 1, diffArea.endLine, 'void-sweepBG') diffArea._removeStylesFns.add(() => { fn1?.(); fn2?.(); }) } + + const newDiffAreaCode = fullFileText.split('\n').slice((diffArea.startLine - 1), (diffArea.endLine - 1) + 1).join('\n') + const computedDiffs = findDiffs(diffArea.originalCode, newDiffAreaCode) + + for (let computedDiff of computedDiffs) { + this._addDiff(computedDiff, diffArea) + } + } // highlight the ctrlK zone if (diffArea.type === 'CtrlKZone') { - const consistentZoneId = this._zoneStyleService.addConsistentItemToURI({ uri, fn: (editor) => { const domNode = document.createElement('div'); domNode.style.zIndex = '1' + domNode.style.display = 'hidden' // start hidden until mount and get computed size - // domNode.className = 'void-redBG' const viewZone: IViewZone = { - // afterLineNumber: computedDiff.startLine - 1, - afterLineNumber: diffArea.startLine, - // __TODO__ heightInPx update dynamically - heightInPx: 100, - // heightInLines: 1, - // minWidthInPx: 200, + afterLineNumber: diffArea.startLine - 1, domNode: domNode, suppressMouseDown: false, }; - + // mount zone let zoneId: string | null = null - editor.changeViewZones(accessor => { zoneId = accessor.addZone(viewZone) }) - const fn1 = () => editor.changeViewZones(accessor => { if (zoneId) accessor.removeZone(zoneId) }) - - - // __TODO fix this resize part - // on resize - domNode.onresize = () => { - console.log('RESIZING!!!!') - viewZone.heightInPx = domNode.clientHeight - editor.changeViewZones(accessor => { if (zoneId) accessor.layoutZone(zoneId) }) - } + editor.changeViewZones(accessor => { zoneId = accessor.addZone(viewZone); }) + // mount react this._instantiationService.invokeFunction(accessor => { const props: QuickEditPropsType = { diffareaid: diffArea.diffareaid, onUserUpdateText(text) { diffArea.userText = text; }, + onMount: () => onResize() } mountCtrlK(domNode, accessor, props) - // __TODO__ dismount }) - return () => { fn1(); } + // observe + const onResize = () => { + domNode.style.display = 'block' + console.log('resizing'); + viewZone.heightInPx = domNode.clientHeight + + editor.changeViewZones(accessor => { + if (zoneId) { + console.log('CALLING LAYOUT', viewZone.heightInPx); + accessor.layoutZone(zoneId) + } + }) + } + const observer = new ResizeObserver(onResize) + console.log('observing...') + observer.observe(domNode) + onResize() + + return () => { + editor.changeViewZones(accessor => { if (zoneId) accessor.removeZone(zoneId) }); + domNode.remove(); + observer.disconnect(); + // __TODO__ dismount + } }, }) diffArea._removeStylesFns.add(() => this._zoneStyleService.removeConsistentItemFromURI(consistentZoneId)); @@ -515,7 +533,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { this._writeText(uri, entireModelCode, { startColumn: 1, startLineNumber: 1, endLineNumber: numLines, endColumn: Number.MAX_SAFE_INTEGER }) // restore all the decorations - this._refreshDiffsInURI(uri) + this._refreshStylesAndDiffsInURI(uri) } const beforeSnapshot: HistorySnapshot = getCurrentSnapshot() @@ -592,6 +610,28 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { return diffArea2 } + private _diffidPool = 0 // each diff has an id + private _addDiff(computedDiff: ComputedDiff, diffZone: DiffZone): Diff { + + const uri = diffZone._URI + + const diffid = this._diffidPool++ + + // create a Diff of it + const newDiff: Diff = { + ...computedDiff, + diffid: diffid, + diffareaid: diffZone.diffareaid, + } + + const fn = this._addDiffStylesToURI(uri, newDiff) + diffZone._removeStylesFns.add(fn) + + this.diffOfId[diffid] = newDiff + diffZone._diffOfId[diffid] = newDiff + + return newDiff + } @@ -660,7 +700,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { } - private _refreshDiffsInURI(uri: URI) { + private _refreshStylesAndDiffsInURI(uri: URI) { const content = this._readURI(uri) if (content === null) return @@ -668,41 +708,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { this._clearAllEffects(uri) // 2. recompute all diffs on each editor with this URI - - const fullFileText = this._readURI(uri) ?? '' - - - // go thru all diffareas in this URI, creating diffs and adding styles to it - for (let diffareaid of this.diffAreasOfURI[uri.fsPath]) { - const diffArea = this.diffAreaOfId[diffareaid] - - if (diffArea.type === 'DiffZone') { - - console.log('DA start and end:', diffArea.startLine, diffArea.endLine) - const newDiffAreaCode = fullFileText.split('\n').slice((diffArea.startLine - 1), (diffArea.endLine - 1) + 1).join('\n') - const computedDiffs = findDiffs(diffArea.originalCode, newDiffAreaCode) - - for (let computedDiff of computedDiffs) { - const diffid = this._diffidPool++ - - // create a Diff of it - const newDiff: Diff = { - ...computedDiff, - diffid: diffid, - diffareaid: diffArea.diffareaid, - } - - const fn = this._addDiffStylesToURI(uri, newDiff) - diffArea._removeStylesFns.add(fn) - - this.diffOfId[diffid] = newDiff - diffArea._diffOfId[diffid] = newDiff - } - } - - // update styles on this DiffArea - this._addDiffAreaStylesToURI(uri) - } + this._addDiffAreaStylesToURI(uri) } @@ -916,14 +922,14 @@ ${userMessage} ], onText: ({ newText, fullText }) => { this._writeDiffZoneLLMText(diffZone, fullText, latestCurrentFileEnd, latestOriginalFileStart) - this._refreshDiffsInURI(uri) + this._refreshStylesAndDiffsInURI(uri) }, onFinalMessage: ({ fullText }) => { this._writeText(uri, fullText, { startLineNumber: diffZone.startLine, startColumn: 1, endLineNumber: diffZone.endLine, endColumn: Number.MAX_SAFE_INTEGER }, // 1-indexed ) diffZone._streamState = { isStreaming: false, line: null } - this._refreshDiffsInURI(uri) + this._refreshStylesAndDiffsInURI(uri) onFinishEdit() }, onError: (e) => { @@ -960,9 +966,9 @@ ${userMessage} } const ctrlKZone = this._addDiffArea(adding) - onFinishEdit() + this._refreshStylesAndDiffsInURI(uri) - this._refreshDiffsInURI(uri) + onFinishEdit() return ctrlKZone.diffareaid } @@ -1070,7 +1076,7 @@ ${userMessage} this._deleteDiffArea(diffArea) } - this._refreshDiffsInURI(uri) + this._refreshStylesAndDiffsInURI(uri) onFinishEdit() @@ -1152,7 +1158,7 @@ ${userMessage} this._deleteDiffArea(diffArea) } - this._refreshDiffsInURI(uri) + this._refreshStylesAndDiffsInURI(uri) onFinishEdit() diff --git a/src/vs/workbench/contrib/void/browser/quickEditActions.ts b/src/vs/workbench/contrib/void/browser/quickEditActions.ts index 76a1e194..6adb0437 100644 --- a/src/vs/workbench/contrib/void/browser/quickEditActions.ts +++ b/src/vs/workbench/contrib/void/browser/quickEditActions.ts @@ -10,6 +10,7 @@ import { IInlineDiffsService } from './inlineDiffsService.js'; export type QuickEditPropsType = { diffareaid: number, onUserUpdateText: (text: string) => void; + onMount: () => void; } export type QuickEdit = { diff --git a/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlKChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlKChat.tsx index 01a1a22b..2d16024f 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlKChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlKChat.tsx @@ -1,5 +1,5 @@ -import React, { FormEvent, useCallback, useRef, useState } from 'react'; +import React, { FormEvent, useCallback, useEffect, useRef, useState } from 'react'; import { useSettingsState, useSidebarState, useThreadsState, useQuickEditState, useAccessor } from '../util/services.js'; import { OnError } from '../../../../../../../platform/void/common/llmMessageTypes.js'; import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'; @@ -7,7 +7,7 @@ import { getCmdKey } from '../../../helpers/getCmdKey.js'; import { VoidInputBox } from '../util/inputs.js'; import { QuickEditPropsType } from '../../../quickEditActions.js'; -export const CtrlKChat = ({ diffareaid, onUserUpdateText }: QuickEditPropsType) => { +export const CtrlKChat = ({ diffareaid, onUserUpdateText, onMount }: QuickEditPropsType) => { const accessor = useAccessor() @@ -16,7 +16,7 @@ export const CtrlKChat = ({ diffareaid, onUserUpdateText }: QuickEditPropsType) const inputBoxRef: React.MutableRefObject = useRef(null); - // -- imported state -- + useEffect(() => onMount(), [onMount]) // state of current message const [instructions, setInstructions] = useState('') // the user's instructions @@ -47,7 +47,8 @@ export const CtrlKChat = ({ diffareaid, onUserUpdateText }: QuickEditPropsType) transition-all duration-200 rounded-md bg-vscode-input-bg - border border-vscode-commandcenter-inactive-border focus-within:border-vscode-commandcenter-active-border hover:border-vscode-commandcenter-active-border` + border border-vscode-commandcenter-inactive-border focus-within:border-vscode-commandcenter-active-border hover:border-vscode-commandcenter-active-border + ` } onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) { @@ -83,12 +84,12 @@ export const CtrlKChat = ({ diffareaid, onUserUpdateText }: QuickEditPropsType) inputBoxRef={inputBoxRef} multiline={true} /> +
- From ff7830e704aa8ff204ddec265480f1846871edb8 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Sat, 28 Dec 2024 22:30:46 -0500 Subject: [PATCH 15/25] resize observer mostly works --- .../void/browser/inlineDiffsService.ts | 28 ++++++------------- .../contrib/void/browser/quickEditActions.ts | 2 +- .../browser/react/src/ctrl-k-tsx/CtrlK.tsx | 2 +- .../react/src/ctrl-k-tsx/CtrlKChat.tsx | 22 +++++++++++++-- 4 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts index d17b263f..e2ad724e 100644 --- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts +++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts @@ -298,39 +298,27 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { let zoneId: string | null = null editor.changeViewZones(accessor => { zoneId = accessor.addZone(viewZone); }) + + // mount react this._instantiationService.invokeFunction(accessor => { const props: QuickEditPropsType = { diffareaid: diffArea.diffareaid, + onChangeHeight(height) { + if (height === undefined) return + domNode.style.display = 'block' + viewZone.heightInPx = height + editor.changeViewZones(accessor => { if (zoneId) { accessor.layoutZone(zoneId) } }) + }, onUserUpdateText(text) { diffArea.userText = text; }, - onMount: () => onResize() } mountCtrlK(domNode, accessor, props) }) - // observe - const onResize = () => { - domNode.style.display = 'block' - console.log('resizing'); - viewZone.heightInPx = domNode.clientHeight - - editor.changeViewZones(accessor => { - if (zoneId) { - console.log('CALLING LAYOUT', viewZone.heightInPx); - accessor.layoutZone(zoneId) - } - }) - } - const observer = new ResizeObserver(onResize) - console.log('observing...') - observer.observe(domNode) - onResize() return () => { editor.changeViewZones(accessor => { if (zoneId) accessor.removeZone(zoneId) }); domNode.remove(); - observer.disconnect(); - // __TODO__ dismount } }, }) diff --git a/src/vs/workbench/contrib/void/browser/quickEditActions.ts b/src/vs/workbench/contrib/void/browser/quickEditActions.ts index 6adb0437..3773c90a 100644 --- a/src/vs/workbench/contrib/void/browser/quickEditActions.ts +++ b/src/vs/workbench/contrib/void/browser/quickEditActions.ts @@ -9,8 +9,8 @@ import { IInlineDiffsService } from './inlineDiffsService.js'; export type QuickEditPropsType = { diffareaid: number, + onChangeHeight: (height: number) => void; onUserUpdateText: (text: string) => void; - onMount: () => void; } export type QuickEdit = { diff --git a/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlK.tsx b/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlK.tsx index e57acf4a..6f79e10d 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlK.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlK.tsx @@ -8,7 +8,7 @@ export const CtrlK = (props: QuickEditPropsType) => { const isDark = useIsDark() - return
+ return
diff --git a/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlKChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlKChat.tsx index 2d16024f..e44876f1 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlKChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlKChat.tsx @@ -7,16 +7,31 @@ import { getCmdKey } from '../../../helpers/getCmdKey.js'; import { VoidInputBox } from '../util/inputs.js'; import { QuickEditPropsType } from '../../../quickEditActions.js'; -export const CtrlKChat = ({ diffareaid, onUserUpdateText, onMount }: QuickEditPropsType) => { +export const CtrlKChat = ({ diffareaid, onUserUpdateText, onChangeHeight }: QuickEditPropsType) => { const accessor = useAccessor() const inlineDiffsService = accessor.get('IInlineDiffsService') + const formRef = useRef(null) const inputBoxRef: React.MutableRefObject = useRef(null); - useEffect(() => onMount(), [onMount]) + useEffect(() => { + console.log('mounting resize observer') + const inputContainer = formRef.current + if (!inputContainer) return; + + // only observing 1 element + const resizeObserver = new ResizeObserver((entries) => { + const height = entries[0].contentRect.height + console.log('NEW HEIGHT', height) + onChangeHeight(height) + }); + resizeObserver.observe(inputContainer); + return () => { resizeObserver.disconnect(); }; + }, [onChangeHeight]); + // state of current message const [instructions, setInstructions] = useState('') // the user's instructions @@ -41,6 +56,7 @@ export const CtrlKChat = ({ diffareaid, onUserUpdateText, onMount }: QuickEditPr }, []) return
From ef8bba00b936e7cc3f930ef7e1a501a3748c0146 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Sun, 29 Dec 2024 19:16:16 -0800 Subject: [PATCH 16/25] update ctrlk --- .../void/browser/inlineDiffsService.ts | 57 ++++--- .../contrib/void/browser/quickEditActions.ts | 1 + .../react/src/ctrl-k-tsx/CtrlKChat.tsx | 150 +++++++++++------- .../react/src/markdown/ChatMarkdownRender.tsx | 5 +- .../react/src/sidebar-tsx/SidebarChat.tsx | 2 +- 5 files changed, 130 insertions(+), 85 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts index e2ad724e..51066365 100644 --- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts +++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts @@ -56,10 +56,10 @@ registerColor('void.sweepIdxBG', configOfBG(sweepIdxBG), '', true); // similar to ServiceLLM -export type StartStreamingOpts = { +export type StartApplyingOpts = { featureName: 'Ctrl+K'; - diffareaid: number; // id of the CtrlK area - userMessage?: undefined; + diffareaid: number; // id of the CtrlK area (contains text selection) + userMessage: string; // user message } | { featureName: 'Ctrl+L'; userMessage: string; @@ -103,6 +103,7 @@ type CtrlKZone = { type: 'CtrlKZone'; originalCode?: undefined; userText: string | null; + _alreadyMounted: boolean; } & CommonZoneProps @@ -154,7 +155,7 @@ type HistorySnapshot = { export interface IInlineDiffsService { readonly _serviceBrand: undefined; - startStreaming(opts: StartStreamingOpts, userMessage: string): number | undefined; + startApplying(opts: StartApplyingOpts): number | undefined; interruptStreaming(diffareaid: number): void; addCtrlKZone(opts: AddCtrlKOpts): number; } @@ -199,7 +200,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { for (const change of e.changes) { this._realignAllDiffAreasLines(uri, change.text, change.range) } this._refreshStylesAndDiffsInURI(uri) - // // diffArea should be removed if we just discovered it has no more diffs in it + // // TODO diffArea should be removed if we just discovered it has no more diffs in it // for (const diffareaid of this.diffAreasOfURI[uri.fsPath]) { // const diffArea = this.diffAreaOfId[diffareaid] // if (Object.keys(diffArea._diffOfId).length === 0 && !diffArea._sweepState.isStreaming) { @@ -281,6 +282,9 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { } // highlight the ctrlK zone if (diffArea.type === 'CtrlKZone') { + if (diffArea._alreadyMounted) continue + diffArea._alreadyMounted = true + const consistentZoneId = this._zoneStyleService.addConsistentItemToURI({ uri, fn: (editor) => { @@ -311,6 +315,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { editor.changeViewZones(accessor => { if (zoneId) { accessor.layoutZone(zoneId) } }) }, onUserUpdateText(text) { diffArea.userText = text; }, + initText: diffArea.userText, } mountCtrlK(domNode, accessor, props) }) @@ -476,7 +481,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { const { snapshottedDiffAreaOfId, entireFileCode: entireModelCode } = structuredClone(snapshot) // don't want to destroy the snapshot // delete all current decorations (diffs, sweep styles) so we don't have any unwanted leftover decorations - this._clearAllEffects(uri) + this._clearAllDiffZoneEffects(uri) // for each diffarea in this uri, stop streaming if currently streaming for (const diffareaid in this.diffAreaOfId) { @@ -510,6 +515,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { ...snapshottedDiffArea as DiffAreaSnapshot, _URI: uri, _removeStylesFns: new Set(), + _alreadyMounted: false, } } this.diffAreasOfURI[uri.fsPath].add(diffareaid) @@ -561,27 +567,30 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { // clear diffZone effects (diffs) if (diffArea.type === 'DiffZone') this._deleteDiffs(diffArea) - else if (diffArea.type === 'CtrlKZone') { - - } diffArea._removeStylesFns.forEach(removeStyles => removeStyles()) } + + + private _clearAllDiffAreaEffects(diffArea: DiffArea) { + this._deleteEffects(diffArea) + diffArea._removeStylesFns.clear() + } + // clears all Diffs (and their styles) and all styles of DiffAreas - private _clearAllEffects(uri: URI) { + private _clearAllDiffZoneEffects(uri: URI) { for (let diffareaid of this.diffAreasOfURI[uri.fsPath]) { const diffArea = this.diffAreaOfId[diffareaid] - this._deleteEffects(diffArea) - diffArea._removeStylesFns.clear() + if (diffArea.type !== 'DiffZone') continue + this._clearAllDiffAreaEffects(diffArea) } } - // delete all diffs, update diffAreaOfId, update diffAreasOfModelId private _deleteDiffArea(diffArea: DiffArea) { - // we had clear all diffs, but not really needed + this._clearAllDiffAreaEffects(diffArea) delete this.diffAreaOfId[diffArea.diffareaid] this.diffAreasOfURI[diffArea._URI.fsPath].delete(diffArea.diffareaid.toString()) } @@ -692,10 +701,10 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { const content = this._readURI(uri) if (content === null) return - // 1. clear Diffs and styles - this._clearAllEffects(uri) + // 1. clear Diffs and DiffZone styles + this._clearAllDiffZoneEffects(uri) - // 2. recompute all diffs on each editor with this URI + // 2. recompute all Diffs on each editor with this URI this._addDiffAreaStylesToURI(uri) @@ -768,7 +777,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { - private _initializeStream(opts: StartStreamingOpts): DiffZone | undefined { + private _initializeApplyStream(opts: StartApplyingOpts): DiffZone | undefined { const { featureName } = opts @@ -807,6 +816,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { userMessage = opts.userMessage } else if (featureName === 'Ctrl+K') { + console.log('INIT APPLY STREAM CTRLK') const { diffareaid } = opts @@ -818,8 +828,9 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { endLine = endLine_ // check if there's overlap with any other ctrlKZones and if so, focus them - for (const diffareaid of this.diffAreasOfURI[uri.fsPath]) { - const da2 = this.diffAreaOfId[diffareaid] + for (const diffareaid2 of this.diffAreasOfURI[uri.fsPath]) { + if (diffareaid.toString() === diffareaid2) continue + const da2 = this.diffAreaOfId[diffareaid2] if (!da2) continue const noOverlap = da2.startLine > endLine || da2.endLine < startLine if (!noOverlap) { @@ -827,6 +838,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { return } } + console.log('userTEXT', userText) if (!userText) return userMessage = userText @@ -948,6 +960,7 @@ ${userMessage} type: 'CtrlKZone', startLine: startLine, endLine: endLine, + _alreadyMounted: false, _URI: uri, userText: null, _removeStylesFns: new Set(), @@ -962,8 +975,8 @@ ${userMessage} - public startStreaming(opts: StartStreamingOpts) { - const addedDiffZone = this._initializeStream(opts) + public startApplying(opts: StartApplyingOpts) { + const addedDiffZone = this._initializeApplyStream(opts) return addedDiffZone?.diffareaid } diff --git a/src/vs/workbench/contrib/void/browser/quickEditActions.ts b/src/vs/workbench/contrib/void/browser/quickEditActions.ts index 3773c90a..0693206a 100644 --- a/src/vs/workbench/contrib/void/browser/quickEditActions.ts +++ b/src/vs/workbench/contrib/void/browser/quickEditActions.ts @@ -11,6 +11,7 @@ export type QuickEditPropsType = { diffareaid: number, onChangeHeight: (height: number) => void; onUserUpdateText: (text: string) => void; + initText: string | null; } export type QuickEdit = { diff --git a/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlKChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlKChat.tsx index e44876f1..e253a0f7 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlKChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/ctrl-k-tsx/CtrlKChat.tsx @@ -6,25 +6,26 @@ import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox import { getCmdKey } from '../../../helpers/getCmdKey.js'; import { VoidInputBox } from '../util/inputs.js'; import { QuickEditPropsType } from '../../../quickEditActions.js'; +import { ButtonStop, ButtonSubmit } from '../sidebar-tsx/SidebarChat.js'; -export const CtrlKChat = ({ diffareaid, onUserUpdateText, onChangeHeight }: QuickEditPropsType) => { +export const CtrlKChat = ({ diffareaid, onUserUpdateText, onChangeHeight, initText }: QuickEditPropsType) => { const accessor = useAccessor() const inlineDiffsService = accessor.get('IInlineDiffsService') - const formRef = useRef(null) + const sizerRef = useRef(null) const inputBoxRef: React.MutableRefObject = useRef(null); useEffect(() => { console.log('mounting resize observer') - const inputContainer = formRef.current + const inputContainer = sizerRef.current if (!inputContainer) return; // only observing 1 element const resizeObserver = new ResizeObserver((entries) => { - const height = entries[0].contentRect.height + const height = entries[0].borderBoxSize[0].blockSize console.log('NEW HEIGHT', height) onChangeHeight(height) }); @@ -32,83 +33,114 @@ export const CtrlKChat = ({ diffareaid, onUserUpdateText, onChangeHeight }: Quic return () => { resizeObserver.disconnect(); }; }, [onChangeHeight]); - // state of current message - const [instructions, setInstructions] = useState('') // the user's instructions + const [instructions, setInstructions] = useState(initText ?? '') // the user's instructions const onChangeText = useCallback((newStr: string) => { setInstructions(newStr) onUserUpdateText(newStr) }, [setInstructions]) const isDisabled = !instructions.trim() - const currentlyStreamingRef = useRef(undefined) + const currentlyStreamingIdRef = useRef(undefined) + const [isStreaming, setIsStreaming] = useState(false) const onSubmit = useCallback((e: FormEvent) => { - currentlyStreamingRef.current = inlineDiffsService.startStreaming({ + if (currentlyStreamingIdRef.current !== undefined) return + currentlyStreamingIdRef.current = inlineDiffsService.startApplying({ featureName: 'Ctrl+K', diffareaid: diffareaid, - }, instructions) + userMessage: instructions, + }) + setIsStreaming(true) }, [inlineDiffsService, diffareaid, instructions]) const onInterrupt = useCallback(() => { - if (currentlyStreamingRef.current !== undefined) - inlineDiffsService.interruptStreaming(currentlyStreamingRef.current) - }, []) + if (currentlyStreamingIdRef.current !== undefined) + inlineDiffsService.interruptStreaming(currentlyStreamingIdRef.current) + setIsStreaming(false) + }, [inlineDiffsService]) - return { - if (e.key === 'Enter' && !e.shiftKey) { - onSubmit(e) - return - } - }} - onSubmit={(e) => { - if (isDisabled) { - // __TODO__ show disabled - return - } - console.log('submit!') - onSubmit(e) - }} - onClick={(e) => { - if (e.currentTarget === e.target) { - inputBoxRef.current?.focus() - } - }} - > -
{ + if (!inputBoxRef.current) return + if (alreadySetRef.current) return + alreadySetRef.current = true + inputBoxRef.current.value = instructions + }, [initText, instructions]) + + return
+ { + if (e.key === 'Enter' && !e.shiftKey) { + onSubmit(e) + return + } + }} + onSubmit={(e) => { + if (isDisabled) { + // __TODO__ show disabled + return + } + console.log('submit!') + onSubmit(e) + }} + onClick={(e) => { + if (e.currentTarget === e.target) { + inputBoxRef.current?.focus() + } + }} > - {/* text input */} - - -
+
+
+ {/* left (input) */} +
+ {/* text input */} + +
+ + {/* right (button) */} +
+ {/* submit / stop button */} + {isStreaming ? + // stop button + + : + // submit button (up arrow) + + } +
+ +
+
- - - + +
} 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 3fa5140c..f3e95818 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 @@ -47,9 +47,8 @@ const CodeButtonsOnHover = ({ diffRepr: text }: { diffRepr: string }) => {