Path: blob/main/extensions/copilot/src/extension/prompts/node/panel/referencesAtPosition.tsx
13405 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 { PromptElement, PromptElementProps, PromptPiece, PromptReference, PromptSizing } from '@vscode/prompt-tsx';6import type * as vscode from 'vscode';7import { TextDocumentSnapshot } from '../../../../platform/editing/common/textDocumentSnapshot';8import { isScenarioAutomation } from '../../../../platform/env/common/envService';9import { IVSCodeExtensionContext } from '../../../../platform/extContext/common/extensionContext';10import { IIgnoreService } from '../../../../platform/ignore/common/ignoreService';11import { ILanguageFeaturesService } from '../../../../platform/languages/common/languageFeaturesService';12import { ILogService } from '../../../../platform/log/common/logService';13import { TreeSitterOffsetRange } from '../../../../platform/parser/node/nodes';14import { IParserService, treeSitterOffsetRangeToVSCodeRange, vscodeToTreeSitterOffsetRange } from '../../../../platform/parser/node/parserService';15import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry';16import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';17import { getLanguage } from '../../../../util/common/languages';18import { ExtensionMode, Location, Selection, Uri } from '../../../../vscodeTypes';19import { asyncComputeWithTimeBudget } from '../../../context/node/resolvers/selectionContextHelpers';20import { determineNodeToDocument } from '../../../prompt/node/definitionAroundCursor';21import { CodeBlock } from './safeElements';2223type Props = PromptElementProps<{24document: TextDocumentSnapshot;25/**26* Range of interest for which definitions are to be found.27* @remark if not provided, will use active selection in currently active editor28*/29position: vscode.Position;30/**31* Timeout for finding implementations in milliseconds. Defaults to 200ms.32*/33timeoutMs?: number;34}>;3536type CodeExcerpt = {37languageId: string;38uri: Uri;39code: string;40excerptRange: vscode.Range;41};4243/**44* @remark Excludes references that are in copilot-ignored files.45*/46export class ReferencesAtPosition extends PromptElement<Props> {47private static DEFAULT_TIMEOUT_MS = 200;4849constructor(50props: Props,51@IIgnoreService private readonly ignoreService: IIgnoreService,52@IVSCodeExtensionContext private readonly extensionContext: IVSCodeExtensionContext,53@ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService,54@IWorkspaceService private readonly workspaceService: IWorkspaceService,55@ILogService private readonly logService: ILogService,56@IParserService private readonly parserService: IParserService,57@ITelemetryService private readonly telemetryService: ITelemetryService,58) {59super(props);60}6162async render(state: void, sizing: PromptSizing) {63if (await this.ignoreService.isCopilotIgnored(this.props.document.uri)) {64return <ignoredFiles value={[this.props.document.uri]} />;65}6667const timeout = this.extensionContext.extensionMode === ExtensionMode.Test && !isScenarioAutomation68? 069: (this.props.timeoutMs === undefined ? ReferencesAtPosition.DEFAULT_TIMEOUT_MS : this.props.timeoutMs);7071const [definitions, usages] = await this.findReferences(timeout);7273this.logService.debug(`Found ${definitions.length} implementation(s)/definition(s), ${usages.length} usages`);74if (definitions.length > 0) {75this.logService.debug(`Implementation(s)/definition(s) found:` + JSON.stringify(definitions, null, '\t'));76}77if (usages.length > 0) {78this.logService.debug(`Usages found:` + JSON.stringify(usages, null, '\t'));79}8081return (82<>83{this.renderCodeExcerpts(`Relevant definition${definitions.length > 1 ? 's' : ''}:`, definitions)}84{this.renderCodeExcerpts(`Other usages:`, usages)}85</>86);87}8889private renderCodeExcerpts(title: string, excerts: CodeExcerpt[]): PromptPiece<any, any> | undefined {90if (excerts.length > 0) {91return (92<>93{title}<br /><br />94{excerts.map(excerpt => {95const lineCommentStart = getLanguage(excerpt.languageId).lineComment.start;96const filePath = `${lineCommentStart} FILEPATH: ${excerpt.uri.path}`;97const code = `${filePath}\n${excerpt.code}`;98return (99<CodeBlock uri={excerpt.uri} languageId={excerpt.languageId} code={code} references={[new PromptReference(new Location(excerpt.uri, excerpt.excerptRange))]} />100);101})}102<br /><br />103</>104);105}106return undefined;107}108109private async findReferences(timeoutMs: number): Promise<[CodeExcerpt[], CodeExcerpt[]]> {110const { document, position } = this.props;111112const findReference = async () => {113try {114const refs = await this.languageFeaturesService.getReferences(document.uri, position);115this.logService.debug(`Found ${refs.length} references: ` + JSON.stringify(refs, null, '\t'));116return refs;117} catch (e) {118return [];119}120};121122const foundRefs = await asyncComputeWithTimeBudget(this.logService, this.telemetryService, document, timeoutMs * 3, findReference, []);123124const nonIgnoredDefs = [];125const nonIgnoredRefs = [];126for (const ref of foundRefs) {127if (await this.ignoreService.isCopilotIgnored(ref.uri)) {128continue;129}130131const docContainingRef = await this.workspaceService.openTextDocumentAndSnapshot(ref.uri);132const treeSitterAST = this.parserService.getTreeSitterAST(docContainingRef);133if (!treeSitterAST) {134continue;135}136137const range = vscodeToTreeSitterOffsetRange(ref.range, docContainingRef);138const calls = await treeSitterAST.getCallExpressions(range);139const functions = await treeSitterAST.getFunctionDefinitions();140if (calls.length > 0) {141nonIgnoredRefs.push({142languageId: docContainingRef.languageId,143uri: docContainingRef.uri,144code: calls[0].text,145excerptRange: treeSitterOffsetRangeToVSCodeRange(docContainingRef, calls[0]),146} as CodeExcerpt);147} else if (functions.some(f => TreeSitterOffsetRange.doIntersect(f, range))) {148// since language service gives us only links to identifiers, expand to whole implementation/definition using tree-sitter149const nodeToDocument = await determineNodeToDocument(this.parserService, this.telemetryService, {150document: docContainingRef,151language: getLanguage(document.languageId),152wholeRange: ref.range,153selection: new Selection(ref.range.start, ref.range.end),154fileIndentInfo: undefined,155});156const excerptRange = nodeToDocument.range;157nonIgnoredDefs.push({158languageId: docContainingRef.languageId,159uri: docContainingRef.uri,160code: docContainingRef.getText(excerptRange),161excerptRange,162} satisfies CodeExcerpt);163}164}165return [nonIgnoredDefs, nonIgnoredRefs];166}167}168169170