diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 2b784b5bf5e1e..ebe6670dcdf32 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -60,6 +60,7 @@ export class MenuId { static readonly DebugWatchContext = new MenuId('DebugWatchContext'); static readonly DebugToolBar = new MenuId('DebugToolBar'); static readonly DebugToolBarStop = new MenuId('DebugToolBarStop'); + static readonly DebugDisassemblyContext = new MenuId('DebugDisassemblyContext'); static readonly DebugCallStackToolbar = new MenuId('DebugCallStackToolbar'); static readonly DebugCreateConfiguration = new MenuId('DebugCreateConfiguration'); static readonly EditorContext = new MenuId('EditorContext'); diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index fe281b6c1b143..9ff5d8385c0eb 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -31,7 +31,7 @@ import { CallStackEditorContribution } from './callStackEditorContribution.js'; import { CallStackView } from './callStackView.js'; import { ReplAccessibleView } from './replAccessibleView.js'; import { registerColors } from './debugColors.js'; -import { ADD_CONFIGURATION_ID, ADD_TO_WATCH_ID, ADD_TO_WATCH_LABEL, CALLSTACK_BOTTOM_ID, CALLSTACK_BOTTOM_LABEL, CALLSTACK_DOWN_ID, CALLSTACK_DOWN_LABEL, CALLSTACK_TOP_ID, CALLSTACK_TOP_LABEL, CALLSTACK_UP_ID, CALLSTACK_UP_LABEL, CONTINUE_ID, CONTINUE_LABEL, COPY_EVALUATE_PATH_ID, COPY_EVALUATE_PATH_LABEL, COPY_STACK_TRACE_ID, COPY_VALUE_ID, COPY_VALUE_LABEL, DEBUG_COMMAND_CATEGORY, DEBUG_CONSOLE_QUICK_ACCESS_PREFIX, DEBUG_QUICK_ACCESS_PREFIX, DEBUG_RUN_COMMAND_ID, DEBUG_RUN_LABEL, DEBUG_START_COMMAND_ID, DEBUG_START_LABEL, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, DISCONNECT_ID, DISCONNECT_LABEL, EDIT_EXPRESSION_COMMAND_ID, JUMP_TO_CURSOR_ID, NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL, OPEN_LOADED_SCRIPTS_LABEL, PAUSE_ID, PAUSE_LABEL, PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL, REMOVE_EXPRESSION_COMMAND_ID, RESTART_FRAME_ID, RESTART_LABEL, RESTART_SESSION_ID, SELECT_AND_START_ID, SELECT_AND_START_LABEL, SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL, SELECT_DEBUG_SESSION_ID, SELECT_DEBUG_SESSION_LABEL, SET_EXPRESSION_COMMAND_ID, SHOW_LOADED_SCRIPTS_ID, STEP_INTO_ID, STEP_INTO_LABEL, STEP_INTO_TARGET_ID, STEP_INTO_TARGET_LABEL, STEP_OUT_ID, STEP_OUT_LABEL, STEP_OVER_ID, STEP_OVER_LABEL, STOP_ID, STOP_LABEL, TERMINATE_THREAD_ID, TOGGLE_INLINE_BREAKPOINT_ID } from './debugCommands.js'; +import { ADD_CONFIGURATION_ID, ADD_TO_WATCH_ID, ADD_TO_WATCH_LABEL, CALLSTACK_BOTTOM_ID, CALLSTACK_BOTTOM_LABEL, CALLSTACK_DOWN_ID, CALLSTACK_DOWN_LABEL, CALLSTACK_TOP_ID, CALLSTACK_TOP_LABEL, CALLSTACK_UP_ID, CALLSTACK_UP_LABEL, CONTINUE_ID, CONTINUE_LABEL, COPY_EVALUATE_PATH_ID, COPY_EVALUATE_PATH_LABEL, COPY_STACK_TRACE_ID, COPY_VALUE_ID, COPY_VALUE_LABEL, DEBUG_COMMAND_CATEGORY, DEBUG_CONSOLE_QUICK_ACCESS_PREFIX, DEBUG_QUICK_ACCESS_PREFIX, DEBUG_RUN_COMMAND_ID, DEBUG_RUN_LABEL, DEBUG_START_COMMAND_ID, DEBUG_START_LABEL, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, DISCONNECT_ID, DISCONNECT_LABEL, EDIT_EXPRESSION_COMMAND_ID, JUMP_TO_CURSOR_ID, NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL, OPEN_LOADED_SCRIPTS_LABEL, PAUSE_ID, PAUSE_LABEL, PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL, REMOVE_EXPRESSION_COMMAND_ID, RESTART_FRAME_ID, RESTART_LABEL, RESTART_SESSION_ID, SELECT_AND_START_ID, SELECT_AND_START_LABEL, SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL, SELECT_DEBUG_SESSION_ID, SELECT_DEBUG_SESSION_LABEL, SET_EXPRESSION_COMMAND_ID, SHOW_LOADED_SCRIPTS_ID, STEP_INTO_ID, STEP_INTO_LABEL, STEP_INTO_TARGET_ID, STEP_INTO_TARGET_LABEL, STEP_OUT_ID, STEP_OUT_LABEL, STEP_OVER_ID, STEP_OVER_LABEL, STOP_ID, STOP_LABEL, TERMINATE_THREAD_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_ADDRESS_ID, COPY_ADDRESS_LABEL, TOGGLE_BREAKPOINT_ID } from './debugCommands.js'; import { DebugConsoleQuickAccess } from './debugConsoleQuickAccess.js'; import { RunToCursorAction, SelectionToReplAction, SelectionToWatchExpressionsAction } from './debugEditorActions.js'; import { DebugEditorContribution } from './debugEditorContribution.js'; @@ -378,6 +378,28 @@ MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { when: CONTEXT_DEBUGGERS_AVAILABLE }); +// Disassembly + +MenuRegistry.appendMenuItem(MenuId.DebugDisassemblyContext, { + group: '1_edit', + command: { + id: COPY_ADDRESS_ID, + title: COPY_ADDRESS_LABEL, + }, + order: 2, + when: CONTEXT_DEBUGGERS_AVAILABLE +}); + +MenuRegistry.appendMenuItem(MenuId.DebugDisassemblyContext, { + group: '3_breakpoints', + command: { + id: TOGGLE_BREAKPOINT_ID, + title: nls.localize({ key: 'miToggleBreakpoint', comment: ['&& denotes a mnemonic'] }, "Toggle Breakpoint"), + }, + order: 2, + when: CONTEXT_DEBUGGERS_AVAILABLE +}); + // Breakpoint actions are registered from breakpointsView.ts // Install Debuggers diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 2d3cb0c46c836..1c89884407cdb 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -40,6 +40,8 @@ import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; export const ADD_CONFIGURATION_ID = 'debug.addConfiguration'; +export const COPY_ADDRESS_ID = 'editor.debug.action.copyAddress'; +export const TOGGLE_BREAKPOINT_ID = 'editor.debug.action.toggleBreakpoint'; export const TOGGLE_INLINE_BREAKPOINT_ID = 'editor.debug.action.toggleInlineBreakpoint'; export const COPY_STACK_TRACE_ID = 'debug.copyStackTrace'; export const REVERSE_CONTINUE_ID = 'workbench.action.debug.reverseContinue'; @@ -104,6 +106,7 @@ export const CALLSTACK_UP_LABEL = nls.localize2('callStackUp', "Navigate Up Call export const CALLSTACK_DOWN_LABEL = nls.localize2('callStackDown', "Navigate Down Call Stack"); export const COPY_EVALUATE_PATH_LABEL = nls.localize2('copyAsExpression', "Copy as Expression"); export const COPY_VALUE_LABEL = nls.localize2('copyValue', "Copy Value"); +export const COPY_ADDRESS_LABEL = nls.localize2('copyAddress', "Copy Address"); export const ADD_TO_WATCH_LABEL = nls.localize2('addToWatchExpressions', "Add to Watch"); export const SELECT_DEBUG_CONSOLE_LABEL = nls.localize2('selectDebugConsole', "Select Debug Console"); diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index f6a5af3713a50..81b57c8b35718 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -25,18 +25,19 @@ import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uri import { PanelFocusContext } from '../../../common/contextkeys.js'; import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; import { openBreakpointSource } from './breakpointsView.js'; -import { DisassemblyView } from './disassemblyView.js'; +import { DisassemblyView, IDisassembledInstructionEntry } from './disassemblyView.js'; import { Repl } from './repl.js'; import { BREAKPOINT_EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DISASSEMBLE_REQUEST_SUPPORTED, CONTEXT_DISASSEMBLY_VIEW_FOCUS, CONTEXT_EXCEPTION_WIDGET_VISIBLE, CONTEXT_FOCUSED_STACK_FRAME_HAS_INSTRUCTION_POINTER_REFERENCE, CONTEXT_IN_DEBUG_MODE, CONTEXT_LANGUAGE_SUPPORTS_DISASSEMBLE_REQUEST, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, IDebugConfiguration, IDebugEditorContribution, IDebugService, REPL_VIEW_ID, WATCH_VIEW_ID } from '../common/debug.js'; import { getEvaluatableExpressionAtPosition } from '../common/debugUtils.js'; import { DisassemblyViewInput } from '../common/disassemblyViewInput.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; +import { TOGGLE_BREAKPOINT_ID } from '../../../../workbench/contrib/debug/browser/debugCommands.js'; class ToggleBreakpointAction extends Action2 { constructor() { super({ - id: 'editor.debug.action.toggleBreakpoint', + id: TOGGLE_BREAKPOINT_ID, title: { ...nls.localize2('toggleBreakpointAction', "Debug: Toggle Breakpoint"), mnemonicTitle: nls.localize({ key: 'miToggleBreakpoint', comment: ['&& denotes a mnemonic'] }, "Toggle &&Breakpoint"), @@ -57,13 +58,13 @@ class ToggleBreakpointAction extends Action2 { }); } - async run(accessor: ServicesAccessor): Promise { + async run(accessor: ServicesAccessor, entry?: IDisassembledInstructionEntry): Promise { const editorService = accessor.get(IEditorService); const debugService = accessor.get(IDebugService); const activePane = editorService.activeEditorPane; if (activePane instanceof DisassemblyView) { - const location = activePane.focusedAddressAndOffset; + const location = entry ? activePane.getAddressAndOffset(entry) : activePane.focusedAddressAndOffset; if (location) { const bps = debugService.getModel().getInstructionBreakpoints(); const toRemove = bps.find(bp => bp.address === location.address); diff --git a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts index c98e35153df96..0f15bf5d2e787 100644 --- a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts +++ b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts @@ -6,7 +6,7 @@ import { PixelRatio } from '../../../../base/browser/pixelRatio.js'; import { $, Dimension, addStandardDisposableListener, append } from '../../../../base/browser/dom.js'; import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js'; -import { ITableRenderer, ITableVirtualDelegate } from '../../../../base/browser/ui/table/table.js'; +import { ITableContextMenuEvent, ITableRenderer, ITableVirtualDelegate } from '../../../../base/browser/ui/table/table.js'; import { binarySearch2 } from '../../../../base/common/arrays.js'; import { Color } from '../../../../base/common/color.js'; import { Emitter } from '../../../../base/common/event.js'; @@ -25,7 +25,7 @@ import { localize } from '../../../../nls.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { TextEditorSelectionRevealType } from '../../../../platform/editor/common/editor.js'; -import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { WorkbenchTable } from '../../../../platform/list/browser/listService.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { IStorageService } from '../../../../platform/storage/common/storage.js'; @@ -43,8 +43,14 @@ import { getUriFromSource } from '../common/debugSource.js'; import { isUri, sourcesEqual } from '../common/debugUtils.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { IEditorGroup } from '../../../services/editor/common/editorGroupsService.js'; - -interface IDisassembledInstructionEntry { +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; +import { IMenu, IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; +import { CommandsRegistry } from '../../../../platform/commands/common/commands.js'; +import { COPY_ADDRESS_ID, COPY_ADDRESS_LABEL } from '../../../../workbench/contrib/debug/browser/debugCommands.js'; +import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; +import { getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; + +export interface IDisassembledInstructionEntry { allowBreakpoint: boolean; isBreakpointSet: boolean; isBreakpointEnabled: boolean; @@ -62,7 +68,6 @@ interface IDisassembledInstructionEntry { address: bigint; } - // Special entry as a placeholer when disassembly is not available const disassemblyNotAvailable: IDisassembledInstructionEntry = { allowBreakpoint: false, @@ -91,6 +96,7 @@ export class DisassemblyView extends EditorPane { private _enableSourceCodeRender: boolean = true; private _loadingLock: boolean = false; private readonly _referenceToMemoryAddress = new Map(); + private menu: IMenu; constructor( group: IEditorGroup, @@ -100,9 +106,14 @@ export class DisassemblyView extends EditorPane { @IConfigurationService private readonly _configurationService: IConfigurationService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IDebugService private readonly _debugService: IDebugService, + @IContextMenuService private readonly _contextMenuService: IContextMenuService, + @IMenuService menuService: IMenuService, + @IContextKeyService contextKeyService: IContextKeyService, ) { super(DISASSEMBLY_VIEW_ID, group, telemetryService, themeService, storageService); + this.menu = menuService.createMenu(MenuId.DebugDisassemblyContext, contextKeyService); + this._register(this.menu); this._disassembledInstructions = undefined; this._onDidChangeStackFrame = this._register(new Emitter({ leakWarningThreshold: 1000 })); this._previousDebuggingState = _debugService.state; @@ -180,6 +191,10 @@ export class DisassemblyView extends EditorPane { return undefined; } + return this.getAddressAndOffset(element); + } + + getAddressAndOffset(element: IDisassembledInstructionEntry) { const reference = element.instructionReference; const offset = Number(element.address - this.getReferenceAddress(reference)!); return { reference, offset, address: element.address }; @@ -273,6 +288,8 @@ export class DisassemblyView extends EditorPane { } })); + this._register(this._disassembledInstructions.onContextMenu(e => this.onContextMenu(e))); + this._register(this._debugService.getViewModel().onDidFocusStackFrame(({ stackFrame }) => { if (this._disassembledInstructions && stackFrame?.instructionPointerReference) { this.goToInstructionAndOffset(stackFrame.instructionPointerReference, 0); @@ -633,6 +650,15 @@ export class DisassemblyView extends EditorPane { this._referenceToMemoryAddress.clear(); this._disassembledInstructions?.splice(0, this._disassembledInstructions.length, [disassemblyNotAvailable]); } + + private onContextMenu(e: ITableContextMenuEvent): void { + const actions = getFlatContextMenuActions(this.menu.getActions({ shouldForwardArgs: true })); + this._contextMenuService.showContextMenu({ + getAnchor: () => e.anchor, + getActions: () => actions, + getActionsContext: () => e.element + }); + } } interface IBreakpointColumnTemplateData { @@ -1006,3 +1032,16 @@ export class DisassemblyViewContribution implements IWorkbenchContribution { this._onDidChangeModelLanguage?.dispose(); } } + +CommandsRegistry.registerCommand({ + metadata: { + description: COPY_ADDRESS_LABEL, + }, + id: COPY_ADDRESS_ID, + handler: async (accessor: ServicesAccessor, entry?: IDisassembledInstructionEntry) => { + if (entry?.instruction?.address) { + const clipboardService = accessor.get(IClipboardService); + clipboardService.writeText(entry.instruction.address); + } + } +});