Path: blob/main/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts
5241 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 { IDragAndDropData } from '../../../../base/browser/dnd.js';6import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';7import { IHighlight } from '../../../../base/browser/ui/highlightedlabel/highlightedLabel.js';8import { IListVirtualDelegate, ListDragOverEffectPosition, ListDragOverEffectType } from '../../../../base/browser/ui/list/list.js';9import { ElementsDragAndDropData, ListViewTargetSector } from '../../../../base/browser/ui/list/listView.js';10import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js';11import { ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction, ITreeMouseEvent, ITreeNode } from '../../../../base/browser/ui/tree/tree.js';12import { RunOnceScheduler } from '../../../../base/common/async.js';13import { Codicon } from '../../../../base/common/codicons.js';14import { FuzzyScore } from '../../../../base/common/filters.js';15import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';16import { localize } from '../../../../nls.js';17import { getContextMenuActions, } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';18import { Action2, IMenuService, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js';19import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';20import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';21import { ContextKeyExpr, IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';22import { IContextMenuService, IContextViewService } from '../../../../platform/contextview/browser/contextView.js';23import { IHoverService } from '../../../../platform/hover/browser/hover.js';24import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';25import { ILogService } from '../../../../platform/log/common/log.js';26import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';27import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';28import { WorkbenchAsyncDataTree } from '../../../../platform/list/browser/listService.js';29import { IOpenerService } from '../../../../platform/opener/common/opener.js';30import { IThemeService } from '../../../../platform/theme/common/themeService.js';31import { ViewAction, ViewPane } from '../../../browser/parts/views/viewPane.js';32import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js';33import { FocusedViewContext } from '../../../common/contextkeys.js';34import { IViewDescriptorService } from '../../../common/views.js';35import { CONTEXT_CAN_VIEW_MEMORY, CONTEXT_EXPRESSION_SELECTED, CONTEXT_VARIABLE_IS_READONLY, CONTEXT_VARIABLE_TYPE, CONTEXT_WATCH_EXPRESSIONS_EXIST, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_WATCH_ITEM_TYPE, IDebugConfiguration, IDebugService, IDebugViewWithVariables, IExpression, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, WATCH_VIEW_ID, CONTEXT_DEBUG_TYPE } from '../common/debug.js';36import { Expression, Variable, VisualizedExpression } from '../common/debugModel.js';37import { AbstractExpressionDataSource, AbstractExpressionsRenderer, expressionAndScopeLabelProvider, IExpressionTemplateData, IInputBoxOptions, renderViewTree } from './baseDebugView.js';38import { COPY_WATCH_EXPRESSION_COMMAND_ID, setDataBreakpointInfoResponse } from './debugCommands.js';39import { DebugExpressionRenderer } from './debugExpressionRenderer.js';40import { watchExpressionsAdd, watchExpressionsRemoveAll } from './debugIcons.js';41import { VariablesRenderer, VisualizedVariableRenderer } from './variablesView.js';4243const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024;44let ignoreViewUpdates = false;45let useCachedEvaluation = false;4647export class WatchExpressionsView extends ViewPane implements IDebugViewWithVariables {4849private watchExpressionsUpdatedScheduler: RunOnceScheduler;50private needsRefresh = false;51private tree!: WorkbenchAsyncDataTree<IDebugService | IExpression, IExpression, FuzzyScore>;52private watchExpressionsExist: IContextKey<boolean>;53private expressionRenderer: DebugExpressionRenderer;5455public get treeSelection() {56return this.tree.getSelection();57}5859constructor(60options: IViewletViewOptions,61@IContextMenuService contextMenuService: IContextMenuService,62@IDebugService private readonly debugService: IDebugService,63@IKeybindingService keybindingService: IKeybindingService,64@IInstantiationService instantiationService: IInstantiationService,65@IViewDescriptorService viewDescriptorService: IViewDescriptorService,66@IConfigurationService configurationService: IConfigurationService,67@IContextKeyService contextKeyService: IContextKeyService,68@IOpenerService openerService: IOpenerService,69@IThemeService themeService: IThemeService,70@IHoverService hoverService: IHoverService,71@IMenuService private readonly menuService: IMenuService,72@ILogService private readonly logService: ILogService73) {74super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);7576this.watchExpressionsUpdatedScheduler = new RunOnceScheduler(() => {77this.needsRefresh = false;78this.tree.updateChildren();79}, 50);80this.watchExpressionsExist = CONTEXT_WATCH_EXPRESSIONS_EXIST.bindTo(contextKeyService);81this.watchExpressionsExist.set(this.debugService.getModel().getWatchExpressions().length > 0);82this.expressionRenderer = instantiationService.createInstance(DebugExpressionRenderer);83}8485protected override renderBody(container: HTMLElement): void {86super.renderBody(container);8788this.element.classList.add('debug-pane');89container.classList.add('debug-watch');90const treeContainer = renderViewTree(container);9192const expressionsRenderer = this.instantiationService.createInstance(WatchExpressionsRenderer, this.expressionRenderer);93this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree<IDebugService | IExpression, IExpression, FuzzyScore>, 'WatchExpressions', treeContainer, new WatchExpressionsDelegate(),94[95expressionsRenderer,96this.instantiationService.createInstance(VariablesRenderer, this.expressionRenderer),97this.instantiationService.createInstance(VisualizedVariableRenderer, this.expressionRenderer),98],99this.instantiationService.createInstance(WatchExpressionsDataSource), {100accessibilityProvider: new WatchExpressionsAccessibilityProvider(),101identityProvider: { getId: (element: IExpression) => element.getId() },102keyboardNavigationLabelProvider: {103getKeyboardNavigationLabel: (e: IExpression) => {104if (e === this.debugService.getViewModel().getSelectedExpression()?.expression) {105// Don't filter input box106return undefined;107}108109return expressionAndScopeLabelProvider.getKeyboardNavigationLabel(e);110}111},112dnd: new WatchExpressionsDragAndDrop(this.debugService),113overrideStyles: this.getLocationBasedColors().listOverrideStyles114});115this._register(this.tree);116this.tree.setInput(this.debugService);117CONTEXT_WATCH_EXPRESSIONS_FOCUSED.bindTo(this.tree.contextKeyService);118119this._register(VisualizedVariableRenderer.rendererOnVisualizationRange(this.debugService.getViewModel(), this.tree));120this._register(this.tree.onContextMenu(e => this.onContextMenu(e)));121this._register(this.tree.onMouseDblClick(e => this.onMouseDblClick(e)));122this._register(this.debugService.getModel().onDidChangeWatchExpressions(async we => {123this.watchExpressionsExist.set(this.debugService.getModel().getWatchExpressions().length > 0);124if (!this.isBodyVisible()) {125this.needsRefresh = true;126} else {127if (we && !we.name) {128// We are adding a new input box, no need to re-evaluate watch expressions129useCachedEvaluation = true;130}131await this.tree.updateChildren();132useCachedEvaluation = false;133if (we instanceof Expression) {134this.tree.reveal(we);135}136}137}));138this._register(this.debugService.getViewModel().onDidFocusStackFrame(() => {139if (!this.isBodyVisible()) {140this.needsRefresh = true;141return;142}143144if (!this.watchExpressionsUpdatedScheduler.isScheduled()) {145this.watchExpressionsUpdatedScheduler.schedule();146}147}));148this._register(this.debugService.getViewModel().onWillUpdateViews(() => {149if (!ignoreViewUpdates) {150this.tree.updateChildren();151}152}));153154this._register(this.onDidChangeBodyVisibility(visible => {155if (visible && this.needsRefresh) {156this.watchExpressionsUpdatedScheduler.schedule();157}158}));159let horizontalScrolling: boolean | undefined;160this._register(this.debugService.getViewModel().onDidSelectExpression(e => {161const expression = e?.expression;162if (expression && this.tree.hasNode(expression)) {163horizontalScrolling = this.tree.options.horizontalScrolling;164if (horizontalScrolling) {165this.tree.updateOptions({ horizontalScrolling: false });166}167168if (expression.name) {169// Only rerender if the input is already done since otherwise the tree is not yet aware of the new element170this.tree.rerender(expression);171}172} else if (!expression && horizontalScrolling !== undefined) {173this.tree.updateOptions({ horizontalScrolling: horizontalScrolling });174horizontalScrolling = undefined;175}176}));177178this._register(this.debugService.getViewModel().onDidEvaluateLazyExpression(async e => {179if (e instanceof Variable && this.tree.hasNode(e)) {180await this.tree.updateChildren(e, false, true);181await this.tree.expand(e);182}183}));184}185186protected override layoutBody(height: number, width: number): void {187super.layoutBody(height, width);188this.tree.layout(height, width);189}190191override focus(): void {192super.focus();193this.tree.domFocus();194}195196collapseAll(): void {197this.tree.collapseAll();198}199200private onMouseDblClick(e: ITreeMouseEvent<IExpression>): void {201if ((e.browserEvent.target as HTMLElement).className.indexOf('twistie') >= 0) {202// Ignore double click events on twistie203return;204}205206const element = e.element;207// double click on primitive value: open input box to be able to select and copy value.208const selectedExpression = this.debugService.getViewModel().getSelectedExpression();209if ((element instanceof Expression && element !== selectedExpression?.expression) || (element instanceof VisualizedExpression && element.treeItem.canEdit)) {210this.debugService.getViewModel().setSelectedExpression(element, false);211} else if (!element) {212// Double click in watch panel triggers to add a new watch expression213this.debugService.addWatchExpression();214}215}216217private async onContextMenu(e: ITreeContextMenuEvent<IExpression>): Promise<void> {218const element = e.element;219if (!element) {220return;221}222223const selection = this.tree.getSelection();224225const contextKeyService = element && await getContextForWatchExpressionMenuWithDataAccess(this.contextKeyService, element, this.debugService, this.logService);226const menu = this.menuService.getMenuActions(MenuId.DebugWatchContext, contextKeyService, { arg: element, shouldForwardArgs: false });227const { secondary } = getContextMenuActions(menu, 'inline');228229this.contextMenuService.showContextMenu({230getAnchor: () => e.anchor,231getActions: () => secondary,232getActionsContext: () => element && selection.includes(element) ? selection : element ? [element] : []233});234}235}236237class WatchExpressionsDelegate implements IListVirtualDelegate<IExpression> {238239getHeight(_element: IExpression): number {240return 22;241}242243getTemplateId(element: IExpression): string {244if (element instanceof Expression) {245return WatchExpressionsRenderer.ID;246}247248if (element instanceof VisualizedExpression) {249return VisualizedVariableRenderer.ID;250}251252// Variable253return VariablesRenderer.ID;254}255}256257function isDebugService(element: any): element is IDebugService {258return typeof element.getConfigurationManager === 'function';259}260261class WatchExpressionsDataSource extends AbstractExpressionDataSource<IDebugService, IExpression> {262263public override hasChildren(element: IExpression | IDebugService): boolean {264return isDebugService(element) || element.hasChildren;265}266267protected override doGetChildren(element: IDebugService | IExpression): Promise<Array<IExpression>> {268if (isDebugService(element)) {269const debugService = element;270const watchExpressions = debugService.getModel().getWatchExpressions();271const viewModel = debugService.getViewModel();272return Promise.all(watchExpressions.map(we => !!we.name && !useCachedEvaluation273? we.evaluate(viewModel.focusedSession!, viewModel.focusedStackFrame!, 'watch').then(() => we)274: Promise.resolve(we)));275}276277return element.getChildren();278}279}280281282export class WatchExpressionsRenderer extends AbstractExpressionsRenderer {283284static readonly ID = 'watchexpression';285286constructor(287private readonly expressionRenderer: DebugExpressionRenderer,288@IMenuService private readonly menuService: IMenuService,289@IContextKeyService private readonly contextKeyService: IContextKeyService,290@IDebugService debugService: IDebugService,291@IContextViewService contextViewService: IContextViewService,292@IHoverService hoverService: IHoverService,293@IConfigurationService private configurationService: IConfigurationService,294) {295super(debugService, contextViewService, hoverService);296}297298get templateId() {299return WatchExpressionsRenderer.ID;300}301302public override renderElement(node: ITreeNode<IExpression, FuzzyScore>, index: number, data: IExpressionTemplateData): void {303data.elementDisposable.clear();304data.elementDisposable.add(this.configurationService.onDidChangeConfiguration(e => {305if (e.affectsConfiguration('debug.showVariableTypes')) {306super.renderExpressionElement(node.element, node, data);307}308}));309super.renderExpressionElement(node.element, node, data);310}311312protected renderExpression(expression: IExpression, data: IExpressionTemplateData, highlights: IHighlight[]): void {313let text: string;314data.type.textContent = '';315const showType = this.configurationService.getValue<IDebugConfiguration>('debug').showVariableTypes;316if (showType && expression.type) {317text = typeof expression.value === 'string' ? `${expression.name}: ` : expression.name;318//render type319data.type.textContent = expression.type + ' =';320} else {321text = typeof expression.value === 'string' ? `${expression.name} =` : expression.name;322}323324let title: string;325if (expression.type) {326if (showType) {327title = `${expression.name}`;328} else {329title = expression.type === expression.value ?330expression.type :331`${expression.type}`;332}333} else {334title = expression.value;335}336337data.label.set(text, highlights, title);338data.elementDisposable.add(this.expressionRenderer.renderValue(data.value, expression, {339showChanged: true,340maxValueLength: MAX_VALUE_RENDER_LENGTH_IN_VIEWLET,341colorize: true,342session: expression.getSession(),343}));344}345346protected getInputBoxOptions(expression: IExpression, settingValue: boolean): IInputBoxOptions {347if (settingValue) {348return {349initialValue: expression.value,350ariaLabel: localize('typeNewValue', "Type new value"),351onFinish: async (value: string, success: boolean) => {352if (success && value) {353const focusedFrame = this.debugService.getViewModel().focusedStackFrame;354if (focusedFrame && (expression instanceof Variable || expression instanceof Expression)) {355await expression.setExpression(value, focusedFrame);356this.debugService.getViewModel().updateViews();357}358}359}360};361}362363return {364initialValue: expression.name ? expression.name : '',365ariaLabel: localize('watchExpressionInputAriaLabel', "Type watch expression"),366placeholder: localize('watchExpressionPlaceholder', "Expression to watch"),367onFinish: (value: string, success: boolean) => {368if (success && value) {369this.debugService.renameWatchExpression(expression.getId(), value);370ignoreViewUpdates = true;371this.debugService.getViewModel().updateViews();372ignoreViewUpdates = false;373} else if (!expression.name) {374this.debugService.removeWatchExpressions(expression.getId());375}376}377};378}379380protected override renderActionBar(actionBar: ActionBar, expression: IExpression) {381const contextKeyService = getContextForWatchExpressionMenu(this.contextKeyService, expression);382const context = expression;383const menu = this.menuService.getMenuActions(MenuId.DebugWatchContext, contextKeyService, { arg: context, shouldForwardArgs: false });384385const { primary } = getContextMenuActions(menu, 'inline');386387actionBar.clear();388actionBar.context = context;389actionBar.push(primary, { icon: true, label: false });390}391}392393/**394* Gets a context key overlay that has context for the given expression.395*/396function getContextForWatchExpressionMenu(parentContext: IContextKeyService, expression: IExpression, additionalContext: [string, unknown][] = []) {397const session = expression.getSession();398return parentContext.createOverlay([399[CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT.key, 'evaluateName' in expression],400[CONTEXT_WATCH_ITEM_TYPE.key, expression instanceof Expression ? 'expression' : expression instanceof Variable ? 'variable' : undefined],401[CONTEXT_CAN_VIEW_MEMORY.key, !!session?.capabilities.supportsReadMemoryRequest && expression.memoryReference !== undefined],402[CONTEXT_VARIABLE_IS_READONLY.key, !!expression.presentationHint?.attributes?.includes('readOnly') || expression.presentationHint?.lazy],403[CONTEXT_VARIABLE_TYPE.key, expression.type],404[CONTEXT_DEBUG_TYPE.key, session?.configuration.type],405...additionalContext406]);407}408409/**410* Gets a context key overlay that has context for the given expression, including data access info.411*/412async function getContextForWatchExpressionMenuWithDataAccess(parentContext: IContextKeyService, expression: IExpression, debugService: IDebugService, logService: ILogService) {413const session = expression.getSession();414if (!session || !session.capabilities.supportsDataBreakpoints) {415return getContextForWatchExpressionMenu(parentContext, expression);416}417418const contextKeys: [string, unknown][] = [];419const stackFrame = debugService.getViewModel().focusedStackFrame;420let dataBreakpointInfoResponse;421422try {423// Per DAP spec:424// - If evaluateName is available: use it as an expression (top-level evaluation)425// - Otherwise, check if it's a Variable: use name + parent reference (container-relative)426// - Otherwise: use name as an expression427if ('evaluateName' in expression && expression.evaluateName) {428// Use evaluateName if available (more precise for evaluation context)429dataBreakpointInfoResponse = await session.dataBreakpointInfo(430expression.evaluateName as string,431undefined,432stackFrame?.frameId433);434} else if (expression instanceof Variable) {435// Variable without evaluateName: use name relative to parent container436dataBreakpointInfoResponse = await session.dataBreakpointInfo(437expression.name,438expression.parent.reference,439stackFrame?.frameId440);441} else {442// Expression without evaluateName: use name as the expression to evaluate443dataBreakpointInfoResponse = await session.dataBreakpointInfo(444expression.name,445undefined,446stackFrame?.frameId447);448}449} catch (error) {450// silently continue without data breakpoint support for this item451logService.error('Failed to get data breakpoint info for watch expression:', error);452}453454const dataBreakpointId = dataBreakpointInfoResponse?.dataId;455const dataBreakpointAccessTypes = dataBreakpointInfoResponse?.accessTypes;456setDataBreakpointInfoResponse(dataBreakpointInfoResponse);457458if (!dataBreakpointAccessTypes) {459contextKeys.push([CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED.key, !!dataBreakpointId]);460} else {461for (const accessType of dataBreakpointAccessTypes) {462switch (accessType) {463case 'read':464contextKeys.push([CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED.key, !!dataBreakpointId]);465break;466case 'write':467contextKeys.push([CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED.key, !!dataBreakpointId]);468break;469case 'readWrite':470contextKeys.push([CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED.key, !!dataBreakpointId]);471break;472}473}474}475476return getContextForWatchExpressionMenu(parentContext, expression, contextKeys);477}478479480class WatchExpressionsAccessibilityProvider implements IListAccessibilityProvider<IExpression> {481482getWidgetAriaLabel(): string {483return localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'watchAriaTreeLabel' }, "Debug Watch Expressions");484}485486getAriaLabel(element: IExpression): string {487if (element instanceof Expression) {488return localize('watchExpressionAriaLabel', "{0}, value {1}", element.name, element.value);489}490491// Variable492return localize('watchVariableAriaLabel', "{0}, value {1}", element.name, element.value);493}494}495496class WatchExpressionsDragAndDrop implements ITreeDragAndDrop<IExpression> {497498constructor(private debugService: IDebugService) { }499onDragStart?(data: IDragAndDropData, originalEvent: DragEvent): void {500if (data instanceof ElementsDragAndDropData) {501originalEvent.dataTransfer!.setData('text/plain', data.elements[0].name);502}503}504505onDragOver(data: IDragAndDropData, targetElement: IExpression | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction {506if (!(data instanceof ElementsDragAndDropData)) {507return false;508}509510const expressions = (data as ElementsDragAndDropData<IExpression>).elements;511if (!(expressions.length > 0 && expressions[0] instanceof Expression)) {512return false;513}514515let dropEffectPosition: ListDragOverEffectPosition | undefined = undefined;516if (targetIndex === undefined) {517// Hovering over the list518dropEffectPosition = ListDragOverEffectPosition.After;519targetIndex = -1;520} else {521// Hovering over an element522switch (targetSector) {523case ListViewTargetSector.TOP:524case ListViewTargetSector.CENTER_TOP:525dropEffectPosition = ListDragOverEffectPosition.Before; break;526case ListViewTargetSector.CENTER_BOTTOM:527case ListViewTargetSector.BOTTOM:528dropEffectPosition = ListDragOverEffectPosition.After; break;529}530}531532return { accept: true, effect: { type: ListDragOverEffectType.Move, position: dropEffectPosition }, feedback: [targetIndex] } satisfies ITreeDragOverReaction;533}534535getDragURI(element: IExpression): string | null {536if (!(element instanceof Expression) || element === this.debugService.getViewModel().getSelectedExpression()?.expression) {537return null;538}539540return element.getId();541}542543getDragLabel(elements: IExpression[]): string | undefined {544if (elements.length === 1) {545return elements[0].name;546}547548return undefined;549}550551drop(data: IDragAndDropData, targetElement: IExpression, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): void {552if (!(data instanceof ElementsDragAndDropData)) {553return;554}555556const draggedElement = (data as ElementsDragAndDropData<IExpression>).elements[0];557if (!(draggedElement instanceof Expression)) {558throw new Error('Invalid dragged element');559}560561const watches = this.debugService.getModel().getWatchExpressions();562const sourcePosition = watches.indexOf(draggedElement);563564let targetPosition;565if (targetElement instanceof Expression) {566targetPosition = watches.indexOf(targetElement);567568switch (targetSector) {569case ListViewTargetSector.BOTTOM:570case ListViewTargetSector.CENTER_BOTTOM:571targetPosition++; break;572}573574if (sourcePosition < targetPosition) {575targetPosition--;576}577} else {578targetPosition = watches.length - 1;579}580581this.debugService.moveWatchExpression(draggedElement.getId(), targetPosition);582}583584dispose(): void { }585}586587registerAction2(class Collapse extends ViewAction<WatchExpressionsView> {588constructor() {589super({590id: 'watch.collapse',591viewId: WATCH_VIEW_ID,592title: localize('collapse', "Collapse All"),593f1: false,594icon: Codicon.collapseAll,595precondition: CONTEXT_WATCH_EXPRESSIONS_EXIST,596menu: {597id: MenuId.ViewTitle,598order: 30,599group: 'navigation',600when: ContextKeyExpr.equals('view', WATCH_VIEW_ID)601}602});603}604605runInView(_accessor: ServicesAccessor, view: WatchExpressionsView) {606view.collapseAll();607}608});609610export const ADD_WATCH_ID = 'workbench.debug.viewlet.action.addWatchExpression'; // Use old and long id for backwards compatibility611export const ADD_WATCH_LABEL = localize('addWatchExpression', "Add Expression");612613registerAction2(class AddWatchExpressionAction extends Action2 {614constructor() {615super({616id: ADD_WATCH_ID,617title: ADD_WATCH_LABEL,618f1: false,619icon: watchExpressionsAdd,620menu: {621id: MenuId.ViewTitle,622group: 'navigation',623when: ContextKeyExpr.equals('view', WATCH_VIEW_ID)624}625});626}627628run(accessor: ServicesAccessor): void {629const debugService = accessor.get(IDebugService);630debugService.addWatchExpression();631}632});633634export const REMOVE_WATCH_EXPRESSIONS_COMMAND_ID = 'workbench.debug.viewlet.action.removeAllWatchExpressions';635export const REMOVE_WATCH_EXPRESSIONS_LABEL = localize('removeAllWatchExpressions', "Remove All Expressions");636registerAction2(class RemoveAllWatchExpressionsAction extends Action2 {637constructor() {638super({639id: REMOVE_WATCH_EXPRESSIONS_COMMAND_ID, // Use old and long id for backwards compatibility640title: REMOVE_WATCH_EXPRESSIONS_LABEL,641f1: false,642icon: watchExpressionsRemoveAll,643precondition: CONTEXT_WATCH_EXPRESSIONS_EXIST,644menu: {645id: MenuId.ViewTitle,646order: 20,647group: 'navigation',648when: ContextKeyExpr.equals('view', WATCH_VIEW_ID)649}650});651}652653run(accessor: ServicesAccessor): void {654const debugService = accessor.get(IDebugService);655debugService.removeWatchExpressions();656}657});658659registerAction2(class CopyExpression extends ViewAction<WatchExpressionsView> {660constructor() {661super({662id: COPY_WATCH_EXPRESSION_COMMAND_ID,663title: localize('copyWatchExpression', "Copy Expression"),664f1: false,665viewId: WATCH_VIEW_ID,666precondition: CONTEXT_WATCH_EXPRESSIONS_EXIST,667keybinding: {668primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyC,669weight: KeybindingWeight.WorkbenchContrib,670when: ContextKeyExpr.and(671FocusedViewContext.isEqualTo(WATCH_VIEW_ID),672CONTEXT_EXPRESSION_SELECTED.negate(),673),674},675menu: {676id: MenuId.DebugWatchContext,677order: 20,678group: '3_modification',679when: CONTEXT_WATCH_ITEM_TYPE.isEqualTo('expression')680}681});682}683684runInView(accessor: ServicesAccessor, view: WatchExpressionsView, value?: IExpression): void {685const clipboardService = accessor.get(IClipboardService);686if (!value) {687value = view.treeSelection.at(-1);688}689if (value) {690clipboardService.writeText(value.name);691}692}693});694695696