Skip to content

Commit

Permalink
Support for terminal profiles. (#12066)
Browse files Browse the repository at this point in the history
Support for terminal profiles. Fixes #11503

- UI and service to manage terminal profiles
- Handle profiles in preferences according to VS Code schema
- API and contribution markup for contributing profiles and activation
event handling

contributed on behalf of STMicroelectronics

Signed-off-by: Thomas Mäder <t.s.maeder@gmail.com>
  • Loading branch information
tsmaeder authored Jan 19, 2023
1 parent 1b5ff9e commit 945a97b
Show file tree
Hide file tree
Showing 25 changed files with 939 additions and 66 deletions.
12 changes: 12 additions & 0 deletions packages/core/i18n/nls.de.json
Original file line number Diff line number Diff line change
Expand Up @@ -447,12 +447,24 @@
"confirmCloseNever": "Niemals bestätigen.",
"enableCopy": "Aktivieren von ctrl-c (cmd-c unter macOS) zum Kopieren von markiertem Text",
"enablePaste": "Aktivieren von ctrl-v (cmd-v unter macOS) zum Einfügen aus der Zwischenablage",
"profileNew": "Neues Terminal (mit Profil)...",
"profileDefault": "Standardprofil wählen...",
"selectProfile": "Wählen Sie ein Profil für das neue Terminal",
"shellArgsLinux": "Die Befehlszeilenargumente, die im Linux-Terminal zu verwenden sind.",
"shellArgsOsx": "Die Befehlszeilenargumente, die im macOS-Terminal zu verwenden sind.",
"shellArgsWindows": "Die Befehlszeilenargumente, die im Windows-Terminal zu verwenden sind.",
"shellLinux": "Der Pfad der Shell, die das Terminal unter Linux verwendet (Standard: '{0}'}).",
"shellOsx": "Der Pfad der Shell, die das Terminal unter macOS verwendet (Standard: '{0}'}).",
"shellWindows": "Der Pfad der Shell, die das Terminal unter Windows verwendet. (Standard: '{0}').",
"shell.deprecated": "Dies ist veraltet, neu können Sie Ihre Shell konfigurieren, indem Sie ein Profil unter 'terminal.integrated.profiles.{0}' anlegen und dessen Namen in 'terminal.integrated.defaultProfile.{0}' als Standard setzen.",
"defaultProfile": "Das Standardprofil unter {0}",
"profiles": "Die Profile welche zur Erzeugung eines Terminals verwendet werden können. Setzen Sie den Pfad von Hand mit optionalen Parametern.\n\nSezen Sie ein Profile auf `null` um es zu verbergen, z.B.: `{0}: null`.",
"profilePath": "Der Pfad der Shell, den dieses Profil benutzt.",
"profileSource": "Eine Profilquelle, die Shellpfade automatisch erkennt. Unübliche Installationsorte werden nicht unterstützt und müssen manuell erfasst werden",
"profileArgs": "Die Shellparameter, welche dieses Profil verwendet.",
"profileEnv": "Ein Objekt mit Umgebungsvariablen die zum Terminalprozess hinzugefügt werden. Setzen Sie Variablen auf `null` um sie aus der Basisumgebung zu löschen",
"profileIcon": "Eine codicon ID zur Verwendung mit diesem Terminal. \nterminal-tmux:\"$(terminal-tmux)\"",
"profileColor": "ID einer Terminal-Themenfarbe zur Verwendung mit diesem Terminal.",
"terminate": "Ende",
"terminateActive": "Möchten Sie die aktive Terminalsitzung beenden?",
"terminateActiveMultiple": "Sollen die {0} aktiven Terminalsitzungen beendet werden?"
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/common/uri.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export class URI {
return new URI(Uri.revive(components));
}

public static fromFilePath(path: string): URI {
return new URI(Uri.file(path));
}

private readonly codeUri: Uri;
private _path: Path | undefined;

Expand Down
1 change: 1 addition & 0 deletions packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ export interface CommandRegistryExt {
}

export interface TerminalServiceExt {
$startProfile(providerId: string, cancellationToken: theia.CancellationToken): Promise<string>;
$terminalCreated(id: string, name: string): void;
$terminalNameChanged(id: string, name: string): void;
$terminalOpened(id: string, processId: number, terminalId: number, cols: number, rows: number): void;
Expand Down
18 changes: 18 additions & 0 deletions packages/plugin-ext/src/common/plugin-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,17 @@ export interface PluginPackageContribution {
jsonValidation?: PluginJsonValidationContribution[];
resourceLabelFormatters?: ResourceLabelFormatter[];
localizations?: PluginPackageLocalization[];
terminal?: PluginPackageTerminal;
}

export interface PluginPackageTerminalProfile {
title: string,
id: string,
icon?: string
}

export interface PluginPackageTerminal {
profiles: PluginPackageTerminalProfile[]
}

export interface PluginPackageLocalization {
Expand Down Expand Up @@ -555,6 +566,13 @@ export interface PluginContribution {
problemPatterns?: ProblemPatternContribution[];
resourceLabelFormatters?: ResourceLabelFormatter[];
localizations?: Localization[];
terminalProfiles?: TerminalProfile[];
}

export interface TerminalProfile {
title: string,
id: string,
icon?: string
}

export interface Localization {
Expand Down
4 changes: 4 additions & 0 deletions packages/plugin-ext/src/hosted/browser/hosted-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,10 @@ export class HostedPluginSupport {
return this.activateByEvent(`onFileSystem:${event.scheme}`);
}

activateByTerminalProfile(profileId: string): Promise<void> {
return this.activateByEvent(`onTerminalProfile:${profileId}`);
}

protected ensureFileSystemActivation(event: FileSystemProviderActivationEvent): void {
event.waitUntil(this.activateByFileSystem(event));
}
Expand Down
16 changes: 15 additions & 1 deletion packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ import {
Localization,
PluginPackageTranslation,
Translation,
PluginIdentifiers
PluginIdentifiers,
TerminalProfile
} from '../../../common/plugin-protocol';
import * as fs from 'fs';
import * as path from 'path';
Expand Down Expand Up @@ -358,9 +359,22 @@ export class TheiaPluginScanner implements PluginScanner {
console.error(`Could not read '${rawPlugin.name}' contribution 'localizations'.`, rawPlugin.contributes.colors, err);
}

try {
contributions.terminalProfiles = this.readTerminals(rawPlugin);
} catch (err) {
console.error(`Could not read '${rawPlugin.name}' contribution 'terminals'.`, rawPlugin.contributes.terminal, err);
}

return contributions;
}

protected readTerminals(pck: PluginPackage): TerminalProfile[] | undefined {
if (!pck?.contributes?.terminal?.profiles) {
return undefined;
}
return pck.contributes.terminal.profiles.filter(profile => profile.id && profile.title);
}

protected readLocalizations(pck: PluginPackage): Localization[] | undefined {
if (!pck.contributes || !pck.contributes.localizations) {
return undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ import { PluginIconThemeService } from './plugin-icon-theme-service';
import { ContributionProvider } from '@theia/core/lib/common';
import * as monaco from '@theia/monaco-editor-core';
import { ThemeIcon } from '@theia/monaco-editor-core/esm/vs/platform/theme/common/themeService';
import { ContributedTerminalProfileStore, TerminalProfileStore } from '@theia/terminal/lib/browser/terminal-profile-service';
import { TerminalWidget } from '@theia/terminal/lib/browser/base/terminal-widget';
import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-service';
import { PluginTerminalRegistry } from './plugin-terminal-registry';

@injectable()
export class PluginContributionHandler {
Expand Down Expand Up @@ -106,6 +110,15 @@ export class PluginContributionHandler {
@inject(PluginIconThemeService)
protected readonly iconThemeService: PluginIconThemeService;

@inject(TerminalService)
protected readonly terminalService: TerminalService;

@inject(PluginTerminalRegistry)
protected readonly pluginTerminalRegistry: PluginTerminalRegistry;

@inject(ContributedTerminalProfileStore)
protected readonly contributedProfileStore: TerminalProfileStore;

@inject(ContributionProvider) @named(LabelProviderContribution)
protected readonly contributionProvider: ContributionProvider<LabelProviderContribution>;

Expand Down Expand Up @@ -356,6 +369,28 @@ export class PluginContributionHandler {
}
}

const self = this;
if (contributions.terminalProfiles) {
for (const profile of contributions.terminalProfiles) {
pushContribution(`terminalProfiles.${profile.id}`, () => {
this.contributedProfileStore.registerTerminalProfile(profile.title, {
async start(): Promise<TerminalWidget> {
const terminalId = await self.pluginTerminalRegistry.start(profile.id);
const result = self.terminalService.getById(terminalId);
if (!result) {
throw new Error(`Error starting terminal from profile ${profile.id}`);
}
return result;

}
});
return Disposable.create(() => {
this.contributedProfileStore.unregisterTerminalProfile(profile.id);
});
});
}
}

return toDispose;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ import { bindTreeViewDecoratorUtilities, TreeViewDecoratorService } from './view
import { CodeEditorWidgetUtil } from './menus/vscode-theia-menu-mappings';
import { PluginMenuCommandAdapter } from './menus/plugin-menu-command-adapter';
import './theme-icon-override';
import { PluginTerminalRegistry } from './plugin-terminal-registry';

export default new ContainerModule((bind, unbind, isBound, rebind) => {

Expand Down Expand Up @@ -240,4 +241,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {

bind(PluginAuthenticationServiceImpl).toSelf().inSingletonScope();
rebind(AuthenticationService).toService(PluginAuthenticationServiceImpl);

bind(PluginTerminalRegistry).toSelf().inSingletonScope();
});
27 changes: 27 additions & 0 deletions packages/plugin-ext/src/main/browser/plugin-terminal-registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// *****************************************************************************
// Copyright (C) 2022 STMicroelectronics and others.
//
// 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
// *****************************************************************************

import { injectable } from '@theia/core/shared/inversify';

@injectable()
export class PluginTerminalRegistry {

startCallback: (id: string) => Promise<string>;

start(profileId: string): Promise<string> {
return this.startCallback(profileId);
}
}
61 changes: 35 additions & 26 deletions packages/plugin-ext/src/main/browser/terminal-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

import { interfaces } from '@theia/core/shared/inversify';
import { ApplicationShell, WidgetOpenerOptions } from '@theia/core/lib/browser';
import { CancellationToken } from '@theia/core/shared/vscode-languageserver-protocol';
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';
Expand All @@ -28,13 +27,18 @@ import { ShellTerminalServerProxy } from '@theia/terminal/lib/common/shell-termi
import { TerminalLink, TerminalLinkProvider } from '@theia/terminal/lib/browser/terminal-link-provider';
import { URI } from '@theia/core/lib/common/uri';
import { getIconClass } from '../../plugin/terminal-ext';
import { PluginTerminalRegistry } from './plugin-terminal-registry';
import { CancellationToken } from '@theia/core';
import { HostedPluginSupport } from '../../hosted/browser/hosted-plugin';

/**
* Plugin api service allows working with terminal emulator.
*/
export class TerminalServiceMainImpl implements TerminalServiceMain, TerminalLinkProvider, Disposable {

private readonly terminals: TerminalService;
private readonly pluginTerminalRegistry: PluginTerminalRegistry;
private readonly hostedPluginSupport: HostedPluginSupport;
private readonly shell: ApplicationShell;
private readonly extProxy: TerminalServiceExt;
private readonly shellTerminalServer: ShellTerminalServerProxy;
Expand All @@ -44,6 +48,8 @@ export class TerminalServiceMainImpl implements TerminalServiceMain, TerminalLin

constructor(rpc: RPCProtocol, container: interfaces.Container) {
this.terminals = container.get(TerminalService);
this.pluginTerminalRegistry = container.get(PluginTerminalRegistry);
this.hostedPluginSupport = container.get(HostedPluginSupport);
this.shell = container.get(ApplicationShell);
this.shellTerminalServer = container.get(ShellTerminalServerProxy);
this.extProxy = rpc.getProxy(MAIN_RPC_CONTEXT.TERMINAL_EXT);
Expand All @@ -59,9 +65,16 @@ export class TerminalServiceMainImpl implements TerminalServiceMain, TerminalLin
this.extProxy.$initEnvironmentVariableCollections(serializedCollections);
}

this.pluginTerminalRegistry.startCallback = id => this.startProfile(id);

container.bind(TerminalLinkProvider).toDynamicValue(() => this);
}

async startProfile(id: string): Promise<string> {
await this.hostedPluginSupport.activateByTerminalProfile(id);
return this.extProxy.$startProfile(id, CancellationToken.None);
}

$setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: SerializableEnvironmentVariableCollection | undefined): void {
if (collection) {
this.shellTerminalServer.setCollection(extensionIdentifier, persistent, collection);
Expand Down Expand Up @@ -124,32 +137,28 @@ export class TerminalServiceMainImpl implements TerminalServiceMain, TerminalLin
}

async $createTerminal(id: string, options: TerminalOptions, parentId?: string, isPseudoTerminal?: boolean): Promise<string> {
try {
const terminal = await this.terminals.newTerminal({
id,
title: options.name,
iconClass: getIconClass(options),
shellPath: options.shellPath,
shellArgs: options.shellArgs,
cwd: options.cwd ? new URI(options.cwd) : undefined,
env: options.env,
strictEnv: options.strictEnv,
destroyTermOnClose: true,
useServerTitle: false,
attributes: options.attributes,
hideFromUser: options.hideFromUser,
location: this.getTerminalLocation(options, parentId),
isPseudoTerminal,
isTransient: options.isTransient
});
if (options.message) {
terminal.writeLine(options.message);
}
terminal.start();
return terminal.id;
} catch (error) {
throw new Error('Failed to create terminal. Cause: ' + error);
const terminal = await this.terminals.newTerminal({
id,
title: options.name,
iconClass: getIconClass(options),
shellPath: options.shellPath,
shellArgs: options.shellArgs,
cwd: options.cwd ? new URI(options.cwd) : undefined,
env: options.env,
strictEnv: options.strictEnv,
destroyTermOnClose: true,
useServerTitle: false,
attributes: options.attributes,
hideFromUser: options.hideFromUser,
location: this.getTerminalLocation(options, parentId),
isPseudoTerminal,
isTransient: options.isTransient
});
if (options.message) {
terminal.writeLine(options.message);
}
terminal.start();
return terminal.id;
}

protected getTerminalLocation(options: TerminalOptions, parentId?: string): TerminalLocation | TerminalEditorLocationOptions | { parentTerminal: string; } | undefined {
Expand Down
5 changes: 5 additions & 0 deletions packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ import {
InputBoxValidationSeverity,
TerminalLink,
TerminalLocation,
TerminalProfile,
InlayHint,
InlayHintKind,
InlayHintLabelPart,
Expand Down Expand Up @@ -548,6 +549,9 @@ export function createAPIFactory(
registerTerminalLinkProvider(provider: theia.TerminalLinkProvider): theia.Disposable {
return terminalExt.registerTerminalLinkProvider(provider);
},
registerTerminalProfileProvider(id: string, provider: theia.TerminalProfileProvider): theia.Disposable {
return terminalExt.registerTerminalProfileProvider(id, provider);
},
get activeColorTheme(): theia.ColorTheme {
return themingExt.activeColorTheme;
},
Expand Down Expand Up @@ -1252,6 +1256,7 @@ export function createAPIFactory(
SourceControlInputBoxValidationType,
FileDecoration,
TerminalLink,
TerminalProfile,
CancellationError,
ExtensionMode,
LinkedEditingRanges,
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-ext/src/plugin/plugin-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export class PluginManagerExtImpl implements PluginManagerExt, PluginManager {
'workspaceContains',
'onView',
'onUri',
'onTerminalProfile',
'onWebviewPanel',
'onFileSystem',
'onCustomEditor',
Expand Down
Loading

0 comments on commit 945a97b

Please sign in to comment.