Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vscode: Support location in TerminalOptions #12006

Merged
merged 2 commits into from
Jan 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -1909,6 +1909,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