Skip to content

Commit

Permalink
vscode: Support location in TerminalOptions
Browse files Browse the repository at this point in the history
* Add support for TerminalOptions.location
* Implement TerminalLocation
* Implement TerminalSplitLocationOptions
* Implement TerminalEditorLocationOptions
* Keep (bottom area aka TerminalLocation.Panel) as default target

Fixes eclipse-theia#11506

Contributed on behalf of STMicroelectronics

Signed-off-by: Olaf Lessenich <[email protected]>
  • Loading branch information
xai committed Dec 14, 2022
1 parent ac7a9ff commit 183c46f
Show file tree
Hide file tree
Showing 9 changed files with 214 additions and 15 deletions.
2 changes: 1 addition & 1 deletion packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ export interface TerminalServiceMain {
* Create new Terminal with Terminal options.
* @param options - object with parameters to create new terminal.
*/
$createTerminal(id: string, options: theia.TerminalOptions, isPseudoTerminal?: boolean): Promise<string>;
$createTerminal(id: string, options: theia.TerminalOptions, parentId?: string, isPseudoTerminal?: boolean): Promise<string>;

/**
* Send text to the terminal by id.
Expand Down
25 changes: 22 additions & 3 deletions packages/plugin-ext/src/main/browser/terminal-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 } 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';
Expand Down Expand Up @@ -122,7 +122,7 @@ export class TerminalServiceMainImpl implements TerminalServiceMain, TerminalLin
terminal.resize(cols, rows);
}

async $createTerminal(id: string, options: TerminalOptions, isPseudoTerminal?: boolean): Promise<string> {
async $createTerminal(id: string, options: TerminalOptions, parentId?: string, isPseudoTerminal?: boolean): Promise<string> {
try {
const terminal = await this.terminals.newTerminal({
id,
Expand All @@ -136,6 +136,7 @@ export class TerminalServiceMainImpl implements TerminalServiceMain, TerminalLin
useServerTitle: false,
attributes: options.attributes,
hideFromUser: options.hideFromUser,
location: this.getTerminalLocation(options, parentId),
isPseudoTerminal
});
if (options.message) {
Expand All @@ -148,6 +149,24 @@ export class TerminalServiceMainImpl implements TerminalServiceMain, TerminalLin
}
}

protected getTerminalLocation(options: TerminalOptions, parentId?: string): TerminalLocation | TerminalEditorLocationOptions | { parentTerminal: string; } | undefined {
if (options.location && typeof options.location === 'number') {
return options.location;
} else if (options.location && typeof options.location === 'object') {
if ('parentTerminal' in options.location) {
if (!parentId) {
throw new Error('parentTerminal is set but no parentId is provided');
}
console.log('parentId ' + parentId);
return { 'parentTerminal': parentId };
} else {
return options.location;
}
}

return undefined;
}

$sendText(id: string, text: string, addNewLine?: boolean): void {
const terminal = this.terminals.getById(id);
if (terminal) {
Expand Down
4 changes: 3 additions & 1 deletion packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ import {
TextDocumentChangeReason,
InputBoxValidationSeverity,
TerminalLink,
TerminalLocation,
InlayHint,
InlayHintKind,
InlayHintLabelPart,
Expand Down Expand Up @@ -1135,7 +1136,8 @@ export function createAPIFactory(
ExtensionKind,
InlineCompletionItem,
InlineCompletionList,
InlineCompletionTriggerKind
InlineCompletionTriggerKind,
TerminalLocation
};
};
}
Expand Down
17 changes: 16 additions & 1 deletion packages/plugin-ext/src/plugin/terminal-ext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,22 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
shellArgs: shellArgs
};
}
this.proxy.$createTerminal(id, options, !!pseudoTerminal);

let parentId;

if (options.location && typeof options.location === 'object' && 'parentTerminal' in options.location) {
const parentTerminal = options.location.parentTerminal;
if (parentTerminal instanceof TerminalExtImpl) {
for (const [k, v] of this._terminals) {
if (v === parentTerminal) {
parentId = k;
break;
}
}
}
}

this.proxy.$createTerminal(id, options, parentId, !!pseudoTerminal);

let creationOptions: theia.TerminalOptions | theia.ExtensionTerminalOptions = options;
// make sure to pass ExtensionTerminalOptions as creation options
Expand Down
5 changes: 5 additions & 0 deletions packages/plugin-ext/src/plugin/types-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1695,6 +1695,11 @@ export class TerminalLink {
}
}

export enum TerminalLocation {
Panel = 1,
Editor = 2
}

@es5ClassCompat
export class FileDecoration {

Expand Down
54 changes: 54 additions & 0 deletions packages/plugin/src/theia.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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.
*/
Expand Down
73 changes: 72 additions & 1 deletion packages/terminal/src/browser/base/terminal-widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,79 @@ export interface TerminalExitStatus {
readonly code: number | undefined;
}

export type TerminalLocationOptions = TerminalLocation | TerminalEditorLocation | TerminalSplitLocation;

export enum TerminalLocation {
Panel = 1,
Editor = 2
}

export interface TerminalEditorLocation {
readonly viewColumn: number;
readonly preserveFocus?: boolean;
}

export interface TerminalSplitLocation {
readonly parentTerminal: string;
}

export enum ViewColumn {
/**
* A *symbolic* editor column representing the currently active column. This value
* can be used when opening editors, but the *resolved* {@link TextEditor.viewColumn viewColumn}-value
* of editors will always be `One`, `Two`, `Three`,... or `undefined` but never `Active`.
*/
Active = -1,
/**
* A *symbolic* editor column representing the column to the side of the active one. This value
* can be used when opening editors, but the *resolved* {@link TextEditor.viewColumn viewColumn}-value
* of editors will always be `One`, `Two`, `Three`,... or `undefined` but never `Beside`.
*/
Beside = -2,
/**
* The first editor column.
*/
One = 1,
/**
* The second editor column.
*/
Two = 2,
/**
* The third editor column.
*/
Three = 3,
/**
* The fourth editor column.
*/
Four = 4,
/**
* The fifth editor column.
*/
Five = 5,
/**
* The sixth editor column.
*/
Six = 6,
/**
* The seventh editor column.
*/
Seven = 7,
/**
* The eighth editor column.
*/
Eight = 8,
/**
* The ninth editor column.
*/
Nine = 9
}

/**
* Terminal UI widget.
*/
export abstract class TerminalWidget extends BaseWidget {

abstract processId: Promise<number>;

/**
* Get the current executable and arguments.
*/
Expand All @@ -54,6 +120,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 readonly location: TerminalLocationOptions;

/** The last CWD assigned to the terminal, useful when attempting getCwdURI on a task terminal fails */
lastCwd: URI;

Expand Down Expand Up @@ -211,4 +280,6 @@ export interface TerminalWidgetOptions {
* When enabled the terminal will run the process as normal but not be surfaced to the user until `Terminal.show` is called.
*/
readonly hideFromUser?: boolean;

readonly location?: TerminalLocationOptions;
}
44 changes: 37 additions & 7 deletions packages/terminal/src/browser/terminal-frontend-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, ViewColumn } 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';
Expand Down Expand Up @@ -644,20 +644,50 @@ export class TerminalFrontendContribution implements FrontendApplicationContribu

// TODO: reuse WidgetOpenHandler.open
open(widget: TerminalWidget, options?: WidgetOpenerOptions): void {
const area = widget.location === TerminalLocation.Editor ? 'main' : 'bottom';
const widgetOptions: ApplicationShell.WidgetOptions = { area: area, ...(options && options.widgetOptions) };
let preserveFocus = false;

if (typeof widget.location === 'object') {
if ('parentTerminal' in widget.location) {
widgetOptions.ref = this.getById(widget.location.parentTerminal);
widgetOptions.mode = 'split-right';
} else if ('viewColumn' in widget.location) {
preserveFocus = widget.location.preserveFocus ?? false;
switch (widget.location.viewColumn) {
case ViewColumn.Active:
widgetOptions.ref = this.shell.currentWidget;
widgetOptions.mode = 'tab-before';
break;
case ViewColumn.Beside:
widgetOptions.ref = this.shell.currentWidget;
widgetOptions.mode = 'tab-after';
break;
default:
const widgets = this.all.filter(t => t.isVisible);
const index = widget.location.viewColumn - 1;
if (index < widgets.length) {
widgetOptions.ref = widgets[index];
widgetOptions.mode = 'open-to-left';
} else {
widgetOptions.ref = widgets[widgets.length - 1];
widgetOptions.mode = 'open-to-right';
}
}
}
}

const op: WidgetOpenerOptions = {
mode: 'activate',
...options,
widgetOptions: {
area: 'bottom',
...(options && options.widgetOptions)
}
widgetOptions: widgetOptions
};
if (!widget.isAttached) {
this.shell.addWidget(widget, op.widgetOptions);
}
if (op.mode === 'activate') {
if (op.mode === 'activate' && !preserveFocus) {
this.shell.activateWidget(widget.id);
} else if (op.mode === 'reveal') {
} else if (op.mode === 'reveal' || preserveFocus) {
this.shell.revealWidget(widget.id);
}
}
Expand Down
5 changes: 4 additions & 1 deletion packages/terminal/src/browser/terminal-widget-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, TerminalLocationOptions, 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';
Expand Down Expand Up @@ -53,6 +53,7 @@ export interface TerminalContribution {
export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget, ExtractableWidget {
readonly isExtractable: boolean = true;
secondaryWindow: Window | undefined;
location: TerminalLocationOptions;

static LABEL = nls.localizeByDefault('Terminal');

Expand Down Expand Up @@ -128,6 +129,8 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget
));
}

this.location = this.options.location || TerminalLocation.Panel;

this.title.closable = true;
this.addClass('terminal-container');

Expand Down

0 comments on commit 183c46f

Please sign in to comment.