diff --git a/extensions/void/src/extension/extension.ts b/extensions/void/src/extension/extension.ts index cc7cbed6..e0eb3e90 100644 --- a/extensions/void/src/extension/extension.ts +++ b/extensions/void/src/extension/extension.ts @@ -25,6 +25,13 @@ declare module 'vscode' { } } +// this comes from vscode.d.ts +declare module 'vscode' { + export namespace languages { + export function addInlineDiff(editor: vscode.TextEditor, originalText: string, modifiedRange: Range): void; + } +} + const roundRangeToLines = (selection: vscode.Selection) => { let endLine = selection.end.character === 0 ? selection.end.line - 1 : selection.end.line // e.g. if the user triple clicks, it selects column=0, line=line -> column=0, line=line+1 return new vscode.Range(selection.start.line, 0, endLine, Number.MAX_SAFE_INTEGER) @@ -62,6 +69,9 @@ export function activate(context: vscode.ExtensionContext) { const { selectionStr, filePath } = getSelection(editor) + vscode.languages.addInlineDiff(editor, 'oldText', editor.selection) + + // send message to the webview (Sidebar.tsx) sidebarWebviewProvider.webview.then(webview => webview.postMessage({ type: 'ctrl+l', selection: { selectionStr, filePath } } satisfies MessageToSidebar)); }) diff --git a/src/vs/editor/browser/services/inlineDiffService.ts b/src/vs/editor/browser/services/inlineDiffService.ts new file mode 100644 index 00000000..c694405a --- /dev/null +++ b/src/vs/editor/browser/services/inlineDiffService.ts @@ -0,0 +1,115 @@ +// This file was added by the Void team + +// src/vs/editor/browser/services/inlineDiffService.ts +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 { IModelDecorationOptions, IModelDeltaDecoration } from '../../common/model.js'; +import { ICodeEditor, IViewZone } from '../editorBrowser.js'; +import { IRange } from '../../common/core/range.js'; + + +export interface IInlineDiffService { + readonly _serviceBrand: undefined; + + addDiff(editor: ICodeEditor, originalText: string, modifiedRange: IRange): void; + removeDiffs(editor: ICodeEditor): void; +} + +export const IInlineDiffService = createDecorator('inlineDiffService'); + +class InlineDiffService extends Disposable implements IInlineDiffService { + private readonly _diffDecorations = new Map(); + private readonly _diffZones = new Map(); + _serviceBrand: undefined; + + private static readonly ADDED_DECORATION: IModelDecorationOptions = { + className: 'inline-diff-added', + description: 'inline-diff-added', + isWholeLine: false, + minimap: { + color: { id: 'minimapGutter.addedBackground' }, + position: 2 + }, + overviewRuler: { + color: { id: 'editorOverviewRuler.addedForeground' }, + position: 7 + } + }; + + constructor( + + ) { + super(); + } + + public addDiff: IInlineDiffService['addDiff'] = (editor, originalText, modifiedRange) => { + + // Clear existing diffs + this.removeDiffs(editor); + + // Add decoration for modified text + const decorations: IModelDeltaDecoration[] = [{ + range: modifiedRange, + options: InlineDiffService.ADDED_DECORATION + }]; + + const newDecorations = editor.deltaDecorations([], decorations); + this._diffDecorations.set(editor, newDecorations); + + // Add view zone for original text + editor.changeViewZones(accessor => { + const domNode = document.createElement('div'); + domNode.className = 'inline-diff-deleted monaco-editor'; + + // Create inner container for proper padding + const innerContainer = document.createElement('div'); + innerContainer.className = 'view-line'; + innerContainer.textContent = originalText; + domNode.appendChild(innerContainer); + + const viewZone: IViewZone = { + afterLineNumber: modifiedRange.startLineNumber - 1, + heightInLines: originalText.split('\n').length, + domNode: domNode, + suppressMouseDown: true, + marginDomNode: this.createGutterElement(editor) + }; + + const zoneId = accessor.addZone(viewZone); + this._diffZones.set(editor, [zoneId]); + }); + } + + private createGutterElement(editor: ICodeEditor): HTMLElement { + const gutterDiv = document.createElement('div'); + gutterDiv.className = 'inline-diff-gutter'; + gutterDiv.innerHTML = '
-
'; + return gutterDiv; + } + + public removeDiffs(editor: ICodeEditor): void { + // Clear decorations + const decorationIds = this._diffDecorations.get(editor) || []; + editor.deltaDecorations(decorationIds, []); + this._diffDecorations.delete(editor); + + // Clear view zones + editor.changeViewZones(accessor => { + const zoneIds = this._diffZones.get(editor) || []; + zoneIds.forEach(id => accessor.removeZone(id)); + }); + this._diffZones.delete(editor); + } + + override dispose(): void { + super.dispose(); + this._diffDecorations.clear(); + this._diffZones.clear(); + } +} + +// Register the service +registerSingleton(IInlineDiffService, InlineDiffService, InstantiationType.Eager); + + diff --git a/src/vs/editor/browser/widget/diffEditor/style.css b/src/vs/editor/browser/widget/diffEditor/style.css index 4489d84b..0c48cc0d 100644 --- a/src/vs/editor/browser/widget/diffEditor/style.css +++ b/src/vs/editor/browser/widget/diffEditor/style.css @@ -425,3 +425,27 @@ margin: 0 4px; } } + +/* Void added this: */ +.inline-diff-deleted { + background-color: var(--vscode-diffEditor-removedTextBackground); + text-decoration: line-through; +} + +.inline-diff-added { + background-color: var(--vscode-diffEditor-insertedTextBackground); +} + +.inline-diff-gutter { + width: 100%; + height: 100%; +} + +.inline-diff-deleted-gutter { + opacity: 0.7; + padding-left: 4px; +} + +.view-line { + padding: 0 4px; +} diff --git a/src/vs/workbench/api/browser/mainThreadInlineDiff.ts b/src/vs/workbench/api/browser/mainThreadInlineDiff.ts new file mode 100644 index 00000000..899e59ac --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadInlineDiff.ts @@ -0,0 +1,82 @@ +// Void created this file +// it comes from mainThreadCodeInsets.ts + +import { Disposable } from '../../../base/common/lifecycle.js'; +import { ICodeEditorService } from '../../../editor/browser/services/codeEditorService.js'; +import { MainContext, MainThreadInlineDiffShape } from '../common/extHost.protocol.js'; +import { IInlineDiffService } from '../../../editor/browser/services/inlineDiffService.js'; +import { ICodeEditor } from '../../../editor/browser/editorBrowser.js'; +import { IRange } from '../../../editor/common/core/range.js'; +import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js'; + + +@extHostNamedCustomer(MainContext.MainThreadInlineDiff) +export class MainThreadInlineDiff extends Disposable implements MainThreadInlineDiffShape { + + // private readonly _proxy: ExtHostEditorInsetsShape; + // private readonly _disposables = new DisposableStore(); + + constructor( + context: IExtHostContext, + @IInlineDiffService private readonly _inlineDiff: IInlineDiffService, + @ICodeEditorService private readonly _editorService: ICodeEditorService, + ) { + super(); + } + // this._proxy = context.getProxy(ExtHostContext.ExtHostEditorInsets); + + // dispose(): void { + // this._disposables.dispose(); + // } + + $addDiff(editorId: string, originalText: string, range: IRange): void { + + let editor: ICodeEditor | undefined; + editorId = editorId.substr(0, editorId.indexOf(',')); //todo@jrieken HACK + + for (const candidate of this._editorService.listCodeEditors()) { + if (candidate.getId() === editorId + // && candidate.hasModel() && isEqual(candidate.getModel().uri, URI.revive(uri)) + ) { + editor = candidate; + break; + } + } + + if (!editor) { + // setTimeout(() => this._proxy.$onDidDispose(editorId)); + return; + } + + this._inlineDiff.addDiff(editor, originalText, range) + + + // return editor + + // const disposables = new DisposableStore(); + + // const remove = () => { + // disposables.dispose(); + // this._proxy.$onDidDispose(handle); + // this._insets.delete(handle); + // }; + + // disposables.add(editor.onDidChangeModel(remove)); + // disposables.add(editor.onDidDispose(remove)); + + } + + // $disposeEditorInset(handle: number): void { + // const inset = this.getInset(handle); + // this._insets.delete(handle); + // inset.dispose(); + // } + + // private getInset(handle: number): EditorWebviewZone { + // const inset = this._insets.get(handle); + // if (!inset) { + // throw new Error('Unknown inset'); + // } + // return inset; + // } +} diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 3635e182..250dfd25 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -109,6 +109,7 @@ import { ProxyIdentifier } from '../../services/extensions/common/proxyIdentifie import { ExcludeSettingOptions, TextSearchCompleteMessageType, TextSearchContextNew, TextSearchMatchNew } from '../../services/search/common/searchExtTypes.js'; import type * as vscode from 'vscode'; import { ExtHostCodeMapper } from './extHostCodeMapper.js'; +import { ExtHostInlineDiff } from './extHostInlineDiff.js'; export interface IExtensionRegistries { mine: ExtensionDescriptionRegistry; @@ -221,6 +222,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostSpeech = rpcProtocol.set(ExtHostContext.ExtHostSpeech, new ExtHostSpeech(rpcProtocol)); const extHostEmbeddings = rpcProtocol.set(ExtHostContext.ExtHostEmbeddings, new ExtHostEmbeddings(rpcProtocol)); + // Void added this: + const extHostInlineDiff = rpcProtocol.set(ExtHostContext.ExtHostInlineDiff, new ExtHostInlineDiff(rpcProtocol.getProxy(MainContext.MainThreadInlineDiff), extHostEditors)); + // Check that no named customers are missing const expected = Object.values>(ExtHostContext); rpcProtocol.assertRegistered(expected); @@ -519,6 +523,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // namespace: languages const languages: typeof vscode.languages = { + createDiagnosticCollection(name?: string): vscode.DiagnosticCollection { return extHostDiagnostics.createDiagnosticCollection(extension.identifier, name); }, @@ -553,6 +558,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostLanguageFeatures.registerCodeLensProvider(extension, checkSelector(selector), provider); }, + // Void added addInlineDiff here: + addInlineDiff(editor: vscode.TextEditor, originalText: string, modifiedRange: vscode.Range): void { + extHostInlineDiff.addDiff(editor, originalText, modifiedRange) + }, + // Void added this (I think will need to add this back when add ctrl+K) // registerVoidCtrlKProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable { // return extHostLanguageFeatures.registerCodeLensProvider(extension, checkSelector(selector), provider); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 4900f545..6f631861 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2984,7 +2984,10 @@ export const MainContext = { MainThreadTesting: createProxyIdentifier('MainThreadTesting'), MainThreadLocalization: createProxyIdentifier('MainThreadLocalizationShape'), MainThreadAiRelatedInformation: createProxyIdentifier('MainThreadAiRelatedInformation'), - MainThreadAiEmbeddingVector: createProxyIdentifier('MainThreadAiEmbeddingVector') + MainThreadAiEmbeddingVector: createProxyIdentifier('MainThreadAiEmbeddingVector'), + + // Void added this: + MainThreadInlineDiff: createProxyIdentifier('MainThreadInlineDiff'), }; export const ExtHostContext = { @@ -3055,5 +3058,17 @@ export const ExtHostContext = { ExtHostTimeline: createProxyIdentifier('ExtHostTimeline'), ExtHostTesting: createProxyIdentifier('ExtHostTesting'), ExtHostTelemetry: createProxyIdentifier('ExtHostTelemetry'), - ExtHostLocalization: createProxyIdentifier('ExtHostLocalization') + ExtHostLocalization: createProxyIdentifier('ExtHostLocalization'), + + // Void added this: + ExtHostInlineDiff: createProxyIdentifier('ExtHostInlineDiff'), // Void added this }; + + +// Void added these: +export interface ExtHostInlineDiffShape { + $onDidDispose(handle: number): void; +} +export interface MainThreadInlineDiffShape { + $addDiff(editorId: string, originalText: string, range: IRange): void; +} diff --git a/src/vs/workbench/api/common/extHostInlineDiff.ts b/src/vs/workbench/api/common/extHostInlineDiff.ts new file mode 100644 index 00000000..cacfe8fa --- /dev/null +++ b/src/vs/workbench/api/common/extHostInlineDiff.ts @@ -0,0 +1,62 @@ +// This file was created by Void +// reference extHostCodeInsets.ts + +import { Emitter } from '../../../base/common/event.js'; +import { DisposableStore } from '../../../base/common/lifecycle.js'; +import { ExtHostInlineDiffShape, MainThreadInlineDiffShape } from './extHost.protocol.js'; +import * as vscode from 'vscode' +import { ExtHostTextEditor } from './extHostTextEditor.js'; +import { ExtHostEditors } from './extHostTextEditors.js'; +import { Range } from '../../../workbench/api/common/extHostTypeConverters.js' + +export class ExtHostInlineDiff implements ExtHostInlineDiffShape { + + private readonly _disposables = new DisposableStore(); + private _insets = new Map }>(); + + constructor( + private readonly _proxy: MainThreadInlineDiffShape, + private readonly _editors: ExtHostEditors, + ) { } + + + dispose(): void { + this._insets.forEach(value => value.inset.dispose()); + this._disposables.dispose(); + } + + + addDiff(editor: vscode.TextEditor, originalText: string, modifiedRange: vscode.Range) { + + let apiEditor: ExtHostTextEditor | undefined; + for (const candidate of this._editors.getVisibleTextEditors(true)) { + if (candidate.value === editor) { + apiEditor = candidate; + break; + } + } + if (!apiEditor) { + throw new Error('not a visible editor'); + } + + const id = apiEditor.id; + // let uri = apiEditor.value.document.uri; + + // convert to IRange + const range = Range.from(modifiedRange) + + this._proxy.$addDiff(id, originalText, range) + + } + + + + // main thread calls this when disposes diff with this particular handle + $onDidDispose(handle: number): void { + const value = this._insets.get(handle); + if (value) { + value.inset.dispose(); + } + } + +} diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index d4c58df4..770907f7 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ declare module 'vscode' { - /** * The version of the editor. */ @@ -14074,6 +14073,9 @@ declare module 'vscode' { */ export namespace languages { + /** Void added this: */ + export function addInlineDiff(editor: TextEditor, originalText: string, modifiedRange: Range): void; + /** * Return the identifiers of all known languages. * @returns Promise resolving to an array of identifier strings.