Skip to content

Commit 5720724

Browse files
authoredAug 25, 2023
[vscode] Support EnvironmentVariableCollection description #12696 (#12838)
* 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>
1 parent 1f94559 commit 5720724

File tree

12 files changed

+164
-16
lines changed

12 files changed

+164
-16
lines changed
 

‎packages/core/src/browser/shell/tab-bars.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { Root, createRoot } from 'react-dom/client';
3838
import { SelectComponent } from '../widgets/select-component';
3939
import { createElement } from 'react';
4040
import { PreviewableWidget } from '../widgets/previewable-widget';
41+
import { EnhancedPreviewWidget } from '../widgets/enhanced-preview-widget';
4142

4243
/** The class name added to hidden content nodes, which are required to render vertical side bars. */
4344
const HIDDEN_CONTENT_CLASS = 'theia-TabBar-hidden-content';
@@ -504,7 +505,13 @@ export class TabBarRenderer extends TabBar.Renderer {
504505
labelElement.classList.add('theia-horizontal-tabBar-hover-title');
505506
labelElement.textContent = title.label;
506507
hoverBox.append(labelElement);
507-
if (title.caption) {
508+
const widget = title.owner;
509+
if (EnhancedPreviewWidget.is(widget)) {
510+
const enhancedPreviewNode = widget.getEnhancedPreviewNode();
511+
if (enhancedPreviewNode) {
512+
hoverBox.appendChild(enhancedPreviewNode);
513+
}
514+
} else if (title.caption) {
508515
const captionElement = document.createElement('p');
509516
captionElement.classList.add('theia-horizontal-tabBar-hover-caption');
510517
captionElement.textContent = title.caption;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// *****************************************************************************
2+
// Copyright (C) 2023 STMicroelectronics and others.
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-only WITH Classpath-exception-2.0
15+
// *****************************************************************************
16+
17+
import { isFunction, isObject } from '../../common';
18+
19+
export interface EnhancedPreviewWidget {
20+
getEnhancedPreviewNode(): Node | undefined;
21+
}
22+
23+
export namespace EnhancedPreviewWidget {
24+
export function is(arg: unknown): arg is EnhancedPreviewWidget {
25+
return isObject<EnhancedPreviewWidget>(arg) && isFunction(arg.getEnhancedPreviewNode);
26+
}
27+
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ import type {
111111
TimelineChangeEvent,
112112
TimelineProviderDescriptor
113113
} from '@theia/timeline/lib/common/timeline-model';
114-
import { SerializableEnvironmentVariableCollection } from '@theia/terminal/lib/common/base-terminal-protocol';
114+
import { SerializableEnvironmentVariableCollection, SerializableExtensionEnvironmentVariableCollection } from '@theia/terminal/lib/common/base-terminal-protocol';
115115
import { ThemeType } from '@theia/core/lib/common/theme';
116116
import { Disposable } from '@theia/core/lib/common/disposable';
117117
import { isString, isObject, PickOptions, QuickInputButtonHandle } from '@theia/core/lib/common';
@@ -405,7 +405,7 @@ export interface TerminalServiceMain {
405405
*/
406406
$disposeByTerminalId(id: number, waitOnExit?: boolean | string): void;
407407

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

410410
/**
411411
* Set the terminal widget name.

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

+5-5
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-servi
2222
import { TerminalServiceMain, TerminalServiceExt, MAIN_RPC_CONTEXT } from '../../common/plugin-api-rpc';
2323
import { RPCProtocol } from '../../common/rpc-protocol';
2424
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
25-
import { SerializableEnvironmentVariableCollection } from '@theia/terminal/lib/common/base-terminal-protocol';
25+
import { SerializableEnvironmentVariableCollection, SerializableExtensionEnvironmentVariableCollection } from '@theia/terminal/lib/common/base-terminal-protocol';
2626
import { ShellTerminalServerProxy } from '@theia/terminal/lib/common/shell-terminal-protocol';
2727
import { TerminalLink, TerminalLinkProvider } from '@theia/terminal/lib/browser/terminal-link-provider';
2828
import { URI } from '@theia/core/lib/common/uri';
@@ -75,11 +75,11 @@ export class TerminalServiceMainImpl implements TerminalServiceMain, TerminalLin
7575
return this.extProxy.$startProfile(id, CancellationToken.None);
7676
}
7777

78-
$setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: SerializableEnvironmentVariableCollection | undefined): void {
79-
if (collection) {
80-
this.shellTerminalServer.setCollection(extensionIdentifier, persistent, collection);
78+
$setEnvironmentVariableCollection(persistent: boolean, collection: SerializableExtensionEnvironmentVariableCollection): void {
79+
if (collection.collection) {
80+
this.shellTerminalServer.setCollection(collection.extensionIdentifier, persistent, collection.collection, collection.description);
8181
} else {
82-
this.shellTerminalServer.deleteCollection(extensionIdentifier);
82+
this.shellTerminalServer.deleteCollection(collection.extensionIdentifier);
8383
}
8484
}
8585

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

+13-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { RPCProtocol } from '../common/rpc-protocol';
2020
import { Event, Emitter } from '@theia/core/lib/common/event';
2121
import { Deferred } from '@theia/core/lib/common/promise-util';
2222
import * as theia from '@theia/plugin';
23+
import * as Converter from './type-converters';
2324
import { Disposable, EnvironmentVariableMutatorType, TerminalExitReason, ThemeIcon } from './types-impl';
2425
import { SerializableEnvironmentVariableCollection } from '@theia/terminal/lib/common/base-terminal-protocol';
2526
import { ProvidedTerminalLink } from '../common/plugin-api-rpc-model';
@@ -313,7 +314,11 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
313314

314315
private syncEnvironmentVariableCollection(extensionIdentifier: string, collection: EnvironmentVariableCollection): void {
315316
const serialized = [...collection.map.entries()];
316-
this.proxy.$setEnvironmentVariableCollection(extensionIdentifier, collection.persistent, serialized.length === 0 ? undefined : serialized);
317+
this.proxy.$setEnvironmentVariableCollection(collection.persistent, {
318+
extensionIdentifier,
319+
collection: serialized.length === 0 ? undefined : serialized,
320+
description: Converter.fromMarkdownOrString(collection.description)
321+
});
317322
}
318323

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

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

350+
public get description(): string | theia.MarkdownString | undefined { return this._description; }
351+
public set description(value: string | theia.MarkdownString | undefined) {
352+
this._description = value;
353+
this.onDidChangeCollectionEmitter.fire();
354+
}
355+
344356
public get persistent(): boolean { return this._persistent; }
345357
public set persistent(value: boolean) {
346358
this._persistent = value;

‎packages/plugin-ext/src/plugin/type-converters.ts

+10
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,16 @@ export function fromMarkdown(markup: theia.MarkdownString | theia.MarkedString):
201201
}
202202
}
203203

204+
export function fromMarkdownOrString(value: string | theia.MarkdownString | undefined): string | MarkdownStringDTO | undefined {
205+
if (value === undefined) {
206+
return undefined;
207+
} else if (typeof value === 'string') {
208+
return value;
209+
} else {
210+
return fromMarkdown(value);
211+
}
212+
}
213+
204214
export function toMarkdown(value: MarkdownStringDTO): PluginMarkdownStringImpl {
205215
const implemented = new PluginMarkdownStringImpl(value.value, value.supportThemeIcons);
206216
implemented.isTrusted = value.isTrusted;

‎packages/plugin/src/theia.d.ts

+6
Original file line numberDiff line numberDiff line change
@@ -3585,6 +3585,12 @@ export module '@theia/plugin' {
35853585
* A collection of mutations that an extension can apply to a process environment.
35863586
*/
35873587
export interface EnvironmentVariableCollection {
3588+
3589+
/**
3590+
* A description for the environment variable collection, this will be used to describe the changes in the UI.
3591+
*/
3592+
description: string | MarkdownString | undefined;
3593+
35883594
/**
35893595
* Whether the collection should be cached for the workspace and applied to the terminal
35903596
* across window reloads. When true the collection will be active immediately such when the

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

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { CommandLineOptions } from '@theia/process/lib/common/shell-command-buil
2020
import { TerminalSearchWidget } from '../search/terminal-search-widget';
2121
import { TerminalProcessInfo, TerminalExitReason } from '../../common/base-terminal-protocol';
2222
import URI from '@theia/core/lib/common/uri';
23+
import { MarkdownString } from '@theia/core/lib/common/markdown-rendering/markdown-string';
2324

2425
export interface TerminalDimensions {
2526
cols: number;
@@ -58,6 +59,9 @@ export abstract class TerminalWidget extends BaseWidget {
5859
*/
5960
abstract processInfo: Promise<TerminalProcessInfo>;
6061

62+
/** The ids of extensions contributing to the environment of this terminal mapped to the provided description for their changes. */
63+
abstract envVarCollectionDescriptionsByExtension: Promise<Map<string, string | MarkdownString | undefined>>;
64+
6165
/** Terminal kind that indicates whether a terminal is created by a user or by some extension for a user */
6266
abstract readonly kind: 'user' | string;
6367

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ export class TerminalFrontendContribution implements FrontendApplicationContribu
250250
this.storageService.getData<string>(ENVIRONMENT_VARIABLE_COLLECTIONS_KEY).then(data => {
251251
if (data) {
252252
const collectionsJson: SerializableExtensionEnvironmentVariableCollection[] = JSON.parse(data);
253-
collectionsJson.forEach(c => this.shellTerminalServer.setCollection(c.extensionIdentifier, true, c.collection));
253+
collectionsJson.forEach(c => this.shellTerminalServer.setCollection(c.extensionIdentifier, true, c.collection ? c.collection : [], c.description));
254254
}
255255
});
256256
});

‎packages/terminal/src/browser/terminal-widget-impl.ts

+65-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ import { Key } from '@theia/core/lib/browser/keys';
4343
import { nls } from '@theia/core/lib/common/nls';
4444
import { TerminalMenus } from './terminal-frontend-contribution';
4545
import debounce = require('p-debounce');
46+
import { MarkdownString, MarkdownStringImpl } from '@theia/core/lib/common/markdown-rendering/markdown-string';
47+
import { EnhancedPreviewWidget } from '@theia/core/lib/browser/widgets/enhanced-preview-widget';
48+
import { MarkdownRenderer, MarkdownRendererFactory } from '@theia/core/lib/browser/markdown-rendering/markdown-renderer';
4649

4750
export const TERMINAL_WIDGET_FACTORY_ID = 'terminal';
4851

@@ -57,7 +60,7 @@ export interface TerminalContribution {
5760
}
5861

5962
@injectable()
60-
export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget, ExtractableWidget {
63+
export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget, ExtractableWidget, EnhancedPreviewWidget {
6164
readonly isExtractable: boolean = true;
6265
secondaryWindow: Window | undefined;
6366
location: TerminalLocationOptions;
@@ -81,6 +84,7 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget
8184
protected lastMousePosition: { x: number, y: number } | undefined;
8285
protected isAttachedCloseListener: boolean = false;
8386
protected shown = false;
87+
protected enhancedPreviewNode: Node | undefined;
8488
override lastCwd = new URI();
8589

8690
@inject(WorkspaceService) protected readonly workspaceService: WorkspaceService;
@@ -98,6 +102,13 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget
98102
@inject(TerminalThemeService) protected readonly themeService: TerminalThemeService;
99103
@inject(ShellCommandBuilder) protected readonly shellCommandBuilder: ShellCommandBuilder;
100104
@inject(ContextMenuRenderer) protected readonly contextMenuRenderer: ContextMenuRenderer;
105+
@inject(MarkdownRendererFactory) protected readonly markdownRendererFactory: MarkdownRendererFactory;
106+
107+
protected _markdownRenderer: MarkdownRenderer | undefined;
108+
protected get markdownRenderer(): MarkdownRenderer {
109+
this._markdownRenderer ||= this.markdownRendererFactory();
110+
return this._markdownRenderer;
111+
}
101112

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

440+
get envVarCollectionDescriptionsByExtension(): Promise<Map<string, string | MarkdownString | undefined>> {
441+
if (!IBaseTerminalServer.validateId(this.terminalId)) {
442+
return Promise.reject(new Error('terminal is not started'));
443+
}
444+
return this.shellTerminalServer.getEnvVarCollectionDescriptionsByExtension(this.terminalId);
445+
}
446+
429447
get terminalId(): number {
430448
return this._terminalId;
431449
}
@@ -762,6 +780,10 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget
762780
if (this.exitStatus) {
763781
this.onTermDidClose.fire(this);
764782
}
783+
if (this.enhancedPreviewNode) {
784+
// don't use preview node anymore. rendered markdown will be disposed on super call
785+
this.enhancedPreviewNode = undefined;
786+
}
765787
super.dispose();
766788
}
767789

@@ -867,4 +889,46 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget
867889
private disableEnterWhenAttachCloseListener(): boolean {
868890
return this.isAttachedCloseListener;
869891
}
892+
893+
getEnhancedPreviewNode(): Node | undefined {
894+
if (this.enhancedPreviewNode) {
895+
return this.enhancedPreviewNode;
896+
}
897+
898+
this.enhancedPreviewNode = document.createElement('div');
899+
900+
Promise.all([this.envVarCollectionDescriptionsByExtension, this.processId, this.processInfo])
901+
.then((values: [Map<string, string | MarkdownString | undefined>, number, TerminalProcessInfo]) => {
902+
const extensions = values[0];
903+
const processId = values[1];
904+
const processInfo = values[2];
905+
906+
const markdown = new MarkdownStringImpl();
907+
markdown.appendMarkdown('Process ID: ' + processId + '\\\n');
908+
markdown.appendMarkdown('Command line: ' +
909+
processInfo.executable +
910+
' ' +
911+
processInfo.arguments.join(' ') +
912+
'\n\n---\n\n');
913+
markdown.appendMarkdown('The following extensions have contributed to this terminal\'s environment:\n');
914+
extensions.forEach((value, key) => {
915+
if (value === undefined) {
916+
markdown.appendMarkdown('* ' + key + '\n');
917+
} else if (typeof value === 'string') {
918+
markdown.appendMarkdown('* ' + key + ': ' + value + '\n');
919+
} else {
920+
markdown.appendMarkdown('* ' + key + ': ' + value.value + '\n');
921+
}
922+
});
923+
924+
const enhancedPreviewNode = this.enhancedPreviewNode;
925+
if (!this.isDisposed && enhancedPreviewNode) {
926+
const result = this.markdownRenderer.render(markdown);
927+
this.toDispose.push(result);
928+
enhancedPreviewNode.appendChild(result.element);
929+
}
930+
});
931+
932+
return this.enhancedPreviewNode;
933+
}
870934
}

‎packages/terminal/src/common/base-terminal-protocol.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

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

2021
export interface TerminalProcessInfo {
2122
executable: string
@@ -28,6 +29,7 @@ export interface IBaseTerminalServer extends RpcServer<IBaseTerminalClient> {
2829
create(IBaseTerminalServerOptions: object): Promise<number>;
2930
getProcessId(id: number): Promise<number>;
3031
getProcessInfo(id: number): Promise<TerminalProcessInfo>;
32+
getEnvVarCollectionDescriptionsByExtension(id: number): Promise<Map<string, string | MarkdownString | undefined>>;
3133
getCwdURI(id: number): Promise<string>;
3234
resize(id: number, cols: number, rows: number): Promise<void>;
3335
attach(id: number): Promise<number>;
@@ -48,7 +50,7 @@ export interface IBaseTerminalServer extends RpcServer<IBaseTerminalClient> {
4850
/**
4951
* Sets an extension's environment variable collection.
5052
*/
51-
setCollection(extensionIdentifier: string, persistent: boolean, collection: SerializableEnvironmentVariableCollection): void;
53+
setCollection(extensionIdentifier: string, persistent: boolean, collection: SerializableEnvironmentVariableCollection, description: string | MarkdownString | undefined): void;
5254
/**
5355
* Deletes an extension's environment variable collection.
5456
*/
@@ -157,6 +159,7 @@ export interface EnvironmentVariableCollection {
157159

158160
export interface EnvironmentVariableCollectionWithPersistence extends EnvironmentVariableCollection {
159161
readonly persistent: boolean;
162+
readonly description: string | MarkdownString | undefined;
160163
}
161164

162165
export enum EnvironmentVariableMutatorType {
@@ -189,7 +192,8 @@ export interface MergedEnvironmentVariableCollection {
189192

190193
export interface SerializableExtensionEnvironmentVariableCollection {
191194
extensionIdentifier: string,
192-
collection: SerializableEnvironmentVariableCollection
195+
collection: SerializableEnvironmentVariableCollection | undefined,
196+
description: string | MarkdownString | undefined
193197
}
194198

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

‎packages/terminal/src/node/base-terminal-server.ts

+17-3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
} from '../common/base-terminal-protocol';
3434
import { TerminalProcess, ProcessManager, TaskTerminalProcess } from '@theia/process/lib/node';
3535
import { ShellProcess } from './shell-process';
36+
import { MarkdownString } from '@theia/core/lib/common/markdown-rendering/markdown-string';
3637

3738
@injectable()
3839
export abstract class BaseTerminalServer implements IBaseTerminalServer {
@@ -102,6 +103,18 @@ export abstract class BaseTerminalServer implements IBaseTerminalServer {
102103
};
103104
}
104105

106+
async getEnvVarCollectionDescriptionsByExtension(id: number): Promise<Map<string, string | MarkdownString | undefined>> {
107+
const terminal = this.processManager.get(id);
108+
if (!(terminal instanceof TerminalProcess)) {
109+
throw new Error(`terminal "${id}" does not exist`);
110+
}
111+
const result = new Map<string, string | MarkdownString | undefined>();
112+
this.collections.forEach((value, key) => {
113+
result.set(key, value.description);
114+
});
115+
return result;
116+
}
117+
105118
async getCwdURI(id: number): Promise<string> {
106119
const terminal = this.processManager.get(id);
107120
if (!(terminal instanceof TerminalProcess)) {
@@ -189,8 +202,8 @@ export abstract class BaseTerminalServer implements IBaseTerminalServer {
189202
*--------------------------------------------------------------------------------------------*/
190203
// some code copied and modified from https://github.com/microsoft/vscode/blob/1.49.0/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts
191204

192-
setCollection(extensionIdentifier: string, persistent: boolean, collection: SerializableEnvironmentVariableCollection): void {
193-
const translatedCollection = { persistent, map: new Map<string, EnvironmentVariableMutator>(collection) };
205+
setCollection(extensionIdentifier: string, persistent: boolean, collection: SerializableEnvironmentVariableCollection, description: string | MarkdownString | undefined): void {
206+
const translatedCollection = { persistent, description, map: new Map<string, EnvironmentVariableMutator>(collection) };
194207
this.collections.set(extensionIdentifier, translatedCollection);
195208
this.updateCollections();
196209
}
@@ -211,7 +224,8 @@ export abstract class BaseTerminalServer implements IBaseTerminalServer {
211224
if (collection.persistent) {
212225
collectionsJson.push({
213226
extensionIdentifier,
214-
collection: [...this.collections.get(extensionIdentifier)!.map.entries()]
227+
collection: [...this.collections.get(extensionIdentifier)!.map.entries()],
228+
description: collection.description
215229
});
216230
}
217231
});

0 commit comments

Comments
 (0)
Please sign in to comment.