Skip to content

Commit d8c9a2e

Browse files
xailucas-koehler
andauthored
vscode: Support location in TerminalOptions (#12006)
* 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]>
1 parent a733057 commit d8c9a2e

File tree

11 files changed

+196
-18
lines changed

11 files changed

+196
-18
lines changed

packages/core/src/common/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export * from './contribution-filter';
4343
export * from './nls';
4444
export * from './numbers';
4545
export * from './performance';
46+
export * from './view-column';
4647

4748
import { environment } from '@theia/application-package/lib/environment';
4849
export { environment };
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// *****************************************************************************
2+
// Copyright (C) 2022 STMicroelectronics.
3+
//
4+
// This program and the accompanying materials are made available under the
5+
// terms of the Eclipse Public License v. 2.0 which is available at
6+
// http://www.eclipse.org/legal/epl-2.0.
7+
//
8+
// This Source Code may also be made available under the following Secondary
9+
// Licenses when the conditions for such availability set forth in the Eclipse
10+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
// with the GNU Classpath Exception which is available at
12+
// https://www.gnu.org/software/classpath/license.html.
13+
//
14+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
// *****************************************************************************
16+
17+
/**
18+
* Denotes a column in the editor window.
19+
* Columns are used to show editors side by side.
20+
*/
21+
export enum ViewColumn {
22+
Active = -1,
23+
Beside = -2,
24+
One = 1,
25+
Two = 2,
26+
Three = 3,
27+
Four = 4,
28+
Five = 5,
29+
Six = 6,
30+
Seven = 7,
31+
Eight = 8,
32+
Nine = 9
33+
}

packages/plugin-ext/src/common/plugin-api-rpc.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ export interface TerminalServiceMain {
303303
* Create new Terminal with Terminal options.
304304
* @param options - object with parameters to create new terminal.
305305
*/
306-
$createTerminal(id: string, options: theia.TerminalOptions, isPseudoTerminal?: boolean): Promise<string>;
306+
$createTerminal(id: string, options: theia.TerminalOptions, parentId?: string, isPseudoTerminal?: boolean): Promise<string>;
307307

308308
/**
309309
* Send text to the terminal by id.

packages/plugin-ext/src/main/browser/terminal-main.ts

+21-3
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616

1717
import { interfaces } from '@theia/core/shared/inversify';
1818
import { ApplicationShell, WidgetOpenerOptions } from '@theia/core/lib/browser';
19-
import { TerminalOptions } from '@theia/plugin';
2019
import { CancellationToken } from '@theia/core/shared/vscode-languageserver-protocol';
21-
import { TerminalWidget } from '@theia/terminal/lib/browser/base/terminal-widget';
20+
import { TerminalEditorLocationOptions, TerminalOptions } from '@theia/plugin';
21+
import { TerminalLocation, TerminalWidget } from '@theia/terminal/lib/browser/base/terminal-widget';
2222
import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-service';
2323
import { TerminalServiceMain, TerminalServiceExt, MAIN_RPC_CONTEXT } from '../../common/plugin-api-rpc';
2424
import { RPCProtocol } from '../../common/rpc-protocol';
@@ -122,7 +122,7 @@ export class TerminalServiceMainImpl implements TerminalServiceMain, TerminalLin
122122
terminal.resize(cols, rows);
123123
}
124124

125-
async $createTerminal(id: string, options: TerminalOptions, isPseudoTerminal?: boolean): Promise<string> {
125+
async $createTerminal(id: string, options: TerminalOptions, parentId?: string, isPseudoTerminal?: boolean): Promise<string> {
126126
try {
127127
const terminal = await this.terminals.newTerminal({
128128
id,
@@ -136,6 +136,7 @@ export class TerminalServiceMainImpl implements TerminalServiceMain, TerminalLin
136136
useServerTitle: false,
137137
attributes: options.attributes,
138138
hideFromUser: options.hideFromUser,
139+
location: this.getTerminalLocation(options, parentId),
139140
isPseudoTerminal
140141
});
141142
if (options.message) {
@@ -148,6 +149,23 @@ export class TerminalServiceMainImpl implements TerminalServiceMain, TerminalLin
148149
}
149150
}
150151

152+
protected getTerminalLocation(options: TerminalOptions, parentId?: string): TerminalLocation | TerminalEditorLocationOptions | { parentTerminal: string; } | undefined {
153+
if (typeof options.location === 'number' && Object.values(TerminalLocation).includes(options.location)) {
154+
return options.location;
155+
} else if (options.location && typeof options.location === 'object') {
156+
if ('parentTerminal' in options.location) {
157+
if (!parentId) {
158+
throw new Error('parentTerminal is set but no parentId is provided');
159+
}
160+
return { 'parentTerminal': parentId };
161+
} else {
162+
return options.location;
163+
}
164+
}
165+
166+
return undefined;
167+
}
168+
151169
$sendText(id: string, text: string, addNewLine?: boolean): void {
152170
const terminal = this.terminals.getById(id);
153171
if (terminal) {

packages/plugin-ext/src/plugin/plugin-context.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ import {
152152
TextDocumentChangeReason,
153153
InputBoxValidationSeverity,
154154
TerminalLink,
155+
TerminalLocation,
155156
InlayHint,
156157
InlayHintKind,
157158
InlayHintLabelPart,
@@ -1088,7 +1089,7 @@ export function createAPIFactory(
10881089
notebook: theia.NotebookDocument,
10891090
controller: theia.NotebookController
10901091
): (void | Thenable<void>) { },
1091-
onDidChangeSelectedNotebooks: () => Disposable.create(() => {}),
1092+
onDidChangeSelectedNotebooks: () => Disposable.create(() => { }),
10921093
updateNotebookAffinity: (notebook: theia.NotebookDocument, affinity: theia.NotebookControllerAffinity) => undefined,
10931094
dispose: () => undefined,
10941095
};
@@ -1099,7 +1100,7 @@ export function createAPIFactory(
10991100
) {
11001101
return {
11011102
rendererId,
1102-
onDidReceiveMessage: () => Disposable.create(() => {} ),
1103+
onDidReceiveMessage: () => Disposable.create(() => { }),
11031104
postMessage: () => Promise.resolve({}),
11041105
};
11051106
},
@@ -1284,6 +1285,7 @@ export function createAPIFactory(
12841285
TabInputNotebookDiff: NotebookDiffEditorTabInput,
12851286
TabInputWebview: WebviewEditorTabInput,
12861287
TabInputTerminal: TerminalEditorTabInput,
1288+
TerminalLocation
12871289
};
12881290
};
12891291
}

packages/plugin-ext/src/plugin/terminal-ext.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,22 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
8585
shellArgs: shellArgs
8686
};
8787
}
88-
this.proxy.$createTerminal(id, options, !!pseudoTerminal);
88+
89+
let parentId;
90+
91+
if (options.location && typeof options.location === 'object' && 'parentTerminal' in options.location) {
92+
const parentTerminal = options.location.parentTerminal;
93+
if (parentTerminal instanceof TerminalExtImpl) {
94+
for (const [k, v] of this._terminals) {
95+
if (v === parentTerminal) {
96+
parentId = k;
97+
break;
98+
}
99+
}
100+
}
101+
}
102+
103+
this.proxy.$createTerminal(id, options, parentId, !!pseudoTerminal);
89104

90105
let creationOptions: theia.TerminalOptions | theia.ExtensionTerminalOptions = options;
91106
// make sure to pass ExtensionTerminalOptions as creation options

packages/plugin-ext/src/plugin/types-impl.ts

+5
Original file line numberDiff line numberDiff line change
@@ -1910,6 +1910,11 @@ export class TerminalLink {
19101910
}
19111911
}
19121912

1913+
export enum TerminalLocation {
1914+
Panel = 1,
1915+
Editor = 2
1916+
}
1917+
19131918
@es5ClassCompat
19141919
export class FileDecoration {
19151920

packages/plugin/src/theia.d.ts

+54
Original file line numberDiff line numberDiff line change
@@ -3001,6 +3001,11 @@ export module '@theia/plugin' {
30013001
*/
30023002
message?: string;
30033003

3004+
/**
3005+
* The {@link TerminalLocation} or {@link TerminalEditorLocationOptions} or {@link TerminalSplitLocationOptions} for the terminal.
3006+
*/
3007+
location?: TerminalLocation | TerminalEditorLocationOptions | TerminalSplitLocationOptions;
3008+
30043009
/**
30053010
* Terminal attributes. Can be useful to apply some implementation specific information.
30063011
*/
@@ -3067,6 +3072,11 @@ export module '@theia/plugin' {
30673072
* control it.
30683073
*/
30693074
pty: Pseudoterminal;
3075+
3076+
/**
3077+
* The {@link TerminalLocation} or {@link TerminalEditorLocationOptions} or {@link TerminalSplitLocationOptions} for the terminal.
3078+
*/
3079+
location?: TerminalLocation | TerminalEditorLocationOptions | TerminalSplitLocationOptions;
30703080
}
30713081

30723082
/**
@@ -3207,6 +3217,50 @@ export module '@theia/plugin' {
32073217
constructor(startIndex: number, length: number, tooltip?: string);
32083218
}
32093219

3220+
/**
3221+
* The location of the {@link Terminal}.
3222+
*/
3223+
export enum TerminalLocation {
3224+
/**
3225+
* In the terminal view
3226+
*/
3227+
Panel = 1,
3228+
/**
3229+
* In the editor area
3230+
*/
3231+
Editor = 2,
3232+
}
3233+
3234+
/**
3235+
* Assumes a {@link TerminalLocation} of editor and allows specifying a {@link ViewColumn} and
3236+
* {@link TerminalEditorLocationOptions.preserveFocus preserveFocus } property
3237+
*/
3238+
export interface TerminalEditorLocationOptions {
3239+
/**
3240+
* A view column in which the {@link Terminal terminal} should be shown in the editor area.
3241+
* Use {@link ViewColumn.Active active} to open in the active editor group, other values are
3242+
* adjusted to be `Min(column, columnCount + 1)`, the
3243+
* {@link ViewColumn.Active active}-column is not adjusted. Use
3244+
* {@linkcode ViewColumn.Beside} to open the editor to the side of the currently active one.
3245+
*/
3246+
viewColumn: ViewColumn;
3247+
/**
3248+
* An optional flag that when `true` will stop the {@link Terminal} from taking focus.
3249+
*/
3250+
preserveFocus?: boolean;
3251+
}
3252+
3253+
/**
3254+
* Uses the parent {@link Terminal}'s location for the terminal
3255+
*/
3256+
export interface TerminalSplitLocationOptions {
3257+
/**
3258+
* The parent terminal to split this terminal beside. This works whether the parent terminal
3259+
* is in the panel or the editor area.
3260+
*/
3261+
parentTerminal: Terminal;
3262+
}
3263+
32103264
/**
32113265
* A file decoration represents metadata that can be rendered with a file.
32123266
*/

packages/terminal/src/browser/base/terminal-widget.ts

+22-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
1515
// *****************************************************************************
1616

17-
import { Event } from '@theia/core';
17+
import { Event, ViewColumn } from '@theia/core';
1818
import { BaseWidget } from '@theia/core/lib/browser';
1919
import { CommandLineOptions } from '@theia/process/lib/common/shell-command-builder';
2020
import { TerminalSearchWidget } from '../search/terminal-search-widget';
@@ -30,13 +30,28 @@ export interface TerminalExitStatus {
3030
readonly code: number | undefined;
3131
}
3232

33+
export type TerminalLocationOptions = TerminalLocation | TerminalEditorLocation | TerminalSplitLocation;
34+
35+
export enum TerminalLocation {
36+
Panel = 1,
37+
Editor = 2
38+
}
39+
40+
export interface TerminalEditorLocation {
41+
readonly viewColumn: ViewColumn;
42+
readonly preserveFocus?: boolean;
43+
}
44+
45+
export interface TerminalSplitLocation {
46+
readonly parentTerminal: string;
47+
}
48+
3349
/**
3450
* Terminal UI widget.
3551
*/
3652
export abstract class TerminalWidget extends BaseWidget {
3753

3854
abstract processId: Promise<number>;
39-
4055
/**
4156
* Get the current executable and arguments.
4257
*/
@@ -54,6 +69,9 @@ export abstract class TerminalWidget extends BaseWidget {
5469
/** Terminal widget can be hidden from users until explicitly shown once. */
5570
abstract readonly hiddenFromUser: boolean;
5671

72+
/** The position of the terminal widget. */
73+
abstract readonly location: TerminalLocationOptions;
74+
5775
/** The last CWD assigned to the terminal, useful when attempting getCwdURI on a task terminal fails */
5876
lastCwd: URI;
5977

@@ -211,4 +229,6 @@ export interface TerminalWidgetOptions {
211229
* When enabled the terminal will run the process as normal but not be surfaced to the user until `Terminal.show` is called.
212230
*/
213231
readonly hideFromUser?: boolean;
232+
233+
readonly location?: TerminalLocationOptions;
214234
}

packages/terminal/src/browser/terminal-frontend-contribution.ts

+35-8
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ import {
2525
isOSX,
2626
SelectionService,
2727
Emitter,
28-
Event
28+
Event,
29+
ViewColumn
2930
} from '@theia/core/lib/common';
3031
import {
3132
ApplicationShell, KeybindingContribution, KeyCode, Key, WidgetManager,
@@ -36,7 +37,7 @@ import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/li
3637
import { TERMINAL_WIDGET_FACTORY_ID, TerminalWidgetFactoryOptions, TerminalWidgetImpl } from './terminal-widget-impl';
3738
import { TerminalKeybindingContexts } from './terminal-keybinding-contexts';
3839
import { TerminalService } from './base/terminal-service';
39-
import { TerminalWidgetOptions, TerminalWidget } from './base/terminal-widget';
40+
import { TerminalWidgetOptions, TerminalWidget, TerminalLocation } from './base/terminal-widget';
4041
import { UriAwareCommandHandler } from '@theia/core/lib/common/uri-command-handler';
4142
import { ShellTerminalServerProxy } from '../common/shell-terminal-protocol';
4243
import URI from '@theia/core/lib/common/uri';
@@ -644,20 +645,46 @@ export class TerminalFrontendContribution implements FrontendApplicationContribu
644645

645646
// TODO: reuse WidgetOpenHandler.open
646647
open(widget: TerminalWidget, options?: WidgetOpenerOptions): void {
648+
const area = widget.location === TerminalLocation.Editor ? 'main' : 'bottom';
649+
const widgetOptions: ApplicationShell.WidgetOptions = { area: area, ...options?.widgetOptions };
650+
let preserveFocus = false;
651+
652+
if (typeof widget.location === 'object') {
653+
if ('parentTerminal' in widget.location) {
654+
widgetOptions.ref = this.getById(widget.location.parentTerminal);
655+
widgetOptions.mode = 'split-right';
656+
} else if ('viewColumn' in widget.location) {
657+
preserveFocus = widget.location.preserveFocus ?? false;
658+
switch (widget.location.viewColumn) {
659+
case ViewColumn.Active:
660+
widgetOptions.ref = this.shell.currentWidget;
661+
widgetOptions.mode = 'tab-after';
662+
break;
663+
case ViewColumn.Beside:
664+
widgetOptions.ref = this.shell.currentWidget;
665+
widgetOptions.mode = 'split-right';
666+
break;
667+
default:
668+
widgetOptions.area = 'main';
669+
const mainAreaTerminals = this.shell.getWidgets('main').filter(w => w instanceof TerminalWidget && w.isVisible);
670+
const column = Math.min(widget.location.viewColumn, mainAreaTerminals.length);
671+
widgetOptions.mode = widget.location.viewColumn <= mainAreaTerminals.length ? 'split-left' : 'split-right';
672+
widgetOptions.ref = mainAreaTerminals[column - 1];
673+
}
674+
}
675+
}
676+
647677
const op: WidgetOpenerOptions = {
648678
mode: 'activate',
649679
...options,
650-
widgetOptions: {
651-
area: 'bottom',
652-
...(options && options.widgetOptions)
653-
}
680+
widgetOptions: widgetOptions
654681
};
655682
if (!widget.isAttached) {
656683
this.shell.addWidget(widget, op.widgetOptions);
657684
}
658-
if (op.mode === 'activate') {
685+
if (op.mode === 'activate' && !preserveFocus) {
659686
this.shell.activateWidget(widget.id);
660-
} else if (op.mode === 'reveal') {
687+
} else if (op.mode === 'reveal' || preserveFocus) {
661688
this.shell.revealWidget(widget.id);
662689
}
663690
}

0 commit comments

Comments
 (0)