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 EnvironmentVariableCollection description #12696 #12838

Merged
merged 6 commits into from
Aug 25, 2023
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;
tsmaeder marked this conversation as resolved.
Show resolved Hide resolved
}

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 @@ -404,7 +404,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 @@ -422,6 +433,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 @@ -756,6 +774,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 @@ -859,4 +881,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 @@ -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 Expand Up @@ -186,7 +189,8 @@ export interface MergedEnvironmentVariableCollection {

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

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