Path: blob/main/extensions/copilot/src/extension/codeBlocks/vscode-node/provider.ts
13399 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as vscode from 'vscode';6import { ITelemetryService } from '../../../platform/telemetry/common/telemetry';7import { getLanguage, getLanguageForResource, ILanguage, WellKnownLanguageId, wellKnownLanguages } from '../../../util/common/languages';8import { isUri } from '../../../util/common/types';9import { createSha256Hash } from '../../../util/common/crypto';10import { IInstantiationService, ServicesAccessor } from '../../../util/vs/platform/instantiation/common/instantiation';11import { Location, Range, Uri } from '../../../vscodeTypes';12import { findWordInReferences } from '../../linkify/vscode-node/findWord';13import { PromptReference } from '../../prompt/common/conversation';1415const codeBlockScheme = 'vscode-chat-code-block';1617/**18* Hovers that are provided by a language provider in cases where the correct types are not known.19*20* A good example of this is how js/ts shows `any` for any unknown types. In these cases, we instead want to try looking21* up a more helpful hover using the workspace symbols.22*/23const genericHoverMessages: RegExp[] = [24/^\n```(typescript|javascript|tsx|jsx)\S*\nany\n```\n$/i,25];2627/**28* Groupings of languages that can reference each other for intellisense.29*30* For example, when trying to look up a symbol in a JS code block, we shouldn't bother31* looking up symbols in c++ or markdown files.32*/33const languageReferenceGroups: readonly Set<string>[] = [34new Set<WellKnownLanguageId>([35'typescript',36'javascript',37'typescriptreact',38'javascriptreact',39]),4041// Put all other languages in their own group42...Array.from(wellKnownLanguages.keys(), lang => new Set([lang]))43];4445/**46* Provides support for Intellisense chat code blocks.47*/48class CodeBlockIntelliSenseProvider implements vscode.DefinitionProvider, vscode.ImplementationProvider, vscode.TypeDefinitionProvider, vscode.HoverProvider {4950constructor(51@IInstantiationService private readonly instantiationService: IInstantiationService,52@ITelemetryService private readonly telemetryService: ITelemetryService,53) { }5455async provideDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.LocationLink[] | undefined> {56return this.goTo('vscode.experimental.executeDefinitionProvider_recursive', document, position, token);57}5859async provideImplementation(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.LocationLink[] | undefined> {60return this.goTo('vscode.experimental.executeImplementationProvider_recursive', document, position, token);61}6263async provideTypeDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.LocationLink[] | undefined> {64return this.goTo('vscode.experimental.executeTypeDefinitionProvider_recursive', document, position, token);65}6667async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.Hover | undefined> {68const localHoverResponse = await this.execHover(document.uri, position);69const localHovers = this.filterOutGenericHovers(localHoverResponse);70if (localHovers?.length) {71return this.convertHover(localHovers);72}7374if (token.isCancellationRequested) {75return;76}7778const referencesCtx = await this.getReferencesContext(document, position, token);79if (!referencesCtx || token.isCancellationRequested) {80return;81}8283for (const wordMatch of referencesCtx.wordMatches) {84const hovers = await this.execHover(wordMatch.uri, wordMatch.range.start);85if (token.isCancellationRequested) {86return;87}88if (hovers?.length) {89return this.convertHover(hovers);90}91}9293return this.convertHover(localHoverResponse);94}9596private async execHover(uri: Uri, position: vscode.Position): Promise<vscode.Hover[]> {97return vscode.commands.executeCommand<vscode.Hover[]>('vscode.experimental.executeHoverProvider_recursive', uri, position);98}99100private convertHover(hovers: readonly vscode.Hover[]): vscode.Hover | undefined {101return hovers.length ?102new vscode.Hover(hovers.flatMap(x => x.contents), hovers[0].range)103: undefined;104}105106private filterOutGenericHovers(localHoverResponse: vscode.Hover[]): vscode.Hover[] {107return localHoverResponse.filter(hover => {108return hover.contents.some(entry => {109if (typeof entry === 'string') {110return entry.length;111}112113if (!entry.value.length) {114return false;115}116117for (const pattern of genericHoverMessages) {118if (pattern.test(entry.value)) {119return false;120}121}122123return true;124});125});126}127128129private async goTo(command: string, document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.LocationLink[] | undefined> {130const codeBlockId = await createSha256Hash(document.uri.fragment);131if (token.isCancellationRequested) {132return;133}134135/* __GDPR__136"codeBlock.action.goTo" : {137"owner": "mjbvz",138"comment": "Counts interactions with code blocks in chat responses",139"languageId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Language of the currently open document." },140"command": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The go to command being run." },141"codeBlockId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Unique hash of the code block." }142}143*/144this.telemetryService.sendMSFTTelemetryEvent('codeBlock.action.goTo', {145languageId: document.languageId,146command,147codeBlockId,148});149150const localLocations = await this.executeGoToInChatBlocks(command, document, position);151if (localLocations?.length) {152return localLocations;153}154155if (token.isCancellationRequested) {156return;157}158159return this.executeGoToInChatReferences(command, document, position, token);160}161162private async executeGoToInChatBlocks(command: string, document: vscode.TextDocument, position: vscode.Position): Promise<vscode.LocationLink[] | undefined> {163const result = await this.executeGoTo(command, document.uri, position);164return result?.map((result): vscode.LocationLink => {165if ('uri' in result) {166return {167targetRange: result.range,168targetUri: result.uri,169};170} else {171return result;172}173});174}175176private async executeGoTo(command: string, uri: vscode.Uri, position: vscode.Position): Promise<Array<vscode.Location | vscode.LocationLink> | undefined> {177return vscode.commands.executeCommand<Array<vscode.Location | vscode.LocationLink>>(command, uri, position);178}179180private async executeGoToInChatReferences(command: string, document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<Array<vscode.LocationLink> | undefined> {181const ctx = await this.getReferencesContext(document, position, token);182if (!ctx || token.isCancellationRequested) {183return;184}185186for (const wordMatch of ctx.wordMatches) {187const result = await this.executeGoTo(command, wordMatch.uri, wordMatch.range.start);188if (token.isCancellationRequested) {189return;190}191192if (result) {193return result.map((result): vscode.LocationLink => {194if ('uri' in result) {195return {196targetRange: result.range,197targetUri: result.uri,198originSelectionRange: ctx.wordRange,199};200} else {201return {202targetSelectionRange: result.targetSelectionRange,203targetRange: result.targetRange,204targetUri: result.targetUri,205originSelectionRange: ctx.wordRange,206};207}208});209}210}211212return undefined;213}214215private async getReferencesContext(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<{ wordRange: vscode.Range; wordMatches: vscode.Location[] } | undefined> {216const references = this.getReferences(document);217if (!references?.length) {218return;219}220221const wordRange = document.getWordRangeAtPosition(position);222if (!wordRange) {223return;224}225226const word = document.getText(wordRange);227const wordMatches = await this.instantiationService.invokeFunction(accessor => findWordInReferences(accessor, references, word, {}, token));228return { wordRange, wordMatches };229}230231private getReferences(document: vscode.TextDocument): readonly PromptReference[] {232const refs = this.extractReferences(document);233234// Filter out references that don't belong to the same language family235const docLang = getLanguage(document);236const docLangGroup = getReferenceGroupForLanguage(docLang);237if (!docLangGroup) {238// Unknown language so skip filtering239return refs;240}241242return refs.filter(ref => {243const uri = refToUri(ref);244if (!uri) {245return false;246}247248const lang = getLanguageForResource(uri);249if (!docLangGroup.has(lang.languageId)) {250return false;251}252253return true;254});255}256257private extractReferences(document: vscode.TextDocument): readonly PromptReference[] {258try {259const fragment = decodeURIComponent(document.uri.fragment);260const parsedFragment = JSON.parse(fragment);261return parsedFragment.references.map((ref: any): PromptReference => {262if ('range' in ref) {263return new PromptReference(new Location(264Uri.from(ref.uri),265new Range(ref.range.startLineNumber - 1, ref.range.startColumn - 1, ref.range.endLineNumber - 1, ref.range.endColumn - 1)));266} else {267return new PromptReference(Uri.from(ref.uri));268}269});270} catch {271return [];272}273}274}275276function refToUri(ref: PromptReference) {277return isUri(ref.anchor)278? ref.anchor279: 'uri' in ref.anchor280? ref.anchor.uri281: 'value' in ref.anchor && isUri(ref.anchor.value) ? ref.anchor.value : undefined;282}283284function getReferenceGroupForLanguage(docLang: ILanguage) {285return languageReferenceGroups.find(group => group.has(docLang.languageId));286}287288export function register(accessor: ServicesAccessor): vscode.Disposable {289const goToProvider = accessor.get(IInstantiationService).createInstance(CodeBlockIntelliSenseProvider);290const selector: vscode.DocumentSelector = { scheme: codeBlockScheme, exclusive: true };291292return vscode.Disposable.from(293vscode.languages.registerDefinitionProvider(selector, goToProvider),294vscode.languages.registerTypeDefinitionProvider(selector, goToProvider),295vscode.languages.registerImplementationProvider(selector, goToProvider),296vscode.languages.registerHoverProvider(selector, goToProvider),297);298}299300301