diff --git a/src/vs/platform/diagnostics/common/diagnostics.ts b/src/vs/platform/diagnostics/common/diagnostics.ts index cc8a4cb379b..a1d0573823b 100644 --- a/src/vs/platform/diagnostics/common/diagnostics.ts +++ b/src/vs/platform/diagnostics/common/diagnostics.ts @@ -94,8 +94,9 @@ export interface IWorkspaceInformation extends IWorkspace { rendererSessionId: string; } -export function isRemoteDiagnosticError(x: any): x is IRemoteDiagnosticError { - return !!x.hostName && !!x.errorMessage; +export function isRemoteDiagnosticError(x: unknown): x is IRemoteDiagnosticError { + const candidate = x as IRemoteDiagnosticError | undefined; + return !!candidate?.hostName && !!candidate?.errorMessage; } export class NullDiagnosticsService implements IDiagnosticsService { diff --git a/src/vs/platform/diagnostics/node/diagnosticsService.ts b/src/vs/platform/diagnostics/node/diagnosticsService.ts index e523217c108..ee9cb6dad4d 100644 --- a/src/vs/platform/diagnostics/node/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/node/diagnosticsService.ts @@ -486,7 +486,7 @@ export class DiagnosticsService implements IDiagnosticsService { // Format name with indent let name: string; if (isRoot) { - name = item.pid === mainPid ? `${this.productService.applicationName} main` : 'remote agent'; + name = item.pid === mainPid ? this.productService.applicationName : 'remote-server'; } else { if (mapProcessToName.has(item.pid)) { name = mapProcessToName.get(item.pid)!; diff --git a/src/vs/platform/editor/common/editor.ts b/src/vs/platform/editor/common/editor.ts index 7c856237f67..4fb1a9eb9aa 100644 --- a/src/vs/platform/editor/common/editor.ts +++ b/src/vs/platform/editor/common/editor.ts @@ -7,6 +7,7 @@ import { equals } from '../../../base/common/arrays.js'; import { IDisposable } from '../../../base/common/lifecycle.js'; import { URI } from '../../../base/common/uri.js'; import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js'; +import { IRectangle } from '../../window/common/window.js'; export interface IResolvableEditorModel extends IDisposable { @@ -300,12 +301,25 @@ export interface IEditorOptions { transient?: boolean; /** - * A hint that the editor should have compact chrome when showing if possible. - * - * Note: this currently is only working if AUX_GROUP is specified as target to - * open the editor in a floating window. + * Options that only apply when `AUX_WINDOW_GROUP` is used for opening. */ - compact?: boolean; + auxiliary?: { + + /** + * Define the bounds of the editor window. + */ + bounds?: Partial; + + /** + * Show editor compact, hiding unnecessary elements. + */ + compact?: boolean; + + /** + * Show the editor always on top of other windows. + */ + alwaysOnTop?: boolean; + }; } export interface ITextEditorSelection { diff --git a/src/vs/platform/extensions/electron-main/extensionHostStarter.ts b/src/vs/platform/extensions/electron-main/extensionHostStarter.ts index fa606b291a3..08c5dec4698 100644 --- a/src/vs/platform/extensions/electron-main/extensionHostStarter.ts +++ b/src/vs/platform/extensions/electron-main/extensionHostStarter.ts @@ -108,6 +108,7 @@ export class ExtensionHostStarter extends Disposable implements IDisposable, IEx extHost.start({ ...opts, type: 'extensionHost', + name: 'extension-host', entryPoint: 'vs/workbench/api/node/extensionHostProcess', args: ['--skipWorkspaceStorageLock'], execArgv: opts.execArgv, diff --git a/src/vs/platform/process/common/process.ts b/src/vs/platform/process/common/process.ts index 50f155da42c..8495e91904a 100644 --- a/src/vs/platform/process/common/process.ts +++ b/src/vs/platform/process/common/process.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { ProcessItem } from '../../../base/common/processes.js'; import { ISandboxConfiguration } from '../../../base/parts/sandbox/common/sandboxTypes.js'; -import { PerformanceInfo, SystemInfo } from '../../diagnostics/common/diagnostics.js'; +import { IRemoteDiagnosticError, PerformanceInfo, SystemInfo } from '../../diagnostics/common/diagnostics.js'; import { createDecorator } from '../../instantiation/common/instantiation.js'; // Since data sent through the service is serialized to JSON, functions will be lost, so Color objects @@ -57,12 +58,21 @@ export interface ProcessExplorerWindowConfiguration extends ISandboxConfiguratio export const IProcessMainService = createDecorator('processService'); +export interface IResolvedProcessInformation { + readonly pidToNames: [number, string][]; + readonly processes: { name: string; rootProcess: ProcessItem | IRemoteDiagnosticError }[]; +} + export interface IProcessMainService { + readonly _serviceBrand: undefined; + getSystemStatus(): Promise; stopTracing(): Promise; openProcessExplorer(data: ProcessExplorerData): Promise; + resolve(): Promise; + // Used by the process explorer $getSystemInfo(): Promise; $getPerformanceInfo(): Promise; diff --git a/src/vs/platform/process/electron-main/processMainService.ts b/src/vs/platform/process/electron-main/processMainService.ts index df2af9b7a85..be12b2c8938 100644 --- a/src/vs/platform/process/electron-main/processMainService.ts +++ b/src/vs/platform/process/electron-main/processMainService.ts @@ -11,12 +11,12 @@ import { IProcessEnvironment, isMacintosh } from '../../../base/common/platform. import { listProcesses } from '../../../base/node/ps.js'; import { validatedIpcMain } from '../../../base/parts/ipc/electron-main/ipcMain.js'; import { getNLSLanguage, getNLSMessages, localize } from '../../../nls.js'; -import { IDiagnosticsService, isRemoteDiagnosticError, PerformanceInfo, SystemInfo } from '../../diagnostics/common/diagnostics.js'; +import { IDiagnosticsService, IRemoteDiagnosticError, isRemoteDiagnosticError, PerformanceInfo, SystemInfo } from '../../diagnostics/common/diagnostics.js'; import { IDiagnosticsMainService } from '../../diagnostics/electron-main/diagnosticsMainService.js'; import { IDialogMainService } from '../../dialogs/electron-main/dialogMainService.js'; import { IEnvironmentMainService } from '../../environment/electron-main/environmentMainService.js'; import { ICSSDevelopmentService } from '../../cssDev/node/cssDevService.js'; -import { IProcessMainService, ProcessExplorerData, ProcessExplorerWindowConfiguration } from '../common/process.js'; +import { IProcessMainService, IResolvedProcessInformation, ProcessExplorerData, ProcessExplorerWindowConfiguration } from '../common/process.js'; import { ILogService } from '../../log/common/log.js'; import { INativeHostMainService } from '../../native/electron-main/nativeHostMainService.js'; import product from '../../product/common/product.js'; @@ -26,6 +26,7 @@ import { IStateService } from '../../state/node/state.js'; import { UtilityProcess } from '../../utilityProcess/electron-main/utilityProcess.js'; import { zoomLevelToZoomFactor } from '../../window/common/window.js'; import { IWindowState } from '../../window/electron-main/window.js'; +import { ProcessItem } from '../../../base/common/processes.js'; const processExplorerWindowState = 'issue.processExplorerWindowState'; @@ -62,6 +63,46 @@ export class ProcessMainService implements IProcessMainService { this.registerListeners(); } + async resolve(): Promise { + const mainProcessInfo = await this.diagnosticsMainService.getMainDiagnostics(); + + const pidToNames: [number, string][] = []; + for (const window of mainProcessInfo.windows) { + pidToNames.push([window.pid, `window [${window.id}] (${window.title})`]); + } + + for (const { pid, name } of UtilityProcess.getAll()) { + pidToNames.push([pid, name]); + } + + const processes: { name: string; rootProcess: ProcessItem | IRemoteDiagnosticError }[] = []; + + try { + processes.push({ name: localize('local', "Local"), rootProcess: await listProcesses(process.pid) }); + + const remoteDiagnostics = await this.diagnosticsMainService.getRemoteDiagnostics({ includeProcesses: true }); + remoteDiagnostics.forEach(data => { + if (isRemoteDiagnosticError(data)) { + processes.push({ + name: data.hostName, + rootProcess: data + }); + } else { + if (data.processes) { + processes.push({ + name: data.hostName, + rootProcess: data.processes + }); + } + } + }); + } catch (e) { + this.logService.error(`Listing processes failed: ${e}`); + } + + return { pidToNames, processes }; + } + //#region Register Listeners private registerListeners(): void { diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index 9e7389c9826..9e653ef16d4 100644 --- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -169,6 +169,7 @@ export class SharedProcess extends Disposable { this.utilityProcess.start({ type: 'shared-process', + name: 'shared-process', entryPoint: 'vs/code/electron-utility/sharedProcess/sharedProcessMain', payload: this.createSharedProcessConfiguration(), respondToAuthRequestsFromMainProcess: true, diff --git a/src/vs/platform/terminal/electron-main/electronPtyHostStarter.ts b/src/vs/platform/terminal/electron-main/electronPtyHostStarter.ts index d4835c5879f..dc1afec78f1 100644 --- a/src/vs/platform/terminal/electron-main/electronPtyHostStarter.ts +++ b/src/vs/platform/terminal/electron-main/electronPtyHostStarter.ts @@ -57,6 +57,7 @@ export class ElectronPtyHostStarter extends Disposable implements IPtyHostStarte this.utilityProcess.start({ type: 'ptyHost', + name: 'pty-host', entryPoint: 'vs/platform/terminal/node/ptyHostMain', execArgv, args: ['--logsPath', this._environmentMainService.logsHome.with({ scheme: Schemas.file }).fsPath], diff --git a/src/vs/platform/utilityProcess/common/utilityProcessWorkerService.ts b/src/vs/platform/utilityProcess/common/utilityProcessWorkerService.ts index fe5b5f1dcc6..dd55f49a2d4 100644 --- a/src/vs/platform/utilityProcess/common/utilityProcessWorkerService.ts +++ b/src/vs/platform/utilityProcess/common/utilityProcessWorkerService.ts @@ -15,6 +15,11 @@ export interface IUtilityProcessWorkerProcess { * forked process to identify it easier. */ readonly type: string; + + /** + * A human-readable name for the utility process. + */ + readonly name: string; } export interface IOnDidTerminateUtilityrocessWorkerProcess { diff --git a/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts b/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts index 6378c6d4c0c..937492c6bbb 100644 --- a/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts +++ b/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts @@ -26,6 +26,11 @@ export interface IUtilityProcessConfiguration { */ readonly type: string; + /** + * A human-readable name for the utility process. + */ + readonly name: string; + /** * The entry point to load in the utility process. */ @@ -306,7 +311,7 @@ export class UtilityProcess extends Disposable { this.processPid = process.pid; if (typeof process.pid === 'number') { - UtilityProcess.all.set(process.pid, { pid: process.pid, name: isWindowUtilityProcessConfiguration(configuration) ? `${configuration.type} [${configuration.responseWindowId}]` : configuration.type }); + UtilityProcess.all.set(process.pid, { pid: process.pid, name: isWindowUtilityProcessConfiguration(configuration) ? `${configuration.name} [${configuration.responseWindowId}]` : configuration.name }); } this.log('successfully created', Severity.Info); diff --git a/src/vs/platform/utilityProcess/electron-main/utilityProcessWorkerMainService.ts b/src/vs/platform/utilityProcess/electron-main/utilityProcessWorkerMainService.ts index 1ce3d5a18a5..83547d425a4 100644 --- a/src/vs/platform/utilityProcess/electron-main/utilityProcessWorkerMainService.ts +++ b/src/vs/platform/utilityProcess/electron-main/utilityProcessWorkerMainService.ts @@ -126,6 +126,7 @@ class UtilityProcessWorker extends Disposable { return this.utilityProcess.start({ type: this.configuration.process.type, + name: this.configuration.process.name, entryPoint: this.configuration.process.moduleId, parentLifecycleBound: windowPid, windowLifecycleBound: true, diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index 3d394c573d0..ddf6d46f7db 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -429,4 +429,3 @@ export function zoomLevelToZoomFactor(zoomLevel = 0): number { export const DEFAULT_WINDOW_SIZE = { width: 1200, height: 800 } as const; export const DEFAULT_AUX_WINDOW_SIZE = { width: 1024, height: 768 } as const; -export const DEFAULT_COMPACT_AUX_WINDOW_SIZE = { width: 640, height: 640 } as const; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts index 804fd736053..3caa15f12e5 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts @@ -101,7 +101,7 @@ async function executeMoveToAction(accessor: ServicesAccessor, moveTo: MoveToNew const widget = (_sessionId ? widgetService.getWidgetBySessionId(_sessionId) : undefined) ?? widgetService.lastFocusedWidget; if (!widget || !widget.viewModel || widget.location !== ChatAgentLocation.Panel) { - await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { pinned: true, compact: moveTo === MoveToNewLocation.Window } }, moveTo === MoveToNewLocation.Window ? AUX_WINDOW_GROUP : ACTIVE_GROUP); + await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { pinned: true, auxiliary: { compact: true, bounds: { width: 640, height: 640 } } } }, moveTo === MoveToNewLocation.Window ? AUX_WINDOW_GROUP : ACTIVE_GROUP); return; } @@ -111,7 +111,7 @@ async function executeMoveToAction(accessor: ServicesAccessor, moveTo: MoveToNew widget.clear(); await widget.waitForReady(); - const options: IChatEditorOptions = { target: { sessionId }, pinned: true, viewState, compact: moveTo === MoveToNewLocation.Window }; + const options: IChatEditorOptions = { target: { sessionId }, pinned: true, viewState, auxiliary: { compact: true, bounds: { width: 640, height: 640 } } }; await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options }, moveTo === MoveToNewLocation.Window ? AUX_WINDOW_GROUP : ACTIVE_GROUP); } diff --git a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts index dddffbe2517..64f980a9e27 100644 --- a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts @@ -248,13 +248,13 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { const msgContainer = append(desc, $('div.msg')); const actionbar = new ActionBar(desc); - actionbar.onDidRun(({ error }) => error && this._notificationService.error(error)); + const listener = actionbar.onDidRun(({ error }) => error && this._notificationService.error(error)); const timeContainer = append(element, $('.time')); const activationTime = append(timeContainer, $('div.activation-time')); const profileTime = append(timeContainer, $('div.profile-time')); - const disposables = [actionbar]; + const disposables = [actionbar, listener]; return { root, @@ -468,7 +468,7 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { this._list.splice(0, this._list.length, this._elements || undefined); - this._list.onContextMenu((e) => { + this._register(this._list.onContextMenu((e) => { if (!e.element) { return; } @@ -504,7 +504,7 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { getAnchor: () => e.anchor, getActions: () => actions }); - }); + })); } public layout(dimension: Dimension): void { diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/process.contribution.ts b/src/vs/workbench/contrib/issue/electron-sandbox/process.contribution.ts index 75826f51d8e..f336def40a3 100644 --- a/src/vs/workbench/contrib/issue/electron-sandbox/process.contribution.ts +++ b/src/vs/workbench/contrib/issue/electron-sandbox/process.contribution.ts @@ -16,6 +16,9 @@ import { IProgressService, ProgressLocation } from '../../../../platform/progres import { IProcessMainService } from '../../../../platform/process/common/process.js'; import './processService.js'; import './processMainService.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { AUX_WINDOW_GROUP, IEditorService } from '../../../services/editor/common/editorService.js'; +import { ProcessExplorerEditorInput } from '../../processExplorer/electron-sandbox/processExplorerEditoInput.js'; //#region Commands @@ -34,8 +37,14 @@ class OpenProcessExplorer extends Action2 { override async run(accessor: ServicesAccessor): Promise { const processService = accessor.get(IWorkbenchProcessService); + const configurationService = accessor.get(IConfigurationService); + const editorService = accessor.get(IEditorService); - return processService.openProcessExplorer(); + if (configurationService.getValue('application.useNewProcessExplorer') !== true) { + return processService.openProcessExplorer(); + } + + editorService.openEditor({ resource: ProcessExplorerEditorInput.RESOURCE, options: { pinned: true, auxiliary: { compact: true, bounds: { width: 800, height: 500 }, alwaysOnTop: true } } }, AUX_WINDOW_GROUP); } } registerAction2(OpenProcessExplorer); diff --git a/src/vs/workbench/contrib/processExplorer/electron-sandbox/media/processExplorer.css b/src/vs/workbench/contrib/processExplorer/electron-sandbox/media/processExplorer.css new file mode 100644 index 00000000000..0b617a8ea5a --- /dev/null +++ b/src/vs/workbench/contrib/processExplorer/electron-sandbox/media/processExplorer.css @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.process-explorer .row { + display: flex; +} + +.process-explorer .row .cell:not(:first-of-type) { + padding-left: 10px; +} + +.process-explorer .row .cell:not(:last-of-type) { + padding-right: 10px; +} + +.process-explorer .row:not(.header) .cell { + border-right: 1px solid var(--vscode-tree-tableColumnsBorder); +} + +.process-explorer .row.header { + font-weight: 600; + border-bottom: 1px solid var(--vscode-tree-tableColumnsBorder); +} + +.process-explorer .row .cell.name { + text-align: left; + flex-grow: 1; + overflow: hidden; + text-overflow: ellipsis; +} + +.process-explorer .row .cell.cpu { + flex: 0 0 60px; +} + +.process-explorer .row .cell.memory { + flex: 0 0 90px; +} + +.process-explorer .row .cell.pid { + flex: 0 0 50px; +} + +.mac:not(.fullscreen) .process-explorer .monaco-list:focus::before { + /* Rounded corners to make focus outline appear properly (unless fullscreen) */ + border-bottom-right-radius: 5px; + border-bottom-left-radius: 5px; +} +.mac:not(.fullscreen).macos-bigsur-or-newer .process-explorer .monaco-list:focus::before { + /* macOS Big Sur increased rounded corners size */ + border-bottom-right-radius: 10px; + border-bottom-left-radius: 10px; +} + +.process-explorer .monaco-list-row:first-of-type { + border-bottom: 1px solid var(--vscode-tree-tableColumnsBorder); +} diff --git a/src/vs/workbench/contrib/processExplorer/electron-sandbox/processExplorer.contribution.ts b/src/vs/workbench/contrib/processExplorer/electron-sandbox/processExplorer.contribution.ts new file mode 100644 index 00000000000..f1d943c1be6 --- /dev/null +++ b/src/vs/workbench/contrib/processExplorer/electron-sandbox/processExplorer.contribution.ts @@ -0,0 +1,73 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from '../../../../nls.js'; +import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { EditorPaneDescriptor, IEditorPaneRegistry } from '../../../browser/editor.js'; +import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; +import { IEditorSerializer, EditorExtensions, IEditorFactoryRegistry } from '../../../common/editor.js'; +import { EditorInput } from '../../../common/editor/editorInput.js'; +import { IEditorResolverService, RegisteredEditorPriority } from '../../../services/editor/common/editorResolverService.js'; +import { ProcessExplorerEditorInput } from './processExplorerEditoInput.js'; +import { ProcessExplorerEditor } from './processExplorerEditor.js'; + +class ProcessExplorerEditorContribution implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.processExplorerEditor'; + + constructor( + @IEditorResolverService editorResolverService: IEditorResolverService, + @IInstantiationService instantiationService: IInstantiationService + ) { + editorResolverService.registerEditor( + `${ProcessExplorerEditorInput.RESOURCE.scheme}:**/**`, + { + id: ProcessExplorerEditorInput.ID, + label: localize('promptOpenWith.processExplorer.displayName', "Process Explorer"), + priority: RegisteredEditorPriority.exclusive + }, + { + singlePerResource: true, + canSupportResource: resource => resource.scheme === ProcessExplorerEditorInput.RESOURCE.scheme + }, + { + createEditorInput: () => { + return { + editor: instantiationService.createInstance(ProcessExplorerEditorInput), + options: { + pinned: true + } + }; + } + } + ); + } +} + +registerWorkbenchContribution2(ProcessExplorerEditorContribution.ID, ProcessExplorerEditorContribution, WorkbenchPhase.BlockStartup); + +Registry.as(EditorExtensions.EditorPane).registerEditorPane( + EditorPaneDescriptor.create(ProcessExplorerEditor, ProcessExplorerEditor.ID, localize('processExplorer', "Process Explorer")), + [new SyncDescriptor(ProcessExplorerEditorInput)] +); + +class ProcessExplorerEditorInputSerializer implements IEditorSerializer { + + canSerialize(editorInput: EditorInput): boolean { + return true; + } + + serialize(editorInput: EditorInput): string { + return ''; + } + + deserialize(instantiationService: IInstantiationService): EditorInput { + return ProcessExplorerEditorInput.instance; + } +} + +Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(ProcessExplorerEditorInput.ID, ProcessExplorerEditorInputSerializer); diff --git a/src/vs/workbench/contrib/processExplorer/electron-sandbox/processExplorerControl.ts b/src/vs/workbench/contrib/processExplorer/electron-sandbox/processExplorerControl.ts new file mode 100644 index 00000000000..a027b854676 --- /dev/null +++ b/src/vs/workbench/contrib/processExplorer/electron-sandbox/processExplorerControl.ts @@ -0,0 +1,515 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import './media/processExplorer.css'; +import { localize } from '../../../../nls.js'; +import { INativeHostService } from '../../../../platform/native/common/native.js'; +import { $, append, Dimension, getDocument } from '../../../../base/browser/dom.js'; +import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; +import { IIdentityProvider, IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js'; +import { IDataSource, ITreeRenderer, ITreeNode, ITreeContextMenuEvent } from '../../../../base/browser/ui/tree/tree.js'; +import { ProcessItem } from '../../../../base/common/processes.js'; +import { IRemoteDiagnosticError, isRemoteDiagnosticError } from '../../../../platform/diagnostics/common/diagnostics.js'; +import { ByteSize } from '../../../../platform/files/common/files.js'; +import { KeyCode } from '../../../../base/common/keyCodes.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { WorkbenchDataTree } from '../../../../platform/list/browser/listService.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js'; +import { IProductService } from '../../../../platform/product/common/productService.js'; +import { IAction, Separator, toAction } from '../../../../base/common/actions.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; +import { coalesce } from '../../../../base/common/arrays.js'; +import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { RenderIndentGuides } from '../../../../base/browser/ui/tree/abstractTree.js'; +import { isWindows } from '../../../../base/common/platform.js'; +import { IProcessMainService } from '../../../../platform/process/common/process.js'; +import { Delayer } from '../../../../base/common/async.js'; + +const DEBUG_FLAGS_PATTERN = /\s--inspect(?:-brk|port)?=(?\d+)?/; +const DEBUG_PORT_PATTERN = /\s--inspect-port=(?\d+)/; + +//#region --- process explorer tree + +interface IProcessTree { + readonly processes: IProcessInformation; +} + +interface IProcessInformation { + readonly processRoots: IMachineProcessInformation[]; +} + +interface IMachineProcessInformation { + readonly name: string; + readonly rootProcess: ProcessItem | IRemoteDiagnosticError; +} + +function isMachineProcessInformation(item: unknown): item is IMachineProcessInformation { + const candidate = item as IMachineProcessInformation | undefined; + + return !!candidate?.name && !!candidate?.rootProcess; +} + +function isProcessInformation(item: unknown): item is IProcessInformation { + const candidate = item as IProcessInformation | undefined; + + return !!candidate?.processRoots; +} + +function isProcessItem(item: unknown): item is ProcessItem { + const candidate = item as ProcessItem | undefined; + + return typeof candidate?.pid === 'number'; +} + +class ProcessListDelegate implements IListVirtualDelegate { + + getHeight() { + return 22; + } + + getTemplateId(element: IProcessInformation | IMachineProcessInformation | ProcessItem | IRemoteDiagnosticError) { + if (isProcessItem(element)) { + return 'process'; + } + + if (isMachineProcessInformation(element)) { + return 'machine'; + } + + if (isRemoteDiagnosticError(element)) { + return 'error'; + } + + if (isProcessInformation(element)) { + return 'header'; + } + + return ''; + } +} + +class ProcessTreeDataSource implements IDataSource { + + hasChildren(element: IProcessTree | IProcessInformation | IMachineProcessInformation | ProcessItem | IRemoteDiagnosticError): boolean { + if (isRemoteDiagnosticError(element)) { + return false; + } + + if (isProcessItem(element)) { + return !!element.children?.length; + } + + return true; + } + + getChildren(element: IProcessTree | IProcessInformation | IMachineProcessInformation | ProcessItem | IRemoteDiagnosticError) { + if (isProcessItem(element)) { + return element.children ?? []; + } + + if (isRemoteDiagnosticError(element)) { + return []; + } + + if (isProcessInformation(element)) { + if (element.processRoots.length > 1) { + return element.processRoots; // If there are multiple process roots, return these, otherwise go directly to the root process + } + + if (element.processRoots.length > 0) { + return [element.processRoots[0].rootProcess]; + } + + return []; + } + + if (isMachineProcessInformation(element)) { + return [element.rootProcess]; + } + + return element.processes ? [element.processes] : []; + } +} + +function createRow(container: HTMLElement) { + const row = append(container, $('.row')); + + const name = append(row, $('.cell.name')); + const cpu = append(row, $('.cell.cpu')); + const memory = append(row, $('.cell.memory')); + const pid = append(row, $('.cell.pid')); + + return { name, cpu, memory, pid }; +} + +interface IProcessRowTemplateData { + readonly name: HTMLElement; +} + +interface IProcessItemTemplateData extends IProcessRowTemplateData { + readonly cpu: HTMLElement; + readonly memory: HTMLElement; + readonly pid: HTMLElement; +} + +class ProcessHeaderTreeRenderer implements ITreeRenderer { + + readonly templateId: string = 'header'; + + renderTemplate(container: HTMLElement): IProcessItemTemplateData { + return createRow(container); + } + + renderElement(node: ITreeNode, index: number, templateData: IProcessItemTemplateData, height: number | undefined): void { + templateData.name.textContent = localize('processName', "Process Name"); + templateData.cpu.textContent = localize('processCpu', "CPU (%)"); + templateData.pid.textContent = localize('processPid', "PID"); + templateData.memory.textContent = localize('processMemory', "Memory (MB)"); + } + + renderTwistie(element: IProcessInformation, twistieElement: HTMLElement): boolean { + return false; + } + + disposeTemplate(templateData: unknown): void { + // Nothing to do + } +} + +class MachineRenderer implements ITreeRenderer { + + readonly templateId: string = 'machine'; + + renderTemplate(container: HTMLElement): IProcessRowTemplateData { + return createRow(container); + } + + renderElement(node: ITreeNode, index: number, templateData: IProcessRowTemplateData, height: number | undefined): void { + templateData.name.textContent = node.element.name; + } + + disposeTemplate(templateData: IProcessRowTemplateData): void { + // Nothing to do + } +} + +class ErrorRenderer implements ITreeRenderer { + + readonly templateId: string = 'error'; + + renderTemplate(container: HTMLElement): IProcessRowTemplateData { + return createRow(container); + } + + renderElement(node: ITreeNode, index: number, templateData: IProcessRowTemplateData, height: number | undefined): void { + templateData.name.textContent = node.element.errorMessage; + } + + disposeTemplate(templateData: IProcessRowTemplateData): void { + // Nothing to do + } +} + +class ProcessRenderer implements ITreeRenderer { + + readonly templateId: string = 'process'; + + constructor(private totalMem: number, private model: ProcessExplorerModel) { } + + renderTemplate(container: HTMLElement): IProcessItemTemplateData { + return createRow(container); + } + + renderElement(node: ITreeNode, index: number, templateData: IProcessItemTemplateData, height: number | undefined): void { + const { element } = node; + + const pid = element.pid.toFixed(0); + + templateData.name.textContent = this.model.getName(element.pid, element.name); + templateData.name.title = element.cmd; + + templateData.cpu.textContent = element.load.toFixed(0); + templateData.pid.textContent = pid; + templateData.pid.parentElement!.id = `pid-${pid}`; + + const memory = isWindows ? element.mem : (this.totalMem * (element.mem / 100)); + templateData.memory.textContent = (memory / ByteSize.MB).toFixed(0); + } + + disposeTemplate(templateData: IProcessItemTemplateData): void { + // Nothing to do + } +} + +class ProcessAccessibilityProvider implements IListAccessibilityProvider { + + getWidgetAriaLabel(): string { + return localize('processExplorer', "Process Explorer"); + } + + getAriaLabel(element: IMachineProcessInformation | ProcessItem | IRemoteDiagnosticError): string | null { + if (isProcessItem(element) || isMachineProcessInformation(element)) { + return element.name; + } + + if (isRemoteDiagnosticError(element)) { + return element.hostName; + } + + return null; + } +} + +class ProcessIdentityProvider implements IIdentityProvider { + + getId(element: IRemoteDiagnosticError | ProcessItem | IMachineProcessInformation): { toString(): string } { + if (isProcessItem(element)) { + return element.pid.toString(); + } + + if (isRemoteDiagnosticError(element)) { + return element.hostName; + } + + if (isProcessInformation(element)) { + return 'processes'; + } + + if (isMachineProcessInformation(element)) { + return element.name; + } + + return 'header'; + } +} + +//#endregion + +export class ProcessExplorerControl extends Disposable { + + private dimensions: Dimension | undefined = undefined; + + private readonly model: ProcessExplorerModel; + private tree: WorkbenchDataTree | undefined; + + private readonly delayer = this._register(new Delayer(1000)); + + constructor( + container: HTMLElement, + @INativeHostService private readonly nativeHostService: INativeHostService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IProductService private readonly productService: IProductService, + @IContextMenuService private readonly contextMenuService: IContextMenuService, + @ICommandService private readonly commandService: ICommandService, + @IProcessMainService private readonly processMainService: IProcessMainService + ) { + super(); + + this.model = new ProcessExplorerModel(this.productService); + this.create(container); + } + + private async create(container: HTMLElement): Promise { + const { totalmem } = await this.nativeHostService.getOSStatistics(); + this.createProcessTree(container, totalmem); + + this.update(); + } + + private createProcessTree(container: HTMLElement, totalmem: number): void { + container.classList.add('process-explorer'); + container.id = 'process-explorer'; + + const renderers = [ + new ProcessRenderer(totalmem, this.model), + new ProcessHeaderTreeRenderer(), + new MachineRenderer(), + new ErrorRenderer() + ]; + + this.tree = this._register(this.instantiationService.createInstance( + WorkbenchDataTree, + 'processExplorer', + container, + new ProcessListDelegate(), + renderers, + new ProcessTreeDataSource(), + { + accessibilityProvider: new ProcessAccessibilityProvider(), + identityProvider: new ProcessIdentityProvider(), + expandOnlyOnTwistieClick: true, + renderIndentGuides: RenderIndentGuides.OnHover + })); + + this._register(this.tree.onKeyDown(e => this.onTreeKeyDown(e))); + this._register(this.tree.onContextMenu(e => this.onTreeContextMenu(container, e))); + + this.tree.setInput(this.model); + this.layoutTree(); + } + + private async onTreeKeyDown(e: KeyboardEvent): Promise { + const event = new StandardKeyboardEvent(e); + if (event.keyCode === KeyCode.KeyE && event.altKey) { + const selectionPids = this.getSelectedPids(); + await Promise.all(selectionPids.map(pid => this.nativeHostService.killProcess(pid, 'SIGTERM'))); + } + } + + private onTreeContextMenu(container: HTMLElement, e: ITreeContextMenuEvent): void { + if (!isProcessItem(e.element)) { + return; + } + + const item = e.element; + const pid = Number(item.pid); + + const actions: IAction[] = []; + + actions.push(toAction({ id: 'killProcess', label: localize('killProcess', "Kill Process"), run: () => this.nativeHostService.killProcess(pid, 'SIGTERM') })); + actions.push(toAction({ id: 'forceKillProcess', label: localize('forceKillProcess', "Force Kill Process"), run: () => this.nativeHostService.killProcess(pid, 'SIGKILL') })); + + actions.push(new Separator()); + + actions.push(toAction({ + id: 'copy', + label: localize('copy', "Copy"), + run: () => { + const selectionPids = this.getSelectedPids(); + + if (!selectionPids?.includes(pid)) { + selectionPids.length = 0; // If the selection does not contain the right clicked item, copy the right clicked item only. + selectionPids.push(pid); + } + + const rows = selectionPids?.map(e => getDocument(container).getElementById(`pid-${e}`)).filter(e => !!e); + if (rows) { + const text = rows.map(e => e.innerText).filter(e => !!e); + this.nativeHostService.writeClipboardText(text.join('\n')); + } + } + })); + + actions.push(toAction({ + id: 'copyAll', + label: localize('copyAll', "Copy All"), + run: () => { + const processList = getDocument(container).getElementById('process-explorer'); + if (processList) { + this.nativeHostService.writeClipboardText(processList.innerText); + } + } + })); + + if (this.isDebuggable(item.cmd)) { + actions.push(new Separator()); + actions.push(toAction({ id: 'debug', label: localize('debug', "Debug"), run: () => this.attachTo(item) })); + } + + this.contextMenuService.showContextMenu({ + getAnchor: () => e.anchor, + getActions: () => actions + }); + } + + private isDebuggable(cmd: string): boolean { + const matches = DEBUG_FLAGS_PATTERN.exec(cmd); + + return (matches && matches.groups!.port !== '0') || cmd.indexOf('node ') >= 0 || cmd.indexOf('node.exe') >= 0; + } + + private attachTo(item: ProcessItem): void { + const config: { type: string; request: string; name: string; port?: number; processId?: string } = { + type: 'node', + request: 'attach', + name: `process ${item.pid}` + }; + + let matches = DEBUG_FLAGS_PATTERN.exec(item.cmd); + if (matches) { + config.port = Number(matches.groups!.port); + } else { + config.processId = String(item.pid); // no port -> try to attach via pid (send SIGUSR1) + } + + // a debug-port=n or inspect-port=n overrides the port + matches = DEBUG_PORT_PATTERN.exec(item.cmd); + if (matches) { + config.port = Number(matches.groups!.port); // override port + } + + this.commandService.executeCommand('debug.startFromConfig', config); + } + + private getSelectedPids(): number[] { + return coalesce(this.tree?.getSelection()?.map(e => { + if (!isProcessItem(e)) { + return undefined; + } + + return e.pid; + }) ?? []); + } + + private async update(): Promise { + const { processes, pidToNames } = await this.processMainService.resolve(); + + this.model.update(processes, pidToNames); + + this.tree?.updateChildren(); + this.layoutTree(); + + this.delayer.trigger(() => this.update()); + } + + focus(): void { + this.tree?.domFocus(); + } + + layout(dimension: Dimension): void { + this.dimensions = dimension; + + this.layoutTree(); + } + + private layoutTree(): void { + if (this.dimensions && this.tree) { + this.tree.layout(this.dimensions.height, this.dimensions.width); + } + } +} + +class ProcessExplorerModel implements IProcessTree { + + processes: IProcessInformation = { processRoots: [] }; + + private readonly mapPidToName = new Map(); + + constructor(@IProductService private productService: IProductService) { } + + update(processRoots: IMachineProcessInformation[], pidToNames: [number, string][]): void { + + // PID to Names + this.mapPidToName.clear(); + + for (const [pid, name] of pidToNames) { + this.mapPidToName.set(pid, name); + } + + // Processes + processRoots.forEach((info, index) => { + if (isProcessItem(info.rootProcess)) { + info.rootProcess.name = index === 0 ? this.productService.applicationName : 'remote-server'; + } + }); + + this.processes = { processRoots }; + } + + getName(pid: number, fallback: string): string { + return this.mapPidToName.get(pid) ?? fallback; + } +} diff --git a/src/vs/workbench/contrib/processExplorer/electron-sandbox/processExplorerEditoInput.ts b/src/vs/workbench/contrib/processExplorer/electron-sandbox/processExplorerEditoInput.ts new file mode 100644 index 00000000000..6ef73d347ff --- /dev/null +++ b/src/vs/workbench/contrib/processExplorer/electron-sandbox/processExplorerEditoInput.ts @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Codicon } from '../../../../base/common/codicons.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { URI } from '../../../../base/common/uri.js'; +import { localize } from '../../../../nls.js'; +import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js'; +import { EditorInputCapabilities, IUntypedEditorInput } from '../../../common/editor.js'; +import { EditorInput } from '../../../common/editor/editorInput.js'; + +const processExplorerEditorIcon = registerIcon('process-explorer-editor-label-icon', Codicon.serverProcess, localize('processExplorerEditorLabelIcon', 'Icon of the process explorer editor label.')); + +export class ProcessExplorerEditorInput extends EditorInput { + + static readonly ID = 'workbench.editors.processEditorInput'; + + static readonly RESOURCE = URI.from({ + scheme: 'process-explorer', + path: 'default' + }); + + private static _instance: ProcessExplorerEditorInput; + static get instance() { + if (!ProcessExplorerEditorInput._instance || ProcessExplorerEditorInput._instance.isDisposed()) { + ProcessExplorerEditorInput._instance = new ProcessExplorerEditorInput(); + } + + return ProcessExplorerEditorInput._instance; + } + + override get typeId(): string { return ProcessExplorerEditorInput.ID; } + + override get capabilities(): EditorInputCapabilities { return EditorInputCapabilities.Readonly | EditorInputCapabilities.Singleton; } + + readonly resource = ProcessExplorerEditorInput.RESOURCE; + + override getName(): string { + return localize('processExplorerInputName', "Process Explorer"); + } + + override getIcon(): ThemeIcon { + return processExplorerEditorIcon; + } + + override matches(other: EditorInput | IUntypedEditorInput): boolean { + if (super.matches(other)) { + return true; + } + + return other instanceof ProcessExplorerEditorInput; + } +} diff --git a/src/vs/workbench/contrib/processExplorer/electron-sandbox/processExplorerEditor.ts b/src/vs/workbench/contrib/processExplorer/electron-sandbox/processExplorerEditor.ts new file mode 100644 index 00000000000..fa160083ae5 --- /dev/null +++ b/src/vs/workbench/contrib/processExplorer/electron-sandbox/processExplorerEditor.ts @@ -0,0 +1,42 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Dimension } from '../../../../base/browser/dom.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { IStorageService } from '../../../../platform/storage/common/storage.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { EditorPane } from '../../../browser/parts/editor/editorPane.js'; +import { IEditorGroup } from '../../../services/editor/common/editorGroupsService.js'; +import { ProcessExplorerControl } from './processExplorerControl.js'; + +export class ProcessExplorerEditor extends EditorPane { + + static readonly ID: string = 'workbench.editor.processExplorer'; + + private processExplorerControl: ProcessExplorerControl | undefined = undefined; + + constructor( + group: IEditorGroup, + @ITelemetryService telemetryService: ITelemetryService, + @IThemeService themeService: IThemeService, + @IStorageService storageService: IStorageService, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { + super(ProcessExplorerEditor.ID, group, telemetryService, themeService, storageService); + } + + protected override createEditor(parent: HTMLElement): void { + this.processExplorerControl = this._register(this.instantiationService.createInstance(ProcessExplorerControl, parent)); + } + + override focus(): void { + this.processExplorerControl?.focus(); + } + + override layout(dimension: Dimension): void { + this.processExplorerControl?.layout(dimension); + } +} diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 88e0091fe2c..1b4d7c1dd9d 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -30,6 +30,7 @@ import { applicationConfigurationNodeBase, securityConfigurationNodeBase } from import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from '../../platform/window/electron-sandbox/window.js'; import { DefaultAccountManagementContribution } from '../services/accounts/common/defaultAccount.js'; import { registerWorkbenchContribution2, WorkbenchPhase } from '../common/contributions.js'; +import product from '../../platform/product/common/product.js'; // Actions (function registerActions(): void { @@ -147,7 +148,12 @@ import { registerWorkbenchContribution2, WorkbenchPhase } from '../common/contri 'included': !isWindows, 'scope': ConfigurationScope.APPLICATION, 'markdownDescription': localize('application.shellEnvironmentResolutionTimeout', "Controls the timeout in seconds before giving up resolving the shell environment when the application is not already launched from a terminal. See our [documentation](https://go.microsoft.com/fwlink/?linkid=2149667) for more information.") - } + }, + 'application.useNewProcessExplorer': { + 'type': 'boolean', + 'default': product.quality !== 'stable', // TODO@bpasero decide on a default + 'description': localize('useNewProcessExplorer', "Controls whether a the process explorer opens in a floating window."), + }, } }); diff --git a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts index b11cd64b76e..8ca61d329e1 100644 --- a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts @@ -21,7 +21,7 @@ import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; -import { DEFAULT_AUX_WINDOW_SIZE, DEFAULT_COMPACT_AUX_WINDOW_SIZE, IRectangle, WindowMinimumSize } from '../../../../platform/window/common/window.js'; +import { DEFAULT_AUX_WINDOW_SIZE, IRectangle, WindowMinimumSize } from '../../../../platform/window/common/window.js'; import { BaseWindow } from '../../../browser/window.js'; import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js'; import { IHostService } from '../../host/browser/host.js'; @@ -319,7 +319,7 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili height: activeWindow.outerHeight }; - const defaultSize = options?.compact ? DEFAULT_COMPACT_AUX_WINDOW_SIZE : DEFAULT_AUX_WINDOW_SIZE; + const defaultSize = DEFAULT_AUX_WINDOW_SIZE; const width = Math.max(options?.bounds?.width ?? defaultSize.width, WindowMinimumSize.WIDTH); const height = Math.max(options?.bounds?.height ?? defaultSize.height, WindowMinimumSize.HEIGHT); diff --git a/src/vs/workbench/services/editor/common/editorGroupFinder.ts b/src/vs/workbench/services/editor/common/editorGroupFinder.ts index 0ae0eaadb1c..afcc959e8b1 100644 --- a/src/vs/workbench/services/editor/common/editorGroupFinder.ts +++ b/src/vs/workbench/services/editor/common/editorGroupFinder.ts @@ -92,7 +92,11 @@ function doFindGroup(input: EditorInputWithOptions | IUntypedEditorInput, prefer // Group: Aux Window else if (preferredGroup === AUX_WINDOW_GROUP) { - group = editorGroupService.createAuxiliaryEditorPart({ compact: options?.compact }).then(group => group.activeGroup); + group = editorGroupService.createAuxiliaryEditorPart({ + bounds: options?.auxiliary?.bounds, + compact: options?.auxiliary?.compact, + alwaysOnTop: options?.auxiliary?.alwaysOnTop + }).then(group => group.activeGroup); } // Group: Unspecified without a specific index to open diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index 1388eac0a75..018c4348b9a 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -565,7 +565,7 @@ export interface IEditorGroupsService extends IEditorGroupsContainer { * Opens a new window with a full editor part instantiated * in there at the optional position and size on screen. */ - createAuxiliaryEditorPart(options?: { bounds?: Partial; compact?: boolean }): Promise; + createAuxiliaryEditorPart(options?: { bounds?: Partial; compact?: boolean; alwaysOnTop?: boolean }): Promise; /** * Returns the instantiation service that is scoped to the diff --git a/src/vs/workbench/services/files/electron-sandbox/watcherClient.ts b/src/vs/workbench/services/files/electron-sandbox/watcherClient.ts index 0d59c18c285..2c8554796b3 100644 --- a/src/vs/workbench/services/files/electron-sandbox/watcherClient.ts +++ b/src/vs/workbench/services/files/electron-sandbox/watcherClient.ts @@ -35,7 +35,8 @@ export class UniversalWatcherClient extends AbstractUniversalWatcherClient { // the process automatically when the window closes or reloads. const { client, onDidTerminate } = disposables.add(await this.utilityProcessWorkerWorkbenchService.createWorker({ moduleId: 'vs/platform/files/node/watcher/watcherMain', - type: 'fileWatcher' + type: 'fileWatcher', + name: 'file-watcher' })); // React on unexpected termination of the watcher process diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 801eccfff62..e7c40ead98b 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -122,6 +122,7 @@ import './contrib/issue/electron-sandbox/issue.contribution.js'; // Process import './contrib/issue/electron-sandbox/process.contribution.js'; +import './contrib/processExplorer/electron-sandbox/processExplorer.contribution.js'; // Remote import './contrib/remote/electron-sandbox/remote.contribution.js';