Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/debug/browser/baseDebugView.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 { IKeyboardEvent } from '../../../../base/browser/keyboardEvent.js';
8
import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';
9
import { HighlightedLabel, IHighlight } from '../../../../base/browser/ui/highlightedlabel/highlightedLabel.js';
10
import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';
11
import { IInputValidationOptions, InputBox } from '../../../../base/browser/ui/inputbox/inputBox.js';
12
import { IKeyboardNavigationLabelProvider } from '../../../../base/browser/ui/list/list.js';
13
import { IAsyncDataSource, ITreeNode, ITreeRenderer } from '../../../../base/browser/ui/tree/tree.js';
14
import { Codicon } from '../../../../base/common/codicons.js';
15
import { FuzzyScore, createMatches } from '../../../../base/common/filters.js';
16
import { createSingleCallFunction } from '../../../../base/common/functional.js';
17
import { KeyCode } from '../../../../base/common/keyCodes.js';
18
import { DisposableStore, IDisposable, dispose, toDisposable } from '../../../../base/common/lifecycle.js';
19
import { removeAnsiEscapeCodes } from '../../../../base/common/strings.js';
20
import { ThemeIcon } from '../../../../base/common/themables.js';
21
import { localize } from '../../../../nls.js';
22
import { ICommandService } from '../../../../platform/commands/common/commands.js';
23
import { IContextViewService } from '../../../../platform/contextview/browser/contextView.js';
24
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
25
import { defaultInputBoxStyles } from '../../../../platform/theme/browser/defaultStyles.js';
26
import { IDebugService, IExpression, IScope } from '../common/debug.js';
27
import { Variable } from '../common/debugModel.js';
28
import { IDebugVisualizerService } from '../common/debugVisualizers.js';
29
import { LinkDetector } from './linkDetector.js';
30
31
const $ = dom.$;
32
33
export interface IRenderValueOptions {
34
showChanged?: boolean;
35
maxValueLength?: number;
36
/** If set, a hover will be shown on the element. Requires a disposable store for usage. */
37
hover?: false | {
38
commands: { id: string; args: unknown[] }[];
39
commandService: ICommandService;
40
};
41
colorize?: boolean;
42
linkDetector?: LinkDetector;
43
}
44
45
export interface IVariableTemplateData {
46
expression: HTMLElement;
47
name: HTMLElement;
48
type: HTMLElement;
49
value: HTMLElement;
50
label: HighlightedLabel;
51
lazyButton: HTMLElement;
52
}
53
54
export function renderViewTree(container: HTMLElement): HTMLElement {
55
const treeContainer = $('.');
56
treeContainer.classList.add('debug-view-content', 'file-icon-themable-tree');
57
container.appendChild(treeContainer);
58
return treeContainer;
59
}
60
61
export interface IInputBoxOptions {
62
initialValue: string;
63
ariaLabel: string;
64
placeholder?: string;
65
validationOptions?: IInputValidationOptions;
66
onFinish: (value: string, success: boolean) => void;
67
}
68
69
export interface IExpressionTemplateData {
70
expression: HTMLElement;
71
name: HTMLSpanElement;
72
type: HTMLSpanElement;
73
value: HTMLSpanElement;
74
inputBoxContainer: HTMLElement;
75
actionBar?: ActionBar;
76
elementDisposable: DisposableStore;
77
templateDisposable: IDisposable;
78
label: HighlightedLabel;
79
lazyButton: HTMLElement;
80
currentElement: IExpression | undefined;
81
}
82
83
/** Splits highlights based on matching of the {@link expressionAndScopeLabelProvider} */
84
export const splitExpressionOrScopeHighlights = (e: IExpression | IScope, highlights: IHighlight[]) => {
85
const nameEndsAt = e.name.length;
86
const labelBeginsAt = e.name.length + 2;
87
const name: IHighlight[] = [];
88
const value: IHighlight[] = [];
89
for (const hl of highlights) {
90
if (hl.start < nameEndsAt) {
91
name.push({ start: hl.start, end: Math.min(hl.end, nameEndsAt) });
92
}
93
if (hl.end > labelBeginsAt) {
94
value.push({ start: Math.max(hl.start - labelBeginsAt, 0), end: hl.end - labelBeginsAt });
95
}
96
}
97
98
return { name, value };
99
};
100
101
/** Keyboard label provider for expression and scope tree elements. */
102
export const expressionAndScopeLabelProvider: IKeyboardNavigationLabelProvider<IExpression | IScope> = {
103
getKeyboardNavigationLabel(e) {
104
const stripAnsi = e.getSession()?.rememberedCapabilities?.supportsANSIStyling;
105
return `${e.name}: ${stripAnsi ? removeAnsiEscapeCodes(e.value) : e.value}`;
106
},
107
};
108
109
export abstract class AbstractExpressionDataSource<Input, Element extends IExpression> implements IAsyncDataSource<Input, Element> {
110
constructor(
111
@IDebugService protected debugService: IDebugService,
112
@IDebugVisualizerService protected debugVisualizer: IDebugVisualizerService,
113
) { }
114
115
public abstract hasChildren(element: Input | Element): boolean;
116
117
public async getChildren(element: Input | Element): Promise<Element[]> {
118
const vm = this.debugService.getViewModel();
119
const children = await this.doGetChildren(element);
120
return Promise.all(children.map(async r => {
121
const vizOrTree = vm.getVisualizedExpression(r as IExpression);
122
if (typeof vizOrTree === 'string') {
123
const viz = await this.debugVisualizer.getVisualizedNodeFor(vizOrTree, r);
124
if (viz) {
125
vm.setVisualizedExpression(r, viz);
126
return viz as IExpression as Element;
127
}
128
} else if (vizOrTree) {
129
return vizOrTree as Element;
130
}
131
132
133
return r;
134
}));
135
}
136
137
protected abstract doGetChildren(element: Input | Element): Promise<Element[]>;
138
}
139
140
export abstract class AbstractExpressionsRenderer<T = IExpression> implements ITreeRenderer<T, FuzzyScore, IExpressionTemplateData> {
141
142
constructor(
143
@IDebugService protected debugService: IDebugService,
144
@IContextViewService private readonly contextViewService: IContextViewService,
145
@IHoverService protected readonly hoverService: IHoverService,
146
) { }
147
148
abstract get templateId(): string;
149
150
renderTemplate(container: HTMLElement): IExpressionTemplateData {
151
const templateDisposable = new DisposableStore();
152
const expression = dom.append(container, $('.expression'));
153
const name = dom.append(expression, $('span.name'));
154
const lazyButton = dom.append(expression, $('span.lazy-button'));
155
lazyButton.classList.add(...ThemeIcon.asClassNameArray(Codicon.eye));
156
157
templateDisposable.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), lazyButton, localize('debug.lazyButton.tooltip', "Click to expand")));
158
const type = dom.append(expression, $('span.type'));
159
160
const value = dom.append(expression, $('span.value'));
161
162
const label = templateDisposable.add(new HighlightedLabel(name));
163
164
const inputBoxContainer = dom.append(expression, $('.inputBoxContainer'));
165
166
let actionBar: ActionBar | undefined;
167
if (this.renderActionBar) {
168
dom.append(expression, $('.span.actionbar-spacer'));
169
actionBar = templateDisposable.add(new ActionBar(expression));
170
}
171
172
const template: IExpressionTemplateData = { expression, name, type, value, label, inputBoxContainer, actionBar, elementDisposable: new DisposableStore(), templateDisposable, lazyButton, currentElement: undefined };
173
174
templateDisposable.add(dom.addDisposableListener(lazyButton, dom.EventType.CLICK, () => {
175
if (template.currentElement) {
176
this.debugService.getViewModel().evaluateLazyExpression(template.currentElement);
177
}
178
}));
179
180
return template;
181
}
182
183
public abstract renderElement(node: ITreeNode<T, FuzzyScore>, index: number, data: IExpressionTemplateData): void;
184
185
protected renderExpressionElement(element: IExpression, node: ITreeNode<T, FuzzyScore>, data: IExpressionTemplateData): void {
186
data.currentElement = element;
187
this.renderExpression(node.element, data, createMatches(node.filterData));
188
if (data.actionBar) {
189
this.renderActionBar!(data.actionBar, element, data);
190
}
191
const selectedExpression = this.debugService.getViewModel().getSelectedExpression();
192
if (element === selectedExpression?.expression || (element instanceof Variable && element.errorMessage)) {
193
const options = this.getInputBoxOptions(element, !!selectedExpression?.settingWatch);
194
if (options) {
195
data.elementDisposable.add(this.renderInputBox(data.name, data.value, data.inputBoxContainer, options));
196
}
197
}
198
}
199
200
renderInputBox(nameElement: HTMLElement, valueElement: HTMLElement, inputBoxContainer: HTMLElement, options: IInputBoxOptions): IDisposable {
201
nameElement.style.display = 'none';
202
valueElement.style.display = 'none';
203
inputBoxContainer.style.display = 'initial';
204
dom.clearNode(inputBoxContainer);
205
206
const inputBox = new InputBox(inputBoxContainer, this.contextViewService, { ...options, inputBoxStyles: defaultInputBoxStyles });
207
208
inputBox.value = options.initialValue;
209
inputBox.focus();
210
inputBox.select();
211
212
const done = createSingleCallFunction((success: boolean, finishEditing: boolean) => {
213
nameElement.style.display = '';
214
valueElement.style.display = '';
215
inputBoxContainer.style.display = 'none';
216
const value = inputBox.value;
217
dispose(toDispose);
218
219
if (finishEditing) {
220
this.debugService.getViewModel().setSelectedExpression(undefined, false);
221
options.onFinish(value, success);
222
}
223
});
224
225
const toDispose = [
226
inputBox,
227
dom.addStandardDisposableListener(inputBox.inputElement, dom.EventType.KEY_DOWN, (e: IKeyboardEvent) => {
228
const isEscape = e.equals(KeyCode.Escape);
229
const isEnter = e.equals(KeyCode.Enter);
230
if (isEscape || isEnter) {
231
e.preventDefault();
232
e.stopPropagation();
233
done(isEnter, true);
234
}
235
}),
236
dom.addDisposableListener(inputBox.inputElement, dom.EventType.BLUR, () => {
237
done(true, true);
238
}),
239
dom.addDisposableListener(inputBox.inputElement, dom.EventType.CLICK, e => {
240
// Do not expand / collapse selected elements
241
e.preventDefault();
242
e.stopPropagation();
243
})
244
];
245
246
return toDisposable(() => {
247
done(false, false);
248
});
249
}
250
251
protected abstract renderExpression(expression: T, data: IExpressionTemplateData, highlights: IHighlight[]): void;
252
protected abstract getInputBoxOptions(expression: IExpression, settingValue: boolean): IInputBoxOptions | undefined;
253
254
protected renderActionBar?(actionBar: ActionBar, expression: IExpression, data: IExpressionTemplateData): void;
255
256
disposeElement(node: ITreeNode<T, FuzzyScore>, index: number, templateData: IExpressionTemplateData): void {
257
templateData.elementDisposable.clear();
258
}
259
260
disposeTemplate(templateData: IExpressionTemplateData): void {
261
templateData.elementDisposable.dispose();
262
templateData.templateDisposable.dispose();
263
}
264
}
265
266