Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/test/simulation/fixtures/multiFileEdit/issue-8098/debugUtils.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 { equalsIgnoreCase } from '../../../../base/common/strings.js';
7
import { IDebuggerContribution, IDebugSession, IConfigPresentation } from './debug.js';
8
import { URI as uri } from '../../../../base/common/uri.js';
9
import { isAbsolute } from '../../../../base/common/path.js';
10
import { deepClone } from '../../../../base/common/objects.js';
11
import { Schemas } from '../../../../base/common/network.js';
12
import { IEditorService } from '../../../services/editor/common/editorService.js';
13
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
14
import { ITextModel } from '../../../../editor/common/model.js';
15
import { Position } from '../../../../editor/common/core/position.js';
16
import { IRange, Range } from '../../../../editor/common/core/range.js';
17
import { CancellationToken } from '../../../../base/common/cancellation.js';
18
import { coalesce } from '../../../../base/common/arrays.js';
19
import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js';
20
21
const _formatPIIRegexp = /{([^}]+)}/g;
22
23
export function formatPII(value: string, excludePII: boolean, args: { [key: string]: string } | undefined): string {
24
return value.replace(_formatPIIRegexp, function (match, group) {
25
if (excludePII && group.length > 0 && group[0] !== '_') {
26
return match;
27
}
28
29
return args && args.hasOwnProperty(group) ?
30
args[group] :
31
match;
32
});
33
}
34
35
/**
36
* Filters exceptions (keys marked with "!") from the given object. Used to
37
* ensure exception data is not sent on web remotes, see #97628.
38
*/
39
export function filterExceptionsFromTelemetry<T extends { [key: string]: unknown }>(data: T): Partial<T> {
40
const output: Partial<T> = {};
41
for (const key of Object.keys(data) as (keyof T & string)[]) {
42
if (!key.startsWith('!')) {
43
output[key] = data[key];
44
}
45
}
46
47
return output;
48
}
49
50
51
export function isSessionAttach(session: IDebugSession): boolean {
52
return session.configuration.request === 'attach' && !getExtensionHostDebugSession(session) && (!session.parentSession || isSessionAttach(session.parentSession));
53
}
54
55
/**
56
* Returns the session or any parent which is an extension host debug session.
57
* Returns undefined if there's none.
58
*/
59
export function getExtensionHostDebugSession(session: IDebugSession): IDebugSession | void {
60
let type = session.configuration.type;
61
if (!type) {
62
return;
63
}
64
65
if (type === 'vslsShare') {
66
type = (<any>session.configuration).adapterProxy.configuration.type;
67
}
68
69
if (equalsIgnoreCase(type, 'extensionhost') || equalsIgnoreCase(type, 'pwa-extensionhost')) {
70
return session;
71
}
72
73
return session.parentSession ? getExtensionHostDebugSession(session.parentSession) : undefined;
74
}
75
76
// only a debugger contributions with a label, program, or runtime attribute is considered a "defining" or "main" debugger contribution
77
export function isDebuggerMainContribution(dbg: IDebuggerContribution) {
78
return dbg.type && (dbg.label || dbg.program || dbg.runtime);
79
}
80
81
export function getExactExpressionStartAndEnd(lineContent: string, looseStart: number, looseEnd: number): { start: number; end: number } {
82
let matchingExpression: string | undefined = undefined;
83
let startOffset = 0;
84
85
// Some example supported expressions: myVar.prop, a.b.c.d, myVar?.prop, myVar->prop, MyClass::StaticProp, *myVar
86
// Match any character except a set of characters which often break interesting sub-expressions
87
const expression: RegExp = /([^()\[\]{}<>\s+\-/%~#^;=|,`!]|\->)+/g;
88
let result: RegExpExecArray | null = null;
89
90
// First find the full expression under the cursor
91
while (result = expression.exec(lineContent)) {
92
const start = result.index + 1;
93
const end = start + result[0].length;
94
95
if (start <= looseStart && end >= looseEnd) {
96
matchingExpression = result[0];
97
startOffset = start;
98
break;
99
}
100
}
101
102
// If there are non-word characters after the cursor, we want to truncate the expression then.
103
// For example in expression 'a.b.c.d', if the focus was under 'b', 'a.b' would be evaluated.
104
if (matchingExpression) {
105
const subExpression: RegExp = /(\w|\p{L})+/gu;
106
let subExpressionResult: RegExpExecArray | null = null;
107
while (subExpressionResult = subExpression.exec(matchingExpression)) {
108
const subEnd = subExpressionResult.index + 1 + startOffset + subExpressionResult[0].length;
109
if (subEnd >= looseEnd) {
110
break;
111
}
112
}
113
114
if (subExpressionResult) {
115
matchingExpression = matchingExpression.substring(0, subExpression.lastIndex);
116
}
117
}
118
119
return matchingExpression ?
120
{ start: startOffset, end: startOffset + matchingExpression.length - 1 } :
121
{ start: 0, end: 0 };
122
}
123
124
export async function getEvaluatableExpressionAtPosition(languageFeaturesService: ILanguageFeaturesService, model: ITextModel, position: Position, token?: CancellationToken): Promise<{ range: IRange; matchingExpression: string } | null> {
125
if (languageFeaturesService.evaluatableExpressionProvider.has(model)) {
126
const supports = languageFeaturesService.evaluatableExpressionProvider.ordered(model);
127
128
const results = coalesce(await Promise.all(supports.map(async support => {
129
try {
130
return await support.provideEvaluatableExpression(model, position, token ?? CancellationToken.None);
131
} catch (err) {
132
return undefined;
133
}
134
})));
135
136
if (results.length > 0) {
137
let matchingExpression = results[0].expression;
138
const range = results[0].range;
139
140
if (!matchingExpression) {
141
const lineContent = model.getLineContent(position.lineNumber);
142
matchingExpression = lineContent.substring(range.startColumn - 1, range.endColumn - 1);
143
}
144
145
return { range, matchingExpression };
146
}
147
} else { // old one-size-fits-all strategy
148
const lineContent = model.getLineContent(position.lineNumber);
149
const { start, end } = getExactExpressionStartAndEnd(lineContent, position.column, position.column);
150
151
// use regex to extract the sub-expression #9821
152
const matchingExpression = lineContent.substring(start - 1, end);
153
return {
154
matchingExpression,
155
range: new Range(position.lineNumber, start, position.lineNumber, start + matchingExpression.length)
156
};
157
}
158
159
return null;
160
}
161
162
// RFC 2396, Appendix A: https://www.ietf.org/rfc/rfc2396.txt
163
const _schemePattern = /^[a-zA-Z][a-zA-Z0-9\+\-\.]+:/;
164
165
export function isUri(s: string | undefined): boolean {
166
// heuristics: a valid uri starts with a scheme and
167
// the scheme has at least 2 characters so that it doesn't look like a drive letter.
168
return !!(s && s.match(_schemePattern));
169
}
170
171
function stringToUri(source: PathContainer): string | undefined {
172
if (typeof source.path === 'string') {
173
if (typeof source.sourceReference === 'number' && source.sourceReference > 0) {
174
// if there is a source reference, don't touch path
175
} else {
176
if (isUri(source.path)) {
177
return <string><unknown>uri.parse(source.path);
178
} else {
179
// assume path
180
if (isAbsolute(source.path)) {
181
return <string><unknown>uri.file(source.path);
182
} else {
183
// leave relative path as is
184
}
185
}
186
}
187
}
188
return source.path;
189
}
190
191
function uriToString(source: PathContainer): string | undefined {
192
if (typeof source.path === 'object') {
193
const u = uri.revive(source.path);
194
if (u) {
195
if (u.scheme === Schemas.file) {
196
return u.fsPath;
197
} else {
198
return u.toString();
199
}
200
}
201
}
202
return source.path;
203
}
204
205
// path hooks helpers
206
207
interface PathContainer {
208
path?: string;
209
sourceReference?: number;
210
}
211
212
export function convertToDAPaths(message: DebugProtocol.ProtocolMessage, toUri: boolean): DebugProtocol.ProtocolMessage {
213
214
const fixPath = toUri ? stringToUri : uriToString;
215
216
// since we modify Source.paths in the message in place, we need to make a copy of it (see #61129)
217
const msg = deepClone(message);
218
219
convertPaths(msg, (toDA: boolean, source: PathContainer | undefined) => {
220
if (toDA && source) {
221
source.path = fixPath(source);
222
}
223
});
224
return msg;
225
}
226
227
export function convertToVSCPaths(message: DebugProtocol.ProtocolMessage, toUri: boolean): DebugProtocol.ProtocolMessage {
228
229
const fixPath = toUri ? stringToUri : uriToString;
230
231
// since we modify Source.paths in the message in place, we need to make a copy of it (see #61129)
232
const msg = deepClone(message);
233
234
convertPaths(msg, (toDA: boolean, source: PathContainer | undefined) => {
235
if (!toDA && source) {
236
source.path = fixPath(source);
237
}
238
});
239
return msg;
240
}
241
242
function convertPaths(msg: DebugProtocol.ProtocolMessage, fixSourcePath: (toDA: boolean, source: PathContainer | undefined) => void): void {
243
244
switch (msg.type) {
245
case 'event': {
246
const event = <DebugProtocol.Event>msg;
247
switch (event.event) {
248
case 'output':
249
fixSourcePath(false, (<DebugProtocol.OutputEvent>event).body.source);
250
break;
251
case 'loadedSource':
252
fixSourcePath(false, (<DebugProtocol.LoadedSourceEvent>event).body.source);
253
break;
254
case 'breakpoint':
255
fixSourcePath(false, (<DebugProtocol.BreakpointEvent>event).body.breakpoint.source);
256
break;
257
default:
258
break;
259
}
260
break;
261
}
262
case 'request': {
263
const request = <DebugProtocol.Request>msg;
264
switch (request.command) {
265
case 'setBreakpoints':
266
fixSourcePath(true, (<DebugProtocol.SetBreakpointsArguments>request.arguments).source);
267
break;
268
case 'breakpointLocations':
269
fixSourcePath(true, (<DebugProtocol.BreakpointLocationsArguments>request.arguments).source);
270
break;
271
case 'source':
272
fixSourcePath(true, (<DebugProtocol.SourceArguments>request.arguments).source);
273
break;
274
case 'gotoTargets':
275
fixSourcePath(true, (<DebugProtocol.GotoTargetsArguments>request.arguments).source);
276
break;
277
case 'launchVSCode':
278
request.arguments.args.forEach((arg: PathContainer | undefined) => fixSourcePath(false, arg));
279
break;
280
default:
281
break;
282
}
283
break;
284
}
285
case 'response': {
286
const response = <DebugProtocol.Response>msg;
287
if (response.success && response.body) {
288
switch (response.command) {
289
case 'stackTrace':
290
(<DebugProtocol.StackTraceResponse>response).body.stackFrames.forEach(frame => fixSourcePath(false, frame.source));
291
break;
292
case 'loadedSources':
293
(<DebugProtocol.LoadedSourcesResponse>response).body.sources.forEach(source => fixSourcePath(false, source));
294
break;
295
case 'scopes':
296
(<DebugProtocol.ScopesResponse>response).body.scopes.forEach(scope => fixSourcePath(false, scope.source));
297
break;
298
case 'setFunctionBreakpoints':
299
(<DebugProtocol.SetFunctionBreakpointsResponse>response).body.breakpoints.forEach(bp => fixSourcePath(false, bp.source));
300
break;
301
case 'setBreakpoints':
302
(<DebugProtocol.SetBreakpointsResponse>response).body.breakpoints.forEach(bp => fixSourcePath(false, bp.source));
303
break;
304
case 'disassemble':
305
{
306
const di = <DebugProtocol.DisassembleResponse>response;
307
di.body?.instructions.forEach(di => fixSourcePath(false, di.location));
308
}
309
break;
310
case 'locations':
311
fixSourcePath(false, (<DebugProtocol.LocationsResponse>response).body?.source);
312
break;
313
default:
314
break;
315
}
316
}
317
break;
318
}
319
}
320
}
321
322
export function getVisibleAndSorted<T extends { presentation?: IConfigPresentation }>(array: T[]): T[] {
323
return array.filter(config => !config.presentation?.hidden).sort((first, second) => {
324
if (!first.presentation) {
325
if (!second.presentation) {
326
return 0;
327
}
328
return 1;
329
}
330
if (!second.presentation) {
331
return -1;
332
}
333
if (!first.presentation.group) {
334
if (!second.presentation.group) {
335
return compareOrders(first.presentation.order, second.presentation.order);
336
}
337
return 1;
338
}
339
if (!second.presentation.group) {
340
return -1;
341
}
342
if (first.presentation.group !== second.presentation.group) {
343
return first.presentation.group.localeCompare(second.presentation.group);
344
}
345
346
return compareOrders(first.presentation.order, second.presentation.order);
347
});
348
}
349
350
function compareOrders(first: number | undefined, second: number | undefined): number {
351
if (typeof first !== 'number') {
352
if (typeof second !== 'number') {
353
return 0;
354
}
355
356
return 1;
357
}
358
if (typeof second !== 'number') {
359
return -1;
360
}
361
362
return first - second;
363
}
364
365
export async function saveAllBeforeDebugStart(configurationService: IConfigurationService, editorService: IEditorService): Promise<void> {
366
const saveBeforeStartConfig: string = configurationService.getValue('debug.saveBeforeStart', { overrideIdentifier: editorService.activeTextEditorLanguageId });
367
if (saveBeforeStartConfig !== 'none') {
368
await editorService.saveAll();
369
if (saveBeforeStartConfig === 'allEditorsInActiveGroup') {
370
const activeEditor = editorService.activeEditorPane;
371
if (activeEditor && activeEditor.input.resource?.scheme === Schemas.untitled) {
372
// Make sure to save the active editor in case it is in untitled file it wont be saved as part of saveAll #111850
373
await editorService.save({ editor: activeEditor.input, groupId: activeEditor.group.id });
374
}
375
}
376
}
377
await configurationService.reloadConfiguration();
378
}
379
380
export const sourcesEqual = (a: DebugProtocol.Source | undefined, b: DebugProtocol.Source | undefined): boolean =>
381
!a || !b ? a === b : a.name === b.name && a.path === b.path && a.sourceReference === b.sourceReference;
382
383