Change:
This is the debug hover: ! I think it would be great if each value has a context menu (or inline buttons) to trigger (contributable) actions. Built-in actions could be: copy value copy expression watch Actions contributed by extensions could be: visualize This is already supported in the sidebar: !
good overall idea and I like it. I think we could implement it fairly easily by adding a new menu type to the hover tree. We can even open it for extensions as you suggested. However I would be interested in what other apps do you know that have a context menu on the hover? Might this be a sort of an anti pattern? I know that the debug hover is a bit heavy and a context menu might fit well there, but I would be interested in how other products do it. Maybe try out Visual Studio, Chrome Dev Tools and others... fyi
I do believe that's using the tree widget, which supports inline actions: <img width="334" alt="CleanShot 2021-06-08 at 10 46 42 src="https://user-"I'd assume either a (though there is quite a bit already in these views) or: <img width="652" alt="CleanShot 2021-06-08 at 10 48 01 src="https://user-"<img width="659" alt="CleanShot 2021-06-08 at 10 48 46 src="https://user-"
VS has quite a few nested context menus :D 
I think that would be possible since it's a toolbar, we do show more inline actions in peek/diff views: <img width="157" alt="CleanShot 2021-06-08 at 10 57 38 src="https://user-"
Cool ideas. Maybe just a right click context menu might work best. But an additional gear on the end to make it discoverable will help. Ellipsis look good in the example you provided but in practice we have a lost of overflowing text and it is all shown with ellipsis, so I think it would be very confusing, as some ellipsis would be clickable and some not... is right, it can be made possible if we make it a toolbar, however I would be worried about opening inline actions to extensions since horizontal space is already limited. I would first only open secondary actions - the one behind a context menu. Though I am flexible.
can you verify this one?

Discussion:
static readonly DebugCallStackContext = new MenuId('DebugCallStackContext'); static readonly DebugConsoleContext = new MenuId('DebugConsoleContext'); static readonly DebugVariablesContext = new MenuId('DebugVariablesContext'); <ins> static readonly DebugHoverContext = new MenuId('DebugHoverContext'); </ins> static readonly DebugWatchContext = new MenuId('DebugWatchContext'); static readonly DebugToolBar = new MenuId('DebugToolBar'); static readonly DebugToolBarStop = new MenuId('DebugToolBarStop');

registerDebugViewMenuItem(MenuId.DebugCallStackContext, COPY_STACK_TRACE_ID, nls.localize('copyStackTrace', "Copy Call Stack"), 20, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('stackFrame'), undefined, '3_modification'); registerDebugViewMenuItem(MenuId.DebugVariablesContext, VIEW_MEMORY_ID, nls.localize('viewMemory', "View Binary Data"), 15, CONTEXT_CAN_VIEW_MEMORY, CONTEXT_IN_DEBUG_MODE, 'inline', icons.debugInspectMemory); registerDebugViewMenuItem(MenuId.DebugVariablesContext, SET_VARIABLE_ID, nls.localize('setValue', "Set Value"), 10, ContextKeyExpr.or(CONTEXT_SET_VARIABLE_SUPPORTED, ContextKeyExpr.and(CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, CONTEXT_SET_EXPRESSION_SUPPORTED)), CONTEXT_VARIABLE_IS_READONLY.toNegated(), '3_modification'); registerDebugViewMenuItem(MenuId.DebugVariablesContext, COPY_VALUE_ID, nls.localize('copyValue', "Copy Value"), 10, undefined, undefined, '5_cutcopypaste'); registerDebugViewMenuItem(MenuId.DebugVariablesContext, COPY_EVALUATE_PATH_ID, nls.localize('copyAsExpression', "Copy as Expression"), 20, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, undefined, '5_cutcopypaste');

registerDebugViewMenuItem(MenuId.DebugVariablesContext, BREAK_WHEN_VALUE_CHANGES_ID, nls.localize('breakWhenValueChanges', "Break on Value Change"), 210, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, undefined, 'z_commands'); registerDebugViewMenuItem(MenuId.DebugVariablesContext, BREAK_WHEN_VALUE_IS_ACCESSED_ID, nls.localize('breakWhenValueIsAccessed', "Break on Value Access"), 220, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, undefined, 'z_commands'); <ins> registerDebugViewMenuItem(MenuId.DebugHoverContext, VIEW_MEMORY_ID, nls.localize('viewMemory', "View Binary Data"), 15, CONTEXT_CAN_VIEW_MEMORY, CONTEXT_IN_DEBUG_MODE, 'inline', icons.debugInspectMemory); registerDebugViewMenuItem(MenuId.DebugHoverContext, COPY_VALUE_ID, nls.localize('copyValue', "Copy Value"), 10, undefined, undefined, '5_cutcopypaste'); registerDebugViewMenuItem(MenuId.DebugHoverContext, COPY_EVALUATE_PATH_ID, nls.localize('copyAsExpression', "Copy as Expression"), 20, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, undefined, '5_cutcopypaste'); registerDebugViewMenuItem(MenuId.DebugHoverContext, ADD_TO_WATCH_ID, nls.localize('addToWatchExpressions', "Add to Watch"), 100, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, undefined, 'z_commands'); registerDebugViewMenuItem(MenuId.DebugHoverContext, BREAK_WHEN_VALUE_IS_READ_ID, nls.localize('breakWhenValueIsRead', "Break on Value Read"), 200, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, undefined, 'z_commands'); registerDebugViewMenuItem(MenuId.DebugHoverContext, BREAK_WHEN_VALUE_CHANGES_ID, nls.localize('breakWhenValueChanges', "Break on Value Change"), 210, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, undefined, 'z_commands'); registerDebugViewMenuItem(MenuId.DebugHoverContext, BREAK_WHEN_VALUE_IS_ACCESSED_ID, nls.localize('breakWhenValueIsAccessed', "Break on Value Access"), 220, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, undefined, 'z_commands'); </ins> registerDebugViewMenuItem(MenuId.DebugWatchContext, ADD_WATCH_ID, ADD_WATCH_LABEL, 10, undefined, undefined, '3_modification'); registerDebugViewMenuItem(MenuId.DebugWatchContext, EDIT_EXPRESSION_COMMAND_ID, nls.localize('editWatchExpression', "Edit Expression"), 20, CONTEXT_WATCH_ITEM_TYPE.isEqualTo('expression'), undefined, '3_modification'); registerDebugViewMenuItem(MenuId.DebugWatchContext, SET_EXPRESSION_COMMAND_ID, nls.localize('setValue', "Set Value"), 30, ContextKeyExpr.or(ContextKeyExpr.and(CONTEXT_WATCH_ITEM_TYPE.isEqualTo('expression'), CONTEXT_SET_EXPRESSION_SUPPORTED), ContextKeyExpr.and(CONTEXT_WATCH_ITEM_TYPE.isEqualTo('variable'), CONTEXT_SET_VARIABLE_SUPPORTED)), CONTEXT_VARIABLE_IS_READONLY.toNegated(), '3_modification');

import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; <del> import { IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; </del> <ins> import { IAsyncDataSource, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; </ins> import { coalesce } from 'vs/base/common/arrays'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { KeyCode } from 'vs/base/common/keyCodes';

import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import * as nls from 'vs/nls'; <ins> import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; </ins> import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { ILogService } from 'vs/platform/log/common/log'; import { asCssVariable, editorHoverBackground, editorHoverBorder, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry'; import { renderExpressionValue } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; <del> import { VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView'; </del> <ins> import { VariablesRenderer, openContextMenuForVariableTreeElement } from 'vs/workbench/contrib/debug/browser/variablesView'; </ins> import { IDebugService, IDebugSession, IExpression, IExpressionContainer, IStackFrame } from 'vs/workbench/contrib/debug/common/debug'; import { Expression, Variable } from 'vs/workbench/contrib/debug/common/debugModel'; import { getEvaluatableExpressionAtPosition } from 'vs/workbench/contrib/debug/common/debugUtils';

private editor: ICodeEditor, @IDebugService private readonly debugService: IDebugService, @IInstantiationService private readonly instantiationService: IInstantiationService, <ins> @IMenuService private readonly menuService: IMenuService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IContextMenuService private readonly contextMenuService: IContextMenuService, </ins> ) { this.toDispose = [];

this.domNode.style.border = `1px solid ${asCssVariable(editorHoverBorder)}`; this.domNode.style.color = asCssVariable(editorHoverForeground); <ins> this.toDispose.push(this.tree.onContextMenu(async e => await this.onContextMenu(e))); </ins> this.toDispose.push(this.tree.onDidChangeContentHeight(() => { if (!this.isUpdatingTree) { // Don't do a layout in the middle of the async setInput

this.editor.addContentWidget(this); } <ins> private async onContextMenu(e: ITreeContextMenuEvent<IExpression>): Promise<void> { const variable = e.element; if (!(variable instanceof Variable) || !variable.value) { return; } return openContextMenuForVariableTreeElement(this.contextKeyService, this.menuService, this.contextMenuService, MenuId.DebugHoverContext, e); } </ins> private registerListeners(): void { this.toDispose.push(dom.addStandardDisposableListener(this.domNode, 'keydown', (e: IKeyboardEvent) => { if (e.equals(KeyCode.Escape)) {

return; } <del> const toDispose = new DisposableStore(); </del> <ins> return openContextMenuForVariableTreeElement(this.contextKeyService, this.menuService, this.contextMenuService, MenuId.DebugVariablesContext, e); } } </ins> <del> try { const contextKeyService = await getContextForVariableMenuWithDataAccess(this.contextKeyService, variable); const menu = toDispose.add(this.menuService.createMenu(MenuId.DebugVariablesContext, contextKeyService)); const context: IVariablesContext = getVariablesContext(variable); const secondary: IAction[] = []; createAndFillInContextMenuActions(menu, { arg: context, shouldForwardArgs: false }, { primary: [], secondary }, 'inline'); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => secondary }); } finally { toDispose.dispose(); } </del> <ins> export async function openContextMenuForVariableTreeElement(parentContextKeyService: IContextKeyService, menuService: IMenuService, contextMenuService: IContextMenuService, menuId: MenuId, e: ITreeContextMenuEvent<IExpression | IScope>) { const variable = e.element; if (!(variable instanceof Variable) || !variable.value) { return; } const toDispose = new DisposableStore(); try { const contextKeyService = await getContextForVariableMenuWithDataAccess(parentContextKeyService, variable); const menu = toDispose.add(menuService.createMenu(menuId, contextKeyService)); const context: IVariablesContext = getVariablesContext(variable); const secondary: IAction[] = []; createAndFillInContextMenuActions(menu, { arg: context, shouldForwardArgs: false }, { primary: [], secondary }, 'inline'); contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => secondary }); } finally { toDispose.dispose(); </ins> } }