diff --git a/packages/console/src/browser/console-session-manager.ts b/packages/console/src/browser/console-session-manager.ts new file mode 100644 index 0000000000000..4b3c527e32511 --- /dev/null +++ b/packages/console/src/browser/console-session-manager.ts @@ -0,0 +1,107 @@ +/******************************************************************************** + * Copyright (C) 2021 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { injectable } from '@theia/core/shared/inversify'; +import { Emitter, Disposable, DisposableCollection } from '@theia/core'; +import { ConsoleSession } from './console-session'; +import { Severity } from '@theia/core/lib/common/severity'; + +@injectable() +export class ConsoleSessionManager implements Disposable { + + protected readonly sessions = new Map(); + protected _selectedSession: ConsoleSession | undefined; + protected _severity: Severity | undefined; + + protected readonly sessionAddedEmitter = new Emitter(); + protected readonly sessionDeletedEmitter = new Emitter(); + protected readonly sessionWasShownEmitter = new Emitter(); + protected readonly sessionWasHiddenEmitter = new Emitter(); + protected readonly selectedSessionChangedEmitter = new Emitter(); + protected readonly severityChangedEmitter = new Emitter(); + + readonly onSessionAdded = this.sessionAddedEmitter.event; + readonly onSessionDeleted = this.sessionDeletedEmitter.event; + readonly onSessionWasShown = this.sessionWasShownEmitter.event; + readonly onSessionWasHidden = this.sessionWasHiddenEmitter.event; + readonly onSelectedSessionChanged = this.selectedSessionChangedEmitter.event; + readonly onSeverityChanged = this.severityChangedEmitter.event; + + protected readonly toDispose = new DisposableCollection(); + protected readonly toDisposeOnSessionDeletion = new Map(); + + dispose(): void { + this.toDispose.dispose(); + } + + get severity(): Severity | undefined { + return this._severity; + } + + set severity(value: Severity | undefined) { + value = value || Severity.Ignore; + this._severity = value; + for (const session of this.sessions.values()) { + session.severity = value; + } + this.severityChangedEmitter.fire(undefined); + } + + get all(): ConsoleSession[] { + return Array.from(this.sessions.values()); + } + + get selectedSession(): ConsoleSession | undefined { + return this._selectedSession; + } + + set selectedSession(session: ConsoleSession | undefined) { + const oldSession = this.selectedSession; + this._selectedSession = session; + this.selectedSessionChangedEmitter.fire(session); + if (oldSession !== session) { + if (oldSession) { + this.sessionWasHiddenEmitter.fire(oldSession); + } + if (session) { + this.sessionWasShownEmitter.fire(session); + } + } + } + + get(id: string): ConsoleSession | undefined { + return this.sessions.get(id); + } + + add(session: ConsoleSession): void { + this.sessions.set(session.id, session); + this.sessionAddedEmitter.fire(session); + if (this.sessions.size === 1) { + this.selectedSession = session; + } + } + + delete(id: string): void { + const session = this.sessions.get(id); + if (this.sessions.delete(id) && session) { + this.sessionDeletedEmitter.fire(session); + if (this.sessions.size === 0) { + this.selectedSessionChangedEmitter.fire(undefined); + } + } + } + +} diff --git a/packages/console/src/browser/console-session.ts b/packages/console/src/browser/console-session.ts index 6058d5e66ae18..4469db82388da 100644 --- a/packages/console/src/browser/console-session.ts +++ b/packages/console/src/browser/console-session.ts @@ -39,6 +39,7 @@ export abstract class ConsoleSession extends TreeSource { protected selectedSeverity?: Severity; protected readonly selectionEmitter: Emitter = new Emitter(); readonly onSelectionChange = this.selectionEmitter.event; + id: string; get severity(): Severity | undefined { return this.selectedSeverity; diff --git a/packages/console/src/browser/console-widget.ts b/packages/console/src/browser/console-widget.ts index e240ac9bd4a94..54eff1b51fe53 100644 --- a/packages/console/src/browser/console-widget.ts +++ b/packages/console/src/browser/console-widget.ts @@ -25,6 +25,7 @@ import { MonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-pr import { ConsoleHistory } from './console-history'; import { ConsoleContentWidget } from './console-content-widget'; import { ConsoleSession } from './console-session'; +import { ConsoleSessionManager } from './console-session-manager'; export const ConsoleOptions = Symbol('ConsoleWidgetOptions'); export interface ConsoleOptions { @@ -67,6 +68,9 @@ export class ConsoleWidget extends BaseWidget implements StatefulWidget { @inject(ConsoleHistory) protected readonly history: ConsoleHistory; + @inject(ConsoleSessionManager) + protected readonly sessionManager: ConsoleSessionManager; + @inject(MonacoEditorProvider) protected readonly editorProvider: MonacoEditorProvider; @@ -109,6 +113,13 @@ export class ConsoleWidget extends BaseWidget implements StatefulWidget { } })); + this.toDispose.push(this.sessionManager.onSelectedSessionChanged(session => { + // Don't delete the last session by overriding it with 'undefined' + if (session) { + this.session = session; + } + })); + this.updateFont(); if (inputFocusContextKey) { this.toDispose.push(input.onFocusChanged(() => inputFocusContextKey.set(this.hasInputFocus()))); diff --git a/packages/core/src/browser/style/index.css b/packages/core/src/browser/style/index.css index 6b99cbb27913e..8c90772a62c7d 100644 --- a/packages/core/src/browser/style/index.css +++ b/packages/core/src/browser/style/index.css @@ -205,6 +205,7 @@ button.secondary[disabled], .theia-button.secondary[disabled] { border: 1px solid var(--theia-dropdown-border); background: var(--theia-dropdown-background); outline: none; + margin-left: calc(var(--theia-ui-padding)); } .theia-select option { diff --git a/packages/debug/src/browser/console/debug-console-contribution.tsx b/packages/debug/src/browser/console/debug-console-contribution.tsx index dd66d2b757a59..0b24e3e3a8c2c 100644 --- a/packages/debug/src/browser/console/debug-console-contribution.tsx +++ b/packages/debug/src/browser/console/debug-console-contribution.tsx @@ -14,15 +14,19 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { ConsoleSessionManager } from '@theia/console/lib/browser/console-session-manager'; import { ConsoleOptions, ConsoleWidget } from '@theia/console/lib/browser/console-widget'; import { AbstractViewContribution, bindViewContribution, Widget, WidgetFactory } from '@theia/core/lib/browser'; import { ContextKey, ContextKeyService } from '@theia/core/lib/browser/context-key-service'; import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; import { Command, CommandRegistry } from '@theia/core/lib/common/command'; import { Severity } from '@theia/core/lib/common/severity'; -import { inject, injectable, interfaces } from '@theia/core/shared/inversify'; +import { inject, injectable, interfaces, postConstruct } from '@theia/core/shared/inversify'; import * as React from '@theia/core/shared/react'; -import { DebugConsoleSession } from './debug-console-session'; +import { DebugConsoleMode } from '../../common/debug-configuration'; +import { DebugSession } from '../debug-session'; +import { DebugSessionManager } from '../debug-session-manager'; +import { DebugConsoleSession, DebugConsoleSessionFactory } from './debug-console-session'; export type InDebugReplContextKey = ContextKey; export const InDebugReplContextKey = Symbol('inDebugReplContextKey'); @@ -40,8 +44,14 @@ export namespace DebugConsoleCommands { @injectable() export class DebugConsoleContribution extends AbstractViewContribution implements TabBarToolbarContribution { - @inject(DebugConsoleSession) - protected debugConsoleSession: DebugConsoleSession; + @inject(ConsoleSessionManager) + protected consoleSessionManager: ConsoleSessionManager; + + @inject(DebugConsoleSessionFactory) + protected debugConsoleSessionFactory: DebugConsoleSessionFactory; + + @inject(DebugSessionManager) + protected debugSessionManager: DebugSessionManager; constructor() { super({ @@ -55,6 +65,37 @@ export class DebugConsoleContribution extends AbstractViewContribution { + const topParent = this.findParentSession(session); + if (topParent) { + const parentConsoleSession = this.consoleSessionManager.get(topParent.id); + if (parentConsoleSession instanceof DebugConsoleSession) { + session.on('output', event => parentConsoleSession.logOutput(parentConsoleSession.debugSession, event)); + } + } else { + const consoleSession = this.debugConsoleSessionFactory(session); + this.consoleSessionManager.add(consoleSession); + session.on('output', event => consoleSession.logOutput(session, event)); + } + }); + this.debugSessionManager.onDidDestroyDebugSession(session => { + this.consoleSessionManager.delete(session.id); + }); + } + + protected findParentSession(session: DebugSession): DebugSession | undefined { + if (session.configuration.consoleMode !== DebugConsoleMode.MergeWithParent) { + return undefined; + } + let debugSession: DebugSession | undefined = session; + do { + debugSession = debugSession.parentSession; + } while (debugSession?.parentSession && debugSession.configuration.consoleMode === DebugConsoleMode.MergeWithParent); + return debugSession; + } + registerCommands(commands: CommandRegistry): void { super.registerCommands(commands); commands.registerCommand(DebugConsoleCommands.CLEAR, { @@ -71,7 +112,13 @@ export class DebugConsoleContribution extends AbstractViewContribution this.renderSeveritySelector(widget), isVisible: widget => this.withWidget(widget, () => true), - onDidChange: this.debugConsoleSession.onSelectionChange + onDidChange: this.consoleSessionManager.onSeverityChanged + }); + + toolbarRegistry.registerItem({ + id: 'debug-console-session-selector', + render: widget => this.renderDebugConsoleSelector(widget), + isVisible: widget => this.withWidget(widget, () => this.consoleSessionManager.all.length > 1) }); toolbarRegistry.registerItem({ @@ -105,7 +152,6 @@ export class DebugConsoleContribution extends AbstractViewContribution container.get(ContextKeyService).createKey('inDebugRepl', false) ).inSingletonScope(); - bind(DebugConsoleSession).toSelf().inSingletonScope(); - bindViewContribution(bind, DebugConsoleContribution).onActivation((context, _) => { - // eagerly initialize the debug console session - context.container.get(DebugConsoleSession); - return _; + bind(DebugConsoleSession).toSelf().inRequestScope(); + bind(DebugConsoleSessionFactory).toFactory(context => (session: DebugSession) => { + const consoleSession = context.container.get(DebugConsoleSession); + consoleSession.debugSession = session; + return consoleSession; }); + bind(ConsoleSessionManager).toSelf().inSingletonScope(); + bindViewContribution(bind, DebugConsoleContribution); bind(TabBarToolbarContribution).toService(DebugConsoleContribution); bind(WidgetFactory).toDynamicValue(({ container }) => ({ id: DebugConsoleContribution.options.id, @@ -129,7 +177,7 @@ export class DebugConsoleContribution extends AbstractViewContribution severityElements.push()); - const selectedValue = Severity.toString(this.debugConsoleSession.severity || Severity.Ignore); + const selectedValue = Severity.toString(this.consoleSessionManager.severity || Severity.Ignore); return + {availableConsoles} + ; + } + + protected changeDebugConsole = (event: React.ChangeEvent) => { + const id = event.target.value; + const session = this.consoleSessionManager.get(id); + this.consoleSessionManager.selectedSession = session; + }; + protected changeSeverity = (event: React.ChangeEvent) => { - this.debugConsoleSession.severity = Severity.fromValue(event.target.value); + this.consoleSessionManager.severity = Severity.fromValue(event.target.value); }; protected withWidget(widget: Widget | undefined = this.tryGetWidget(), fn: (widget: ConsoleWidget) => T): T | false { diff --git a/packages/debug/src/browser/console/debug-console-session.ts b/packages/debug/src/browser/console/debug-console-session.ts index 20fd98870af01..bbf93519ffd66 100644 --- a/packages/debug/src/browser/console/debug-console-session.ts +++ b/packages/debug/src/browser/console/debug-console-session.ts @@ -15,40 +15,44 @@ ********************************************************************************/ import throttle = require('@theia/core/shared/lodash.throttle'); -import { injectable, inject, postConstruct } from '@theia/core/shared/inversify'; import { DebugProtocol } from 'vscode-debugprotocol/lib/debugProtocol'; import { ConsoleSession, ConsoleItem } from '@theia/console/lib/browser/console-session'; import { AnsiConsoleItem } from '@theia/console/lib/browser/ansi-console-item'; import { DebugSession } from '../debug-session'; -import { DebugSessionManager } from '../debug-session-manager'; import URI from '@theia/core/lib/common/uri'; import { ExpressionContainer, ExpressionItem } from './debug-console-items'; import { Severity } from '@theia/core/lib/common/severity'; +import { injectable, postConstruct } from '@theia/core/shared/inversify'; + +export const DebugConsoleSessionFactory = Symbol('DebugConsoleSessionFactory'); + +export type DebugConsoleSessionFactory = (debugSession: DebugSession) => DebugConsoleSession; @injectable() export class DebugConsoleSession extends ConsoleSession { static uri = new URI().withScheme('debugconsole'); - readonly id = 'debug'; protected items: ConsoleItem[] = []; + protected _debugSession: DebugSession; + // content buffer for [append](#append) method protected uncompletedItemContent: string | undefined; - @inject(DebugSessionManager) - protected readonly manager: DebugSessionManager; - protected readonly completionKinds = new Map(); + get debugSession(): DebugSession { + return this._debugSession; + } + + set debugSession(value: DebugSession) { + this._debugSession = value; + this.id = value.id; + } + @postConstruct() init(): void { - this.toDispose.push(this.manager.onDidCreateDebugSession(session => { - if (this.manager.sessions.length === 1) { - this.clear(); - } - session.on('output', event => this.logOutput(session, event)); - })); this.completionKinds.set('method', monaco.languages.CompletionItemKind.Method); this.completionKinds.set('function', monaco.languages.CompletionItemKind.Function); this.completionKinds.set('constructor', monaco.languages.CompletionItemKind.Constructor); @@ -82,7 +86,7 @@ export class DebugConsoleSession extends ConsoleSession { } protected async completions(model: monaco.editor.ITextModel, position: monaco.Position): Promise { - const session = this.manager.currentSession; + const session = this.debugSession; if (session && session.capabilities.supportsCompletionsRequest) { const column = position.column; const lineNumber = position.lineNumber; @@ -108,7 +112,7 @@ export class DebugConsoleSession extends ConsoleSession { } async execute(value: string): Promise { - const expression = new ExpressionItem(value, () => this.manager.currentSession); + const expression = new ExpressionItem(value, () => this.debugSession); this.items.push(expression); await expression.evaluate(); this.fireDidChange(); @@ -141,7 +145,7 @@ export class DebugConsoleSession extends ConsoleSession { this.fireDidChange(); } - protected async logOutput(session: DebugSession, event: DebugProtocol.OutputEvent): Promise { + async logOutput(session: DebugSession, event: DebugProtocol.OutputEvent): Promise { const body = event.body; const { category, variablesReference } = body; if (category === 'telemetry') { diff --git a/packages/debug/src/browser/debug-session-contribution.ts b/packages/debug/src/browser/debug-session-contribution.ts index ebcab7eac6ab2..2d2fb5caed0a0 100644 --- a/packages/debug/src/browser/debug-session-contribution.ts +++ b/packages/debug/src/browser/debug-session-contribution.ts @@ -90,7 +90,7 @@ export const DebugSessionFactory = Symbol('DebugSessionFactory'); * The [debug session](#DebugSession) factory. */ export interface DebugSessionFactory { - get(sessionId: string, options: DebugSessionOptions): DebugSession; + get(sessionId: string, options: DebugSessionOptions, parentSession?: DebugSession): DebugSession; } @injectable() @@ -115,7 +115,7 @@ export class DefaultDebugSessionFactory implements DebugSessionFactory { @inject(FileService) protected readonly fileService: FileService; - get(sessionId: string, options: DebugSessionOptions): DebugSession { + get(sessionId: string, options: DebugSessionOptions, parentSession?: DebugSession): DebugSession { const connection = new DebugSessionConnection( sessionId, () => new Promise(resolve => @@ -127,6 +127,7 @@ export class DefaultDebugSessionFactory implements DebugSessionFactory { return new DebugSession( sessionId, options, + parentSession, connection, this.terminalService, this.editorManager, diff --git a/packages/debug/src/browser/debug-session-manager.ts b/packages/debug/src/browser/debug-session-manager.ts index b337363392fdf..5384e30bc22dc 100644 --- a/packages/debug/src/browser/debug-session-manager.ts +++ b/packages/debug/src/browser/debug-session-manager.ts @@ -254,9 +254,10 @@ export class DebugSessionManager { } protected async doStart(sessionId: string, options: DebugSessionOptions): Promise { + const parentSession = options.configuration.parentSession && this._sessions.get(options.configuration.parentSession.id); const contrib = this.sessionContributionRegistry.get(options.configuration.type); const sessionFactory = contrib ? contrib.debugSessionFactory() : this.debugSessionFactory; - const session = sessionFactory.get(sessionId, options); + const session = sessionFactory.get(sessionId, options, parentSession); this._sessions.set(sessionId, session); this.debugTypeKey.set(session.configuration.type); diff --git a/packages/debug/src/browser/debug-session.tsx b/packages/debug/src/browser/debug-session.tsx index d5e671bb98004..af133bc9e7391 100644 --- a/packages/debug/src/browser/debug-session.tsx +++ b/packages/debug/src/browser/debug-session.tsx @@ -62,11 +62,14 @@ export class DebugSession implements CompositeTreeElement { this.onDidChangeBreakpointsEmitter.fire(uri); } + protected readonly childSessions = new Map(); + protected readonly toDispose = new DisposableCollection(); constructor( readonly id: string, readonly options: DebugSessionOptions, + readonly parentSession: DebugSession | undefined, protected readonly connection: DebugSessionConnection, protected readonly terminalServer: TerminalService, protected readonly editorManager: EditorManager, @@ -75,6 +78,12 @@ export class DebugSession implements CompositeTreeElement { protected readonly messages: MessageClient, protected readonly fileService: FileService) { this.connection.onRequest('runInTerminal', (request: DebugProtocol.RunInTerminalRequest) => this.runInTerminal(request)); + if (parentSession) { + parentSession.childSessions.set(id, this); + this.toDispose.push(Disposable.create(() => { + this.parentSession?.childSessions?.delete(id); + })); + } this.toDispose.pushAll([ this.onDidChangeEmitter, this.onDidChangeBreakpointsEmitter, @@ -731,14 +740,37 @@ export class DebugSession implements CompositeTreeElement { } render(): React.ReactNode { + let label = ''; + const child = this.getSingleChildSession(); + if (child && child.configuration.compact) { + // Inlines the name of the child debug session + label = `: ${child.label}`; + } return
- {this.label} + {this.label + label} {this.state === DebugState.Stopped ? 'Paused' : 'Running'}
; } - getElements(): IterableIterator { - return this.threads; + getElements(): IterableIterator { + const child = this.getSingleChildSession(); + if (child && child.configuration.compact) { + // Inlines the elements of the child debug session + return child.getElements(); + } + + const items = []; + items.push(...this.threads); + items.push(...this.childSessions.values()); + return items.values(); + } + + protected getSingleChildSession(): DebugSession | undefined { + if (this._threads.size === 0 && this.childSessions.size === 1) { + const child = this.childSessions.values().next().value as DebugSession; + return child; + } + return undefined; } protected async handleContinued({ body: { allThreadsContinued, threadId } }: DebugProtocol.ContinuedEvent): Promise { diff --git a/packages/debug/src/browser/view/debug-threads-source.tsx b/packages/debug/src/browser/view/debug-threads-source.tsx index 05a83b05214f4..11d1bbd9c7962 100644 --- a/packages/debug/src/browser/view/debug-threads-source.tsx +++ b/packages/debug/src/browser/view/debug-threads-source.tsx @@ -44,7 +44,8 @@ export class DebugThreadsSource extends TreeSource { if (this.model.sessionCount === 1 && this.model.session && this.model.session.threadCount) { return this.model.session.threads; } - return this.model.sessions; + const sessions = [...this.model.sessions].filter(e => !e.parentSession); + return sessions.values(); } } diff --git a/packages/debug/src/common/debug-configuration.ts b/packages/debug/src/common/debug-configuration.ts index 6b1d5e4b7cac9..1b5fa6e7f24df 100644 --- a/packages/debug/src/common/debug-configuration.ts +++ b/packages/debug/src/common/debug-configuration.ts @@ -37,6 +37,12 @@ export interface DebugConfiguration { */ [key: string]: any; + parentSession?: { id: string }; + + consoleMode?: DebugConsoleMode; + + compact?: boolean; + /** * The request type of the debug adapter session. */ @@ -74,3 +80,15 @@ export namespace DebugConfiguration { return !!arg && typeof arg === 'object' && 'type' in arg && 'name' in arg && 'request' in arg; } } + +export interface DebugSessionOptions { + parentSession?: { id: string }; + consoleMode?: DebugConsoleMode; + noDebug?: boolean; + compact?: boolean; +} + +export enum DebugConsoleMode { + Separate = 0, + MergeWithParent = 1 +} diff --git a/packages/debug/src/common/debug-model.ts b/packages/debug/src/common/debug-model.ts index ad2880f07f00e..5fd24a6b46217 100644 --- a/packages/debug/src/common/debug-model.ts +++ b/packages/debug/src/common/debug-model.ts @@ -41,6 +41,7 @@ export const DebugAdapterSession = Symbol('DebugAdapterSession'); */ export interface DebugAdapterSession { id: string; + parentSession?: DebugAdapterSession; start(channel: WebSocketChannel): Promise stop(): Promise } diff --git a/packages/debug/src/node/debug-adapter-session-manager.ts b/packages/debug/src/node/debug-adapter-session-manager.ts index 975d32ab4c927..50ccd6236b325 100644 --- a/packages/debug/src/node/debug-adapter-session-manager.ts +++ b/packages/debug/src/node/debug-adapter-session-manager.ts @@ -67,6 +67,14 @@ export class DebugAdapterSessionManager implements MessagingService.Contribution const sessionFactory = registry.debugAdapterSessionFactory(config.type) || this.debugAdapterSessionFactory; const session = sessionFactory.get(sessionId, communicationProvider); this.sessions.set(sessionId, session); + + if (config.parentSession) { + const parentSession = this.sessions.get(config.parentSession.id); + if (parentSession) { + session.parentSession = parentSession; + } + } + return session; } diff --git a/packages/plugin-ext/compile.tsconfig.json b/packages/plugin-ext/compile.tsconfig.json index 3238f0d8c356a..8988050143fbe 100644 --- a/packages/plugin-ext/compile.tsconfig.json +++ b/packages/plugin-ext/compile.tsconfig.json @@ -73,6 +73,9 @@ }, { "path": "../workspace/compile.tsconfig.json" + }, + { + "path": "../console/compile.tsconfig.json" } ] } diff --git a/packages/plugin-ext/package.json b/packages/plugin-ext/package.json index 5f6b59b8df93e..91986d5fe17c2 100644 --- a/packages/plugin-ext/package.json +++ b/packages/plugin-ext/package.json @@ -8,6 +8,7 @@ "@theia/bulk-edit": "1.15.0", "@theia/callhierarchy": "1.15.0", "@theia/core": "1.15.0", + "@theia/console": "1.15.0", "@theia/debug": "1.15.0", "@theia/editor": "1.15.0", "@theia/file-search": "1.15.0", diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index cbce7991a9ffd..c41ff50852d00 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -1615,7 +1615,7 @@ export interface DebugMain { $unregisterDebuggerConfiguration(debugType: string): Promise; $addBreakpoints(breakpoints: Breakpoint[]): Promise; $removeBreakpoints(breakpoints: string[]): Promise; - $startDebugging(folder: theia.WorkspaceFolder | undefined, nameOrConfiguration: string | theia.DebugConfiguration): Promise; + $startDebugging(folder: theia.WorkspaceFolder | undefined, nameOrConfiguration: string | theia.DebugConfiguration, options: theia.DebugSessionOptions): Promise; $customRequest(sessionId: string, command: string, args?: any): 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 01d4b9a60388a..277a4742d3d8b 100644 --- a/packages/plugin-ext/src/main/browser/debug/debug-main.ts +++ b/packages/plugin-ext/src/main/browser/debug/debug-main.ts @@ -30,9 +30,8 @@ import { EditorManager } from '@theia/editor/lib/browser'; import { BreakpointManager, BreakpointsChangeEvent } from '@theia/debug/lib/browser/breakpoint/breakpoint-manager'; import { DebugSourceBreakpoint } from '@theia/debug/lib/browser/model/debug-source-breakpoint'; import { URI as Uri } from '@theia/core/shared/vscode-uri'; -import { DebugConsoleSession } from '@theia/debug/lib/browser/console/debug-console-session'; import { SourceBreakpoint, FunctionBreakpoint } from '@theia/debug/lib/browser/breakpoint/breakpoint-marker'; -import { DebugConfiguration } from '@theia/debug/lib/common/debug-configuration'; +import { DebugConfiguration, DebugSessionOptions } from '@theia/debug/lib/common/debug-configuration'; import { ConnectionMainImpl } from '../connection-main'; import { DebuggerDescription } from '@theia/debug/lib/common/debug-service'; import { DebugProtocol } from 'vscode-debugprotocol'; @@ -50,6 +49,8 @@ import { PluginDebugAdapterContributionRegistrator, PluginDebugService } from '. import { HostedPluginSupport } from '../../../hosted/browser/hosted-plugin'; import { DebugFunctionBreakpoint } from '@theia/debug/lib/browser/model/debug-function-breakpoint'; import { FileService } from '@theia/filesystem/lib/browser/file-service'; +import { ConsoleSessionManager } from '@theia/console/lib/browser/console-session-manager'; +import { DebugConsoleSession } from '@theia/debug/lib/browser/console/debug-console-session'; export class DebugMainImpl implements DebugMain, Disposable { private readonly debugExt: DebugExt; @@ -58,7 +59,7 @@ export class DebugMainImpl implements DebugMain, Disposable { private readonly labelProvider: LabelProvider; private readonly editorManager: EditorManager; private readonly breakpointsManager: BreakpointManager; - private readonly debugConsoleSession: DebugConsoleSession; + private readonly consoleSessionManager: ConsoleSessionManager; private readonly configurationManager: DebugConfigurationManager; private readonly terminalService: TerminalService; private readonly messages: MessageClient; @@ -78,7 +79,7 @@ export class DebugMainImpl implements DebugMain, Disposable { this.labelProvider = container.get(LabelProvider); this.editorManager = container.get(EditorManager); this.breakpointsManager = container.get(BreakpointManager); - this.debugConsoleSession = container.get(DebugConsoleSession); + this.consoleSessionManager = container.get(ConsoleSessionManager); this.configurationManager = container.get(DebugConfigurationManager); this.terminalService = container.get(TerminalService); this.messages = container.get(MessageClient); @@ -114,11 +115,17 @@ export class DebugMainImpl implements DebugMain, Disposable { } async $appendToDebugConsole(value: string): Promise { - this.debugConsoleSession.append(value); + const session = this.consoleSessionManager.selectedSession; + if (session instanceof DebugConsoleSession) { + session.append(value); + } } async $appendLineToDebugConsole(value: string): Promise { - this.debugConsoleSession.appendLine(value); + const session = this.consoleSessionManager.selectedSession; + if (session instanceof DebugConsoleSession) { + session.appendLine(value); + } } async $registerDebuggerContribution(description: DebuggerDescription): Promise { @@ -245,13 +252,13 @@ export class DebugMainImpl implements DebugMain, Disposable { throw new Error(`Debug session '${sessionId}' not found`); } - async $startDebugging(folder: WorkspaceFolder | undefined, nameOrConfiguration: string | DebugConfiguration): Promise { + async $startDebugging(folder: WorkspaceFolder | undefined, nameOrConfiguration: string | DebugConfiguration, options: DebugSessionOptions): Promise { let configuration: DebugConfiguration | undefined; if (typeof nameOrConfiguration === 'string') { - for (const options of this.configurationManager.all) { - if (options.configuration.name === nameOrConfiguration) { - configuration = options.configuration; + for (const configOptions of this.configurationManager.all) { + if (configOptions.configuration.name === nameOrConfiguration) { + configuration = configOptions.configuration; } } } else { @@ -263,6 +270,8 @@ export class DebugMainImpl implements DebugMain, Disposable { return false; } + Object.assign(configuration, options); + const session = await this.sessionManager.start({ configuration, workspaceFolderUri: folder && Uri.revive(folder.uri).toString() diff --git a/packages/plugin-ext/src/main/browser/debug/plugin-debug-session-factory.ts b/packages/plugin-ext/src/main/browser/debug/plugin-debug-session-factory.ts index 638aa507a5a7d..3af3739555fc3 100644 --- a/packages/plugin-ext/src/main/browser/debug/plugin-debug-session-factory.ts +++ b/packages/plugin-ext/src/main/browser/debug/plugin-debug-session-factory.ts @@ -34,6 +34,7 @@ export class PluginDebugSession extends DebugSession { constructor( readonly id: string, readonly options: DebugSessionOptions, + readonly parentSession: DebugSession | undefined, protected readonly connection: DebugSessionConnection, protected readonly terminalServer: TerminalService, protected readonly editorManager: EditorManager, @@ -42,7 +43,7 @@ export class PluginDebugSession extends DebugSession { protected readonly messages: MessageClient, protected readonly fileService: FileService, protected readonly terminalOptionsExt: TerminalOptionsExt | undefined) { - super(id, options, connection, terminalServer, editorManager, breakpoints, labelProvider, messages, fileService); + super(id, options, parentSession, connection, terminalServer, editorManager, breakpoints, labelProvider, messages, fileService); } protected async doCreateTerminal(terminalWidgetOptions: TerminalWidgetOptions): Promise { @@ -71,7 +72,7 @@ export class PluginDebugSessionFactory extends DefaultDebugSessionFactory { super(); } - get(sessionId: string, options: DebugSessionOptions): DebugSession { + get(sessionId: string, options: DebugSessionOptions, parentSession?: DebugSession): DebugSession { const connection = new DebugSessionConnection( sessionId, this.connectionFactory, @@ -80,6 +81,7 @@ export class PluginDebugSessionFactory extends DefaultDebugSessionFactory { return new PluginDebugSession( sessionId, options, + parentSession, connection, this.terminalService, this.editorManager, diff --git a/packages/plugin-ext/src/plugin/node/debug/debug.ts b/packages/plugin-ext/src/plugin/node/debug/debug.ts index b0547e835db5d..2cbb4fd2946b7 100644 --- a/packages/plugin-ext/src/plugin/node/debug/debug.ts +++ b/packages/plugin-ext/src/plugin/node/debug/debug.ts @@ -159,8 +159,8 @@ export class DebugExtImpl implements DebugExt { } } - startDebugging(folder: theia.WorkspaceFolder | undefined, nameOrConfiguration: string | theia.DebugConfiguration): PromiseLike { - return this.proxy.$startDebugging(folder, nameOrConfiguration); + startDebugging(folder: theia.WorkspaceFolder | undefined, nameOrConfiguration: string | theia.DebugConfiguration, options: theia.DebugSessionOptions): PromiseLike { + return this.proxy.$startDebugging(folder, nameOrConfiguration, options); } registerDebugAdapterDescriptorFactory(debugType: string, factory: theia.DebugAdapterDescriptorFactory): Disposable { diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index 1e3634175e982..e71522501ed40 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -773,8 +773,9 @@ export function createAPIFactory( registerDebugAdapterTrackerFactory(debugType: string, factory: theia.DebugAdapterTrackerFactory): Disposable { return debugExt.registerDebugAdapterTrackerFactory(debugType, factory); }, - startDebugging(folder: theia.WorkspaceFolder | undefined, nameOrConfiguration: string | theia.DebugConfiguration): Thenable { - return debugExt.startDebugging(folder, nameOrConfiguration); + startDebugging(folder: theia.WorkspaceFolder | undefined, nameOrConfiguration: string | theia.DebugConfiguration, options: theia.DebugSessionOptions): + Thenable { + return debugExt.startDebugging(folder, nameOrConfiguration, options); }, addBreakpoints(breakpoints: theia.Breakpoint[]): void { debugExt.addBreakpoints(breakpoints); diff --git a/packages/plugin/src/theia.d.ts b/packages/plugin/src/theia.d.ts index 764d152a8d63f..1cfd9155a95cd 100644 --- a/packages/plugin/src/theia.d.ts +++ b/packages/plugin/src/theia.d.ts @@ -9181,6 +9181,38 @@ declare module '@theia/plugin' { body?: any; } + /** + * Options for starting a debug session. + */ + export interface DebugSessionOptions { + + /** + * When specified the newly created debug session is registered as a "child" session of this + * "parent" debug session. + */ + parentSession?: DebugSession; + + /** + * Controls whether this session should have a separate debug console or share it + * with the parent session. Has no effect for sessions which do not have a parent session. + * Defaults to Separate. + */ + consoleMode?: DebugConsoleMode; + + /** + * Controls whether this session should run without debugging, thus ignoring breakpoints. + * When this property is not specified, the value from the parent session (if there is one) is used. + */ + noDebug?: boolean; + + /** + * Controls if the debug session's parent session is shown in the CALL STACK view even if it has only a single child. + * By default, the debug session will never hide its parent. + * If compact is true, debug sessions with a single child are hidden in the CALL STACK view to make the tree more compact. + */ + compact?: boolean; + } + /** * A debug configuration provider allows to add the initial debug configurations to a newly created launch.json * and to resolve a launch configuration before it is used to start a new debug session. @@ -9550,7 +9582,7 @@ declare module '@theia/plugin' { * @param nameOrConfiguration Either the name of a debug or compound configuration or a [DebugConfiguration](#DebugConfiguration) object. * @return A thenable that resolves when debugging could be successfully started. */ - export function startDebugging(folder: WorkspaceFolder | undefined, nameOrConfiguration: string | DebugConfiguration): PromiseLike; + export function startDebugging(folder: WorkspaceFolder | undefined, nameOrConfiguration: string | DebugConfiguration, options: DebugSessionOptions): PromiseLike; /** * Add breakpoints.