From f45ef07a4b3b4e3badc766c771bd68b526e8291b Mon Sep 17 00:00:00 2001 From: Johannes Faltermeier Date: Wed, 16 Aug 2023 11:21:52 +0200 Subject: [PATCH 1/6] [vscode] Support EnvironmentVariableCollection description #12696 * add new field to EnvironmentVariableCollection and implement interface Contributed on behalf of STMicroelectronics Signed-off-by: Johannes Faltermeier --- packages/plugin-ext/src/plugin/terminal-ext.ts | 9 ++++++++- packages/plugin/src/theia.d.ts | 6 ++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/plugin-ext/src/plugin/terminal-ext.ts b/packages/plugin-ext/src/plugin/terminal-ext.ts index f062d28b97a7c..35738a63725b1 100644 --- a/packages/plugin-ext/src/plugin/terminal-ext.ts +++ b/packages/plugin-ext/src/plugin/terminal-ext.ts @@ -14,7 +14,7 @@ // 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 } from '@theia/plugin'; +import { Terminal, TerminalOptions, PseudoTerminalOptions, ExtensionTerminalOptions, TerminalState, MarkdownString } 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'; @@ -339,8 +339,15 @@ export class TerminalServiceExtImpl implements TerminalServiceExt { export class EnvironmentVariableCollection implements theia.EnvironmentVariableCollection { readonly map: Map = new Map(); + private _description?: string | MarkdownString; private _persistent: boolean = true; + public get description(): string | MarkdownString | undefined { return this._description; } + public set description(value: string | MarkdownString | undefined) { + this._description = value; + this.onDidChangeCollectionEmitter.fire(); + } + public get persistent(): boolean { return this._persistent; } public set persistent(value: boolean) { this._persistent = value; diff --git a/packages/plugin/src/theia.d.ts b/packages/plugin/src/theia.d.ts index 46655c88d1b19..cbb57731cb9af 100644 --- a/packages/plugin/src/theia.d.ts +++ b/packages/plugin/src/theia.d.ts @@ -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 From 10df2a8d2ae2c7cf86a14a905f0a63ef2635f186 Mon Sep 17 00:00:00 2001 From: Johannes Faltermeier Date: Wed, 16 Aug 2023 15:11:40 +0200 Subject: [PATCH 2/6] [vscode] Support EnvironmentVariableCollection description #12696 * show information in UI Contributed on behalf of STMicroelectronics Signed-off-by: Johannes Faltermeier --- .../plugin-ext/src/common/plugin-api-rpc.ts | 3 +- .../src/main/browser/terminal-main.ts | 7 +- .../plugin-ext/src/plugin/terminal-ext.ts | 23 +++- .../src/browser/base/terminal-widget.ts | 4 + .../browser/terminal-frontend-contribution.ts | 18 ++- .../browser/terminal-info-toolbar-item.tsx | 109 ++++++++++++++++++ .../src/browser/terminal-widget-impl.ts | 8 ++ .../src/common/base-terminal-protocol.ts | 5 +- .../terminal/src/node/base-terminal-server.ts | 17 ++- 9 files changed, 180 insertions(+), 14 deletions(-) create mode 100644 packages/terminal/src/browser/terminal-info-toolbar-item.tsx diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index 2857dbb4d106f..55a6e003e207f 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -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. diff --git a/packages/plugin-ext/src/main/browser/terminal-main.ts b/packages/plugin-ext/src/main/browser/terminal-main.ts index 61bb2912df614..02a3827087863 100644 --- a/packages/plugin-ext/src/main/browser/terminal-main.ts +++ b/packages/plugin-ext/src/main/browser/terminal-main.ts @@ -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'; @@ -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); } diff --git a/packages/plugin-ext/src/plugin/terminal-ext.ts b/packages/plugin-ext/src/plugin/terminal-ext.ts index 35738a63725b1..dce1898377100 100644 --- a/packages/plugin-ext/src/plugin/terminal-ext.ts +++ b/packages/plugin-ext/src/plugin/terminal-ext.ts @@ -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)) { @@ -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 { @@ -339,11 +352,11 @@ export class TerminalServiceExtImpl implements TerminalServiceExt { export class EnvironmentVariableCollection implements theia.EnvironmentVariableCollection { readonly map: Map = 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(); } diff --git a/packages/terminal/src/browser/base/terminal-widget.ts b/packages/terminal/src/browser/base/terminal-widget.ts index f5a5497fdc84c..443638ead7c22 100644 --- a/packages/terminal/src/browser/base/terminal-widget.ts +++ b/packages/terminal/src/browser/base/terminal-widget.ts @@ -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; @@ -58,6 +59,9 @@ export abstract class TerminalWidget extends BaseWidget { */ abstract processInfo: Promise; + /** The extensions contributing to the environment of this terminal */ + abstract contributingExtensions: Promise>; + /** Terminal kind that indicates whether a terminal is created by a user or by some extension for a user */ abstract readonly kind: 'user' | string; diff --git a/packages/terminal/src/browser/terminal-frontend-contribution.ts b/packages/terminal/src/browser/terminal-frontend-contribution.ts index bd31ca3a32968..6850c491f5eb3 100644 --- a/packages/terminal/src/browser/terminal-frontend-contribution.ts +++ b/packages/terminal/src/browser/terminal-frontend-contribution.ts @@ -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'; @@ -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']; @@ -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 = Promise.resolve(); protected readonly onDidCreateTerminalEmitter = new Emitter(); @@ -250,7 +263,7 @@ export class TerminalFrontendContribution implements FrontendApplicationContribu this.storageService.getData(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)); } }); }); @@ -731,6 +744,7 @@ export class TerminalFrontendContribution implements FrontendApplicationContribu } registerToolbarItems(toolbar: TabBarToolbarRegistry): void { + toolbar.registerItem(new TerminalInfoToolbarItem(this.hoverService, this.markdownRenderer)); toolbar.registerItem({ id: TerminalCommands.SPLIT.id, command: TerminalCommands.SPLIT.id, diff --git a/packages/terminal/src/browser/terminal-info-toolbar-item.tsx b/packages/terminal/src/browser/terminal-info-toolbar-item.tsx new file mode 100644 index 0000000000000..8436e95c4781a --- /dev/null +++ b/packages/terminal/src/browser/terminal-info-toolbar-item.tsx @@ -0,0 +1,109 @@ +// ***************************************************************************** +// 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, Widget } 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'; + + constructor( + protected readonly hoverService: HoverService, + protected readonly markdownRenderer: MarkdownRenderer + ) {} + + render(widget?: Widget): ReactNode { + const toDispose = new DisposableCollection(); + return ( +
this.onMouseEnter(e, toDispose, widget)} + onMouseLeave={e => this.onMouseLeave(toDispose)} + >
+ ); + } + + protected async onMouseEnter( + event: React.MouseEvent, toDispose: DisposableCollection, currentTerminal?: Widget + ): Promise { + const currentTarget = event.currentTarget; + if (currentTerminal instanceof TerminalWidget) { + 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); + toDispose.push(result); + item.appendChild(result.element); + list.appendChild(item); + }); + + this.hoverService.requestHover({ + content: mainDiv, + target: currentTarget, + position: 'right', + }); + } + } + + protected async onMouseLeave(toDispose: DisposableCollection): Promise { + toDispose.dispose(); + } +} diff --git a/packages/terminal/src/browser/terminal-widget-impl.ts b/packages/terminal/src/browser/terminal-widget-impl.ts index 0fb4695889033..72a47d30aa3dc 100644 --- a/packages/terminal/src/browser/terminal-widget-impl.ts +++ b/packages/terminal/src/browser/terminal-widget-impl.ts @@ -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'; @@ -422,6 +423,13 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget return this.shellTerminalServer.getProcessInfo(this.terminalId); } + get contributingExtensions(): Promise> { + 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; } diff --git a/packages/terminal/src/common/base-terminal-protocol.ts b/packages/terminal/src/common/base-terminal-protocol.ts index 23474b66e50f7..1b19753ac47c6 100644 --- a/packages/terminal/src/common/base-terminal-protocol.ts +++ b/packages/terminal/src/common/base-terminal-protocol.ts @@ -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 @@ -28,6 +29,7 @@ export interface IBaseTerminalServer extends RpcServer { create(IBaseTerminalServerOptions: object): Promise; getProcessId(id: number): Promise; getProcessInfo(id: number): Promise; + getContributingExtensions(id: number): Promise>; getCwdURI(id: number): Promise; resize(id: number, cols: number, rows: number): Promise; attach(id: number): Promise; @@ -48,7 +50,7 @@ export interface IBaseTerminalServer extends RpcServer { /** * 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. */ @@ -154,6 +156,7 @@ export interface EnvironmentVariableCollection { export interface EnvironmentVariableCollectionWithPersistence extends EnvironmentVariableCollection { readonly persistent: boolean; + readonly description: string | MarkdownString | undefined; } export enum EnvironmentVariableMutatorType { diff --git a/packages/terminal/src/node/base-terminal-server.ts b/packages/terminal/src/node/base-terminal-server.ts index 786ebb440f47e..e5fbdc1423a2b 100644 --- a/packages/terminal/src/node/base-terminal-server.ts +++ b/packages/terminal/src/node/base-terminal-server.ts @@ -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 { @@ -100,6 +101,18 @@ export abstract class BaseTerminalServer implements IBaseTerminalServer { }; } + async getContributingExtensions(id: number): Promise> { + const terminal = this.processManager.get(id); + if (!(terminal instanceof TerminalProcess)) { + throw new Error(`terminal "${id}" does not exist`); + } + const result = new Map(); + this.collections.forEach((value, key) => { + result.set(key, value.description); + }); + return result; + } + async getCwdURI(id: number): Promise { const terminal = this.processManager.get(id); if (!(terminal instanceof TerminalProcess)) { @@ -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(collection) }; + setCollection(extensionIdentifier: string, persistent: boolean, collection: SerializableEnvironmentVariableCollection, description: string | MarkdownString | undefined): void { + const translatedCollection = { persistent, description, map: new Map(collection) }; this.collections.set(extensionIdentifier, translatedCollection); this.updateCollections(); } From c27c87d3aaae9a945ffe1f56e361e6ea0c0dc0fa Mon Sep 17 00:00:00 2001 From: Johannes Faltermeier Date: Wed, 23 Aug 2023 11:05:20 +0200 Subject: [PATCH 3/6] [vscode] Support EnvironmentVariableCollection description #12696 * fix toolbar item visibility Contributed on behalf of STMicroelectronics Signed-off-by: Johannes Faltermeier --- packages/terminal/src/browser/terminal-info-toolbar-item.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/terminal/src/browser/terminal-info-toolbar-item.tsx b/packages/terminal/src/browser/terminal-info-toolbar-item.tsx index 8436e95c4781a..862b4c8b74ecb 100644 --- a/packages/terminal/src/browser/terminal-info-toolbar-item.tsx +++ b/packages/terminal/src/browser/terminal-info-toolbar-item.tsx @@ -30,6 +30,10 @@ export class TerminalInfoToolbarItem implements ReactTabBarToolbarItem { protected readonly markdownRenderer: MarkdownRenderer ) {} + isVisible(widget?: Widget): boolean { + return widget instanceof TerminalWidget; + } + render(widget?: Widget): ReactNode { const toDispose = new DisposableCollection(); return ( From b80c556793c850a94b0058d666769600a6533bdf Mon Sep 17 00:00:00 2001 From: Johannes Faltermeier Date: Wed, 23 Aug 2023 17:25:24 +0200 Subject: [PATCH 4/6] [vscode] Support EnvironmentVariableCollection description #12696 * add description to SerializableExtensionEnvironmentVariableCollection and use as DTO in $setEnvironmentVariableCollection * add fromMarkdownOrString method to converter * review comments Contributed on behalf of STMicroelectronics Signed-off-by: Johannes Faltermeier --- .../plugin-ext/src/common/plugin-api-rpc.ts | 5 ++--- .../src/main/browser/terminal-main.ts | 13 ++++++------- packages/plugin-ext/src/plugin/terminal-ext.ts | 18 +++++------------- .../plugin-ext/src/plugin/type-converters.ts | 10 ++++++++++ .../src/browser/base/terminal-widget.ts | 4 ++-- .../browser/terminal-frontend-contribution.ts | 2 +- .../src/browser/terminal-info-toolbar-item.tsx | 6 +++--- .../src/browser/terminal-widget-impl.ts | 4 ++-- .../src/common/base-terminal-protocol.ts | 5 +++-- .../terminal/src/node/base-terminal-server.ts | 5 +++-- 10 files changed, 37 insertions(+), 35 deletions(-) diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index 55a6e003e207f..e2cae1588cb5f 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -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'; @@ -404,8 +404,7 @@ export interface TerminalServiceMain { */ $disposeByTerminalId(id: number, waitOnExit?: boolean | string): void; - $setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: SerializableEnvironmentVariableCollection | undefined, - description: string | MarkdownString | undefined): void; + $setEnvironmentVariableCollection(persistent: boolean, collection: SerializableExtensionEnvironmentVariableCollection): void; /** * Set the terminal widget name. diff --git a/packages/plugin-ext/src/main/browser/terminal-main.ts b/packages/plugin-ext/src/main/browser/terminal-main.ts index 02a3827087863..b68d5a800881f 100644 --- a/packages/plugin-ext/src/main/browser/terminal-main.ts +++ b/packages/plugin-ext/src/main/browser/terminal-main.ts @@ -16,13 +16,13 @@ import { interfaces } from '@theia/core/shared/inversify'; import { ApplicationShell, WidgetOpenerOptions } from '@theia/core/lib/browser'; -import { MarkdownString, TerminalEditorLocationOptions, TerminalOptions } from '@theia/plugin'; +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'; 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'; @@ -75,12 +75,11 @@ export class TerminalServiceMainImpl implements TerminalServiceMain, TerminalLin return this.extProxy.$startProfile(id, CancellationToken.None); } - $setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: SerializableEnvironmentVariableCollection | undefined, - description: string | MarkdownString | undefined): void { - if (collection) { - this.shellTerminalServer.setCollection(extensionIdentifier, persistent, collection, description); + $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); } } diff --git a/packages/plugin-ext/src/plugin/terminal-ext.ts b/packages/plugin-ext/src/plugin/terminal-ext.ts index dce1898377100..0cdfaeb886d4b 100644 --- a/packages/plugin-ext/src/plugin/terminal-ext.ts +++ b/packages/plugin-ext/src/plugin/terminal-ext.ts @@ -25,7 +25,6 @@ import { Disposable, EnvironmentVariableMutatorType, TerminalExitReason, ThemeIc 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)) { @@ -315,18 +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.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); - } + this.proxy.$setEnvironmentVariableCollection(collection.persistent, { + extensionIdentifier, + collection: serialized.length === 0 ? undefined : serialized, + description: Converter.fromMarkdownOrString(collection.description) + }); } private setEnvironmentVariableCollection(extensionIdentifier: string, collection: EnvironmentVariableCollection): void { diff --git a/packages/plugin-ext/src/plugin/type-converters.ts b/packages/plugin-ext/src/plugin/type-converters.ts index f3c123fb0cec7..797758b79c3cd 100644 --- a/packages/plugin-ext/src/plugin/type-converters.ts +++ b/packages/plugin-ext/src/plugin/type-converters.ts @@ -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; diff --git a/packages/terminal/src/browser/base/terminal-widget.ts b/packages/terminal/src/browser/base/terminal-widget.ts index 443638ead7c22..b47b322299030 100644 --- a/packages/terminal/src/browser/base/terminal-widget.ts +++ b/packages/terminal/src/browser/base/terminal-widget.ts @@ -59,8 +59,8 @@ export abstract class TerminalWidget extends BaseWidget { */ abstract processInfo: Promise; - /** The extensions contributing to the environment of this terminal */ - abstract contributingExtensions: Promise>; + /** The ids of extensions contributing to the environment of this terminal mapped to the provided description for their changes. */ + abstract envVarCollectionDescriptionsByExtension: Promise>; /** Terminal kind that indicates whether a terminal is created by a user or by some extension for a user */ abstract readonly kind: 'user' | string; diff --git a/packages/terminal/src/browser/terminal-frontend-contribution.ts b/packages/terminal/src/browser/terminal-frontend-contribution.ts index 6850c491f5eb3..dc55bf3875749 100644 --- a/packages/terminal/src/browser/terminal-frontend-contribution.ts +++ b/packages/terminal/src/browser/terminal-frontend-contribution.ts @@ -263,7 +263,7 @@ export class TerminalFrontendContribution implements FrontendApplicationContribu this.storageService.getData(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, undefined)); + collectionsJson.forEach(c => this.shellTerminalServer.setCollection(c.extensionIdentifier, true, c.collection ? c.collection : [], c.description)); } }); }); diff --git a/packages/terminal/src/browser/terminal-info-toolbar-item.tsx b/packages/terminal/src/browser/terminal-info-toolbar-item.tsx index 862b4c8b74ecb..5d115c9d98e04 100644 --- a/packages/terminal/src/browser/terminal-info-toolbar-item.tsx +++ b/packages/terminal/src/browser/terminal-info-toolbar-item.tsx @@ -51,7 +51,7 @@ export class TerminalInfoToolbarItem implements ReactTabBarToolbarItem { ): Promise { const currentTarget = event.currentTarget; if (currentTerminal instanceof TerminalWidget) { - const extensions = await currentTerminal.contributingExtensions; + const extensions = await currentTerminal.envVarCollectionDescriptionsByExtension; const processId = await currentTerminal.processId; const processInfo = await currentTerminal.processInfo; @@ -107,7 +107,7 @@ export class TerminalInfoToolbarItem implements ReactTabBarToolbarItem { } } - protected async onMouseLeave(toDispose: DisposableCollection): Promise { - toDispose.dispose(); + protected async onMouseLeave(hoverResources: DisposableCollection): Promise { + hoverResources.dispose(); } } diff --git a/packages/terminal/src/browser/terminal-widget-impl.ts b/packages/terminal/src/browser/terminal-widget-impl.ts index 72a47d30aa3dc..16c644c51f00f 100644 --- a/packages/terminal/src/browser/terminal-widget-impl.ts +++ b/packages/terminal/src/browser/terminal-widget-impl.ts @@ -423,11 +423,11 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget return this.shellTerminalServer.getProcessInfo(this.terminalId); } - get contributingExtensions(): Promise> { + get envVarCollectionDescriptionsByExtension(): Promise> { if (!IBaseTerminalServer.validateId(this.terminalId)) { return Promise.reject(new Error('terminal is not started')); } - return this.shellTerminalServer.getContributingExtensions(this.terminalId); + return this.shellTerminalServer.getEnvVarCollectionDescriptionsByExtension(this.terminalId); } get terminalId(): number { diff --git a/packages/terminal/src/common/base-terminal-protocol.ts b/packages/terminal/src/common/base-terminal-protocol.ts index 1b19753ac47c6..0ed81c912db98 100644 --- a/packages/terminal/src/common/base-terminal-protocol.ts +++ b/packages/terminal/src/common/base-terminal-protocol.ts @@ -29,7 +29,7 @@ export interface IBaseTerminalServer extends RpcServer { create(IBaseTerminalServerOptions: object): Promise; getProcessId(id: number): Promise; getProcessInfo(id: number): Promise; - getContributingExtensions(id: number): Promise>; + getEnvVarCollectionDescriptionsByExtension(id: number): Promise>; getCwdURI(id: number): Promise; resize(id: number, cols: number, rows: number): Promise; attach(id: number): Promise; @@ -189,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][]; diff --git a/packages/terminal/src/node/base-terminal-server.ts b/packages/terminal/src/node/base-terminal-server.ts index e5fbdc1423a2b..71cab2500ec0a 100644 --- a/packages/terminal/src/node/base-terminal-server.ts +++ b/packages/terminal/src/node/base-terminal-server.ts @@ -101,7 +101,7 @@ export abstract class BaseTerminalServer implements IBaseTerminalServer { }; } - async getContributingExtensions(id: number): Promise> { + async getEnvVarCollectionDescriptionsByExtension(id: number): Promise> { const terminal = this.processManager.get(id); if (!(terminal instanceof TerminalProcess)) { throw new Error(`terminal "${id}" does not exist`); @@ -211,7 +211,8 @@ export abstract class BaseTerminalServer implements IBaseTerminalServer { if (collection.persistent) { collectionsJson.push({ extensionIdentifier, - collection: [...this.collections.get(extensionIdentifier)!.map.entries()] + collection: [...this.collections.get(extensionIdentifier)!.map.entries()], + description: collection.description }); } }); From 81de14db4b1936cb4d8b37eb07fdc08bec3bf03f Mon Sep 17 00:00:00 2001 From: Johannes Faltermeier Date: Thu, 24 Aug 2023 08:35:25 +0200 Subject: [PATCH 5/6] [vscode] Introduce EnhancedPreviewWidget interface #12696 * allow widgets to customize the enhanced preview node Contributed on behalf of STMicroelectronics Signed-off-by: Johannes Faltermeier --- packages/core/src/browser/shell/tab-bars.ts | 13 ++++++--- .../widgets/enhanced-preview-widget.ts | 27 +++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 packages/core/src/browser/widgets/enhanced-preview-widget.ts diff --git a/packages/core/src/browser/shell/tab-bars.ts b/packages/core/src/browser/shell/tab-bars.ts index 6d02fa6cdb84b..eab9044b018a9 100644 --- a/packages/core/src/browser/shell/tab-bars.ts +++ b/packages/core/src/browser/shell/tab-bars.ts @@ -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'; @@ -169,8 +170,8 @@ export class TabBarRenderer extends TabBar.Renderer { const hover = this.tabBar && (this.tabBar.orientation === 'horizontal' && this.corePreferences?.['window.tabbar.enhancedPreview'] === 'classic') ? { title: title.caption } : { - onmouseenter: this.handleMouseEnterEvent - }; + onmouseenter: this.handleMouseEnterEvent + }; return h.li( { @@ -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; diff --git a/packages/core/src/browser/widgets/enhanced-preview-widget.ts b/packages/core/src/browser/widgets/enhanced-preview-widget.ts new file mode 100644 index 0000000000000..923394da08035 --- /dev/null +++ b/packages/core/src/browser/widgets/enhanced-preview-widget.ts @@ -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(arg) && isFunction(arg.getEnhancedPreviewNode); + } +} From bab13b9505905c40757c17bdfb7e95932d72b30d Mon Sep 17 00:00:00 2001 From: Johannes Faltermeier Date: Thu, 24 Aug 2023 09:40:41 +0200 Subject: [PATCH 6/6] [vscode] Support EnvironmentVariableCollection description #12696 * implement enhanced preview for terminal widget * remove terminal info toolbar item Contributed on behalf of STMicroelectronics Signed-off-by: Johannes Faltermeier --- packages/core/src/browser/shell/tab-bars.ts | 4 +- .../browser/terminal-frontend-contribution.ts | 16 +-- .../browser/terminal-info-toolbar-item.tsx | 113 ------------------ .../src/browser/terminal-widget-impl.ts | 60 +++++++++- 4 files changed, 61 insertions(+), 132 deletions(-) delete mode 100644 packages/terminal/src/browser/terminal-info-toolbar-item.tsx diff --git a/packages/core/src/browser/shell/tab-bars.ts b/packages/core/src/browser/shell/tab-bars.ts index eab9044b018a9..c43f42c73bbcf 100644 --- a/packages/core/src/browser/shell/tab-bars.ts +++ b/packages/core/src/browser/shell/tab-bars.ts @@ -170,8 +170,8 @@ export class TabBarRenderer extends TabBar.Renderer { const hover = this.tabBar && (this.tabBar.orientation === 'horizontal' && this.corePreferences?.['window.tabbar.enhancedPreview'] === 'classic') ? { title: title.caption } : { - onmouseenter: this.handleMouseEnterEvent - }; + onmouseenter: this.handleMouseEnterEvent + }; return h.li( { diff --git a/packages/terminal/src/browser/terminal-frontend-contribution.ts b/packages/terminal/src/browser/terminal-frontend-contribution.ts index dc55bf3875749..5916ab41f399e 100644 --- a/packages/terminal/src/browser/terminal-frontend-contribution.ts +++ b/packages/terminal/src/browser/terminal-frontend-contribution.ts @@ -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, HoverService + codicon, CommonCommands, FrontendApplicationContribution, OnWillStopAction, Dialog, ConfirmDialog, FrontendApplication, PreferenceScope, Widget } 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'; @@ -60,8 +60,6 @@ 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']; @@ -218,17 +216,6 @@ 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 = Promise.resolve(); protected readonly onDidCreateTerminalEmitter = new Emitter(); @@ -744,7 +731,6 @@ export class TerminalFrontendContribution implements FrontendApplicationContribu } registerToolbarItems(toolbar: TabBarToolbarRegistry): void { - toolbar.registerItem(new TerminalInfoToolbarItem(this.hoverService, this.markdownRenderer)); toolbar.registerItem({ id: TerminalCommands.SPLIT.id, command: TerminalCommands.SPLIT.id, diff --git a/packages/terminal/src/browser/terminal-info-toolbar-item.tsx b/packages/terminal/src/browser/terminal-info-toolbar-item.tsx deleted file mode 100644 index 5d115c9d98e04..0000000000000 --- a/packages/terminal/src/browser/terminal-info-toolbar-item.tsx +++ /dev/null @@ -1,113 +0,0 @@ -// ***************************************************************************** -// 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, Widget } 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'; - - constructor( - protected readonly hoverService: HoverService, - protected readonly markdownRenderer: MarkdownRenderer - ) {} - - isVisible(widget?: Widget): boolean { - return widget instanceof TerminalWidget; - } - - render(widget?: Widget): ReactNode { - const toDispose = new DisposableCollection(); - return ( -
this.onMouseEnter(e, toDispose, widget)} - onMouseLeave={e => this.onMouseLeave(toDispose)} - >
- ); - } - - protected async onMouseEnter( - event: React.MouseEvent, toDispose: DisposableCollection, currentTerminal?: Widget - ): Promise { - const currentTarget = event.currentTarget; - if (currentTerminal instanceof TerminalWidget) { - const extensions = await currentTerminal.envVarCollectionDescriptionsByExtension; - 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); - toDispose.push(result); - item.appendChild(result.element); - list.appendChild(item); - }); - - this.hoverService.requestHover({ - content: mainDiv, - target: currentTarget, - position: 'right', - }); - } - } - - protected async onMouseLeave(hoverResources: DisposableCollection): Promise { - hoverResources.dispose(); - } -} diff --git a/packages/terminal/src/browser/terminal-widget-impl.ts b/packages/terminal/src/browser/terminal-widget-impl.ts index 16c644c51f00f..b0a1955d3d317 100644 --- a/packages/terminal/src/browser/terminal-widget-impl.ts +++ b/packages/terminal/src/browser/terminal-widget-impl.ts @@ -43,7 +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 } from '@theia/core/lib/common/markdown-rendering/markdown-string'; +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'; @@ -58,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; @@ -82,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; @@ -99,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(); readonly onDidOpen: Event = this.onDidOpenEmitter.event; @@ -764,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(); } @@ -867,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, 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; + } }