diff --git a/src/vs/code/electron-browser/processExplorer/media/processExplorer.css b/src/vs/code/electron-browser/processExplorer/media/processExplorer.css index b8c736e817c..8d4de6a93a8 100644 --- a/src/vs/code/electron-browser/processExplorer/media/processExplorer.css +++ b/src/vs/code/electron-browser/processExplorer/media/processExplorer.css @@ -85,6 +85,11 @@ td { height: 22px; } +.error { + padding-left: 20px; + white-space: nowrap; +} + tbody > tr:hover { background-color: #2A2D2E; } diff --git a/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts b/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts index ed19f54b2cd..6085bd66794 100644 --- a/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts @@ -17,6 +17,7 @@ import { popup } from 'vs/base/parts/contextmenu/electron-browser/contextmenu'; import { ProcessItem } from 'vs/base/common/processes'; import { addDisposableListener } from 'vs/base/browser/dom'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { isRemoteDiagnosticError, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService'; let mapPidToWindowTitle = new Map(); @@ -138,6 +139,46 @@ function updateSectionCollapsedState(shouldExpand: boolean, body: HTMLElement, t } } +function renderProcessFetchError(sectionName: string, errorMessage: string) { + const container = document.getElementById('process-list'); + if (!container) { + return; + } + + const body = document.createElement('tbody'); + + renderProcessGroupHeader(sectionName, body, container); + + const errorRow = document.createElement('tr'); + const data = document.createElement('td'); + data.textContent = errorMessage; + data.className = 'error'; + data.colSpan = 4; + errorRow.appendChild(data); + + body.appendChild(errorRow); + container.appendChild(body); +} + +function renderProcessGroupHeader(sectionName: string, body: HTMLElement, container: HTMLElement) { + const headerRow = document.createElement('tr'); + const data = document.createElement('td'); + data.textContent = sectionName; + data.colSpan = 4; + headerRow.appendChild(data); + + const twistie = document.createElement('img'); + updateSectionCollapsedState(!collapsedStateCache.get(sectionName), body, twistie, sectionName); + data.prepend(twistie); + + listeners.push(addDisposableListener(data, 'click', (e) => { + const isHidden = body.classList.contains('hidden'); + updateSectionCollapsedState(isHidden, body, twistie, sectionName); + })); + + container.appendChild(headerRow); +} + function renderTableSection(sectionName: string, processList: FormattedProcessItem[], renderManySections: boolean, sectionIsLocal: boolean): void { const container = document.getElementById('process-list'); if (!container) { @@ -150,22 +191,7 @@ function renderTableSection(sectionName: string, processList: FormattedProcessIt const body = document.createElement('tbody'); if (renderManySections) { - const headerRow = document.createElement('tr'); - const data = document.createElement('td'); - data.textContent = sectionName; - data.colSpan = 4; - headerRow.appendChild(data); - - const twistie = document.createElement('img'); - updateSectionCollapsedState(!collapsedStateCache.get(sectionName), body, twistie, sectionName); - data.prepend(twistie); - - listeners.push(addDisposableListener(data, 'click', (e) => { - const isHidden = body.classList.contains('hidden'); - updateSectionCollapsedState(isHidden, body, twistie, sectionName); - })); - - container.appendChild(headerRow); + renderProcessGroupHeader(sectionName, body, container); } processList.forEach(p => { @@ -206,7 +232,7 @@ function renderTableSection(sectionName: string, processList: FormattedProcessIt container.appendChild(body); } -function updateProcessInfo(processLists: { [key: string]: ProcessItem }): void { +function updateProcessInfo(processLists: { [key: string]: ProcessItem | IRemoteDiagnosticError }): void { const container = document.getElementById('process-list'); if (!container) { return; @@ -228,7 +254,12 @@ function updateProcessInfo(processLists: { [key: string]: ProcessItem }): void { const hasMultipleMachines = Object.keys(processLists).length > 1; Object.keys(processLists).forEach((key, i) => { const isLocal = i === 0; - renderTableSection(key, getProcessList(processLists[key], isLocal), hasMultipleMachines, isLocal); + const processItem = processLists[key]; + if (isRemoteDiagnosticError(processItem)) { + renderProcessFetchError(key, processItem.errorMessage); + } else { + renderTableSection(key, getProcessList(processItem, isLocal), hasMultipleMachines, isLocal); + } }); } @@ -354,7 +385,7 @@ export function startup(data: ProcessExplorerData): void { windows.forEach(window => mapPidToWindowTitle.set(window.pid, window.title)); }); - ipcRenderer.on('vscode:listProcessesResponse', (_event: Event, processRoots: { [key: string]: ProcessItem }) => { + ipcRenderer.on('vscode:listProcessesResponse', (_event: Event, processRoots: { [key: string]: ProcessItem | IRemoteDiagnosticError }) => { updateProcessInfo(processRoots); requestProcessList(0); }); diff --git a/src/vs/platform/diagnostics/common/diagnosticsService.ts b/src/vs/platform/diagnostics/common/diagnosticsService.ts index bf5fa38f38f..d8d2ed70420 100644 --- a/src/vs/platform/diagnostics/common/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/common/diagnosticsService.ts @@ -30,6 +30,11 @@ export interface IRemoteDiagnosticInfo extends IDiagnosticInfo { hostName: string; } +export interface IRemoteDiagnosticError { + hostName: string; + errorMessage: string; +} + export interface IDiagnosticInfoOptions { includeProcesses?: boolean; folders?: UriComponents[]; @@ -46,4 +51,8 @@ export interface WorkspaceStats { configFiles: WorkspaceStatItem[]; fileCount: number; maxFilesReached: boolean; +} + +export function isRemoteDiagnosticError(x: any): x is IRemoteDiagnosticError { + return !!x.hostName && !!x.errorMessage; } \ No newline at end of file diff --git a/src/vs/platform/diagnostics/electron-main/diagnosticsService.ts b/src/vs/platform/diagnostics/electron-main/diagnosticsService.ts index e5bc282fb55..1b7eb413e8c 100644 --- a/src/vs/platform/diagnostics/electron-main/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/electron-main/diagnosticsService.ts @@ -15,7 +15,7 @@ import { app } from 'electron'; import { basename } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IMachineInfo, WorkspaceStats, SystemInfo } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { IMachineInfo, WorkspaceStats, SystemInfo, IRemoteDiagnosticInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService'; import { collectWorkspaceStats, getMachineInfo } from 'vs/platform/diagnostics/node/diagnosticsService'; import { ProcessItem } from 'vs/base/common/processes'; @@ -92,23 +92,28 @@ export class DiagnosticsService implements IDiagnosticsService { try { const remoteData = await launchService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true }); remoteData.forEach(diagnostics => { - processInfo += `\n\nRemote: ${diagnostics.hostName}`; - if (diagnostics.processes) { - processInfo += `\n${this.formatProcessList(info, diagnostics.processes)}`; - } + if (isRemoteDiagnosticError(diagnostics)) { + processInfo += `\n${diagnostics.errorMessage}`; + workspaceInfo += `\n${diagnostics.errorMessage}`; + } else { + processInfo += `\n\nRemote: ${diagnostics.hostName}`; + if (diagnostics.processes) { + processInfo += `\n${this.formatProcessList(info, diagnostics.processes)}`; + } - if (diagnostics.workspaceMetadata) { - workspaceInfo += `\n| Remote: ${diagnostics.hostName}`; - for (const folder of Object.keys(diagnostics.workspaceMetadata)) { - const metadata = diagnostics.workspaceMetadata[folder]; + if (diagnostics.workspaceMetadata) { + workspaceInfo += `\n| Remote: ${diagnostics.hostName}`; + for (const folder of Object.keys(diagnostics.workspaceMetadata)) { + const metadata = diagnostics.workspaceMetadata[folder]; - let countMessage = `${metadata.fileCount} files`; - if (metadata.maxFilesReached) { - countMessage = `more than ${countMessage}`; + let countMessage = `${metadata.fileCount} files`; + if (metadata.maxFilesReached) { + countMessage = `more than ${countMessage}`; + } + + workspaceInfo += `| Folder (${folder}): ${countMessage}`; + workspaceInfo += this.formatWorkspaceStats(metadata); } - - workspaceInfo += `| Folder (${folder}): ${countMessage}`; - workspaceInfo += this.formatWorkspaceStats(metadata); } } }); @@ -135,7 +140,7 @@ export class DiagnosticsService implements IDiagnosticsService { processArgs: `${info.mainArguments.join(' ')}`, gpuStatus: app.getGPUFeatureStatus(), screenReader: `${app.isAccessibilitySupportEnabled() ? 'yes' : 'no'}`, - remoteData: await launchService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false }) + remoteData: (await launchService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })).filter((x): x is IRemoteDiagnosticInfo => !(x instanceof Error)) }; @@ -169,25 +174,29 @@ export class DiagnosticsService implements IDiagnosticsService { try { const data = await launchService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true }); data.forEach(diagnostics => { - output.push('\n\n'); - output.push(`Remote: ${diagnostics.hostName}`); - output.push(this.formatMachineInfo(diagnostics.machineInfo)); + if (isRemoteDiagnosticError(diagnostics)) { + output.push(`\n${diagnostics.errorMessage}`); + } else { + output.push('\n\n'); + output.push(`Remote: ${diagnostics.hostName}`); + output.push(this.formatMachineInfo(diagnostics.machineInfo)); - if (diagnostics.processes) { - output.push(this.formatProcessList(info, diagnostics.processes)); - } + if (diagnostics.processes) { + output.push(this.formatProcessList(info, diagnostics.processes)); + } - if (diagnostics.workspaceMetadata) { - for (const folder of Object.keys(diagnostics.workspaceMetadata)) { - const metadata = diagnostics.workspaceMetadata[folder]; + if (diagnostics.workspaceMetadata) { + for (const folder of Object.keys(diagnostics.workspaceMetadata)) { + const metadata = diagnostics.workspaceMetadata[folder]; - let countMessage = `${metadata.fileCount} files`; - if (metadata.maxFilesReached) { - countMessage = `more than ${countMessage}`; + let countMessage = `${metadata.fileCount} files`; + if (metadata.maxFilesReached) { + countMessage = `more than ${countMessage}`; + } + + output.push(`Folder (${folder}): ${countMessage}`); + output.push(this.formatWorkspaceStats(metadata)); } - - output.push(`Folder (${folder}): ${countMessage}`); - output.push(this.formatWorkspaceStats(metadata)); } } }); diff --git a/src/vs/platform/issue/electron-main/issueService.ts b/src/vs/platform/issue/electron-main/issueService.ts index d77bde51d7e..328376ad5b5 100644 --- a/src/vs/platform/issue/electron-main/issueService.ts +++ b/src/vs/platform/issue/electron-main/issueService.ts @@ -16,6 +16,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { IWindowState } from 'vs/platform/windows/electron-main/windows'; import { listProcesses } from 'vs/base/node/ps'; +import { isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService'; const DEFAULT_BACKGROUND_COLOR = '#1E1E1E'; @@ -53,8 +54,12 @@ export class IssueService implements IIssueService { processesMap[localize('local', "Local")] = await listProcesses(mainPid); (await this.launchService.getRemoteDiagnostics({ includeProcesses: true })) .forEach(data => { - if (data.processes) { - processesMap[data.hostName] = data.processes; + if (isRemoteDiagnosticError(data)) { + processesMap[data.hostName] = data; + } else { + if (data.processes) { + processesMap[data.hostName] = data.processes; + } } }); } catch (e) { diff --git a/src/vs/platform/launch/electron-main/launchService.ts b/src/vs/platform/launch/electron-main/launchService.ts index 3c46f9424b6..091c8704a2c 100644 --- a/src/vs/platform/launch/electron-main/launchService.ts +++ b/src/vs/platform/launch/electron-main/launchService.ts @@ -19,7 +19,7 @@ import { BrowserWindow, ipcMain, Event as IpcEvent } from 'electron'; import { Event } from 'vs/base/common/event'; import { hasArgs } from 'vs/platform/environment/node/argv'; import { coalesce } from 'vs/base/common/arrays'; -import { IDiagnosticInfoOptions, IDiagnosticInfo, IRemoteDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { IDiagnosticInfoOptions, IDiagnosticInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService'; export const ID = 'launchService'; export const ILaunchService = createDecorator(ID); @@ -71,7 +71,7 @@ export interface ILaunchService { getMainProcessId(): Promise; getMainProcessInfo(): Promise; getLogsPath(): Promise; - getRemoteDiagnostics(options: IRemoteDiagnosticOptions): Promise; + getRemoteDiagnostics(options: IRemoteDiagnosticOptions): Promise<(IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]>; } export class LaunchChannel implements IServerChannel { @@ -290,9 +290,9 @@ export class LaunchService implements ILaunchService { return Promise.resolve(this.environmentService.logsPath); } - getRemoteDiagnostics(options: IRemoteDiagnosticOptions): Promise { + getRemoteDiagnostics(options: IRemoteDiagnosticOptions): Promise<(IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]> { const windows = this.windowsMainService.getWindows(); - const promises: Promise[] = windows.map(window => { + const promises: Promise[] = windows.map(window => { return new Promise((resolve, reject) => { if (window.remoteAuthority) { const replyChannel = `vscode:getDiagnosticInfoResponse${window.id}`; @@ -306,18 +306,14 @@ export class LaunchService implements ILaunchService { ipcMain.once(replyChannel, (_: IpcEvent, data: IRemoteDiagnosticInfo) => { // No data is returned if getting the connection fails. if (!data) { - resolve(); - } - - if (typeof (data) === 'string') { - reject(new Error(data)); + resolve({ hostName: window.remoteAuthority!, errorMessage: `Unable to resolve connection to '${window.remoteAuthority}'.` }); } resolve(data); }); setTimeout(() => { - resolve(); + resolve({ hostName: window.remoteAuthority!, errorMessage: `Fetching remote diagnostics for '${window.remoteAuthority}' timed out.` }); }, 5000); } else { resolve(); @@ -325,7 +321,7 @@ export class LaunchService implements ILaunchService { }); }); - return Promise.all(promises).then(diagnostics => diagnostics.filter((x): x is IRemoteDiagnosticInfo => !!x)); + return Promise.all(promises).then(diagnostics => diagnostics.filter((x): x is IRemoteDiagnosticInfo | IRemoteDiagnosticError => !!x)); } private getFolderURIs(window: ICodeWindow): URI[] {