diff --git a/packages/plugin-ext/src/main/browser/terminal-main.ts b/packages/plugin-ext/src/main/browser/terminal-main.ts index 65a7534d02ca2..ebb15b3234851 100644 --- a/packages/plugin-ext/src/main/browser/terminal-main.ts +++ b/packages/plugin-ext/src/main/browser/terminal-main.ts @@ -16,9 +16,9 @@ import { interfaces } from '@theia/core/shared/inversify'; import { ApplicationShell, WidgetOpenerOptions } from '@theia/core/lib/browser'; -import { TerminalOptions } from '@theia/plugin'; import { CancellationToken } from '@theia/core/shared/vscode-languageserver-protocol'; -import { TerminalWidget } from '@theia/terminal/lib/browser/base/terminal-widget'; +import { TerminalEditorLocationOptions, TerminalOptions, TerminalSplitLocationOptions } from '@theia/plugin'; +import { TerminalLocation, TerminalWidget } from '@theia/terminal/lib/browser/base/terminal-widget'; import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-service'; import { TerminalServiceMain, TerminalServiceExt, MAIN_RPC_CONTEXT } from '../../common/plugin-api-rpc'; import { RPCProtocol } from '../../common/rpc-protocol'; @@ -141,6 +141,7 @@ export class TerminalServiceMainImpl implements TerminalServiceMain, TerminalLin if (options.message) { terminal.writeLine(options.message); } + terminal.target = await this.determineWidgetTarget(options.location); terminal.start(); return terminal.id; } catch (error) { @@ -148,6 +149,21 @@ export class TerminalServiceMainImpl implements TerminalServiceMain, TerminalLin } } + protected async determineWidgetTarget(location?: TerminalLocation | + TerminalEditorLocationOptions | TerminalSplitLocationOptions | undefined): + Promise { + const defaultLocation = TerminalLocation.Panel; + + if (!location) { + return defaultLocation; + } else if (typeof location === 'object' && 'parentTerminal' in location) { + const parentTerminal = this.terminals.getById((await location.parentTerminal.processId).toString()); + return !parentTerminal || !parentTerminal.target ? defaultLocation : parentTerminal.target; + } + + return location; + } + $sendText(id: string, text: string, addNewLine?: boolean): void { const terminal = this.terminals.getById(id); if (terminal) { diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index 0fee9f59bec5f..83be90a82ab72 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -152,6 +152,7 @@ import { TextDocumentChangeReason, InputBoxValidationSeverity, TerminalLink, + TerminalLocation, InlayHint, InlayHintKind, InlayHintLabelPart, @@ -1133,7 +1134,8 @@ export function createAPIFactory( ExtensionKind, InlineCompletionItem, InlineCompletionList, - InlineCompletionTriggerKind + InlineCompletionTriggerKind, + TerminalLocation }; }; } diff --git a/packages/plugin-ext/src/plugin/types-impl.ts b/packages/plugin-ext/src/plugin/types-impl.ts index 78cbc2204fe20..75b2313929128 100644 --- a/packages/plugin-ext/src/plugin/types-impl.ts +++ b/packages/plugin-ext/src/plugin/types-impl.ts @@ -1695,6 +1695,11 @@ export class TerminalLink { } } +export enum TerminalLocation { + Panel = 1, + Editor = 2 +} + @es5ClassCompat export class FileDecoration { diff --git a/packages/plugin/src/theia.d.ts b/packages/plugin/src/theia.d.ts index 5d92ddc800d8f..e55bc510e9ad8 100644 --- a/packages/plugin/src/theia.d.ts +++ b/packages/plugin/src/theia.d.ts @@ -3001,6 +3001,11 @@ export module '@theia/plugin' { */ message?: string; + /** + * The {@link TerminalLocation} or {@link TerminalEditorLocationOptions} or {@link TerminalSplitLocationOptions} for the terminal. + */ + location?: TerminalLocation | TerminalEditorLocationOptions | TerminalSplitLocationOptions; + /** * Terminal attributes. Can be useful to apply some implementation specific information. */ @@ -3067,6 +3072,11 @@ export module '@theia/plugin' { * control it. */ pty: Pseudoterminal; + + /** + * The {@link TerminalLocation} or {@link TerminalEditorLocationOptions} or {@link TerminalSplitLocationOptions} for the terminal. + */ + location?: TerminalLocation | TerminalEditorLocationOptions | TerminalSplitLocationOptions; } /** @@ -3207,6 +3217,50 @@ export module '@theia/plugin' { constructor(startIndex: number, length: number, tooltip?: string); } + /** + * The location of the {@link Terminal}. + */ + export enum TerminalLocation { + /** + * In the terminal view + */ + Panel = 1, + /** + * In the editor area + */ + Editor = 2, + } + + /** + * Assumes a {@link TerminalLocation} of editor and allows specifying a {@link ViewColumn} and + * {@link TerminalEditorLocationOptions.preserveFocus preserveFocus } property + */ + export interface TerminalEditorLocationOptions { + /** + * A view column in which the {@link Terminal terminal} should be shown in the editor area. + * Use {@link ViewColumn.Active active} to open in the active editor group, other values are + * adjusted to be `Min(column, columnCount + 1)`, the + * {@link ViewColumn.Active active}-column is not adjusted. Use + * {@linkcode ViewColumn.Beside} to open the editor to the side of the currently active one. + */ + viewColumn: ViewColumn; + /** + * An optional flag that when `true` will stop the {@link Terminal} from taking focus. + */ + preserveFocus?: boolean; + } + + /** + * Uses the parent {@link Terminal}'s location for the terminal + */ + export interface TerminalSplitLocationOptions { + /** + * The parent terminal to split this terminal beside. This works whether the parent terminal + * is in the panel or the editor area. + */ + parentTerminal: Terminal; + } + /** * A file decoration represents metadata that can be rendered with a file. */ diff --git a/packages/terminal/src/browser/base/terminal-widget.ts b/packages/terminal/src/browser/base/terminal-widget.ts index f1bf943479874..8302a0b739169 100644 --- a/packages/terminal/src/browser/base/terminal-widget.ts +++ b/packages/terminal/src/browser/base/terminal-widget.ts @@ -30,13 +30,22 @@ export interface TerminalExitStatus { readonly code: number | undefined; } +export enum TerminalLocation { + Panel = 1, + Editor = 2 +} + +export interface TerminalEditorLocationOptions { + readonly viewColumn: number; + readonly preserveFocus?: boolean; +} + /** * Terminal UI widget. */ export abstract class TerminalWidget extends BaseWidget { abstract processId: Promise; - /** * Get the current executable and arguments. */ @@ -54,6 +63,9 @@ export abstract class TerminalWidget extends BaseWidget { /** Terminal widget can be hidden from users until explicitly shown once. */ abstract readonly hiddenFromUser: boolean; + /** The position of the terminal widget. */ + abstract target?: TerminalLocation | TerminalEditorLocationOptions; + /** The last CWD assigned to the terminal, useful when attempting getCwdURI on a task terminal fails */ lastCwd: URI; diff --git a/packages/terminal/src/browser/terminal-frontend-contribution.ts b/packages/terminal/src/browser/terminal-frontend-contribution.ts index f17e86fa1b177..47a8a4923ad44 100644 --- a/packages/terminal/src/browser/terminal-frontend-contribution.ts +++ b/packages/terminal/src/browser/terminal-frontend-contribution.ts @@ -36,7 +36,7 @@ import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/li import { TERMINAL_WIDGET_FACTORY_ID, TerminalWidgetFactoryOptions, TerminalWidgetImpl } from './terminal-widget-impl'; import { TerminalKeybindingContexts } from './terminal-keybinding-contexts'; import { TerminalService } from './base/terminal-service'; -import { TerminalWidgetOptions, TerminalWidget } from './base/terminal-widget'; +import { TerminalWidgetOptions, TerminalWidget, TerminalLocation } from './base/terminal-widget'; import { UriAwareCommandHandler } from '@theia/core/lib/common/uri-command-handler'; import { ShellTerminalServerProxy } from '../common/shell-terminal-protocol'; import URI from '@theia/core/lib/common/uri'; @@ -644,11 +644,13 @@ export class TerminalFrontendContribution implements FrontendApplicationContribu // TODO: reuse WidgetOpenHandler.open open(widget: TerminalWidget, options?: WidgetOpenerOptions): void { + const area = widget.target === TerminalLocation.Editor ? 'main' : 'bottom'; + const op: WidgetOpenerOptions = { mode: 'activate', ...options, widgetOptions: { - area: 'bottom', + area: area, ...(options && options.widgetOptions) } }; diff --git a/packages/terminal/src/browser/terminal-widget-impl.ts b/packages/terminal/src/browser/terminal-widget-impl.ts index 7b0448aab6266..cb181d7e21fc8 100644 --- a/packages/terminal/src/browser/terminal-widget-impl.ts +++ b/packages/terminal/src/browser/terminal-widget-impl.ts @@ -25,7 +25,7 @@ import { ShellTerminalServerProxy, IShellTerminalPreferences } from '../common/s import { terminalsPath } from '../common/terminal-protocol'; import { IBaseTerminalServer, TerminalProcessInfo } from '../common/base-terminal-protocol'; import { TerminalWatcher } from '../common/terminal-watcher'; -import { TerminalWidgetOptions, TerminalWidget, TerminalDimensions, TerminalExitStatus } from './base/terminal-widget'; +import { TerminalWidgetOptions, TerminalWidget, TerminalDimensions, TerminalExitStatus, TerminalLocation } from './base/terminal-widget'; import { Deferred } from '@theia/core/lib/common/promise-util'; import { TerminalPreferences, TerminalRendererType, isTerminalRendererType, DEFAULT_TERMINAL_RENDERER_TYPE, CursorStyle } from './terminal-preferences'; import URI from '@theia/core/lib/common/uri'; @@ -73,6 +73,7 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget protected lastMousePosition: { x: number, y: number } | undefined; protected isAttachedCloseListener: boolean = false; protected shown = false; + protected _target: TerminalLocation|undefined; override lastCwd = new URI(); @inject(WorkspaceService) protected readonly workspaceService: WorkspaceService; @@ -382,6 +383,14 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget return this.shellTerminalServer.getProcessId(this.terminalId); } + get target(): TerminalLocation | undefined { + return this._target; + } + + set target(target: TerminalLocation | undefined) { + this._target = target; + } + get processInfo(): Promise { if (!IBaseTerminalServer.validateId(this.terminalId)) { return Promise.reject(new Error('terminal is not started'));