mirror of https://github.com/microsoft/vscode.git
process - convert explorer to editor (#248120)
This commit is contained in:
parent
5f5d2c13f0
commit
708b6aa379
|
@ -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 {
|
||||
|
|
|
@ -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)!;
|
||||
|
|
|
@ -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<IRectangle>;
|
||||
|
||||
/**
|
||||
* Show editor compact, hiding unnecessary elements.
|
||||
*/
|
||||
compact?: boolean;
|
||||
|
||||
/**
|
||||
* Show the editor always on top of other windows.
|
||||
*/
|
||||
alwaysOnTop?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ITextEditorSelection {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<IProcessMainService>('processService');
|
||||
|
||||
export interface IResolvedProcessInformation {
|
||||
readonly pidToNames: [number, string][];
|
||||
readonly processes: { name: string; rootProcess: ProcessItem | IRemoteDiagnosticError }[];
|
||||
}
|
||||
|
||||
export interface IProcessMainService {
|
||||
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
getSystemStatus(): Promise<string>;
|
||||
stopTracing(): Promise<void>;
|
||||
openProcessExplorer(data: ProcessExplorerData): Promise<void>;
|
||||
|
||||
resolve(): Promise<IResolvedProcessInformation>;
|
||||
|
||||
// Used by the process explorer
|
||||
$getSystemInfo(): Promise<SystemInfo>;
|
||||
$getPerformanceInfo(): Promise<PerformanceInfo>;
|
||||
|
|
|
@ -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<IResolvedProcessInformation> {
|
||||
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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<void> {
|
||||
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);
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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<IEditorPaneRegistry>(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<IEditorFactoryRegistry>(EditorExtensions.EditorFactory).registerEditorSerializer(ProcessExplorerEditorInput.ID, ProcessExplorerEditorInputSerializer);
|
|
@ -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)?=(?<port>\d+)?/;
|
||||
const DEBUG_PORT_PATTERN = /\s--inspect-port=(?<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<IMachineProcessInformation | ProcessItem | IRemoteDiagnosticError> {
|
||||
|
||||
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<IProcessTree, IProcessInformation | IMachineProcessInformation | ProcessItem | IRemoteDiagnosticError> {
|
||||
|
||||
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<IProcessInformation, void, IProcessItemTemplateData> {
|
||||
|
||||
readonly templateId: string = 'header';
|
||||
|
||||
renderTemplate(container: HTMLElement): IProcessItemTemplateData {
|
||||
return createRow(container);
|
||||
}
|
||||
|
||||
renderElement(node: ITreeNode<IProcessInformation, void>, 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<IMachineProcessInformation, void, IProcessRowTemplateData> {
|
||||
|
||||
readonly templateId: string = 'machine';
|
||||
|
||||
renderTemplate(container: HTMLElement): IProcessRowTemplateData {
|
||||
return createRow(container);
|
||||
}
|
||||
|
||||
renderElement(node: ITreeNode<IMachineProcessInformation, void>, 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<IRemoteDiagnosticError, void, IProcessRowTemplateData> {
|
||||
|
||||
readonly templateId: string = 'error';
|
||||
|
||||
renderTemplate(container: HTMLElement): IProcessRowTemplateData {
|
||||
return createRow(container);
|
||||
}
|
||||
|
||||
renderElement(node: ITreeNode<IRemoteDiagnosticError, void>, 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<ProcessItem, void, IProcessItemTemplateData> {
|
||||
|
||||
readonly templateId: string = 'process';
|
||||
|
||||
constructor(private totalMem: number, private model: ProcessExplorerModel) { }
|
||||
|
||||
renderTemplate(container: HTMLElement): IProcessItemTemplateData {
|
||||
return createRow(container);
|
||||
}
|
||||
|
||||
renderElement(node: ITreeNode<ProcessItem, void>, 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<IMachineProcessInformation | ProcessItem | IRemoteDiagnosticError> {
|
||||
|
||||
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<IMachineProcessInformation | ProcessItem | IRemoteDiagnosticError> {
|
||||
|
||||
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<IProcessTree, IProcessTree | IMachineProcessInformation | ProcessItem | IProcessInformation | IRemoteDiagnosticError> | 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<void> {
|
||||
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<IProcessTree, IProcessTree | IMachineProcessInformation | ProcessItem | IProcessInformation | IRemoteDiagnosticError>,
|
||||
'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<void> {
|
||||
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<IProcessTree | IMachineProcessInformation | ProcessItem | IProcessInformation | IRemoteDiagnosticError | null>): 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<void> {
|
||||
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<number, string>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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."),
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<IRectangle>; compact?: boolean }): Promise<IAuxiliaryEditorPart>;
|
||||
createAuxiliaryEditorPart(options?: { bounds?: Partial<IRectangle>; compact?: boolean; alwaysOnTop?: boolean }): Promise<IAuxiliaryEditorPart>;
|
||||
|
||||
/**
|
||||
* Returns the instantiation service that is scoped to the
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
|
|
Loading…
Reference in New Issue