Skip to content

Commit

Permalink
[vscode] Support EnvironmentVariableCollection description #12696
Browse files Browse the repository at this point in the history
* show information in UI

Contributed on behalf of STMicroelectronics

Signed-off-by: Johannes Faltermeier <jfaltermeier@eclipsesource.com>
  • Loading branch information
jfaltermeier committed Aug 17, 2023
1 parent f45ef07 commit 60625a5
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 14 deletions.
3 changes: 2 additions & 1 deletion packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,8 @@ export interface TerminalServiceMain {
*/
$disposeByTerminalId(id: number, waitOnExit?: boolean | string): void;

$setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: SerializableEnvironmentVariableCollection | undefined): void;
$setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: SerializableEnvironmentVariableCollection | undefined,
description: string | MarkdownString | undefined): void;

/**
* Set the terminal widget name.
Expand Down
7 changes: 4 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,7 +16,7 @@

import { interfaces } from '@theia/core/shared/inversify';
import { ApplicationShell, WidgetOpenerOptions } from '@theia/core/lib/browser';
import { TerminalEditorLocationOptions, TerminalOptions } from '@theia/plugin';
import { MarkdownString, 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';
Expand Down Expand Up @@ -75,9 +75,10 @@ export class TerminalServiceMainImpl implements TerminalServiceMain, TerminalLin
return this.extProxy.$startProfile(id, CancellationToken.None);
}

$setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: SerializableEnvironmentVariableCollection | undefined): void {
$setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: SerializableEnvironmentVariableCollection | undefined,
description: string | MarkdownString | undefined): void {
if (collection) {
this.shellTerminalServer.setCollection(extensionIdentifier, persistent, collection);
this.shellTerminalServer.setCollection(extensionIdentifier, persistent, collection, description);
} else {
this.shellTerminalServer.deleteCollection(extensionIdentifier);
}
Expand Down
23 changes: 18 additions & 5 deletions packages/plugin-ext/src/plugin/terminal-ext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,18 @@
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { UUID } from '@theia/core/shared/@phosphor/coreutils';
import { Terminal, TerminalOptions, PseudoTerminalOptions, ExtensionTerminalOptions, TerminalState, MarkdownString } from '@theia/plugin';
import { Terminal, TerminalOptions, PseudoTerminalOptions, ExtensionTerminalOptions, TerminalState } from '@theia/plugin';
import { TerminalServiceExt, TerminalServiceMain, PLUGIN_RPC_CONTEXT } from '../common/plugin-api-rpc';
import { RPCProtocol } from '../common/rpc-protocol';
import { Event, Emitter } from '@theia/core/lib/common/event';
import { Deferred } from '@theia/core/lib/common/promise-util';
import * as theia from '@theia/plugin';
import * as Converter from './type-converters';
import { Disposable, EnvironmentVariableMutatorType, TerminalExitReason, ThemeIcon } from './types-impl';
import { SerializableEnvironmentVariableCollection } from '@theia/terminal/lib/common/base-terminal-protocol';
import { ProvidedTerminalLink } from '../common/plugin-api-rpc-model';
import { ThemeIcon as MonacoThemeIcon } from '@theia/monaco-editor-core/esm/vs/platform/theme/common/themeService';
import { MarkdownString as MarkdownStringDTO } from '@theia/core/lib/common/markdown-rendering';

export function getIconUris(iconPath: theia.TerminalOptions['iconPath']): { id: string } | undefined {
if (ThemeIcon.is(iconPath)) {
Expand Down Expand Up @@ -313,7 +315,18 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {

private syncEnvironmentVariableCollection(extensionIdentifier: string, collection: EnvironmentVariableCollection): void {
const serialized = [...collection.map.entries()];
this.proxy.$setEnvironmentVariableCollection(extensionIdentifier, collection.persistent, serialized.length === 0 ? undefined : serialized);
this.proxy.$setEnvironmentVariableCollection(extensionIdentifier, collection.persistent, serialized.length === 0 ? undefined : serialized,
this.descriptionToDTO(collection.description));
}

private descriptionToDTO(value: string | theia.MarkdownString | undefined): string | MarkdownStringDTO | undefined {
if (value === undefined) {
return undefined;
} else if (typeof value === 'string') {
return value;
} else {
return Converter.fromMarkdown(value);
}
}

private setEnvironmentVariableCollection(extensionIdentifier: string, collection: EnvironmentVariableCollection): void {
Expand All @@ -339,11 +352,11 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {

export class EnvironmentVariableCollection implements theia.EnvironmentVariableCollection {
readonly map: Map<string, theia.EnvironmentVariableMutator> = new Map();
private _description?: string | MarkdownString;
private _description?: string | theia.MarkdownString;
private _persistent: boolean = true;

public get description(): string | MarkdownString | undefined { return this._description; }
public set description(value: string | MarkdownString | undefined) {
public get description(): string | theia.MarkdownString | undefined { return this._description; }
public set description(value: string | theia.MarkdownString | undefined) {
this._description = value;
this.onDidChangeCollectionEmitter.fire();
}
Expand Down
4 changes: 4 additions & 0 deletions packages/terminal/src/browser/base/terminal-widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { CommandLineOptions } from '@theia/process/lib/common/shell-command-buil
import { TerminalSearchWidget } from '../search/terminal-search-widget';
import { TerminalProcessInfo, TerminalExitReason } from '../../common/base-terminal-protocol';
import URI from '@theia/core/lib/common/uri';
import { MarkdownString } from '@theia/core/src/common/markdown-rendering/markdown-string';

export interface TerminalDimensions {
cols: number;
Expand Down Expand Up @@ -58,6 +59,9 @@ export abstract class TerminalWidget extends BaseWidget {
*/
abstract processInfo: Promise<TerminalProcessInfo>;

/** The extensions contributing to the environment of this terminal */
abstract contributingExtensions: Promise<Map<string, string | MarkdownString | undefined>>;

/** Terminal kind that indicates whether a terminal is created by a user or by some extension for a user */
abstract readonly kind: 'user' | string;

Expand Down
18 changes: 16 additions & 2 deletions packages/terminal/src/browser/terminal-frontend-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
import {
ApplicationShell, KeybindingContribution, KeyCode, Key, WidgetManager, PreferenceService,
KeybindingRegistry, LabelProvider, WidgetOpenerOptions, StorageService, QuickInputService,
codicon, CommonCommands, FrontendApplicationContribution, OnWillStopAction, Dialog, ConfirmDialog, FrontendApplication, PreferenceScope, Widget
codicon, CommonCommands, FrontendApplicationContribution, OnWillStopAction, Dialog, ConfirmDialog, FrontendApplication, PreferenceScope, Widget, HoverService
} from '@theia/core/lib/browser';
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import { TERMINAL_WIDGET_FACTORY_ID, TerminalWidgetFactoryOptions, TerminalWidgetImpl } from './terminal-widget-impl';
Expand Down Expand Up @@ -60,6 +60,8 @@ import { nls } from '@theia/core/lib/common/nls';
import { Profiles, TerminalPreferences } from './terminal-preferences';
import { ShellTerminalProfile } from './shell-terminal-profile';
import { VariableResolverService } from '@theia/variable-resolver/lib/browser';
import { TerminalInfoToolbarItem } from './terminal-info-toolbar-item';
import { MarkdownRenderer, MarkdownRendererFactory } from '@theia/core/lib/browser/markdown-rendering/markdown-renderer';

export namespace TerminalMenus {
export const TERMINAL = [...MAIN_MENU_BAR, '7_terminal'];
Expand Down Expand Up @@ -216,6 +218,17 @@ export class TerminalFrontendContribution implements FrontendApplicationContribu
@inject(TerminalPreferences)
protected terminalPreferences: TerminalPreferences;

@inject(HoverService)
protected readonly hoverService: HoverService;

@inject(MarkdownRendererFactory) protected readonly markdownRendererFactory: MarkdownRendererFactory;

protected _markdownRenderer: MarkdownRenderer | undefined;
protected get markdownRenderer(): MarkdownRenderer {
this._markdownRenderer ||= this.markdownRendererFactory();
return this._markdownRenderer;
}

protected mergePreferencesPromise: Promise<void> = Promise.resolve();

protected readonly onDidCreateTerminalEmitter = new Emitter<TerminalWidget>();
Expand Down Expand Up @@ -250,7 +263,7 @@ export class TerminalFrontendContribution implements FrontendApplicationContribu
this.storageService.getData<string>(ENVIRONMENT_VARIABLE_COLLECTIONS_KEY).then(data => {
if (data) {
const collectionsJson: SerializableExtensionEnvironmentVariableCollection[] = JSON.parse(data);
collectionsJson.forEach(c => this.shellTerminalServer.setCollection(c.extensionIdentifier, true, c.collection));
collectionsJson.forEach(c => this.shellTerminalServer.setCollection(c.extensionIdentifier, true, c.collection, undefined));
}
});
});
Expand Down Expand Up @@ -731,6 +744,7 @@ export class TerminalFrontendContribution implements FrontendApplicationContribu
}

registerToolbarItems(toolbar: TabBarToolbarRegistry): void {
toolbar.registerItem(new TerminalInfoToolbarItem(this.hoverService, () => this.currentTerminal, this.markdownRenderer));
toolbar.registerItem({
id: TerminalCommands.SPLIT.id,
command: TerminalCommands.SPLIT.id,
Expand Down
112 changes: 112 additions & 0 deletions packages/terminal/src/browser/terminal-info-toolbar-item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// *****************************************************************************
// Copyright (C) 2023 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-only WITH Classpath-exception-2.0
// *****************************************************************************
import { ReactTabBarToolbarItem } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import { ReactNode } from '@theia/core/shared/react';
import React = require('@theia/core/shared/react');
import { HoverService } from '@theia/core/lib/browser';
import { TerminalWidget } from './base/terminal-widget';
import { MarkdownRenderer } from '@theia/core/lib/browser/markdown-rendering/markdown-renderer';
import { MarkdownStringImpl } from '@theia/core/lib/common/markdown-rendering/markdown-string';
import { DisposableCollection } from '@theia/core';

export class TerminalInfoToolbarItem implements ReactTabBarToolbarItem {
readonly id = 'terminal:info';

protected toDispose = new DisposableCollection();

constructor(
protected readonly hoverService: HoverService,
protected readonly terminal: () => TerminalWidget | undefined,
protected readonly markdownRenderer: MarkdownRenderer
) {}

render(): ReactNode {
return (
<div
id={this.id}
className='codicon codicon-terminal-bash action-label'
onMouseEnter={e => this.onMouseEnter(e)}
onMouseLeave={this.onMouseEnter}
></div>
);
}

protected async onMouseEnter(
event: React.MouseEvent<HTMLElement, MouseEvent>
): Promise<void> {
const currentTarget = event.currentTarget;
const currentTerminal = this.terminal();
if (currentTerminal) {
const extensions = await currentTerminal.contributingExtensions;
const processId = await currentTerminal.processId;
const processInfo = await currentTerminal.processInfo;

const mainDiv = document.createElement('div');

const pid = document.createElement('div');
pid.textContent = 'Process ID: ' + processId;
mainDiv.appendChild(pid);

const commandLine = document.createElement('div');
commandLine.textContent =
'Command line: ' +
processInfo.executable +
' ' +
processInfo.arguments.join(' ');
mainDiv.appendChild(commandLine);

mainDiv.appendChild(document.createElement('hr'));

const header = document.createElement('div');
header.textContent =
'The following extensions have contributed to this terminal\'s environment:';
mainDiv.appendChild(header);

const list = document.createElement('ul');
mainDiv.appendChild(list);

extensions.forEach((value, key) => {
const item = document.createElement('li');
let markdown;
if (value === undefined) {
markdown = new MarkdownStringImpl('');
markdown.appendText(key);
} else if (typeof value === 'string') {
markdown = new MarkdownStringImpl('');
markdown.appendText(key + ': ' + value);
} else {
markdown = new MarkdownStringImpl('', value);
markdown.appendText(key + ': ');
markdown.appendMarkdown(value.value);
}
const result = this.markdownRenderer.render(markdown);
this.toDispose.push(result);
item.appendChild(result.element);
list.appendChild(item);
});

this.hoverService.requestHover({
content: mainDiv,
target: currentTarget,
position: 'right',
});
}
}

protected async onMouseLeave(): Promise<void> {
this.toDispose.dispose();
}
}
8 changes: 8 additions & 0 deletions packages/terminal/src/browser/terminal-widget-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { Key } from '@theia/core/lib/browser/keys';
import { nls } from '@theia/core/lib/common/nls';
import { TerminalMenus } from './terminal-frontend-contribution';
import debounce = require('p-debounce');
import { MarkdownString } from '@theia/core/lib/common/markdown-rendering/markdown-string';

export const TERMINAL_WIDGET_FACTORY_ID = 'terminal';

Expand Down Expand Up @@ -422,6 +423,13 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget
return this.shellTerminalServer.getProcessInfo(this.terminalId);
}

get contributingExtensions(): Promise<Map<string, string | MarkdownString | undefined>> {
if (!IBaseTerminalServer.validateId(this.terminalId)) {
return Promise.reject(new Error('terminal is not started'));
}
return this.shellTerminalServer.getContributingExtensions(this.terminalId);
}

get terminalId(): number {
return this._terminalId;
}
Expand Down
5 changes: 4 additions & 1 deletion packages/terminal/src/common/base-terminal-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import { RpcServer } from '@theia/core/lib/common/messaging/proxy-factory';
import { Disposable } from '@theia/core';
import { MarkdownString } from '@theia/core/lib/common/markdown-rendering/markdown-string';

export interface TerminalProcessInfo {
executable: string
Expand All @@ -28,6 +29,7 @@ export interface IBaseTerminalServer extends RpcServer<IBaseTerminalClient> {
create(IBaseTerminalServerOptions: object): Promise<number>;
getProcessId(id: number): Promise<number>;
getProcessInfo(id: number): Promise<TerminalProcessInfo>;
getContributingExtensions(id: number): Promise<Map<string, string | MarkdownString | undefined>>;
getCwdURI(id: number): Promise<string>;
resize(id: number, cols: number, rows: number): Promise<void>;
attach(id: number): Promise<number>;
Expand All @@ -48,7 +50,7 @@ export interface IBaseTerminalServer extends RpcServer<IBaseTerminalClient> {
/**
* Sets an extension's environment variable collection.
*/
setCollection(extensionIdentifier: string, persistent: boolean, collection: SerializableEnvironmentVariableCollection): void;
setCollection(extensionIdentifier: string, persistent: boolean, collection: SerializableEnvironmentVariableCollection, description: string | MarkdownString | undefined): void;
/**
* Deletes an extension's environment variable collection.
*/
Expand Down Expand Up @@ -154,6 +156,7 @@ export interface EnvironmentVariableCollection {

export interface EnvironmentVariableCollectionWithPersistence extends EnvironmentVariableCollection {
readonly persistent: boolean;
readonly description: string | MarkdownString | undefined;
}

export enum EnvironmentVariableMutatorType {
Expand Down
17 changes: 15 additions & 2 deletions packages/terminal/src/node/base-terminal-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
} from '../common/base-terminal-protocol';
import { TerminalProcess, ProcessManager, TaskTerminalProcess } from '@theia/process/lib/node';
import { ShellProcess } from './shell-process';
import { MarkdownString } from '@theia/core/lib/common/markdown-rendering/markdown-string';

@injectable()
export abstract class BaseTerminalServer implements IBaseTerminalServer {
Expand Down Expand Up @@ -100,6 +101,18 @@ export abstract class BaseTerminalServer implements IBaseTerminalServer {
};
}

async getContributingExtensions(id: number): Promise<Map<string, string | MarkdownString | undefined>> {
const terminal = this.processManager.get(id);
if (!(terminal instanceof TerminalProcess)) {
throw new Error(`terminal "${id}" does not exist`);
}
const result = new Map<string, string | MarkdownString | undefined>();
this.collections.forEach((value, key) => {
result.set(key, value.description);
});
return result;
}

async getCwdURI(id: number): Promise<string> {
const terminal = this.processManager.get(id);
if (!(terminal instanceof TerminalProcess)) {
Expand Down Expand Up @@ -176,8 +189,8 @@ export abstract class BaseTerminalServer implements IBaseTerminalServer {
*--------------------------------------------------------------------------------------------*/
// some code copied and modified from https://github.com/microsoft/vscode/blob/1.49.0/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts

setCollection(extensionIdentifier: string, persistent: boolean, collection: SerializableEnvironmentVariableCollection): void {
const translatedCollection = { persistent, map: new Map<string, EnvironmentVariableMutator>(collection) };
setCollection(extensionIdentifier: string, persistent: boolean, collection: SerializableEnvironmentVariableCollection, description: string | MarkdownString | undefined): void {
const translatedCollection = { persistent, description, map: new Map<string, EnvironmentVariableMutator>(collection) };
this.collections.set(extensionIdentifier, translatedCollection);
this.updateCollections();
}
Expand Down

0 comments on commit 60625a5

Please sign in to comment.