Path: blob/main/src/vs/workbench/contrib/debug/browser/baseDebugView.ts
3296 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 * as dom from '../../../../base/browser/dom.js';6import { IKeyboardEvent } from '../../../../base/browser/keyboardEvent.js';7import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';8import { HighlightedLabel, IHighlight } from '../../../../base/browser/ui/highlightedlabel/highlightedLabel.js';9import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';10import { IInputValidationOptions, InputBox } from '../../../../base/browser/ui/inputbox/inputBox.js';11import { IKeyboardNavigationLabelProvider } from '../../../../base/browser/ui/list/list.js';12import { IAsyncDataSource, ITreeNode, ITreeRenderer } from '../../../../base/browser/ui/tree/tree.js';13import { Codicon } from '../../../../base/common/codicons.js';14import { FuzzyScore, createMatches } from '../../../../base/common/filters.js';15import { createSingleCallFunction } from '../../../../base/common/functional.js';16import { KeyCode } from '../../../../base/common/keyCodes.js';17import { DisposableStore, IDisposable, dispose, toDisposable } from '../../../../base/common/lifecycle.js';18import { removeAnsiEscapeCodes } from '../../../../base/common/strings.js';19import { ThemeIcon } from '../../../../base/common/themables.js';20import { localize } from '../../../../nls.js';21import { ICommandService } from '../../../../platform/commands/common/commands.js';22import { IContextViewService } from '../../../../platform/contextview/browser/contextView.js';23import { IHoverService } from '../../../../platform/hover/browser/hover.js';24import { defaultInputBoxStyles } from '../../../../platform/theme/browser/defaultStyles.js';25import { IDebugService, IExpression, IScope } from '../common/debug.js';26import { Variable } from '../common/debugModel.js';27import { IDebugVisualizerService } from '../common/debugVisualizers.js';28import { LinkDetector } from './linkDetector.js';2930const $ = dom.$;3132export interface IRenderValueOptions {33showChanged?: boolean;34maxValueLength?: number;35/** If set, a hover will be shown on the element. Requires a disposable store for usage. */36hover?: false | {37commands: { id: string; args: unknown[] }[];38commandService: ICommandService;39};40colorize?: boolean;41linkDetector?: LinkDetector;42}4344export interface IVariableTemplateData {45expression: HTMLElement;46name: HTMLElement;47type: HTMLElement;48value: HTMLElement;49label: HighlightedLabel;50lazyButton: HTMLElement;51}5253export function renderViewTree(container: HTMLElement): HTMLElement {54const treeContainer = $('.');55treeContainer.classList.add('debug-view-content', 'file-icon-themable-tree');56container.appendChild(treeContainer);57return treeContainer;58}5960export interface IInputBoxOptions {61initialValue: string;62ariaLabel: string;63placeholder?: string;64validationOptions?: IInputValidationOptions;65onFinish: (value: string, success: boolean) => void;66}6768export interface IExpressionTemplateData {69expression: HTMLElement;70name: HTMLSpanElement;71type: HTMLSpanElement;72value: HTMLSpanElement;73inputBoxContainer: HTMLElement;74actionBar?: ActionBar;75elementDisposable: DisposableStore;76templateDisposable: IDisposable;77label: HighlightedLabel;78lazyButton: HTMLElement;79currentElement: IExpression | undefined;80}8182/** Splits highlights based on matching of the {@link expressionAndScopeLabelProvider} */83export const splitExpressionOrScopeHighlights = (e: IExpression | IScope, highlights: IHighlight[]) => {84const nameEndsAt = e.name.length;85const labelBeginsAt = e.name.length + 2;86const name: IHighlight[] = [];87const value: IHighlight[] = [];88for (const hl of highlights) {89if (hl.start < nameEndsAt) {90name.push({ start: hl.start, end: Math.min(hl.end, nameEndsAt) });91}92if (hl.end > labelBeginsAt) {93value.push({ start: Math.max(hl.start - labelBeginsAt, 0), end: hl.end - labelBeginsAt });94}95}9697return { name, value };98};99100/** Keyboard label provider for expression and scope tree elements. */101export const expressionAndScopeLabelProvider: IKeyboardNavigationLabelProvider<IExpression | IScope> = {102getKeyboardNavigationLabel(e) {103const stripAnsi = e.getSession()?.rememberedCapabilities?.supportsANSIStyling;104return `${e.name}: ${stripAnsi ? removeAnsiEscapeCodes(e.value) : e.value}`;105},106};107108export abstract class AbstractExpressionDataSource<Input, Element extends IExpression> implements IAsyncDataSource<Input, Element> {109constructor(110@IDebugService protected debugService: IDebugService,111@IDebugVisualizerService protected debugVisualizer: IDebugVisualizerService,112) { }113114public abstract hasChildren(element: Input | Element): boolean;115116public async getChildren(element: Input | Element): Promise<Element[]> {117const vm = this.debugService.getViewModel();118const children = await this.doGetChildren(element);119return Promise.all(children.map(async r => {120const vizOrTree = vm.getVisualizedExpression(r as IExpression);121if (typeof vizOrTree === 'string') {122const viz = await this.debugVisualizer.getVisualizedNodeFor(vizOrTree, r);123if (viz) {124vm.setVisualizedExpression(r, viz);125return viz as IExpression as Element;126}127} else if (vizOrTree) {128return vizOrTree as Element;129}130131132return r;133}));134}135136protected abstract doGetChildren(element: Input | Element): Promise<Element[]>;137}138139export abstract class AbstractExpressionsRenderer<T = IExpression> implements ITreeRenderer<T, FuzzyScore, IExpressionTemplateData> {140141constructor(142@IDebugService protected debugService: IDebugService,143@IContextViewService private readonly contextViewService: IContextViewService,144@IHoverService protected readonly hoverService: IHoverService,145) { }146147abstract get templateId(): string;148149renderTemplate(container: HTMLElement): IExpressionTemplateData {150const templateDisposable = new DisposableStore();151const expression = dom.append(container, $('.expression'));152const name = dom.append(expression, $('span.name'));153const lazyButton = dom.append(expression, $('span.lazy-button'));154lazyButton.classList.add(...ThemeIcon.asClassNameArray(Codicon.eye));155156templateDisposable.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), lazyButton, localize('debug.lazyButton.tooltip', "Click to expand")));157const type = dom.append(expression, $('span.type'));158159const value = dom.append(expression, $('span.value'));160161const label = templateDisposable.add(new HighlightedLabel(name));162163const inputBoxContainer = dom.append(expression, $('.inputBoxContainer'));164165let actionBar: ActionBar | undefined;166if (this.renderActionBar) {167dom.append(expression, $('.span.actionbar-spacer'));168actionBar = templateDisposable.add(new ActionBar(expression));169}170171const template: IExpressionTemplateData = { expression, name, type, value, label, inputBoxContainer, actionBar, elementDisposable: new DisposableStore(), templateDisposable, lazyButton, currentElement: undefined };172173templateDisposable.add(dom.addDisposableListener(lazyButton, dom.EventType.CLICK, () => {174if (template.currentElement) {175this.debugService.getViewModel().evaluateLazyExpression(template.currentElement);176}177}));178179return template;180}181182public abstract renderElement(node: ITreeNode<T, FuzzyScore>, index: number, data: IExpressionTemplateData): void;183184protected renderExpressionElement(element: IExpression, node: ITreeNode<T, FuzzyScore>, data: IExpressionTemplateData): void {185data.currentElement = element;186this.renderExpression(node.element, data, createMatches(node.filterData));187if (data.actionBar) {188this.renderActionBar!(data.actionBar, element, data);189}190const selectedExpression = this.debugService.getViewModel().getSelectedExpression();191if (element === selectedExpression?.expression || (element instanceof Variable && element.errorMessage)) {192const options = this.getInputBoxOptions(element, !!selectedExpression?.settingWatch);193if (options) {194data.elementDisposable.add(this.renderInputBox(data.name, data.value, data.inputBoxContainer, options));195}196}197}198199renderInputBox(nameElement: HTMLElement, valueElement: HTMLElement, inputBoxContainer: HTMLElement, options: IInputBoxOptions): IDisposable {200nameElement.style.display = 'none';201valueElement.style.display = 'none';202inputBoxContainer.style.display = 'initial';203dom.clearNode(inputBoxContainer);204205const inputBox = new InputBox(inputBoxContainer, this.contextViewService, { ...options, inputBoxStyles: defaultInputBoxStyles });206207inputBox.value = options.initialValue;208inputBox.focus();209inputBox.select();210211const done = createSingleCallFunction((success: boolean, finishEditing: boolean) => {212nameElement.style.display = '';213valueElement.style.display = '';214inputBoxContainer.style.display = 'none';215const value = inputBox.value;216dispose(toDispose);217218if (finishEditing) {219this.debugService.getViewModel().setSelectedExpression(undefined, false);220options.onFinish(value, success);221}222});223224const toDispose = [225inputBox,226dom.addStandardDisposableListener(inputBox.inputElement, dom.EventType.KEY_DOWN, (e: IKeyboardEvent) => {227const isEscape = e.equals(KeyCode.Escape);228const isEnter = e.equals(KeyCode.Enter);229if (isEscape || isEnter) {230e.preventDefault();231e.stopPropagation();232done(isEnter, true);233}234}),235dom.addDisposableListener(inputBox.inputElement, dom.EventType.BLUR, () => {236done(true, true);237}),238dom.addDisposableListener(inputBox.inputElement, dom.EventType.CLICK, e => {239// Do not expand / collapse selected elements240e.preventDefault();241e.stopPropagation();242})243];244245return toDisposable(() => {246done(false, false);247});248}249250protected abstract renderExpression(expression: T, data: IExpressionTemplateData, highlights: IHighlight[]): void;251protected abstract getInputBoxOptions(expression: IExpression, settingValue: boolean): IInputBoxOptions | undefined;252253protected renderActionBar?(actionBar: ActionBar, expression: IExpression, data: IExpressionTemplateData): void;254255disposeElement(node: ITreeNode<T, FuzzyScore>, index: number, templateData: IExpressionTemplateData): void {256templateData.elementDisposable.clear();257}258259disposeTemplate(templateData: IExpressionTemplateData): void {260templateData.elementDisposable.dispose();261templateData.templateDisposable.dispose();262}263}264265266