From bc056d2978262aeab95998388eeb33d1009545fe Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 7 Feb 2024 10:03:28 -0800 Subject: [PATCH] Enable go to definition in chat code blocks (#204555) * Enable go to definition in chat code blocks Hooks up code blocks to the `ITextModelService` so that link decorations work as well * Register ref --- .../contrib/chat/browser/chatListRenderer.ts | 33 +++++++++- .../contrib/chat/browser/codeBlockPart.ts | 64 ++++++++++++++----- 2 files changed, 78 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index b95b85a22d673..9983b28dd4ecb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -24,12 +24,14 @@ import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ResourceMap } from 'vs/base/common/map'; import { marked } from 'vs/base/common/marked/marked'; -import { FileAccess } from 'vs/base/common/network'; +import { FileAccess, Schemas } from 'vs/base/common/network'; import { clamp } from 'vs/base/common/numbers'; import { basename } from 'vs/base/common/path'; import { equalsIgnoreCase } from 'vs/base/common/strings'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IMarkdownRenderResult, MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { Range } from 'vs/editor/common/core/range'; import { localize } from 'vs/nls'; @@ -39,6 +41,7 @@ import { MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; import { FileKind, FileType } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -55,7 +58,7 @@ import { ChatTreeItem, IChatCodeBlockInfo, IChatFileTreeInfo } from 'vs/workbenc import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; import { ChatMarkdownDecorationsRenderer, annotateSpecialMarkdownContent, extractVulnerabilitiesFromText } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; -import { ICodeBlockData, ICodeBlockPart, LocalFileCodeBlockPart, SimpleCodeBlockPart, localFileLanguageId, parseLocalFileData } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; +import { ChatCodeBlockContentProvider, ICodeBlockData, ICodeBlockPart, LocalFileCodeBlockPart, SimpleCodeBlockPart, localFileLanguageId, parseLocalFileData } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { IChatAgentMetadata } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatProgressRenderableResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; @@ -143,6 +146,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer => { + if (input.resource.scheme !== Schemas.vscodeChatCodeBlock) { + return null; + } + const block = this._editorPool.find(input.resource); + if (!block) { + return null; + } + if (input.options?.selection) { + block.editor.setSelection({ + startLineNumber: input.options.selection.startLineNumber, + startColumn: input.options.selection.startColumn, + endLineNumber: input.options.selection.startLineNumber ?? input.options.selection.endLineNumber, + endColumn: input.options.selection.startColumn ?? input.options.selection.endColumn + }); + } + return block.editor; + })); + this._usedReferencesEnabled = configService.getValue('chat.experimental.usedReferences') ?? true; this._register(configService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('chat.experimental.usedReferences')) { @@ -1078,6 +1103,10 @@ class EditorPool extends Disposable { return this.getFromPool(data.type === 'localFile' ? this._localFileEditorPool : this._simpleEditorPool); } + find(resource: URI): SimpleCodeBlockPart | undefined { + return Array.from(this._simpleEditorPool.inUse).find(part => part.uri?.toString() === resource.toString()); + } + private getFromPool(pool: ResourcePool): IDisposableReference { const codeBlock = pool.get(); let stale = false; diff --git a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index 5fb9f1e41f08d..a68207d86c8c2 100644 --- a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -23,9 +23,10 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; -import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; +import { IResolvedTextEditorModel, ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; import { BracketMatchingController } from 'vs/editor/contrib/bracketMatching/browser/bracketMatching'; import { ContextMenuController } from 'vs/editor/contrib/contextmenu/browser/contextmenu'; +import { GotoDefinitionAtPositionEditorContribution } from 'vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition'; import { HoverController } from 'vs/editor/contrib/hover/browser/hover'; import { ViewportSemanticTokensContribution } from 'vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens'; import { SmartSelectController } from 'vs/editor/contrib/smartSelect/browser/smartSelect'; @@ -133,7 +134,7 @@ abstract class BaseCodeBlockPart extends Disposable protected readonly _onDidChangeContentHeight = this._register(new Emitter()); public readonly onDidChangeContentHeight = this._onDidChangeContentHeight.event; - protected readonly editor: CodeEditorWidget; + public readonly editor: CodeEditorWidget; protected readonly toolbar: MenuWorkbenchToolBar; private readonly contextKeyService: IContextKeyService; @@ -322,7 +323,10 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart private currentCodeBlockData: ISimpleCodeBlockData | undefined; - private readonly textModel: ITextModel; + private readonly textModel: Promise; + + private readonly _uri: URI; + constructor( options: ChatEditorOptions, menuId: MenuId, @@ -331,6 +335,7 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, @IModelService modelService: IModelService, + @ITextModelService textModelService: ITextModelService, @IConfigurationService configurationService: IConfigurationService, @IAccessibilityService accessibilityService: IAccessibilityService, @ILanguageService private readonly languageService: ILanguageService, @@ -350,9 +355,12 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart buttonSeparator: undefined, supportIcons: true }); - const modelUri = URI.from({ scheme: Schemas.vscodeChatCodeBlock, path: generateUuid() }); - this.textModel = this._register(this.modelService.createModel('', null, modelUri, false)); - this.editor.setModel(this.textModel); + this._uri = URI.from({ scheme: Schemas.vscodeChatCodeBlock, path: generateUuid() }); + this.textModel = textModelService.createModelReference(this._uri).then(ref => { + this.editor.setModel(ref.object.textEditorModel); + this._register(ref); + return ref.object.textEditorModel; + }); this.vulnsListElement = dom.append(vulnsContainer, $('ul.interactive-result-vulns-list')); @@ -367,7 +375,7 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart } get uri(): URI { - return this.textModel.uri; + return this._uri; } protected override createEditor(instantiationService: IInstantiationService, parent: HTMLElement, options: Readonly): CodeEditorWidget { @@ -383,6 +391,7 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart BracketMatchingController.ID, SmartSelectController.ID, HoverController.ID, + GotoDefinitionAtPositionEditorContribution.ID, ]) })); } @@ -401,8 +410,8 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart } } - protected override updateEditor(data: ISimpleCodeBlockData): void { - this.editor.setModel(this.textModel); + protected override async updateEditor(data: ISimpleCodeBlockData): Promise { + this.editor.setModel(await this.textModel); const text = this.fixCodeText(data.text, data.languageId); this.setText(text); @@ -440,25 +449,26 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart return text; } - private setText(newText: string): void { - const currentText = this.textModel.getValue(EndOfLinePreference.LF); + private async setText(newText: string): Promise { + const model = await this.textModel; + const currentText = model.getValue(EndOfLinePreference.LF); if (newText === currentText) { return; } if (newText.startsWith(currentText)) { const text = newText.slice(currentText.length); - const lastLine = this.textModel.getLineCount(); - const lastCol = this.textModel.getLineMaxColumn(lastLine); - this.textModel.applyEdits([{ range: new Range(lastLine, lastCol, lastLine, lastCol), text }]); + const lastLine = model.getLineCount(); + const lastCol = model.getLineMaxColumn(lastLine); + model.applyEdits([{ range: new Range(lastLine, lastCol, lastLine, lastCol), text }]); } else { // console.log(`Failed to optimize setText`); - this.textModel.setValue(newText); + model.setValue(newText); } } - private setLanguage(vscodeLanguageId: string | undefined): void { - this.textModel.setLanguage(vscodeLanguageId ?? PLAINTEXT_LANGUAGE_ID); + private async setLanguage(vscodeLanguageId: string | undefined): Promise { + (await this.textModel).setLanguage(vscodeLanguageId ?? PLAINTEXT_LANGUAGE_ID); } } @@ -530,3 +540,23 @@ export class LocalFileCodeBlockPart extends BaseCodeBlockPart { + const existing = this._modelService.getModel(resource); + if (existing) { + return existing; + } + return this._modelService.createModel('', null, resource); + } +}