Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/debug/browser/debugExpressionRenderer.ts
3296 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 * as dom from '../../../../base/browser/dom.js';
7
import { IHighlight } from '../../../../base/browser/ui/highlightedlabel/highlightedLabel.js';
8
import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';
9
import { DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js';
10
import { IObservable } from '../../../../base/common/observable.js';
11
import { CommandsRegistry, ICommandService } from '../../../../platform/commands/common/commands.js';
12
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
13
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
14
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
15
import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js';
16
import { IDebugSession, IExpressionValue } from '../common/debug.js';
17
import { Expression, ExpressionContainer, Variable } from '../common/debugModel.js';
18
import { ReplEvaluationResult } from '../common/replModel.js';
19
import { IVariableTemplateData, splitExpressionOrScopeHighlights } from './baseDebugView.js';
20
import { handleANSIOutput } from './debugANSIHandling.js';
21
import { COPY_EVALUATE_PATH_ID, COPY_VALUE_ID } from './debugCommands.js';
22
import { DebugLinkHoverBehavior, DebugLinkHoverBehaviorTypeData, ILinkDetector, LinkDetector } from './linkDetector.js';
23
24
export interface IValueHoverOptions {
25
/** Commands to show in the hover footer. */
26
commands?: { id: string; args: unknown[] }[];
27
}
28
29
export interface IRenderValueOptions {
30
showChanged?: boolean;
31
maxValueLength?: number;
32
/** If not false, a rich hover will be shown on the element. */
33
hover?: false | IValueHoverOptions;
34
colorize?: boolean;
35
highlights?: IHighlight[];
36
37
/**
38
* Indicates areas where VS Code implicitly always supported ANSI escape
39
* sequences. These should be rendered as ANSI when the DA does not specify
40
* any value of `supportsANSIStyling`.
41
* @deprecated
42
*/
43
wasANSI?: boolean;
44
session?: IDebugSession;
45
locationReference?: number;
46
}
47
48
export interface IRenderVariableOptions {
49
showChanged?: boolean;
50
highlights?: IHighlight[];
51
}
52
53
54
const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024;
55
const booleanRegex = /^(true|false)$/i;
56
const stringRegex = /^(['"]).*\1$/;
57
58
const enum Cls {
59
Value = 'value',
60
Unavailable = 'unavailable',
61
Error = 'error',
62
Changed = 'changed',
63
Boolean = 'boolean',
64
String = 'string',
65
Number = 'number',
66
}
67
68
const allClasses: readonly Cls[] = Object.keys({
69
[Cls.Value]: 0,
70
[Cls.Unavailable]: 0,
71
[Cls.Error]: 0,
72
[Cls.Changed]: 0,
73
[Cls.Boolean]: 0,
74
[Cls.String]: 0,
75
[Cls.Number]: 0,
76
} satisfies { [key in Cls]: unknown }) as Cls[];
77
78
export class DebugExpressionRenderer {
79
private displayType: IObservable<boolean>;
80
private readonly linkDetector: LinkDetector;
81
82
constructor(
83
@ICommandService private readonly commandService: ICommandService,
84
@IConfigurationService configurationService: IConfigurationService,
85
@IInstantiationService instantiationService: IInstantiationService,
86
@IHoverService private readonly hoverService: IHoverService,
87
) {
88
this.linkDetector = instantiationService.createInstance(LinkDetector);
89
this.displayType = observableConfigValue('debug.showVariableTypes', false, configurationService);
90
}
91
92
renderVariable(data: IVariableTemplateData, variable: Variable, options: IRenderVariableOptions = {}): IDisposable {
93
const displayType = this.displayType.get();
94
const highlights = splitExpressionOrScopeHighlights(variable, options.highlights || []);
95
96
if (variable.available) {
97
data.type.textContent = '';
98
let text = variable.name;
99
if (variable.value && typeof variable.name === 'string') {
100
if (variable.type && displayType) {
101
text += ': ';
102
data.type.textContent = variable.type + ' =';
103
} else {
104
text += ' =';
105
}
106
}
107
108
data.label.set(text, highlights.name, variable.type && !displayType ? variable.type : variable.name);
109
data.name.classList.toggle('virtual', variable.presentationHint?.kind === 'virtual');
110
data.name.classList.toggle('internal', variable.presentationHint?.visibility === 'internal');
111
} else if (variable.value && typeof variable.name === 'string' && variable.name) {
112
data.label.set(':');
113
}
114
115
data.expression.classList.toggle('lazy', !!variable.presentationHint?.lazy);
116
const commands = [
117
{ id: COPY_VALUE_ID, args: [variable, [variable]] as unknown[] }
118
];
119
if (variable.evaluateName) {
120
commands.push({ id: COPY_EVALUATE_PATH_ID, args: [{ variable }] });
121
}
122
123
return this.renderValue(data.value, variable, {
124
showChanged: options.showChanged,
125
maxValueLength: MAX_VALUE_RENDER_LENGTH_IN_VIEWLET,
126
hover: { commands },
127
highlights: highlights.value,
128
colorize: true,
129
session: variable.getSession(),
130
});
131
}
132
133
renderValue(container: HTMLElement, expressionOrValue: IExpressionValue | string, options: IRenderValueOptions = {}): IDisposable {
134
const store = new DisposableStore();
135
// Use remembered capabilities so REPL elements can render even once a session ends
136
const supportsANSI: boolean = options.session?.rememberedCapabilities?.supportsANSIStyling ?? options.wasANSI ?? false;
137
138
let value = typeof expressionOrValue === 'string' ? expressionOrValue : expressionOrValue.value;
139
140
// remove stale classes
141
for (const cls of allClasses) {
142
container.classList.remove(cls);
143
}
144
container.classList.add(Cls.Value);
145
// when resolving expressions we represent errors from the server as a variable with name === null.
146
if (value === null || ((expressionOrValue instanceof Expression || expressionOrValue instanceof Variable || expressionOrValue instanceof ReplEvaluationResult) && !expressionOrValue.available)) {
147
container.classList.add(Cls.Unavailable);
148
if (value !== Expression.DEFAULT_VALUE) {
149
container.classList.add(Cls.Error);
150
}
151
} else {
152
if (typeof expressionOrValue !== 'string' && options.showChanged && expressionOrValue.valueChanged && value !== Expression.DEFAULT_VALUE) {
153
// value changed color has priority over other colors.
154
container.classList.add(Cls.Changed);
155
expressionOrValue.valueChanged = false;
156
}
157
158
if (options.colorize && typeof expressionOrValue !== 'string') {
159
if (expressionOrValue.type === 'number' || expressionOrValue.type === 'boolean' || expressionOrValue.type === 'string') {
160
container.classList.add(expressionOrValue.type);
161
} else if (!isNaN(+value)) {
162
container.classList.add(Cls.Number);
163
} else if (booleanRegex.test(value)) {
164
container.classList.add(Cls.Boolean);
165
} else if (stringRegex.test(value)) {
166
container.classList.add(Cls.String);
167
}
168
}
169
}
170
171
if (options.maxValueLength && value && value.length > options.maxValueLength) {
172
value = value.substring(0, options.maxValueLength) + '...';
173
}
174
if (!value) {
175
value = '';
176
}
177
178
const session = options.session ?? ((expressionOrValue instanceof ExpressionContainer) ? expressionOrValue.getSession() : undefined);
179
// Only use hovers for links if thre's not going to be a hover for the value.
180
const hoverBehavior: DebugLinkHoverBehaviorTypeData = options.hover === false ? { type: DebugLinkHoverBehavior.Rich, store } : { type: DebugLinkHoverBehavior.None };
181
dom.clearNode(container);
182
const locationReference = options.locationReference ?? (expressionOrValue instanceof ExpressionContainer && expressionOrValue.valueLocationReference);
183
184
let linkDetector: ILinkDetector = this.linkDetector;
185
if (locationReference && session) {
186
linkDetector = this.linkDetector.makeReferencedLinkDetector(locationReference, session);
187
}
188
189
if (supportsANSI) {
190
container.appendChild(handleANSIOutput(value, linkDetector, session ? session.root : undefined, options.highlights));
191
} else {
192
container.appendChild(linkDetector.linkify(value, false, session?.root, true, hoverBehavior, options.highlights));
193
}
194
195
if (options.hover !== false) {
196
const { commands = [] } = options.hover || {};
197
store.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), container, () => {
198
const container = dom.$('div');
199
const markdownHoverElement = dom.$('div.hover-row');
200
const hoverContentsElement = dom.append(markdownHoverElement, dom.$('div.hover-contents'));
201
const hoverContentsPre = dom.append(hoverContentsElement, dom.$('pre.debug-var-hover-pre'));
202
if (supportsANSI) {
203
// note: intentionally using `this.linkDetector` so we don't blindly linkify the
204
// entire contents and instead only link file paths that it contains.
205
hoverContentsPre.appendChild(handleANSIOutput(value, this.linkDetector, session ? session.root : undefined, options.highlights));
206
} else {
207
hoverContentsPre.textContent = value;
208
}
209
container.appendChild(markdownHoverElement);
210
return container;
211
}, {
212
actions: commands.map(({ id, args }) => {
213
const description = CommandsRegistry.getCommand(id)?.metadata?.description;
214
return {
215
label: typeof description === 'string' ? description : description ? description.value : id,
216
commandId: id,
217
run: () => this.commandService.executeCommand(id, ...args),
218
};
219
})
220
}));
221
}
222
223
return store;
224
}
225
}
226
227