Path: blob/main/extensions/copilot/src/extension/prompts/node/panel/symbolDefinitions.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, PromptSizing, UserMessage } 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 { TreeSitterExpressionInfo, TreeSitterExpressionLocationInfo } from '../../../../platform/parser/node/nodes';14import { IParserService, treeSitterOffsetRangeToVSCodeRange } from '../../../../platform/parser/node/parserService';15import { ITabsAndEditorsService } from '../../../../platform/tabs/common/tabsAndEditorsService';16import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry';17import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';18import { ExtensionMode, Location, Uri } from '../../../../vscodeTypes';19import { findAllReferencedClassDeclarationsInSelection, findAllReferencedFunctionImplementationsInSelection, findAllReferencedTypeDeclarationsInSelection } from '../../../context/node/resolvers/selectionContextHelpers';20import { PromptReference } from '../../../prompt/common/conversation';21import { EmbeddedInsideUserMessage, embeddedInsideUserMessageDefault } from '../base/promptElement';22import { Tag } from '../base/tag';23import { CodeBlock } from './safeElements';2425type Props = PromptElementProps<EmbeddedInsideUserMessage & {26// We want this upfront if possible because these could change during async prompt rendering27document?: TextDocumentSnapshot;28/**29* Range of interest for which definitions are to be found.30* @remark if not provided, will use active selection in currently active editor31*/32range?: vscode.Range;33/**34* Timeout for finding implementations in milliseconds. Defaults to 200ms.35*/36timeoutMs?: number;37}>;3839interface State {40activeDocument: TextDocumentSnapshot | undefined;41isIgnored: boolean;42implementations: [/* header/description */ string, TreeSitterExpressionLocationInfo[]][];43}4445export class SymbolDefinitions extends PromptElement<Props, State> {4647private static DEFAULT_TIMEOUT_MS = 200;4849constructor(50props: Props,51@IIgnoreService private readonly ignoreService: IIgnoreService,52@IVSCodeExtensionContext private readonly extensionContext: IVSCodeExtensionContext,53@ITabsAndEditorsService private readonly tabsAndEditorsService: ITabsAndEditorsService,54@IParserService private readonly parserService: IParserService,55@ILogService private readonly logService: ILogService,56@ITelemetryService private readonly telemetryService: ITelemetryService,57@ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService,58@IWorkspaceService private readonly workspaceService: IWorkspaceService,59) {60super(props);61}6263override async prepare(): Promise<State> {64const emptyState: State = { implementations: [], activeDocument: undefined, isIgnored: false };6566let { document: activeDocument, range: selection } = this.props;6768if (!activeDocument) {69const activeEditor = this.tabsAndEditorsService.activeTextEditor;70if (!activeEditor) {71return emptyState;72}73activeDocument ??= TextDocumentSnapshot.create(activeEditor.document);74selection ??= activeEditor.selection;75}7677if (!selection || selection.isEmpty) {78return emptyState;79}8081if (await this.ignoreService.isCopilotIgnored(activeDocument.uri)) {82return { ...emptyState, isIgnored: true };83}8485const timeout = this.extensionContext.extensionMode === ExtensionMode.Test && !isScenarioAutomation86? 087: (this.props.timeoutMs === undefined ? SymbolDefinitions.DEFAULT_TIMEOUT_MS : this.props.timeoutMs);8889const refFinders = [90{ header: 'Relevant function implementations', findImpls: findAllReferencedFunctionImplementationsInSelection },91{ header: 'Relevant class declarations', findImpls: findAllReferencedClassDeclarationsInSelection },92{ header: 'Relevant type declarations', findImpls: findAllReferencedTypeDeclarationsInSelection }93];9495const implementations: [string, TreeSitterExpressionInfo[]][] = [];96for (const { header, findImpls } of refFinders) {97const impls = await findImpls(this.parserService, this.logService, this.telemetryService, this.languageFeaturesService, this.workspaceService, activeDocument, selection, timeout);98implementations.push([header, impls]);99}100return { implementations, activeDocument, isIgnored: false };101}102103override render(state: State, sizing: PromptSizing): PromptPiece<any, any> | undefined {104if (!state.implementations.length || !state.activeDocument) {105return;106}107108const activeDocumentUri = state.activeDocument.uri;109if (state.isIgnored) {110return <ignoredFiles value={[activeDocumentUri]} />;111}112113const combinedElements: UserMessage[] = [];114115for (const [header, implementations] of state.implementations) {116const { references, text, uris } = treeSitterInfoToContext(state.activeDocument, implementations);117if (text.length === 0) {118continue;119}120121const elements = (122<>123{header}:<br />124<br />125{text.map((t, i) => <>126<CodeBlock code={t} languageId={state.activeDocument?.languageId} uri={uris[i]} references={[references[i]]} />127<br />128</>)}129</>130);131const msg = this.props.embeddedInsideUserMessage ?? embeddedInsideUserMessageDefault ? (132<Tag name='symbolDefinitions' priority={this.props.priority}>133{elements}134</Tag>135) : (136<UserMessage priority={this.props.priority}>137{elements}138</UserMessage>139);140141combinedElements.push(msg);142}143144return (<>145{...combinedElements}146</>);147}148}149150export function treeSitterInfoToContext(activeDocument: TextDocumentSnapshot, info: TreeSitterExpressionLocationInfo[]) {151const references = [];152const seenReferences = new Set<string>();153const text: string[] = [];154const uris: Uri[] = [];155for (const impl of info) {156const uri = impl.uri ?? activeDocument.uri;157const range = impl.range ?? treeSitterOffsetRangeToVSCodeRange(activeDocument, impl);158const key = `${uri.toString()}-${range.start.line}-${range.start.character}-${range.end.line}-${range.end.character}`;159if (seenReferences.has(key)) {160continue;161}162seenReferences.add(key);163164references.push(new PromptReference(new Location(uri, range)));165text.push(impl.text);166uris.push(uri);167}168return { references, text, uris };169}170171172