Skip to content

Commit

Permalink
[vscode] Support EnvironmentVariableCollection description #12696 (#1…
Browse files Browse the repository at this point in the history
…2838)

* add new field to EnvironmentVariableCollection and implement interface
* add description to SerializableExtensionEnvironmentVariableCollection and use as DTO in $setEnvironmentVariableCollection
* add fromMarkdownOrString method to converter
* allow widgets to customize the enhanced preview node
* implement enhanced preview for terminal widget

Contributed on behalf of STMicroelectronics

Signed-off-by: Johannes Faltermeier <jfaltermeier@eclipsesource.com>
  • Loading branch information
jfaltermeier authored Aug 25, 2023
1 parent 1f94559 commit 5720724
Show file tree
Hide file tree
Showing 12 changed files with 164 additions and 16 deletions.
9 changes: 8 additions & 1 deletion packages/core/src/browser/shell/tab-bars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { Root, createRoot } from 'react-dom/client';
import { SelectComponent } from '../widgets/select-component';
import { createElement } from 'react';
import { PreviewableWidget } from '../widgets/previewable-widget';
import { EnhancedPreviewWidget } from '../widgets/enhanced-preview-widget';

/** The class name added to hidden content nodes, which are required to render vertical side bars. */
const HIDDEN_CONTENT_CLASS = 'theia-TabBar-hidden-content';
Expand Down Expand Up @@ -504,7 +505,13 @@ export class TabBarRenderer extends TabBar.Renderer {
labelElement.classList.add('theia-horizontal-tabBar-hover-title');
labelElement.textContent = title.label;
hoverBox.append(labelElement);
if (title.caption) {
const widget = title.owner;
if (EnhancedPreviewWidget.is(widget)) {
const enhancedPreviewNode = widget.getEnhancedPreviewNode();
if (enhancedPreviewNode) {
hoverBox.appendChild(enhancedPreviewNode);
}
} else if (title.caption) {
const captionElement = document.createElement('p');
captionElement.classList.add('theia-horizontal-tabBar-hover-caption');
captionElement.textContent = title.caption;
Expand Down
27 changes: 27 additions & 0 deletions packages/core/src/browser/widgets/enhanced-preview-widget.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// *****************************************************************************
// 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 { isFunction, isObject } from '../../common';

export interface EnhancedPreviewWidget {
getEnhancedPreviewNode(): Node | undefined;
}

export namespace EnhancedPreviewWidget {
export function is(arg: unknown): arg is EnhancedPreviewWidget {
return isObject<EnhancedPreviewWidget>(arg) && isFunction(arg.getEnhancedPreviewNode);
}
}
4 changes: 2 additions & 2 deletions packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ import type {
TimelineChangeEvent,
TimelineProviderDescriptor
} from '@theia/timeline/lib/common/timeline-model';
import { SerializableEnvironmentVariableCollection } from '@theia/terminal/lib/common/base-terminal-protocol';
import { SerializableEnvironmentVariableCollection, SerializableExtensionEnvironmentVariableCollection } from '@theia/terminal/lib/common/base-terminal-protocol';
import { ThemeType } from '@theia/core/lib/common/theme';
import { Disposable } from '@theia/core/lib/common/disposable';
import { isString, isObject, PickOptions, QuickInputButtonHandle } from '@theia/core/lib/common';
Expand Down Expand Up @@ -405,7 +405,7 @@ export interface TerminalServiceMain {
*/
$disposeByTerminalId(id: number, waitOnExit?: boolean | string): void;

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

/**
* Set the terminal widget name.
Expand Down
10 changes: 5 additions & 5 deletions packages/plugin-ext/src/main/browser/terminal-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-servi
import { TerminalServiceMain, TerminalServiceExt, MAIN_RPC_CONTEXT } from '../../common/plugin-api-rpc';
import { RPCProtocol } from '../../common/rpc-protocol';
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
import { SerializableEnvironmentVariableCollection } from '@theia/terminal/lib/common/base-terminal-protocol';
import { SerializableEnvironmentVariableCollection, SerializableExtensionEnvironmentVariableCollection } from '@theia/terminal/lib/common/base-terminal-protocol';
import { ShellTerminalServerProxy } from '@theia/terminal/lib/common/shell-terminal-protocol';
import { TerminalLink, TerminalLinkProvider } from '@theia/terminal/lib/browser/terminal-link-provider';
import { URI } from '@theia/core/lib/common/uri';
Expand Down Expand Up @@ -75,11 +75,11 @@ export class TerminalServiceMainImpl implements TerminalServiceMain, TerminalLin
return this.extProxy.$startProfile(id, CancellationToken.None);
}

$setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: SerializableEnvironmentVariableCollection | undefined): void {
if (collection) {
this.shellTerminalServer.setCollection(extensionIdentifier, persistent, collection);
$setEnvironmentVariableCollection(persistent: boolean, collection: SerializableExtensionEnvironmentVariableCollection): void {
if (collection.collection) {
this.shellTerminalServer.setCollection(collection.extensionIdentifier, persistent, collection.collection, collection.description);
} else {
this.shellTerminalServer.deleteCollection(extensionIdentifier);
this.shellTerminalServer.deleteCollection(collection.extensionIdentifier);
}
}

Expand Down
14 changes: 13 additions & 1 deletion packages/plugin-ext/src/plugin/terminal-ext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ 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';
Expand Down Expand Up @@ -313,7 +314,11 @@ 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(collection.persistent, {
extensionIdentifier,
collection: serialized.length === 0 ? undefined : serialized,
description: Converter.fromMarkdownOrString(collection.description)
});
}

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

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

public get description(): string | theia.MarkdownString | undefined { return this._description; }
public set description(value: string | theia.MarkdownString | undefined) {
this._description = value;
this.onDidChangeCollectionEmitter.fire();
}

public get persistent(): boolean { return this._persistent; }
public set persistent(value: boolean) {
this._persistent = value;
Expand Down
10 changes: 10 additions & 0 deletions packages/plugin-ext/src/plugin/type-converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,16 @@ export function fromMarkdown(markup: theia.MarkdownString | theia.MarkedString):
}
}

export function fromMarkdownOrString(value: string | theia.MarkdownString | undefined): string | MarkdownStringDTO | undefined {
if (value === undefined) {
return undefined;
} else if (typeof value === 'string') {
return value;
} else {
return fromMarkdown(value);
}
}

export function toMarkdown(value: MarkdownStringDTO): PluginMarkdownStringImpl {
const implemented = new PluginMarkdownStringImpl(value.value, value.supportThemeIcons);
implemented.isTrusted = value.isTrusted;
Expand Down
6 changes: 6 additions & 0 deletions packages/plugin/src/theia.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3585,6 +3585,12 @@ export module '@theia/plugin' {
* A collection of mutations that an extension can apply to a process environment.
*/
export interface EnvironmentVariableCollection {

/**
* A description for the environment variable collection, this will be used to describe the changes in the UI.
*/
description: string | MarkdownString | undefined;

/**
* Whether the collection should be cached for the workspace and applied to the terminal
* across window reloads. When true the collection will be active immediately such when the
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/lib/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 ids of extensions contributing to the environment of this terminal mapped to the provided description for their changes. */
abstract envVarCollectionDescriptionsByExtension: 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
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,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 ? c.collection : [], c.description));
}
});
});
Expand Down
66 changes: 65 additions & 1 deletion packages/terminal/src/browser/terminal-widget-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ 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, MarkdownStringImpl } from '@theia/core/lib/common/markdown-rendering/markdown-string';
import { EnhancedPreviewWidget } from '@theia/core/lib/browser/widgets/enhanced-preview-widget';
import { MarkdownRenderer, MarkdownRendererFactory } from '@theia/core/lib/browser/markdown-rendering/markdown-renderer';

export const TERMINAL_WIDGET_FACTORY_ID = 'terminal';

Expand All @@ -57,7 +60,7 @@ export interface TerminalContribution {
}

@injectable()
export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget, ExtractableWidget {
export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget, ExtractableWidget, EnhancedPreviewWidget {
readonly isExtractable: boolean = true;
secondaryWindow: Window | undefined;
location: TerminalLocationOptions;
Expand All @@ -81,6 +84,7 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget
protected lastMousePosition: { x: number, y: number } | undefined;
protected isAttachedCloseListener: boolean = false;
protected shown = false;
protected enhancedPreviewNode: Node | undefined;
override lastCwd = new URI();

@inject(WorkspaceService) protected readonly workspaceService: WorkspaceService;
Expand All @@ -98,6 +102,13 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget
@inject(TerminalThemeService) protected readonly themeService: TerminalThemeService;
@inject(ShellCommandBuilder) protected readonly shellCommandBuilder: ShellCommandBuilder;
@inject(ContextMenuRenderer) protected readonly contextMenuRenderer: ContextMenuRenderer;
@inject(MarkdownRendererFactory) protected readonly markdownRendererFactory: MarkdownRendererFactory;

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

protected readonly onDidOpenEmitter = new Emitter<void>();
readonly onDidOpen: Event<void> = this.onDidOpenEmitter.event;
Expand Down Expand Up @@ -426,6 +437,13 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget
return this.shellTerminalServer.getProcessInfo(this.terminalId);
}

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

get terminalId(): number {
return this._terminalId;
}
Expand Down Expand Up @@ -762,6 +780,10 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget
if (this.exitStatus) {
this.onTermDidClose.fire(this);
}
if (this.enhancedPreviewNode) {
// don't use preview node anymore. rendered markdown will be disposed on super call
this.enhancedPreviewNode = undefined;
}
super.dispose();
}

Expand Down Expand Up @@ -867,4 +889,46 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget
private disableEnterWhenAttachCloseListener(): boolean {
return this.isAttachedCloseListener;
}

getEnhancedPreviewNode(): Node | undefined {
if (this.enhancedPreviewNode) {
return this.enhancedPreviewNode;
}

this.enhancedPreviewNode = document.createElement('div');

Promise.all([this.envVarCollectionDescriptionsByExtension, this.processId, this.processInfo])
.then((values: [Map<string, string | MarkdownString | undefined>, number, TerminalProcessInfo]) => {
const extensions = values[0];
const processId = values[1];
const processInfo = values[2];

const markdown = new MarkdownStringImpl();
markdown.appendMarkdown('Process ID: ' + processId + '\\\n');
markdown.appendMarkdown('Command line: ' +
processInfo.executable +
' ' +
processInfo.arguments.join(' ') +
'\n\n---\n\n');
markdown.appendMarkdown('The following extensions have contributed to this terminal\'s environment:\n');
extensions.forEach((value, key) => {
if (value === undefined) {
markdown.appendMarkdown('* ' + key + '\n');
} else if (typeof value === 'string') {
markdown.appendMarkdown('* ' + key + ': ' + value + '\n');
} else {
markdown.appendMarkdown('* ' + key + ': ' + value.value + '\n');
}
});

const enhancedPreviewNode = this.enhancedPreviewNode;
if (!this.isDisposed && enhancedPreviewNode) {
const result = this.markdownRenderer.render(markdown);
this.toDispose.push(result);
enhancedPreviewNode.appendChild(result.element);
}
});

return this.enhancedPreviewNode;
}
}
8 changes: 6 additions & 2 deletions 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>;
getEnvVarCollectionDescriptionsByExtension(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 @@ -157,6 +159,7 @@ export interface EnvironmentVariableCollection {

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

export enum EnvironmentVariableMutatorType {
Expand Down Expand Up @@ -189,7 +192,8 @@ export interface MergedEnvironmentVariableCollection {

export interface SerializableExtensionEnvironmentVariableCollection {
extensionIdentifier: string,
collection: SerializableEnvironmentVariableCollection
collection: SerializableEnvironmentVariableCollection | undefined,
description: string | MarkdownString | undefined
}

export type SerializableEnvironmentVariableCollection = [string, EnvironmentVariableMutator][];
Loading

0 comments on commit 5720724

Please sign in to comment.