diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index 3bcb698ea8659..6f64456060ae2 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -8,7 +8,7 @@ import { URI as uri, UriComponents } from 'vs/base/common/uri'; import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory, IDataBreakpoint, IDebugSessionOptions, IInstructionBreakpoint, DebugConfigurationProviderTriggerKind } from 'vs/workbench/contrib/debug/common/debug'; import { ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext, - IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto, IDataBreakpointDto, IStartDebuggingOptions, IDebugConfiguration + IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto, IDataBreakpointDto, IStartDebuggingOptions, IDebugConfiguration, IThreadFocusDto, IStackFrameFocusDto } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; import severity from 'vs/base/common/severity'; @@ -56,6 +56,29 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb this._debugConfigurationProviders = new Map(); this._debugAdapterDescriptorFactories = new Map(); this._sessions = new Set(); + + this._toDispose.add(this.debugService.getViewModel().onDidFocusThread(({ thread, explicit, session }) => { + if (session) { + const dto: IThreadFocusDto = { + kind: 'thread', + threadId: thread?.threadId, + sessionId: session!.getId(), + }; + this._proxy.$acceptStackFrameFocus(dto); + } + })); + + this._toDispose.add(this.debugService.getViewModel().onDidFocusStackFrame(({ stackFrame, explicit, session }) => { + if (session) { + const dto: IStackFrameFocusDto = { + kind: 'stackFrame', + threadId: stackFrame?.thread.threadId, + frameId: stackFrame?.frameId, + sessionId: session.getId(), + }; + this._proxy.$acceptStackFrameFocus(dto); + } + })); } public dispose(): void { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 54037ceb504f0..6bb3af739e5e7 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1105,6 +1105,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I get breakpoints() { return extHostDebugService.breakpoints; }, + get stackFrameFocus() { + return extHostDebugService.stackFrameFocus; + }, onDidStartDebugSession(listener, thisArg?, disposables?) { return extHostDebugService.onDidStartDebugSession(listener, thisArg, disposables); }, @@ -1120,6 +1123,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I onDidChangeBreakpoints(listener, thisArgs?, disposables?) { return extHostDebugService.onDidChangeBreakpoints(listener, thisArgs, disposables); }, + onDidChangeStackFrameFocus(listener, thisArg?, disposables?) { + return extHostDebugService.onDidChangeStackFrameFocus(listener, thisArg, disposables); + }, registerDebugConfigurationProvider(debugType: string, provider: vscode.DebugConfigurationProvider, triggerKind?: vscode.DebugConfigurationProviderTriggerKind) { return extHostDebugService.registerDebugConfigurationProvider(debugType, provider, triggerKind || DebugConfigurationProviderTriggerKind.Initial); }, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index a1d238329d6bc..752767d7f0d16 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2032,6 +2032,20 @@ export interface IDebugSessionFullDto { export type IDebugSessionDto = IDebugSessionFullDto | DebugSessionUUID; +export interface IThreadFocusDto { + kind: 'thread'; + sessionId: string; + threadId: number | undefined; +} + +export interface IStackFrameFocusDto { + kind: 'stackFrame'; + sessionId: string; + threadId: number | undefined; + frameId: number | undefined; +} + + export interface ExtHostDebugServiceShape { $substituteVariables(folder: UriComponents | undefined, config: IConfig): Promise; $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, sessionId: string): Promise; @@ -2048,6 +2062,7 @@ export interface ExtHostDebugServiceShape { $acceptDebugSessionCustomEvent(session: IDebugSessionDto, event: any): void; $acceptBreakpointsDelta(delta: IBreakpointsDeltaDto): void; $acceptDebugSessionNameChanged(session: IDebugSessionDto, name: string): void; + $acceptStackFrameFocus(focus: IThreadFocusDto | IStackFrameFocusDto | undefined): void; } diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index b4043091e15b7..415df6d572a21 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -12,7 +12,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ISignService } from 'vs/platform/sign/common/sign'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { DebugSessionUUID, ExtHostDebugServiceShape, IBreakpointsDeltaDto, IDebugSessionDto, IFunctionBreakpointDto, ISourceMultiBreakpointDto, MainContext, MainThreadDebugServiceShape } from 'vs/workbench/api/common/extHost.protocol'; +import { DebugSessionUUID, ExtHostDebugServiceShape, IBreakpointsDeltaDto, IThreadFocusDto, IStackFrameFocusDto, IDebugSessionDto, IFunctionBreakpointDto, ISourceMultiBreakpointDto, MainContext, MainThreadDebugServiceShape } from 'vs/workbench/api/common/extHost.protocol'; import { IExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs'; import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; @@ -41,6 +41,8 @@ export interface IExtHostDebugService extends ExtHostDebugServiceShape { onDidReceiveDebugSessionCustomEvent: Event; onDidChangeBreakpoints: Event; breakpoints: vscode.Breakpoint[]; + onDidChangeStackFrameFocus: Event; + stackFrameFocus: vscode.ThreadFocus | vscode.StackFrameFocus | undefined; addBreakpoints(breakpoints0: readonly vscode.Breakpoint[]): Promise; removeBreakpoints(breakpoints0: readonly vscode.Breakpoint[]): Promise; @@ -91,6 +93,9 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E private readonly _onDidChangeBreakpoints: Emitter; + private _stackFrameFocus: vscode.ThreadFocus | vscode.StackFrameFocus | undefined; + private readonly _onDidChangeStackFrameFocus: Emitter; + private _debugAdapters: Map; private _debugAdaptersTrackers: Map; @@ -129,6 +134,8 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E } }); + this._onDidChangeStackFrameFocus = new Emitter(); + this._activeDebugConsole = new ExtHostDebugConsole(this._debugServiceProxy); this._breakpoints = new Map(); @@ -190,6 +197,15 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E // extension debug API + + get stackFrameFocus(): vscode.ThreadFocus | vscode.StackFrameFocus | undefined { + return this._stackFrameFocus; + } + + get onDidChangeStackFrameFocus(): Event { + return this._onDidChangeStackFrameFocus.event; + } + get onDidChangeBreakpoints(): Event { return this._onDidChangeBreakpoints.event; } @@ -584,6 +600,32 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E this.fireBreakpointChanges(a, r, c); } + public async $acceptStackFrameFocus(focusDto: IThreadFocusDto | IStackFrameFocusDto): Promise { + let focus: vscode.ThreadFocus | vscode.StackFrameFocus; + const session = focusDto.sessionId ? await this.getSession(focusDto.sessionId) : undefined; + if (!session) { + throw new Error('no DebugSession found for debug focus context'); + } + + if (focusDto.kind === 'thread') { + focus = { + kind: focusDto.kind, + threadId: focusDto.threadId, + session, + }; + } else { + focus = { + kind: focusDto.kind, + threadId: focusDto.threadId, + frameId: focusDto.frameId, + session, + }; + } + + this._stackFrameFocus = focus; + this._onDidChangeStackFrameFocus.fire(focus); + } + public $provideDebugConfigurations(configProviderHandle: number, folderUri: UriComponents | undefined, token: CancellationToken): Promise { return asPromise(async () => { const provider = this.getConfigProviderByHandle(configProviderHandle); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 4c29fda0a8b78..e2fb876f4dda6 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -618,7 +618,8 @@ export interface IViewModel extends ITreeElement { isMultiSessionView(): boolean; onDidFocusSession: Event; - onDidFocusStackFrame: Event<{ stackFrame: IStackFrame | undefined; explicit: boolean }>; + onDidFocusThread: Event<{ thread: IThread | undefined; explicit: boolean; session: IDebugSession | undefined }>; + onDidFocusStackFrame: Event<{ stackFrame: IStackFrame | undefined; explicit: boolean; session: IDebugSession | undefined }>; onDidSelectExpression: Event<{ expression: IExpression; settingWatch: boolean } | undefined>; onDidEvaluateLazyExpression: Event; onWillUpdateViews: Event; diff --git a/src/vs/workbench/contrib/debug/common/debugViewModel.ts b/src/vs/workbench/contrib/debug/common/debugViewModel.ts index c04aecb15e199..d128a494c9d0c 100644 --- a/src/vs/workbench/contrib/debug/common/debugViewModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugViewModel.ts @@ -17,7 +17,8 @@ export class ViewModel implements IViewModel { private _focusedThread: IThread | undefined; private selectedExpression: { expression: IExpression; settingWatch: boolean } | undefined; private readonly _onDidFocusSession = new Emitter(); - private readonly _onDidFocusStackFrame = new Emitter<{ stackFrame: IStackFrame | undefined; explicit: boolean }>(); + private readonly _onDidFocusThread = new Emitter<{ thread: IThread | undefined; explicit: boolean; session: IDebugSession | undefined }>(); + private readonly _onDidFocusStackFrame = new Emitter<{ stackFrame: IStackFrame | undefined; explicit: boolean; session: IDebugSession | undefined }>(); private readonly _onDidSelectExpression = new Emitter<{ expression: IExpression; settingWatch: boolean } | undefined>(); private readonly _onDidEvaluateLazyExpression = new Emitter(); private readonly _onWillUpdateViews = new Emitter(); @@ -74,6 +75,8 @@ export class ViewModel implements IViewModel { setFocus(stackFrame: IStackFrame | undefined, thread: IThread | undefined, session: IDebugSession | undefined, explicit: boolean): void { const shouldEmitForStackFrame = this._focusedStackFrame !== stackFrame; const shouldEmitForSession = this._focusedSession !== session; + const shouldEmitForThread = this._focusedThread !== thread; + this._focusedStackFrame = stackFrame; this._focusedThread = thread; @@ -98,8 +101,12 @@ export class ViewModel implements IViewModel { if (shouldEmitForSession) { this._onDidFocusSession.fire(session); } + + // should not call onDidFocusThread if onDidFocusStackFrame is called. if (shouldEmitForStackFrame) { - this._onDidFocusStackFrame.fire({ stackFrame, explicit }); + this._onDidFocusStackFrame.fire({ stackFrame, explicit, session }); + } else if (shouldEmitForThread) { + this._onDidFocusThread.fire({ thread, explicit, session }); } } @@ -107,7 +114,11 @@ export class ViewModel implements IViewModel { return this._onDidFocusSession.event; } - get onDidFocusStackFrame(): Event<{ stackFrame: IStackFrame | undefined; explicit: boolean }> { + get onDidFocusThread(): Event<{ thread: IThread | undefined; explicit: boolean; session: IDebugSession | undefined }> { + return this._onDidFocusThread.event; + } + + get onDidFocusStackFrame(): Event<{ stackFrame: IStackFrame | undefined; explicit: boolean; session: IDebugSession | undefined }> { return this._onDidFocusStackFrame.event; } diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 888c6c4c689a9..fe819b5b6c47e 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -23,6 +23,7 @@ export const allApiProposals = Object.freeze({ contribViewsRemote: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsRemote.d.ts', contribViewsWelcome: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsWelcome.d.ts', customEditorMove: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.customEditorMove.d.ts', + debugFocus: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.debugFocus.d.ts', diffCommand: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.diffCommand.d.ts', diffContentOptions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.diffContentOptions.d.ts', documentFiltersExclusive: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.documentFiltersExclusive.d.ts', diff --git a/src/vscode-dts/vscode.proposed.debugFocus.d.ts b/src/vscode-dts/vscode.proposed.debugFocus.d.ts new file mode 100644 index 0000000000000..4526f0a854dfd --- /dev/null +++ b/src/vscode-dts/vscode.proposed.debugFocus.d.ts @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // See https://github.com/microsoft/vscode/issues/63943 + + export interface ThreadFocus { + kind: 'thread'; + + /** + * Debug session for thread. + */ + readonly session: DebugSession; + + /** + * Id of the associated thread (DAP id). May be undefined if thread has become unselected. + */ + readonly threadId: number | undefined; + } + + export interface StackFrameFocus { + kind: 'stackFrame'; + + /** + * Debug session for thread. + */ + readonly session: DebugSession; + + /** + * Id of the associated thread (DAP id). May be undefined if a frame is unselected. + */ + readonly threadId: number | undefined; + /** + * Id of the stack frame (DAP id). May be undefined if a frame is unselected. + */ + readonly frameId: number | undefined; + } + + + export namespace debug { + /** + * The currently focused thread or stack frame id, or `undefined` if this has not been set. (e.g. not in debug mode). + */ + export let stackFrameFocus: ThreadFocus | StackFrameFocus | undefined; + + /** + * An {@link Event} which fires when the {@link debug.stackFrameFocus} changes. Provides a sessionId. threadId is not undefined + * when a thread of frame has gained focus. frameId is defined when a stackFrame has gained focus. + */ + export const onDidChangeStackFrameFocus: Event; + } +}