Skip to content

Commit

Permalink
Enable go to definition in chat code blocks (microsoft#204555)
Browse files Browse the repository at this point in the history
* Enable go to definition in chat code blocks

Hooks up code blocks to the `ITextModelService` so that link decorations work as well

* Register ref
  • Loading branch information
mjbvz authored Feb 7, 2024
1 parent cfa5001 commit bc056d2
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 19 deletions.
33 changes: 31 additions & 2 deletions src/vs/workbench/contrib/chat/browser/chatListRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -143,6 +146,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IChatService private readonly chatService: IChatService,
@IEditorService private readonly editorService: IEditorService,
@ICodeEditorService codeEditorService: ICodeEditorService,
@IThemeService private readonly themeService: IThemeService,
) {
super();
Expand All @@ -152,6 +156,27 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
this._treePool = this._register(this.instantiationService.createInstance(TreePool, this._onDidChangeVisibility.event));
this._contentReferencesListPool = this._register(this.instantiationService.createInstance(ContentReferencesListPool, this._onDidChangeVisibility.event));

this._register(this.instantiationService.createInstance(ChatCodeBlockContentProvider));

this._register(codeEditorService.registerCodeEditorOpenHandler(async (input: ITextResourceEditorInput, _source: ICodeEditor | null, _sideBySide?: boolean): Promise<ICodeEditor | null> => {
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')) {
Expand Down Expand Up @@ -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<ICodeBlockPart>): IDisposableReference<ICodeBlockPart> {
const codeBlock = pool.get();
let stale = false;
Expand Down
64 changes: 47 additions & 17 deletions src/vs/workbench/contrib/chat/browser/codeBlockPart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -133,7 +134,7 @@ abstract class BaseCodeBlockPart<Data extends ICodeBlockData> extends Disposable
protected readonly _onDidChangeContentHeight = this._register(new Emitter<void>());
public readonly onDidChangeContentHeight = this._onDidChangeContentHeight.event;

protected readonly editor: CodeEditorWidget;
public readonly editor: CodeEditorWidget;
protected readonly toolbar: MenuWorkbenchToolBar;
private readonly contextKeyService: IContextKeyService;

Expand Down Expand Up @@ -322,7 +323,10 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart<ISimpleCodeBlockData>

private currentCodeBlockData: ISimpleCodeBlockData | undefined;

private readonly textModel: ITextModel;
private readonly textModel: Promise<ITextModel>;

private readonly _uri: URI;

constructor(
options: ChatEditorOptions,
menuId: MenuId,
Expand All @@ -331,6 +335,7 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart<ISimpleCodeBlockData>
@IInstantiationService instantiationService: IInstantiationService,
@IContextKeyService contextKeyService: IContextKeyService,
@IModelService modelService: IModelService,
@ITextModelService textModelService: ITextModelService,
@IConfigurationService configurationService: IConfigurationService,
@IAccessibilityService accessibilityService: IAccessibilityService,
@ILanguageService private readonly languageService: ILanguageService,
Expand All @@ -350,9 +355,12 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart<ISimpleCodeBlockData>
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'));

Expand All @@ -367,7 +375,7 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart<ISimpleCodeBlockData>
}

get uri(): URI {
return this.textModel.uri;
return this._uri;
}

protected override createEditor(instantiationService: IInstantiationService, parent: HTMLElement, options: Readonly<IEditorConstructionOptions>): CodeEditorWidget {
Expand All @@ -383,6 +391,7 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart<ISimpleCodeBlockData>
BracketMatchingController.ID,
SmartSelectController.ID,
HoverController.ID,
GotoDefinitionAtPositionEditorContribution.ID,
])
}));
}
Expand All @@ -401,8 +410,8 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart<ISimpleCodeBlockData>
}
}

protected override updateEditor(data: ISimpleCodeBlockData): void {
this.editor.setModel(this.textModel);
protected override async updateEditor(data: ISimpleCodeBlockData): Promise<void> {
this.editor.setModel(await this.textModel);
const text = this.fixCodeText(data.text, data.languageId);
this.setText(text);

Expand Down Expand Up @@ -440,25 +449,26 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart<ISimpleCodeBlockData>
return text;
}

private setText(newText: string): void {
const currentText = this.textModel.getValue(EndOfLinePreference.LF);
private async setText(newText: string): Promise<void> {
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<void> {
(await this.textModel).setLanguage(vscodeLanguageId ?? PLAINTEXT_LANGUAGE_ID);
}
}

Expand Down Expand Up @@ -530,3 +540,23 @@ export class LocalFileCodeBlockPart extends BaseCodeBlockPart<ILocalFileCodeBloc
} satisfies ICodeBlockActionContext;
}
}


export class ChatCodeBlockContentProvider extends Disposable implements ITextModelContentProvider {

constructor(
@ITextModelService textModelService: ITextModelService,
@IModelService private readonly _modelService: IModelService,
) {
super();
this._register(textModelService.registerTextModelContentProvider(Schemas.vscodeChatCodeBlock, this));
}

async provideTextContent(resource: URI): Promise<ITextModel | null> {
const existing = this._modelService.getModel(resource);
if (existing) {
return existing;
}
return this._modelService.createModel('', null, resource);
}
}

0 comments on commit bc056d2

Please sign in to comment.