Path: blob/main/extensions/copilot/src/extension/prompt/node/definitionAroundCursor.tsx
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 { PromptElement, PromptElementProps, PromptPiece, PromptSizing, UserMessage } from '@vscode/prompt-tsx';6import type * as vscode from 'vscode';7import { IIgnoreService } from '../../../platform/ignore/common/ignoreService';8import { IChatEndpoint } from '../../../platform/networking/common/networking';9import { TreeSitterOffsetRange } from '../../../platform/parser/node/nodes';10import { NodeToDocumentContext } from '../../../platform/parser/node/parserImpl';11import { IParserService, treeSitterOffsetRangeToVSCodeRange as toRange, vscodeToTreeSitterOffsetRange as toTSOffsetRange } from '../../../platform/parser/node/parserService';12import { ITelemetryService } from '../../../platform/telemetry/common/telemetry';13import { CodeContextRegion, CodeContextTracker } from '../../inlineChat/node/codeContextRegion';14import { IDocumentContext } from './documentContext';1516export type Props = PromptElementProps<{17documentContext: IDocumentContext;18endpointInfo: IChatEndpoint;19/** if not provided, the component will compute based on `documentContext` */20nodeToDocument?: NodeToDocument;21}>;2223export type State =24| {25k: 'found';26nodeToDocument: NodeToDocument;27codeExcerptToDocument: CodeContextRegion;28}29| {30k: 'ignored';31}32;3334export class DefinitionAroundCursor extends PromptElement<Props, State> {3536constructor(37props: Props,38@ITelemetryService private readonly _telemetryService: ITelemetryService,39@IParserService private readonly _parserService: IParserService,40@IIgnoreService private readonly _ignoreService: IIgnoreService,41) {42super(props);43}4445override async prepare(sizing: PromptSizing, progress?: vscode.Progress<vscode.ChatResponseProgressPart | vscode.ChatResponseReferencePart> | undefined, token?: vscode.CancellationToken | undefined): Promise<State> {46if (await this._ignoreService.isCopilotIgnored(this.props.documentContext.document.uri)) {47return { k: 'ignored' };48}49const nodeToDocument = this.props.nodeToDocument ?? await determineNodeToDocument(this._parserService, this._telemetryService, this.props.documentContext);50const contextInfo = generateDocContext(this.props.endpointInfo, this.props.documentContext, nodeToDocument.range);51return {52k: 'found',53nodeToDocument,54codeExcerptToDocument: contextInfo.range,55};56}5758override render(state: State, sizing: PromptSizing): PromptPiece<any, any> | undefined {59if (state.k === 'ignored') {60return <ignoredFiles value={[this.props.documentContext.document.uri]} />;61}62const codeExcerpt = state.codeExcerptToDocument.generatePrompt().join('\n');63return (64<UserMessage>65I have the following code in the selection:{codeExcerpt !== '' ? <br /> : ''}66{state.codeExcerptToDocument.generatePrompt().join('\n')}67</UserMessage>68);69}70}7172export type NodeToDocument = {73readonly range: vscode.Range;74readonly identifier?: string;75};76export async function determineNodeToDocument(parserService: IParserService, telemetryService: ITelemetryService, ctx: IDocumentContext): Promise<{ range: vscode.Range; identifier?: string }> {7778const selectionRange = toTSOffsetRange(ctx.selection, ctx.document);7980const treeSitterAST = parserService.getTreeSitterAST(ctx.document);8182if (treeSitterAST === undefined) {83return {84range: ctx.wholeRange,85};86}8788const startTime = Date.now();89const nodeToDocContext = await treeSitterAST.getNodeToDocument(selectionRange);90const timeSpentMs = Date.now() - startTime;9192const wholeOffsetRange = toTSOffsetRange(ctx.wholeRange, ctx.document);93sendNodeToDocumentTelemetry(telemetryService, selectionRange, wholeOffsetRange, nodeToDocContext, ctx.document.languageId, timeSpentMs);9495const rangeOfNodeToDocument = toRange(ctx.document, nodeToDocContext.nodeToDocument);96return {97identifier: nodeToDocContext.nodeIdentifier,98range: rangeOfNodeToDocument,99};100}101function generateDocContext(endpoint: IChatEndpoint, ctx: IDocumentContext, range: vscode.Range) {102103const tracker = new CodeContextTracker((endpoint.modelMaxPromptTokens * 4) / 3);104const rangeInfo = new CodeContextRegion(tracker, ctx.document, ctx.language);105106// we only want to include the code that's being documented but we need `above` and `below` for the return value's type107// so we just make these code regions empty108const above = new CodeContextRegion(new CodeContextTracker(0), ctx.document, ctx.language);109const below = new CodeContextRegion(new CodeContextTracker(0), ctx.document, ctx.language);110111for (let i = range.start.line, len = range.end.line; i <= len; ++i) {112if ((i === len && range.end.character === 0) // we don't want to include the end line if it's (end.line, 0)113|| !rangeInfo.appendLine(i) // didn't fit114) {115break;116}117}118119rangeInfo.trim(ctx.selection);120121return {122language: ctx.language,123above,124range: rangeInfo,125below,126};127}128function sendNodeToDocumentTelemetry(129telemetryService: ITelemetryService,130selectionRange: TreeSitterOffsetRange,131wholeOffsetRange: TreeSitterOffsetRange,132nodeToDocContext: NodeToDocumentContext,133languageId: string,134timeSpentMs: number135) {136137/* __GDPR__138"getNodeToDocument" : {139"owner": "ulugbekna",140"comment": "Info on success and properties of detecting AST node to document",141"languageId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The language ID of the document" },142"typeOfNodeToDocument": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Type of the AST node offered to be documented (type defined by tree-sitter grammar for that language)" },143"nodeToDocumentStart": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Start offset of the AST node offered to be documented (type defined by tree-sitter grammar for that language)" },144"nodeToDocumentEnd": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "End offset of the AST node offered to be documented (type defined by tree-sitter grammar for that language)" },145"selectionOffsetRangeStart": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The start offset range of the selection in the document" },146"selectionOffsetRangeEnd": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The end offset range of the selection in the document" },147"wholeRangeOffsetRangeStart": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The start offset range of the inline-chat wholeRange" },148"wholeRangeOffsetRangeEnd": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The end offset range of the inline-chat wholeRange" },149"timeSpentMs": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Time (in milliseconds) spent on finding the AST node to document (approximate as it's an async call)" }150}151*/152telemetryService.sendMSFTTelemetryEvent('getNodeToDocument',153{154languageId,155typeOfNodeToDocument: nodeToDocContext.nodeToDocument.type,156nodeToDocumentStart: nodeToDocContext.nodeToDocument.startIndex.toString(),157nodeToDocumentEnd: nodeToDocContext.nodeToDocument.endIndex.toString(),158selectionOffsetRangeStart: selectionRange.startIndex.toString(),159selectionOffsetRangeEnd: selectionRange.endIndex.toString(),160wholeRangeOffsetRangeStart: wholeOffsetRange.startIndex.toString(),161wholeRangeOffsetRangeEnd: wholeOffsetRange.endIndex.toString(),162},163{164timeSpentMs,165}166);167}168169170