Skip to content

Commit

Permalink
vscode: Support location in TerminalOptions (#12006)
Browse files Browse the repository at this point in the history
* vscode: Support location in TerminalOptions

* Add support for TerminalOptions.location
* Implement TerminalLocation
* Implement TerminalSplitLocationOptions
* Implement TerminalEditorLocationOptions
* Keep (bottom area aka TerminalLocation.Panel) as default target

Fixes #11506

Contributed on behalf of ST Microelectronics

Signed-off-by: Olaf Lessenich <[email protected]>

Co-authored-by: Lucas Koehler <[email protected]>
  • Loading branch information
xai and lucas-koehler authored Jan 9, 2023
1 parent a733057 commit d8c9a2e
Show file tree
Hide file tree
Showing 11 changed files with 196 additions and 18 deletions.
1 change: 1 addition & 0 deletions packages/core/src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export * from './contribution-filter';
export * from './nls';
export * from './numbers';
export * from './performance';
export * from './view-column';

import { environment } from '@theia/application-package/lib/environment';
export { environment };
33 changes: 33 additions & 0 deletions packages/core/src/common/view-column.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// *****************************************************************************
// Copyright (C) 2022 STMicroelectronics.
//
// 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
// *****************************************************************************

/**
* Denotes a column in the editor window.
* Columns are used to show editors side by side.
*/
export enum ViewColumn {
Active = -1,
Beside = -2,
One = 1,
Two = 2,
Three = 3,
Four = 4,
Five = 5,
Six = 6,
Seven = 7,
Eight = 8,
Nine = 9
}
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
24 changes: 21 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,23 @@ export class TerminalServiceMainImpl implements TerminalServiceMain, TerminalLin
}
}

protected getTerminalLocation(options: TerminalOptions, parentId?: string): TerminalLocation | TerminalEditorLocationOptions | { parentTerminal: string; } | undefined {
if (typeof options.location === 'number' && Object.values(TerminalLocation).includes(options.location)) {
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');
}
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
6 changes: 4 additions & 2 deletions 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 @@ -1088,7 +1089,7 @@ export function createAPIFactory(
notebook: theia.NotebookDocument,
controller: theia.NotebookController
): (void | Thenable<void>) { },
onDidChangeSelectedNotebooks: () => Disposable.create(() => {}),
onDidChangeSelectedNotebooks: () => Disposable.create(() => { }),
updateNotebookAffinity: (notebook: theia.NotebookDocument, affinity: theia.NotebookControllerAffinity) => undefined,
dispose: () => undefined,
};
Expand All @@ -1099,7 +1100,7 @@ export function createAPIFactory(
) {
return {
rendererId,
onDidReceiveMessage: () => Disposable.create(() => {} ),
onDidReceiveMessage: () => Disposable.create(() => { }),
postMessage: () => Promise.resolve({}),
};
},
Expand Down Expand Up @@ -1284,6 +1285,7 @@ export function createAPIFactory(
TabInputNotebookDiff: NotebookDiffEditorTabInput,
TabInputWebview: WebviewEditorTabInput,
TabInputTerminal: TerminalEditorTabInput,
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 @@ -1910,6 +1910,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
24 changes: 22 additions & 2 deletions packages/terminal/src/browser/base/terminal-widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
// *****************************************************************************

import { Event } from '@theia/core';
import { Event, ViewColumn } from '@theia/core';
import { BaseWidget } from '@theia/core/lib/browser';
import { CommandLineOptions } from '@theia/process/lib/common/shell-command-builder';
import { TerminalSearchWidget } from '../search/terminal-search-widget';
Expand All @@ -30,13 +30,28 @@ 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: ViewColumn;
readonly preserveFocus?: boolean;
}

export interface TerminalSplitLocation {
readonly parentTerminal: string;
}

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

abstract processId: Promise<number>;

/**
* Get the current executable and arguments.
*/
Expand All @@ -54,6 +69,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 +229,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;
}
43 changes: 35 additions & 8 deletions packages/terminal/src/browser/terminal-frontend-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ import {
isOSX,
SelectionService,
Emitter,
Event
Event,
ViewColumn
} from '@theia/core/lib/common';
import {
ApplicationShell, KeybindingContribution, KeyCode, Key, WidgetManager,
Expand All @@ -36,7 +37,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';
Expand Down Expand Up @@ -644,20 +645,46 @@ 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?.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-after';
break;
case ViewColumn.Beside:
widgetOptions.ref = this.shell.currentWidget;
widgetOptions.mode = 'split-right';
break;
default:
widgetOptions.area = 'main';
const mainAreaTerminals = this.shell.getWidgets('main').filter(w => w instanceof TerminalWidget && w.isVisible);
const column = Math.min(widget.location.viewColumn, mainAreaTerminals.length);
widgetOptions.mode = widget.location.viewColumn <= mainAreaTerminals.length ? 'split-left' : 'split-right';
widgetOptions.ref = mainAreaTerminals[column - 1];
}
}
}

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
Loading

0 comments on commit d8c9a2e

Please sign in to comment.