diff --git a/CHANGELOG.md b/CHANGELOG.md index 76a3fd5b2864b..9fbace7b4c1d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - [core] introduce `FRONTEND_CONNECTION_TIMEOUT` environment variable to override application connexion settings. [#13936](https://github.com/eclipse-theia/theia/pull/13936) - Contributed on behalf of STMicroelectronics - [core] tab selected should be adjacent when closing last one [#13912](https://github.com/eclipse-theia/theia/pull/13912) - contributed on behalf of STMicroelectronics - [core] upgraded ws to 8.18.0 [#13903](https://github.com/eclipse-theia/theia/pull/13903) - contributed on behalf of STMicroelectronics +- [debug] implement activeStackItem and related change event in debug namespace [#13900](https://github.com/eclipse-theia/theia/pull/13900) - contributed on behalf of STMicroelectronics [Breaking Changes:](#breaking_changes_not_yet_released) diff --git a/packages/debug/src/browser/debug-session-manager.ts b/packages/debug/src/browser/debug-session-manager.ts index 788db2c5c9009..b462ecba88bb7 100644 --- a/packages/debug/src/browser/debug-session-manager.ts +++ b/packages/debug/src/browser/debug-session-manager.ts @@ -55,11 +55,6 @@ export interface DidChangeBreakpointsEvent { uri: URI } -export interface DidFocusStackFrameEvent { - session: DebugSession; - frame: DebugStackFrame | undefined; -} - export interface DebugSessionCustomEvent { readonly body?: any // eslint-disable-line @typescript-eslint/no-explicit-any readonly event: string @@ -94,9 +89,12 @@ export class DebugSessionManager { protected readonly onDidReceiveDebugSessionCustomEventEmitter = new Emitter(); readonly onDidReceiveDebugSessionCustomEvent: Event = this.onDidReceiveDebugSessionCustomEventEmitter.event; - protected readonly onDidFocusStackFrameEmitter = new Emitter(); + protected readonly onDidFocusStackFrameEmitter = new Emitter(); readonly onDidFocusStackFrame = this.onDidFocusStackFrameEmitter.event; + protected readonly onDidFocusThreadEmitter = new Emitter(); + readonly onDidFocusThread = this.onDidFocusThreadEmitter.event; + protected readonly onDidChangeBreakpointsEmitter = new Emitter(); readonly onDidChangeBreakpoints = this.onDidChangeBreakpointsEmitter.event; protected fireDidChangeBreakpoints(event: DidChangeBreakpointsEvent): void { @@ -518,7 +516,10 @@ export class DebugSessionManager { } this.fireDidChange(current); })); - this.disposeOnCurrentSessionChanged.push(current.onDidFocusStackFrame(frame => this.onDidFocusStackFrameEmitter.fire({ session: current, frame }))); + this.disposeOnCurrentSessionChanged.push(current.onDidFocusStackFrame(frame => this.onDidFocusStackFrameEmitter.fire(frame))); + this.disposeOnCurrentSessionChanged.push(current.onDidFocusThread(thread => this.onDidFocusThreadEmitter.fire(thread))); + const { currentThread } = current; + this.onDidFocusThreadEmitter.fire(currentThread); } this.updateBreakpoints(previous, current); this.open(); diff --git a/packages/debug/src/browser/debug-session.tsx b/packages/debug/src/browser/debug-session.tsx index 66b86ec487b7e..3a80f0efa8905 100644 --- a/packages/debug/src/browser/debug-session.tsx +++ b/packages/debug/src/browser/debug-session.tsx @@ -78,6 +78,11 @@ export class DebugSession implements CompositeTreeElement { return this.onDidFocusStackFrameEmitter.event; } + protected readonly onDidFocusThreadEmitter = new Emitter(); + get onDidFocusThread(): Event { + return this.onDidFocusThreadEmitter.event; + } + protected readonly onDidChangeBreakpointsEmitter = new Emitter(); readonly onDidChangeBreakpoints: Event = this.onDidChangeBreakpointsEmitter.event; protected fireDidChangeBreakpoints(uri: URI): void { @@ -254,8 +259,12 @@ export class DebugSession implements CompositeTreeElement { return this._currentThread; } set currentThread(thread: DebugThread | undefined) { + if (this._currentThread?.id === thread?.id) { + return; + } this.toDisposeOnCurrentThread.dispose(); this._currentThread = thread; + this.onDidFocusThreadEmitter.fire(thread); this.fireDidChange(); if (thread) { this.toDisposeOnCurrentThread.push(thread.onDidChanged(() => this.fireDidChange())); diff --git a/packages/debug/src/browser/model/debug-stack-frame.tsx b/packages/debug/src/browser/model/debug-stack-frame.tsx index d03831d67dae4..65ad04af48b27 100644 --- a/packages/debug/src/browser/model/debug-stack-frame.tsx +++ b/packages/debug/src/browser/model/debug-stack-frame.tsx @@ -49,6 +49,13 @@ export class DebugStackFrame extends DebugStackFrameData implements TreeElement return this.session.id + ':' + this.thread.id + ':' + this.raw.id; } + /** + * Returns the frame identifier from the debug protocol. + */ + get frameId(): number { + return this.raw.id; + } + protected _source: DebugSource | undefined; get source(): DebugSource | undefined { return this._source; diff --git a/packages/debug/src/browser/model/debug-thread.tsx b/packages/debug/src/browser/model/debug-thread.tsx index 1aae18af9d98a..306ff5d1082f6 100644 --- a/packages/debug/src/browser/model/debug-thread.tsx +++ b/packages/debug/src/browser/model/debug-thread.tsx @@ -56,11 +56,18 @@ export class DebugThread extends DebugThreadData implements TreeElement { return this.session.id + ':' + this.raw.id; } + get threadId(): number { + return this.raw.id; + } + protected _currentFrame: DebugStackFrame | undefined; get currentFrame(): DebugStackFrame | undefined { return this._currentFrame; } set currentFrame(frame: DebugStackFrame | undefined) { + if (this._currentFrame?.id === frame?.id) { + return; + } this._currentFrame = frame; this.onDidChangedEmitter.fire(undefined); this.onDidFocusStackFrameEmitter.fire(frame); diff --git a/packages/debug/src/browser/view/debug-threads-widget.ts b/packages/debug/src/browser/view/debug-threads-widget.ts index 7933a84352ad1..aa70d04ec97f9 100644 --- a/packages/debug/src/browser/view/debug-threads-widget.ts +++ b/packages/debug/src/browser/view/debug-threads-widget.ts @@ -99,6 +99,7 @@ export class DebugThreadsWidget extends SourceTreeWidget { this.viewModel.currentSession = node.element; this.debugCallStackItemTypeKey.set('session'); } else if (node.element instanceof DebugThread) { + this.viewModel.currentSession = node.element.session; node.element.session.currentThread = node.element; this.debugCallStackItemTypeKey.set('thread'); } diff --git a/packages/plugin-ext/src/common/plugin-api-rpc-model.ts b/packages/plugin-ext/src/common/plugin-api-rpc-model.ts index 4ae9532f404e9..7a4ecdae2f2b4 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc-model.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc-model.ts @@ -895,3 +895,13 @@ export interface InlineCompletionsProvider; + $onDidChangeActiveFrame(frame: DebugStackFrameDTO | undefined): void; + $onDidChangeActiveThread(thread: DebugThreadDTO | undefined): void; $createDebugSession(debugConfiguration: DebugConfiguration, workspaceFolder: string | undefined): Promise; $terminateDebugSession(sessionId: string): Promise; $getTerminalCreationOptions(debugType: string): Promise; diff --git a/packages/plugin-ext/src/main/browser/debug/debug-main.ts b/packages/plugin-ext/src/main/browser/debug/debug-main.ts index 8e1020a8a7fb1..703912d10d6fb 100644 --- a/packages/plugin-ext/src/main/browser/debug/debug-main.ts +++ b/packages/plugin-ext/src/main/browser/debug/debug-main.ts @@ -25,7 +25,7 @@ import { MAIN_RPC_CONTEXT } from '../../../common/plugin-api-rpc'; import { DebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager'; -import { Breakpoint, WorkspaceFolder } from '../../../common/plugin-api-rpc-model'; +import { Breakpoint, DebugStackFrameDTO, DebugThreadDTO, WorkspaceFolder } from '../../../common/plugin-api-rpc-model'; import { LabelProvider } from '@theia/core/lib/browser'; import { EditorManager } from '@theia/editor/lib/browser'; import { BreakpointManager, BreakpointsChangeEvent } from '@theia/debug/lib/browser/breakpoint/breakpoint-manager'; @@ -56,6 +56,8 @@ import { DebugContribution } from '@theia/debug/lib/browser/debug-contribution'; import { ConnectionImpl } from '../../../common/connection'; import { WorkspaceService } from '@theia/workspace/lib/browser'; import { DebugSessionOptions as TheiaDebugSessionOptions } from '@theia/debug/lib/browser/debug-session-options'; +import { DebugStackFrame } from '@theia/debug/lib/browser/model/debug-stack-frame'; +import { DebugThread } from '@theia/debug/lib/browser/model/debug-thread'; export class DebugMainImpl implements DebugMain, Disposable { private readonly debugExt: DebugExt; @@ -117,7 +119,9 @@ export class DebugMainImpl implements DebugMain, Disposable { this.sessionManager.onDidStartDebugSession(debugSession => this.debugExt.$sessionDidStart(debugSession.id)), this.sessionManager.onDidDestroyDebugSession(debugSession => this.debugExt.$sessionDidDestroy(debugSession.id)), this.sessionManager.onDidChangeActiveDebugSession(event => this.debugExt.$sessionDidChange(event.current && event.current.id)), - this.sessionManager.onDidReceiveDebugSessionCustomEvent(event => this.debugExt.$onSessionCustomEvent(event.session.id, event.event, event.body)) + this.sessionManager.onDidReceiveDebugSessionCustomEvent(event => this.debugExt.$onSessionCustomEvent(event.session.id, event.event, event.body)), + this.sessionManager.onDidFocusStackFrame(stackFrame => this.debugExt.$onDidChangeActiveFrame(this.toDebugStackFrameDTO(stackFrame))), + this.sessionManager.onDidFocusThread(debugThread => this.debugExt.$onDidChangeActiveThread(this.toDebugThreadDTO(debugThread))), ]); } @@ -340,6 +344,21 @@ export class DebugMainImpl implements DebugMain, Disposable { } } + private toDebugStackFrameDTO(stackFrame: DebugStackFrame | undefined): DebugStackFrameDTO | undefined { + return stackFrame ? { + sessionId: stackFrame.session.id, + frameId: stackFrame.frameId, + threadId: stackFrame.thread.threadId + } : undefined; + } + + private toDebugThreadDTO(debugThread: DebugThread | undefined): DebugThreadDTO | undefined { + return debugThread ? { + sessionId: debugThread.session.id, + threadId: debugThread.threadId + } : undefined; + } + private toTheiaPluginApiBreakpoints(breakpoints: (SourceBreakpoint | FunctionBreakpoint)[]): Breakpoint[] { return breakpoints.map(b => this.toTheiaPluginApiBreakpoint(b)); } diff --git a/packages/plugin-ext/src/plugin/debug/debug-ext.ts b/packages/plugin-ext/src/plugin/debug/debug-ext.ts index af9486cab4dca..48c13a6ec9bee 100644 --- a/packages/plugin-ext/src/plugin/debug/debug-ext.ts +++ b/packages/plugin-ext/src/plugin/debug/debug-ext.ts @@ -18,14 +18,14 @@ import { Emitter } from '@theia/core/lib/common/event'; import { Path } from '@theia/core/lib/common/path'; import * as theia from '@theia/plugin'; import { URI } from '@theia/core/shared/vscode-uri'; -import { Breakpoint } from '../../common/plugin-api-rpc-model'; +import { Breakpoint, DebugStackFrameDTO, DebugThreadDTO } from '../../common/plugin-api-rpc-model'; import { DebugConfigurationProviderTriggerKind, DebugExt, DebugMain, PLUGIN_RPC_CONTEXT as Ext, TerminalOptionsExt } from '../../common/plugin-api-rpc'; import { PluginPackageDebuggersContribution } from '../../common/plugin-protocol'; import { RPCProtocol } from '../../common/rpc-protocol'; import { CommandRegistryImpl } from '../command-registry'; import { ConnectionImpl } from '../../common/connection'; import { DEBUG_SCHEME, SCHEME_PATTERN } from '@theia/debug/lib/common/debug-uri-utils'; -import { Disposable, Breakpoint as BreakpointExt, SourceBreakpoint, FunctionBreakpoint, Location, Range, URI as URIImpl } from '../types-impl'; +import { Disposable, Breakpoint as BreakpointExt, SourceBreakpoint, FunctionBreakpoint, Location, Range, URI as URIImpl, DebugStackFrame, DebugThread } from '../types-impl'; import { PluginDebugAdapterSession } from './plugin-debug-adapter-session'; import { PluginDebugAdapterTracker } from './plugin-debug-adapter-tracker'; import { generateUuid } from '@theia/core/lib/common/uuid'; @@ -79,6 +79,9 @@ export class DebugExtImpl implements DebugExt { activeDebugSession: theia.DebugSession | undefined; activeDebugConsole: theia.DebugConsole; + _activeStackItem: theia.DebugStackFrame | theia.DebugThread | undefined; + private readonly onDidChangeActiveStackItemEmitter = new Emitter(); + private readonly _breakpoints = new Map(); private frontendAdapterCreator = new PluginDebugAdapterCreator(); @@ -149,6 +152,10 @@ export class DebugExtImpl implements DebugExt { return this.onDidStartDebugSessionEmitter.event; } + get onDidChangeActiveStackItem(): theia.Event { + return this.onDidChangeActiveStackItemEmitter.event; + } + get onDidChangeBreakpoints(): theia.Event { return this.onDidChangeBreakpointsEmitter.event; } @@ -262,6 +269,40 @@ export class DebugExtImpl implements DebugExt { }); } + set activeStackItem(stackItem: theia.DebugStackFrame | theia.DebugThread | undefined) { + if (this._activeStackItem === stackItem) { + return; + } + this._activeStackItem = stackItem; + this.onDidChangeActiveStackItemEmitter.fire(this.activeStackItem); + } + + get activeStackItem(): theia.DebugStackFrame | theia.DebugThread | undefined { + return this._activeStackItem; + } + + async $onDidChangeActiveThread(debugThread: DebugThreadDTO | undefined): Promise { + if (!debugThread) { + this.activeStackItem = undefined; + return; + } + const session = this.sessions.get(debugThread.sessionId); + if (session) { + this.activeStackItem = new DebugThread(session, debugThread.threadId); + } + } + + async $onDidChangeActiveFrame(debugFrame: DebugStackFrameDTO | undefined): Promise { + if (!debugFrame) { + this.activeStackItem = undefined; + return; + } + const session = this.sessions.get(debugFrame!.sessionId); + if (session) { + this.activeStackItem = new DebugStackFrame(session, debugFrame.threadId, debugFrame.frameId); + } + } + async $onSessionCustomEvent(sessionId: string, event: string, body?: any): Promise { const session = this.sessions.get(sessionId); if (session) { diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index 4367992efd925..1a66df549a33c 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -1101,13 +1101,11 @@ export function createAPIFactory( get onDidChangeBreakpoints(): theia.Event { return debugExt.onDidChangeBreakpoints; }, - /** @stubbed */ get activeStackItem(): DebugThread | DebugStackFrame | undefined { - return undefined; + return debugExt.activeStackItem; }, - /** @stubbed */ get onDidChangeActiveStackItem(): theia.Event { - return Event.None; + return debugExt.onDidChangeActiveStackItem; }, registerDebugAdapterDescriptorFactory(debugType: string, factory: theia.DebugAdapterDescriptorFactory): Disposable { return debugExt.registerDebugAdapterDescriptorFactory(debugType, factory); diff --git a/packages/plugin-ext/src/plugin/types-impl.ts b/packages/plugin-ext/src/plugin/types-impl.ts index 3b9a28057432c..e1b1dd10bb3a7 100644 --- a/packages/plugin-ext/src/plugin/types-impl.ts +++ b/packages/plugin-ext/src/plugin/types-impl.ts @@ -3031,12 +3031,12 @@ export class FunctionBreakpoint extends Breakpoint { } } -export class DebugThread { - private constructor(readonly session: theia.DebugSession, readonly threadId: number) { } +export class DebugThread implements theia.DebugThread { + constructor(readonly session: theia.DebugSession, readonly threadId: number) { } } -export class DebugStackFrame { - private constructor(readonly session: theia.DebugSession, readonly threadId: number, readonly frameId: number) { } +export class DebugStackFrame implements theia.DebugStackFrame { + constructor(readonly session: theia.DebugSession, readonly threadId: number, readonly frameId: number) { } } @es5ClassCompat diff --git a/packages/plugin/src/theia.d.ts b/packages/plugin/src/theia.d.ts index a712b59b05b16..a1a07acfb420f 100644 --- a/packages/plugin/src/theia.d.ts +++ b/packages/plugin/src/theia.d.ts @@ -12548,13 +12548,11 @@ export module '@theia/plugin' { * thread or stack is focused. A thread can be focused any time there is * an active debug session, while a stack frame can only be focused when * a session is paused and the call stack has been retrieved. - * @stubbed */ export const activeStackItem: DebugThread | DebugStackFrame | undefined; /** * An event which fires when the {@link debug.activeStackItem} has changed. - * @stubbed */ export const onDidChangeActiveStackItem: Event;