Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/copilotCLIPromptReferences.ts
13405 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import type { Attachment } from '@github/copilot/sdk';
7
import * as l10n from '@vscode/l10n';
8
import type { ChatPromptReference } from 'vscode';
9
import { isLocation } from '../../../../util/common/types';
10
import { coalesce } from '../../../../util/vs/base/common/arrays';
11
import { Codicon } from '../../../../util/vs/base/common/codicons';
12
import { ResourceSet } from '../../../../util/vs/base/common/map';
13
import { basename } from '../../../../util/vs/base/common/resources';
14
import { isNumber, isString } from '../../../../util/vs/base/common/types';
15
import { URI } from '../../../../util/vs/base/common/uri';
16
import { Range as InternalRange } from '../../../../util/vs/editor/common/core/range';
17
import { SymbolKind } from '../../../../util/vs/workbench/api/common/extHostTypes/symbolInformation';
18
import { ChatReferenceDiagnostic, Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, Range, Uri } from '../../../../vscodeTypes';
19
import { PromptFileIdPrefix } from '../../../prompt/common/chatVariablesCollection';
20
21
/**
22
* Converts a ChatPromptReference into a PromptVariable entry that is used in VS code.
23
*/
24
export function convertReferenceToVariable(ref: ChatPromptReference, attachments: readonly Attachment[]) {
25
const value = ref.value;
26
const range = ref.range ? { start: ref.range[0], endExclusive: ref.range[1] } : undefined;
27
28
if (value && value instanceof ChatReferenceDiagnostic && Array.isArray(value.diagnostics) && value.diagnostics.length && value.diagnostics[0][1].length) {
29
const marker = DiagnosticConverter.from(value.diagnostics[0][1][0]);
30
const refValue = {
31
filterRange: { startLineNumber: marker.startLineNumber, startColumn: marker.startColumn, endLineNumber: marker.endLineNumber, endColumn: marker.endColumn },
32
filterSeverity: marker.severity,
33
filterUri: value.diagnostics[0][0],
34
problemMessage: value.diagnostics[0][1][0].message
35
};
36
return IDiagnosticVariableEntryFilterData.toEntry(refValue);
37
}
38
39
if (isLocation(ref.value) && ref.name.startsWith(`sym:`)) {
40
return {
41
id: ref.id,
42
name: ref.name,
43
fullName: ref.name.substring(4),
44
value: { uri: ref.value.uri, range: toInternalRange(ref.value.range) },
45
// We never send this information to extensions, so default to Property
46
symbolKind: SymbolKind.Property,
47
// We never send this information to extensions, so default to Property
48
icon: Codicon.symbolProperty,
49
kind: 'symbol',
50
range,
51
};
52
}
53
54
if (URI.isUri(value) && ref.name.startsWith(`prompt:`) &&
55
ref.id.startsWith(PromptFileIdPrefix) &&
56
ref.id.endsWith(value.toString())) {
57
return {
58
id: ref.id,
59
name: `prompt:${basename(value)}`,
60
value,
61
kind: 'promptFile',
62
modelDescription: 'Prompt instructions file',
63
isRoot: true,
64
automaticallyAdded: false,
65
range,
66
};
67
}
68
69
const folders = new ResourceSet(attachments.filter(att => att.type === 'directory').map(att => URI.file(att.path)));
70
const isFile = URI.isUri(value) || isLocation(value);
71
const isFolder = URI.isUri(value) && (value.path.endsWith('/') || folders.has(value));
72
return {
73
id: ref.id,
74
name: ref.name,
75
value,
76
modelDescription: ref.modelDescription,
77
range,
78
kind: isFolder ? 'directory' as const : isFile ? 'file' as const : 'generic' as const
79
};
80
}
81
82
function toInternalRange(range: Range): InternalRange {
83
return new InternalRange(range.start.line + 1, range.start.character + 1, range.end.line + 1, range.end.character + 1);
84
}
85
86
namespace DiagnosticTagConverter {
87
88
/**
89
* Additional metadata about the type of a diagnostic.
90
*/
91
export enum DiagnosticTag {
92
/**
93
* Unused or unnecessary code.
94
*
95
* Diagnostics with this tag are rendered faded out. The amount of fading
96
* is controlled by the `"editorUnnecessaryCode.opacity"` theme color. For
97
* example, `"editorUnnecessaryCode.opacity": "#000000c0"` will render the
98
* code with 75% opacity. For high contrast themes, use the
99
* `"editorUnnecessaryCode.border"` theme color to underline unnecessary code
100
* instead of fading it out.
101
*/
102
Unnecessary = 1,
103
104
/**
105
* Deprecated or obsolete code.
106
*
107
* Diagnostics with this tag are rendered with a strike through.
108
*/
109
Deprecated = 2,
110
}
111
export const enum MarkerTag {
112
Unnecessary = 1,
113
Deprecated = 2
114
}
115
116
117
export function from(value: DiagnosticTag) {
118
119
switch (value) {
120
case DiagnosticTag.Unnecessary:
121
return MarkerTag.Unnecessary;
122
case DiagnosticTag.Deprecated:
123
return MarkerTag.Deprecated;
124
default:
125
return undefined;
126
}
127
}
128
}
129
130
131
namespace IDiagnosticVariableEntryFilterData {
132
export const icon = Codicon.error;
133
134
export function fromMarker(marker: Record<string, unknown>) {
135
return {
136
filterUri: marker.resource,
137
owner: marker.owner,
138
problemMessage: marker.message,
139
filterRange: { startLineNumber: marker.startLineNumber, endLineNumber: marker.endLineNumber, startColumn: marker.startColumn, endColumn: marker.endColumn }
140
};
141
}
142
143
export function toEntry(data: Record<string, unknown>) {
144
return {
145
id: id(data),
146
name: label(data),
147
icon,
148
value: data,
149
kind: 'diagnostic',
150
...data,
151
};
152
}
153
154
export function id(data: Record<string, unknown> & { filterRange?: InternalRange }) {
155
return [data.filterUri, data.owner, data.filterSeverity, data.filterRange?.startLineNumber, data.filterRange?.startColumn].join(':');
156
}
157
158
export function label(data: Record<string, unknown> & { problemMessage?: string; filterUri?: Uri }) {
159
const enum TrimThreshold {
160
MaxChars = 30,
161
MaxSpaceLookback = 10,
162
}
163
if (data.problemMessage) {
164
if (data.problemMessage.length < TrimThreshold.MaxChars) {
165
return data.problemMessage;
166
}
167
168
// Trim the message, on a space if it would not lose too much
169
// data (MaxSpaceLookback) or just blindly otherwise.
170
const lastSpace = data.problemMessage.lastIndexOf(' ', TrimThreshold.MaxChars);
171
if (lastSpace === -1 || lastSpace + TrimThreshold.MaxSpaceLookback < TrimThreshold.MaxChars) {
172
return data.problemMessage.substring(0, TrimThreshold.MaxChars) + '…';
173
}
174
return data.problemMessage.substring(0, lastSpace) + '…';
175
}
176
let labelStr = l10n.t("All Problems");
177
if (data.filterUri) {
178
labelStr = l10n.t("Problems in {0}", basename(data.filterUri));
179
}
180
181
return labelStr;
182
}
183
}
184
185
namespace DiagnosticConverter {
186
export function from(value: Diagnostic) {
187
let code: string | { value: string; target: Uri } | undefined;
188
189
if (value.code) {
190
if (isString(value.code) || isNumber(value.code)) {
191
code = String(value.code);
192
} else {
193
code = {
194
value: String(value.code.value),
195
target: value.code.target,
196
};
197
}
198
}
199
200
return {
201
...toInternalRange(value.range),
202
message: value.message,
203
source: value.source,
204
code,
205
severity: DiagnosticSeverityConverter.from(value.severity),
206
relatedInformation: value.relatedInformation && value.relatedInformation.map(DiagnosticRelatedInformationConverter.from),
207
tags: Array.isArray(value.tags) ? coalesce(value.tags.map(DiagnosticTagConverter.from)) : undefined,
208
};
209
}
210
}
211
212
namespace DiagnosticRelatedInformationConverter {
213
export function from(value: DiagnosticRelatedInformation) {
214
return {
215
...toInternalRange(value.location.range),
216
message: value.message,
217
resource: value.location.uri
218
};
219
}
220
}
221
222
namespace DiagnosticSeverityConverter {
223
export enum MarkerSeverity {
224
Hint = 1,
225
Info = 2,
226
Warning = 4,
227
Error = 8,
228
}
229
230
export function from(value: number): MarkerSeverity {
231
switch (value) {
232
case DiagnosticSeverity.Error:
233
return MarkerSeverity.Error;
234
case DiagnosticSeverity.Warning:
235
return MarkerSeverity.Warning;
236
case DiagnosticSeverity.Information:
237
return MarkerSeverity.Info;
238
case DiagnosticSeverity.Hint:
239
return MarkerSeverity.Hint;
240
}
241
return MarkerSeverity.Error;
242
}
243
}
244
245