mirror of
https://github.com/voideditor/void
synced 2026-05-23 09:28:23 +00:00
Merge branch 'new-services' into model-selection
This commit is contained in:
commit
954e611fda
18 changed files with 1222 additions and 2110 deletions
|
|
@ -35,6 +35,5 @@
|
|||
"serviceUrl": "https://open-vsx.org/vscode/gallery",
|
||||
"itemUrl": "https://open-vsx.org/vscode/item"
|
||||
},
|
||||
"builtInExtensions": [
|
||||
]
|
||||
"builtInExtensions": []
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -1,186 +0,0 @@
|
|||
// 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<A extends BaseArea> extends Disposable {
|
||||
|
||||
// protected _areasOfURI: Record<string, Set<number>> = {};
|
||||
// protected _areaOfId: Record<number, A> = {};
|
||||
// protected _areaIdPool = 0; // for generating unique IDs
|
||||
|
||||
// protected _removeStylesFnsOfURI: Record<string, Set<() => 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>;
|
||||
// }
|
||||
|
|
@ -1,185 +0,0 @@
|
|||
// 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<A extends BaseArea> extends Disposable {
|
||||
|
||||
// protected _areasOfURI: Record<string, Set<number>> = {};
|
||||
// protected _areaOfId: Record<number, A> = {};
|
||||
|
||||
// protected _removeStylesFnsOfURI: Record<string, Set<() => 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>;
|
||||
// }
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
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;
|
||||
getEditorsOnURI(uri: URI): ICodeEditor[];
|
||||
addConsistentItemToURI(inputs: AddItemInputs): string;
|
||||
removeConsistentItemFromURI(consistentItemId: string): void;
|
||||
}
|
||||
|
||||
export const IConsistentItemService = createDecorator<IConsistentItemService>('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<string, Set<string> | undefined> = {}
|
||||
private readonly infoOfConsistentItemId: Record<string, AddItemInputs> = {}
|
||||
|
||||
|
||||
// current state of items on each editor, and the fns to call to remove them
|
||||
private readonly itemIdsOfEditorId: Record<string, Set<string> | undefined> = {}
|
||||
private readonly consistentItemIdOfItemId: Record<string, string> = {}
|
||||
private readonly disposeFnOfItemId: Record<string, () => 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]
|
||||
}
|
||||
|
||||
getEditorsOnURI(uri: URI) {
|
||||
const editors = this._editorService.listCodeEditors().filter(editor => editor.getModel()?.uri.fsPath === uri.fsPath)
|
||||
return editors
|
||||
}
|
||||
|
||||
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.getEditorsOnURI(uri)
|
||||
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.getEditorsOnURI(uri)
|
||||
|
||||
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);
|
||||
|
||||
|
||||
|
|
@ -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<string, Diff>; // 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<DiffArea, typeof diffAreaSnapshotKeys[number]>
|
||||
|
||||
|
||||
|
||||
// type HistorySnapshot = {
|
||||
// snapshottedDiffAreaOfId: Record<string, DiffAreaSnapshot>;
|
||||
// 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<IInlineDiffsService>('inlineDiffAreasService');
|
||||
|
||||
// class InlineDiffsService extends Disposable implements IInlineDiffsService {
|
||||
// _serviceBrand: undefined;
|
||||
|
||||
|
||||
// // URI <--> model
|
||||
// removeStylesFnsOfURI: Record<string, Set<Function>> = {} // functions that remove the styles of this uri
|
||||
// diffAreasOfURI: Record<string, Set<string>> = {}
|
||||
|
||||
// diffAreaOfId: Record<string, DiffArea> = {};
|
||||
// diffOfId: Record<string, Diff> = {}; // 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<IModelDecorationOptions>) => {
|
||||
// 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<string, DiffAreaSnapshot> = {}
|
||||
// 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<void>((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()
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
|
||||
|
|
@ -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<IZoneStyleService>('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<string, Set<string> | undefined> = {}
|
||||
private readonly infoOfConsistentZoneId: Record<string, {
|
||||
uri: URI
|
||||
iZoneFn: (editor: ICodeEditor) => IViewZone,
|
||||
iOther?: (editor: ICodeEditor) => (() => void),
|
||||
}> = {}
|
||||
// listener disposables
|
||||
private readonly disposablesOfEditorId: Record<string, Set<IDisposable> | 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<string, Set<string> | undefined> = {}
|
||||
private readonly removeFnOfZoneId: Record<string, () => void> = {}
|
||||
private readonly consistentZoneIdOfZoneId: Record<string, string> = {}
|
||||
|
||||
|
||||
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);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -121,10 +121,7 @@ export const chat_prompt = (instructions: string, selections: CodeSelection[] |
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const inlineDiff_systemMessage = `
|
||||
export const ctrlLStream_systemMessage = `
|
||||
You are a coding assistant that applies a diff to a file. You are given the original file \`original_file\`, a diff \`diff\`, and a new file that you are applying the diff to \`new_file\`.
|
||||
|
||||
Please finish writing the new file \`new_file\`, according to the diff \`diff\`. You must completely re-write the whole file, using the diff.
|
||||
|
|
@ -255,14 +252,106 @@ export default Sidebar;\`\`\`
|
|||
|
||||
|
||||
|
||||
export const generateCtrlKPrompt = ({ selection, prefix, suffix, instructions, }: { selection: string, prefix: string, suffix: string, instructions: string, }) => `\
|
||||
export const ctrlLStream_prompt = ({ originalCode, userMessage }: { originalCode: string, userMessage: string }) => {
|
||||
return `\
|
||||
ORIGINAL_CODE
|
||||
\`\`\`
|
||||
${originalCode}
|
||||
\`\`\`
|
||||
|
||||
DIFF
|
||||
\`\`\`
|
||||
${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.
|
||||
`
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const ctrlKStream_systemMessage = `\
|
||||
`
|
||||
|
||||
|
||||
export const ctrlKStream_prefixAndSuffix = ({ fullFileStr, startLine, endLine }: { fullFileStr: string, startLine: number, endLine: number }) => {
|
||||
|
||||
const fullFileLines = fullFileStr.split('\n')
|
||||
|
||||
// we can optimize this later
|
||||
const MAX_CHARS = 1024
|
||||
/*
|
||||
|
||||
a
|
||||
a
|
||||
a <-- final i (prefix = a\na\n)
|
||||
a
|
||||
|b <-- startLine-1 (middle = b\nc\nd\n) <-- initial i (moves up)
|
||||
c
|
||||
d| <-- endLine-1 <-- initial j (moves down)
|
||||
e
|
||||
e <-- final j (suffix = e\ne\n)
|
||||
e
|
||||
e
|
||||
*/
|
||||
|
||||
let prefix = ''
|
||||
let i = startLine - 1 // 0-indexed exclusive
|
||||
// we'll include fullFileLines[i...(startLine-1)-1].join('\n') in the prefix.
|
||||
while (i !== 0) {
|
||||
const newLine = fullFileLines[i - 1]
|
||||
if (newLine.length + 1 + prefix.length <= MAX_CHARS) { // +1 to include the \n
|
||||
prefix = `${newLine}\n${prefix}`
|
||||
i -= 1
|
||||
}
|
||||
else break
|
||||
}
|
||||
|
||||
let suffix = ''
|
||||
let j = endLine - 1
|
||||
while (j !== fullFileLines.length - 1) {
|
||||
const newLine = fullFileLines[j + 1]
|
||||
if (newLine.length + 1 + suffix.length <= MAX_CHARS) { // +1 to include the \n
|
||||
suffix = `${suffix}\n${newLine}`
|
||||
j += 1
|
||||
}
|
||||
else break
|
||||
}
|
||||
|
||||
return { prefix, suffix }
|
||||
|
||||
}
|
||||
|
||||
export const ctrlKStream_prompt = ({ selection, prefix, suffix, userMessage }: { selection: string, prefix: string, suffix: string, userMessage: string, }) => {
|
||||
const onlySpeaksFIM = false
|
||||
|
||||
if (onlySpeaksFIM) {
|
||||
const preTag = 'PRE'
|
||||
const sufTag = 'SUF'
|
||||
const midTag = 'MID'
|
||||
return `\
|
||||
<${preTag}>
|
||||
/* Original Selection:
|
||||
${selection}*/
|
||||
/* Instructions: ${userMessage}*/
|
||||
${prefix}</${preTag}>
|
||||
<${sufTag}>${suffix}</${sufTag}>
|
||||
<${midTag}>`
|
||||
}
|
||||
// prompt the model on how to do FIM
|
||||
else {
|
||||
const preTag = 'PRE'
|
||||
const sufTag = 'SUF'
|
||||
const midTag = 'MID'
|
||||
return `\
|
||||
Here is the user's original selection:
|
||||
\`\`\`
|
||||
<MID>${selection}</MID>
|
||||
<${midTag}>${selection}</${midTag}>
|
||||
\`\`\`
|
||||
|
||||
The user wants to apply the following instructions to the selection:
|
||||
${instructions}
|
||||
${userMessage}
|
||||
|
||||
Please rewrite the selection following the user's instructions.
|
||||
|
||||
|
|
@ -273,10 +362,11 @@ Instructions to follow:
|
|||
3. Be careful not to duplicate or remove variables, comments, or other syntax by mistake
|
||||
|
||||
Complete the following:
|
||||
\`\`\`
|
||||
<PRE>${prefix}</PRE>
|
||||
<SUF>${suffix}</SUF>
|
||||
<MID>`;
|
||||
<${preTag}>${prefix}</${preTag}>
|
||||
<${sufTag}>${suffix}</${sufTag}>
|
||||
<${midTag}>`
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,19 @@
|
|||
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';
|
||||
import { IInlineDiffsService } from './inlineDiffsService.js';
|
||||
import { InputBox } from '../../../../base/browser/ui/inputbox/inputBox.js';
|
||||
|
||||
|
||||
type InitialZone = { uri: URI, startLine: number, selectedText: string, }
|
||||
|
||||
export type QuickEditPropsType = {
|
||||
quickEditId: number,
|
||||
diffareaid: number,
|
||||
onGetInputBox: (i: InputBox) => void;
|
||||
onChangeHeight: (height: number) => void;
|
||||
onUserUpdateText: (text: string) => void;
|
||||
initText: string | null;
|
||||
}
|
||||
|
||||
export type QuickEdit = {
|
||||
|
|
@ -28,93 +25,23 @@ export type QuickEdit = {
|
|||
}
|
||||
|
||||
|
||||
export interface IQuickEditService {
|
||||
readonly _serviceBrand: undefined;
|
||||
readonly onDidChangeState: Event<void>;
|
||||
addZone(zone: InitialZone): void;
|
||||
}
|
||||
|
||||
export const IQuickEditService = createDecorator<IQuickEditService>('voidQuickEditService');
|
||||
class VoidQuickEditService extends Disposable implements IQuickEditService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
quickEditId: number = 0
|
||||
|
||||
private readonly _onDidChangeState = new Emitter<void>();
|
||||
readonly onDidChangeState: Event<void> = 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() {
|
||||
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<void> {
|
||||
|
||||
const quickEditService = accessor.get(IQuickEditService)
|
||||
const editorService = accessor.get(ICodeEditorService)
|
||||
|
||||
const metricsService = accessor.get(IMetricsService)
|
||||
metricsService.capture('User Action', { type: 'Open Ctrl+K' })
|
||||
|
||||
|
|
@ -125,11 +52,13 @@ 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
|
||||
|
||||
// deselect - clear selection
|
||||
editor.setSelection({ startLineNumber: startLine, endLineNumber: startLine, startColumn: 1, endColumn: 1 })
|
||||
|
||||
const inlineDiffsService = accessor.get(IInlineDiffsService)
|
||||
inlineDiffsService.addCtrlKZone({ startLine, endLine, editor })
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Glass Devtools, Inc. All rights reserved.
|
||||
* Void Editor additions licensed under the AGPL 3.0 License.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useIsDark, useSidebarState } from '../util/services.js'
|
||||
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
|
||||
|
|
@ -8,7 +13,7 @@ export const CtrlK = (props: QuickEditPropsType) => {
|
|||
|
||||
const isDark = useIsDark()
|
||||
|
||||
return <div className={`@@void-scope ${isDark ? 'dark' : ''}`} style={{ width: '100%', height: '100%' }}>
|
||||
return <div className={`@@void-scope ${isDark ? 'dark' : ''}`}>
|
||||
<ErrorBoundary>
|
||||
<CtrlKChat {...props} />
|
||||
</ErrorBoundary>
|
||||
|
|
|
|||
|
|
@ -1,70 +1,153 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Glass Devtools, Inc. All rights reserved.
|
||||
* Void Editor additions licensed under the AGPL 3.0 License.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import React, { FormEvent, useCallback, useRef, useState } from 'react';
|
||||
import { useSettingsState, useSidebarState, useThreadsState, useQuickEditState } from '../util/services.js';
|
||||
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';
|
||||
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 = (props: QuickEditPropsType) => {
|
||||
export const CtrlKChat = ({ diffareaid, onGetInputBox, onUserUpdateText, onChangeHeight, initText }: QuickEditPropsType) => {
|
||||
|
||||
const accessor = useAccessor()
|
||||
const inlineDiffsService = accessor.get('IInlineDiffsService')
|
||||
const sizerRef = useRef<HTMLDivElement | null>(null)
|
||||
const inputBoxRef: React.MutableRefObject<InputBox | null> = useRef(null);
|
||||
|
||||
// -- imported state --
|
||||
useEffect(() => {
|
||||
const inputContainer = sizerRef.current
|
||||
if (!inputContainer) return;
|
||||
|
||||
// only observing 1 element
|
||||
let resizeObserver: ResizeObserver | undefined
|
||||
resizeObserver = new ResizeObserver((entries) => {
|
||||
const height = entries[0].borderBoxSize[0].blockSize
|
||||
onChangeHeight(height)
|
||||
})
|
||||
resizeObserver.observe(inputContainer);
|
||||
|
||||
return () => { resizeObserver?.disconnect(); };
|
||||
}, [onChangeHeight]);
|
||||
|
||||
// state of current message
|
||||
const [instructions, setInstructions] = useState('') // the user's instructions
|
||||
const onChangeText = useCallback((newStr: string) => { setInstructions(newStr) }, [setInstructions])
|
||||
const [instructions, setInstructions] = useState(initText ?? '') // the user's instructions
|
||||
const onChangeText = useCallback((newStr: string) => {
|
||||
setInstructions(newStr)
|
||||
onUserUpdateText(newStr)
|
||||
}, [setInstructions])
|
||||
const isDisabled = !instructions.trim()
|
||||
|
||||
const onSubmit = useCallback((e: FormEvent) => {
|
||||
// TODO
|
||||
}, [])
|
||||
const currentlyStreamingIdRef = useRef<number | undefined>(undefined)
|
||||
const [isStreaming, setIsStreaming] = useState(false)
|
||||
|
||||
return <form
|
||||
className={
|
||||
const onSubmit = useCallback((e: FormEvent) => {
|
||||
if (currentlyStreamingIdRef.current !== undefined) return
|
||||
inputBoxRef.current?.disable()
|
||||
|
||||
currentlyStreamingIdRef.current = inlineDiffsService.startApplying({
|
||||
featureName: 'Ctrl+K',
|
||||
diffareaid: diffareaid,
|
||||
userMessage: instructions,
|
||||
})
|
||||
setIsStreaming(true)
|
||||
}, [inlineDiffsService, diffareaid, instructions])
|
||||
|
||||
const onInterrupt = useCallback(() => {
|
||||
if (currentlyStreamingIdRef.current !== undefined)
|
||||
inlineDiffsService.interruptStreaming(currentlyStreamingIdRef.current)
|
||||
setIsStreaming(false)
|
||||
}, [inlineDiffsService])
|
||||
|
||||
|
||||
// sync init value
|
||||
const alreadySetRef = useRef(false)
|
||||
useEffect(() => {
|
||||
if (!inputBoxRef.current) return
|
||||
if (alreadySetRef.current) return
|
||||
alreadySetRef.current = true
|
||||
inputBoxRef.current.value = instructions
|
||||
}, [initText, instructions])
|
||||
|
||||
return <div className='py-2 w-full max-w-xl' ref={sizerRef}>
|
||||
<form
|
||||
// copied from SidebarChat.tsx
|
||||
`flex flex-col gap-2 p-1 relative input text-left shrink-0
|
||||
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`
|
||||
}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
className={`
|
||||
flex flex-col gap-2 p-1 relative input text-left shrink-0
|
||||
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
|
||||
`
|
||||
}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
onSubmit(e)
|
||||
return
|
||||
}
|
||||
}}
|
||||
onSubmit={(e) => {
|
||||
if (isDisabled) {
|
||||
// __TODO__ show disabled
|
||||
return
|
||||
}
|
||||
console.log('submit!')
|
||||
onSubmit(e)
|
||||
}
|
||||
}}
|
||||
onSubmit={(e) => {
|
||||
console.log('submit!')
|
||||
onSubmit(e)
|
||||
}}
|
||||
onClick={(e) => {
|
||||
if (e.currentTarget === e.target) {
|
||||
inputBoxRef.current?.focus()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
// copied from SidebarChat.tsx
|
||||
`@@[&_textarea]:!void-bg-transparent @@[&_textarea]:!void-outline-none @@[&_textarea]:!void-text-vscode-input-fg @@[&_textarea]:!void-max-h-[100px] @@[&_div.monaco-inputbox]:!void- @@[&_div.monaco-inputbox]:!void-outline-none`
|
||||
}
|
||||
}}
|
||||
onClick={(e) => {
|
||||
if (e.currentTarget === e.target) {
|
||||
inputBoxRef.current?.focus()
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
{/* text input */}
|
||||
<VoidInputBox
|
||||
placeholder={`${getCmdKey()}+K to select`}
|
||||
onChangeText={onChangeText}
|
||||
inputBoxRef={inputBoxRef}
|
||||
multiline={true}
|
||||
/>
|
||||
</div>
|
||||
<div // this div is used to position the input box properly
|
||||
className={`w-full p-2 z-[999]`}
|
||||
>
|
||||
<div className='flex flex-row justify-between items-end gap-1'>
|
||||
{/* left (input) */}
|
||||
<div // copied from SidebarChat.tsx
|
||||
className={`w-full
|
||||
@@[&_textarea]:!void-bg-transparent @@[&_textarea]:!void-outline-none @@[&_textarea]:!void-text-vscode-input-fg @@[&_div.monaco-inputbox]:!void-outline-none`}>
|
||||
{/* text input */}
|
||||
<VoidInputBox
|
||||
placeholder={`${getCmdKey()}+K to select`}
|
||||
onChangeText={onChangeText}
|
||||
onCreateInstance={useCallback((instance: InputBox) => {
|
||||
inputBoxRef.current = instance;
|
||||
onGetInputBox(instance);
|
||||
instance.focus()
|
||||
}, [onGetInputBox])}
|
||||
multiline={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* right (button) */}
|
||||
<div className='flex flex-row items-end'>
|
||||
{/* submit / stop button */}
|
||||
{isStreaming ?
|
||||
// stop button
|
||||
<ButtonStop
|
||||
onClick={onInterrupt}
|
||||
/>
|
||||
:
|
||||
// submit button (up arrow)
|
||||
<ButtonSubmit
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</form>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Glass Devtools, Inc. All rights reserved.
|
||||
* Void Editor additions licensed under the AGPL 3.0 License.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { mountFnGenerator } from '../util/mountFnGenerator.js'
|
||||
import { CtrlK } from './CtrlK.js'
|
||||
|
|
|
|||
|
|
@ -46,10 +46,13 @@ const CodeButtonsOnHover = ({ diffRepr: text }: { diffRepr: string }) => {
|
|||
{copyButtonState}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-secondary btn-sm border text-xs text-vscode-input-fg border-vscode-input-border rounded"
|
||||
onClick={async () => {
|
||||
|
||||
inlineDiffService.startStreaming({ featureName: 'Ctrl+L' }, text)
|
||||
// btn btn-secondary btn-sm border text-xs text-vscode-input-fg border-vscode-input-border rounded
|
||||
className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
|
||||
onClick={() => {
|
||||
inlineDiffService.startApplying({
|
||||
featureName: 'Ctrl+L',
|
||||
userMessage: text,
|
||||
})
|
||||
}}
|
||||
>
|
||||
Apply
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export const Sidebar = ({ className }: { className: string }) => {
|
|||
sidebarStateService.setState({ currentTab: tabs[(index + 1) % tabs.length] as any })
|
||||
}}>clickme {tab}</span> */}
|
||||
|
||||
<div className={`mb-2 w-full ${isHistoryOpen ? '' : 'hidden'}`}>
|
||||
<div className={`mb-2 w-full ${isHistoryOpen ? '' : 'hidden'} ring-2 ring-widget-shadow z-10`}>
|
||||
<ErrorBoundary>
|
||||
<SidebarThreadSelector />
|
||||
</ErrorBoundary>
|
||||
|
|
|
|||
|
|
@ -114,11 +114,11 @@ const DEFAULT_BUTTON_SIZE = 20;
|
|||
export const ButtonSubmit = ({ className, disabled, ...props }: ButtonProps & Required<Pick<ButtonProps, 'disabled'>>) => {
|
||||
|
||||
return <button
|
||||
className={`rounded-full shrink-0 grow-0 cursor-pointer
|
||||
type='submit'
|
||||
className={`size-[20px] rounded-full shrink-0 grow-0 cursor-pointer
|
||||
${disabled ? 'bg-vscode-disabled-fg' : 'bg-white'}
|
||||
${className}
|
||||
`}
|
||||
type='submit'
|
||||
{...props}
|
||||
>
|
||||
<IconArrowUp size={DEFAULT_BUTTON_SIZE} className="stroke-[2]" />
|
||||
|
|
@ -323,7 +323,7 @@ const ChatBubble = ({ chatMessage }: {
|
|||
}
|
||||
|
||||
return <div className={`${role === 'user' ? 'text-right' : 'text-left'}`}>
|
||||
<div className={`inline-block p-2 rounded-lg space-y-2 ${role === 'user' ? 'text-vscode-input-fg' : ''} max-w-full overflow-auto`}>
|
||||
<div className={`text-left inline-block p-2 rounded-lg space-y-2 ${role === 'user' ? 'bg-vscode-input-bg text-vscode-input-fg' : ''} max-w-full overflow-auto`}>
|
||||
{chatbubbleContents}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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<HTMLDivElement | null>(null)
|
||||
|
||||
|
|
@ -311,9 +311,9 @@ export const VoidCodeEditor = ({ initValue, language }: { initValue: string, lan
|
|||
|
||||
scrollbar: {
|
||||
alwaysConsumeMouseWheel: false,
|
||||
vertical: 'auto',
|
||||
horizontal: 'auto',
|
||||
// verticalScrollbarSize: 0,
|
||||
vertical: 'hidden',
|
||||
horizontal: 'hidden',
|
||||
verticalScrollbarSize: 0,
|
||||
horizontalScrollbarSize: 0,
|
||||
},
|
||||
scrollBeyondLastLine: false,
|
||||
|
|
@ -338,8 +338,9 @@ export const VoidCodeEditor = ({ initValue, language }: { initValue: string, lan
|
|||
hideCursorInOverviewRuler: true,
|
||||
overviewRulerBorder: false,
|
||||
glyphMargin: false,
|
||||
|
||||
stickyScroll: {
|
||||
enabled: false
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,9 +12,7 @@ import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js
|
|||
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
|
||||
import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
import { CodeStagingSelection, IThreadHistoryService } from './threadHistoryService.js';
|
||||
// import { IEditorService } from '../../../services/editor/common/editorService.js';
|
||||
|
||||
import { IEditorService } from '../../../services/editor/common/editorService.js';
|
||||
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
|
||||
import { IRange } from '../../../../editor/common/core/range.js';
|
||||
import { ITextModel } from '../../../../editor/common/model.js';
|
||||
|
|
@ -66,18 +64,23 @@ registerAction2(class extends Action2 {
|
|||
|
||||
const stateService = accessor.get(ISidebarStateService)
|
||||
const metricsService = accessor.get(IMetricsService)
|
||||
const editorService = accessor.get(ICodeEditorService)
|
||||
|
||||
metricsService.capture('User Action', { type: 'Ctrl+L' })
|
||||
|
||||
stateService.setState({ isHistoryOpen: false, currentTab: 'chat' })
|
||||
stateService.fireFocusChat()
|
||||
|
||||
const editor = editorService.getActiveCodeEditor()
|
||||
const selectionRange = roundRangeToLines(
|
||||
accessor.get(IEditorService).activeTextEditorControl?.getSelection()
|
||||
// accessor.get(IEditorService).activeTextEditorControl?.getSelection()
|
||||
editor?.getSelection()
|
||||
)
|
||||
|
||||
|
||||
if (selectionRange) {
|
||||
// select whole lines
|
||||
editor?.setSelection({ startLineNumber: selectionRange.startLineNumber, endLineNumber: selectionRange.endLineNumber, startColumn: 1, endColumn: Number.MAX_SAFE_INTEGER })
|
||||
|
||||
const selectionStr = getContentInRange(model, selectionRange)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue