Merge from vscode 2b0b9136329c181a9e381463a1f7dc3a2d105a34 (#4880)

This commit is contained in:
Karl Burtram
2019-04-05 10:09:18 -07:00
committed by GitHub
parent 9bd7e30d18
commit cb5bcf2248
433 changed files with 8915 additions and 8361 deletions

View File

@@ -40,6 +40,7 @@ import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
import { normalizeGitHubUrl } from 'vs/code/electron-browser/issue/issueReporterUtil';
import { Button } from 'vs/base/browser/ui/button/button';
import { withUndefinedAsNull } from 'vs/base/common/types';
import { SystemInfo } from 'vs/platform/diagnostics/common/diagnosticsService';
const MAX_URL_LENGTH = platform.isWindows ? 2081 : 5400;
@@ -79,11 +80,12 @@ export class IssueReporter extends Disposable {
this.initServices(configuration);
const isSnap = process.platform === 'linux' && process.env.SNAP && process.env.SNAP_REVISION;
this.issueReporterModel = new IssueReporterModel({
issueType: configuration.data.issueType || IssueType.Bug,
versionInfo: {
vscodeVersion: `${pkg.name} ${pkg.version} (${product.commit || 'Commit unknown'}, ${product.date || 'Date unknown'})`,
os: `${os.type()} ${os.arch()} ${os.release()}`
os: `${os.type()} ${os.arch()} ${os.release()}${isSnap ? ' snap' : ''}`
},
extensionsDisabled: !!this.environmentService.disableExtensions,
});
@@ -104,7 +106,7 @@ export class IssueReporter extends Disposable {
this.updatePreviewButtonState();
});
ipcRenderer.on('vscode:issueSystemInfoResponse', (_: unknown, info: any) => {
ipcRenderer.on('vscode:issueSystemInfoResponse', (_: unknown, info: SystemInfo) => {
this.logService.trace('issueReporter: Received system data');
this.issueReporterModel.update({ systemInfo: info });
this.receivedSystemInfo = true;
@@ -903,19 +905,19 @@ export class IssueReporter extends Disposable {
private updateSystemInfo(state: IssueReporterModelData) {
const target = document.querySelector('.block-system .block-info');
if (target) {
let tableHtml = '';
Object.keys(state.systemInfo).forEach(k => {
const data = typeof state.systemInfo[k] === 'object'
? Object.keys(state.systemInfo[k]).map(key => `${key}: ${state.systemInfo[k][key]}`).join('<br>')
: state.systemInfo[k];
const systemInfo = state.systemInfo!;
let renderedData = `
<table>
<tr><td>CPUs</td><td>${systemInfo.cpus}</td></tr>
<tr><td>GPU Status</td><td>${Object.keys(systemInfo.gpuStatus).map(key => `${key}: ${systemInfo.gpuStatus[key]}`).join('<br>')}</td></tr>
<tr><td>Load (avg)</td><td>${systemInfo.load}</td></tr>
<tr><td>Memory (System)</td><td>${systemInfo.memory}</td></tr>
<tr><td>Process Argv</td><td>${systemInfo.processArgs}</td></tr>
<tr><td>Screen Reader</td><td>${systemInfo.screenReader}</td></tr>
<tr><td>VM</td><td>${systemInfo.vmHint}</td></tr>
</table>`;
tableHtml += `
<tr>
<td>${k}</td>
<td>${data}</td>
</tr>`;
});
target.innerHTML = `<table>${tableHtml}</table>`;
target.innerHTML = renderedData;
}
}

View File

@@ -5,13 +5,14 @@
import { assign } from 'vs/base/common/objects';
import { IssueType, ISettingSearchResult, IssueReporterExtensionData } from 'vs/platform/issue/common/issue';
import { SystemInfo } from 'vs/platform/diagnostics/common/diagnosticsService';
export interface IssueReporterData {
issueType: IssueType;
issueDescription?: string;
versionInfo?: any;
systemInfo?: any;
systemInfo?: SystemInfo;
processInfo?: any;
workspaceInfo?: any;
@@ -149,13 +150,16 @@ ${this.getInfos()}
|---|---|
`;
Object.keys(this._data.systemInfo).forEach(k => {
const data = typeof this._data.systemInfo[k] === 'object'
? Object.keys(this._data.systemInfo[k]).map(key => `${key}: ${this._data.systemInfo[k][key]}`).join('<br>')
: this._data.systemInfo[k];
if (this._data.systemInfo) {
md += `|${k}|${data}|\n`;
});
md += `|CPUs|${this._data.systemInfo.cpus}|
|GPU Status|${Object.keys(this._data.systemInfo.gpuStatus).map(key => `${key}: ${this._data.systemInfo!.gpuStatus[key]}`).join('<br>')}|
|Load (avg)|${this._data.systemInfo.load}|
|Memory (System)|${this._data.systemInfo.memory}|
|Process Argv|${this._data.systemInfo.processArgs}|
|Screen Reader|${this._data.systemInfo.screenReader}|
|VM|${this._data.systemInfo.vmHint}|`;
}
md += '\n</details>';

View File

@@ -44,7 +44,13 @@ Extensions: none
const issueReporterModel = new IssueReporterModel({
issueType: 0,
systemInfo: {
'GPU Status': {
os: 'Darwin',
cpus: 'Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 x 2800)',
memory: '16.00GB',
vmHint: '0%',
processArgs: '',
screenReader: 'no',
gpuStatus: {
'2d_canvas': 'enabled',
'checker_imaging': 'disabled_off'
}
@@ -65,8 +71,13 @@ OS version: undefined
|Item|Value|
|---|---|
|CPUs|Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 x 2800)|
|GPU Status|2d_canvas: enabled<br>checker_imaging: disabled_off|
|Load (avg)|undefined|
|Memory (System)|16.00GB|
|Process Argv||
|Screen Reader|no|
|VM|0%|
</details>Extensions: none
<!-- generated by issue reporter -->`);
});

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/processExplorer';
import { listProcesses, ProcessItem } from 'vs/base/node/ps';
import { listProcesses } from 'vs/base/node/ps';
import { webFrame, ipcRenderer, clipboard } from 'electron';
import { repeat } from 'vs/base/common/strings';
import { totalmem } from 'os';
@@ -15,6 +15,7 @@ import * as browser from 'vs/base/browser/browser';
import * as platform from 'vs/base/common/platform';
import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu';
import { popup } from 'vs/base/parts/contextmenu/electron-browser/contextmenu';
import { ProcessItem } from 'vs/base/common/processes';
let processList: any[];
let mapPidToWindowTitle = new Map<number, string>();

View File

@@ -20,7 +20,7 @@ export class LogsDataCleaner extends Disposable {
}
private cleanUpOldLogsSoon(): void {
let handle: any = setTimeout(() => {
let handle: NodeJS.Timeout | undefined = setTimeout(() => {
handle = undefined;
const currentLog = basename(this.environmentService.logsPath);
@@ -35,6 +35,11 @@ export class LogsDataCleaner extends Disposable {
}).then(null, onUnexpectedError);
}, 10 * 1000);
this._register(toDisposable(() => clearTimeout(handle)));
this._register(toDisposable(() => {
if (handle) {
clearTimeout(handle);
handle = undefined;
}
}));
}
}

View File

@@ -5,7 +5,7 @@
import { basename, dirname, join } from 'vs/base/common/path';
import { onUnexpectedError } from 'vs/base/common/errors';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { readdir, rimraf, stat } from 'vs/base/node/pfs';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import product from 'vs/platform/product/node/product';
@@ -41,13 +41,13 @@ export class NodeCachedDataCleaner {
const nodeCachedDataRootDir = dirname(this._environmentService.nodeCachedDataDir);
const nodeCachedDataCurrent = basename(this._environmentService.nodeCachedDataDir);
let handle: any = setTimeout(() => {
let handle: NodeJS.Timeout | undefined = setTimeout(() => {
handle = undefined;
readdir(nodeCachedDataRootDir).then(entries => {
const now = Date.now();
const deletes: Promise<any>[] = [];
const deletes: Promise<unknown>[] = [];
entries.forEach(entry => {
// name check
@@ -76,8 +76,11 @@ export class NodeCachedDataCleaner {
}, 30 * 1000);
this._disposables.push({
dispose() { clearTimeout(handle); }
});
this._disposables.push(toDisposable(() => {
if (handle) {
clearTimeout(handle);
handle = undefined;
}
}));
}
}

View File

@@ -24,7 +24,7 @@ export class StorageDataCleaner extends Disposable {
}
private cleanUpStorageSoon(): void {
let handle: any = setTimeout(() => {
let handle: NodeJS.Timeout | undefined = setTimeout(() => {
handle = undefined;
// Leverage the backup workspace file to find out which empty workspace is currently in use to
@@ -52,6 +52,11 @@ export class StorageDataCleaner extends Disposable {
}).then(null, onUnexpectedError);
}, 30 * 1000);
this._register(toDisposable(() => clearTimeout(handle)));
this._register(toDisposable(() => {
if (handle) {
clearTimeout(handle);
handle = undefined;
}
}));
}
}

View File

@@ -44,7 +44,7 @@ import { isUndefinedOrNull, withUndefinedAsNull } from 'vs/base/common/types';
import { KeyboardLayoutMonitor } from 'vs/code/electron-main/keyboard';
import { URI } from 'vs/base/common/uri';
import { WorkspacesChannel } from 'vs/platform/workspaces/node/workspacesIpc';
import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspacesMainService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
import { getMachineId } from 'vs/base/node/id';
import { Win32UpdateService } from 'vs/platform/update/electron-main/updateService.win32';
import { LinuxUpdateService } from 'vs/platform/update/electron-main/updateService.linux';
@@ -53,7 +53,7 @@ import { IIssueService } from 'vs/platform/issue/common/issue';
import { IssueChannel } from 'vs/platform/issue/node/issueIpc';
import { IssueService } from 'vs/platform/issue/electron-main/issueService';
import { LogLevelSetterChannel } from 'vs/platform/log/node/logIpc';
import * as errors from 'vs/base/common/errors';
import { setUnexpectedErrorHandler, onUnexpectedError } from 'vs/base/common/errors';
import { ElectronURLListener } from 'vs/platform/url/electron-main/electronUrlListener';
import { serve as serveDriver } from 'vs/platform/driver/electron-main/driver';
import { connectRemoteAgentManagement, ManagementPersistentConnection, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection';
@@ -81,6 +81,8 @@ import { URLService } from 'vs/platform/url/common/urlService';
import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { nodeWebSocketFactory } from 'vs/platform/remote/node/nodeWebSocketFactory';
import { VSBuffer } from 'vs/base/common/buffer';
import { statSync } from 'fs';
export class CodeApplication extends Disposable {
@@ -114,9 +116,9 @@ export class CodeApplication extends Disposable {
private registerListeners(): void {
// We handle uncaught exceptions here to prevent electron from opening a dialog to the user
errors.setUnexpectedErrorHandler(err => this.onUnexpectedError(err));
setUnexpectedErrorHandler(err => this.onUnexpectedError(err));
process.on('uncaughtException', err => this.onUnexpectedError(err));
process.on('unhandledRejection', (reason: any, promise: Promise<any>) => errors.onUnexpectedError(reason));
process.on('unhandledRejection', (reason: unknown) => onUnexpectedError(reason));
// Contextmenu via IPC support
registerContextMenuListener();
@@ -141,7 +143,7 @@ export class CodeApplication extends Disposable {
// Security related measures (https://electronjs.org/docs/tutorial/security)
// DO NOT CHANGE without consulting the documentation
app.on('web-contents-created', (event: any, contents) => {
app.on('web-contents-created', (event: Electron.Event, contents) => {
contents.on('will-attach-webview', (event: Electron.Event, webPreferences, params) => {
const isValidWebviewSource = (source: string): boolean => {
@@ -190,13 +192,13 @@ export class CodeApplication extends Disposable {
});
let macOpenFileURIs: IURIToOpen[] = [];
let runningTimeout: any = null;
let runningTimeout: NodeJS.Timeout | null = null;
app.on('open-file', (event: Event, path: string) => {
this.logService.trace('App#open-file: ', path);
event.preventDefault();
// Keep in array because more might come!
macOpenFileURIs.push({ uri: URI.file(path) });
macOpenFileURIs.push(getURIToOpenFromPathSync(path));
// Clear previous handler if any
if (runningTimeout !== null) {
@@ -245,7 +247,7 @@ export class CodeApplication extends Disposable {
});
});
ipc.on('vscode:broadcast', (event: Event, windowId: number, broadcast: { channel: string; payload: any; }) => {
ipc.on('vscode:broadcast', (event: Event, windowId: number, broadcast: { channel: string; payload: object; }) => {
if (this.windowsMainService && broadcast.channel && !isUndefinedOrNull(broadcast.payload)) {
this.logService.trace('IPC#vscode:broadcast', broadcast.channel, broadcast.payload);
@@ -257,6 +259,13 @@ export class CodeApplication extends Disposable {
}
});
ipc.on('vscode:extensionHostDebug', (_: Event, windowId: number, broadcast: any) => {
if (this.windowsMainService) {
// Send to all windows (except sender window)
this.windowsMainService.sendToAll('vscode:extensionHostDebug', broadcast, [windowId]);
}
});
ipc.on('vscode:toggleDevTools', (event: Event) => event.sender.toggleDevTools());
ipc.on('vscode:openDevTools', (event: Event) => event.sender.openDevTools());
@@ -290,7 +299,7 @@ export class CodeApplication extends Disposable {
}
}
private onBroadcast(event: string, payload: any): void {
private onBroadcast(event: string, payload: object): void {
// Theme changes
if (event === 'vscode:changeColorTheme' && typeof payload === 'string') {
@@ -571,7 +580,7 @@ export class CodeApplication extends Disposable {
this.windowsMainService.ready(this.userEnv);
// Open our first window
const macOpenFiles = (<any>global).macOpenFiles as string[];
const macOpenFiles: string[] = (<any>global).macOpenFiles;
const context = !!process.env['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP;
const hasCliArgs = hasArgs(args._);
const hasFolderURIs = hasArgs(args['folder-uri']);
@@ -597,7 +606,7 @@ export class CodeApplication extends Disposable {
return this.windowsMainService.open({
context: OpenContext.DOCK,
cli: args,
urisToOpen: macOpenFiles.map(file => ({ uri: URI.file(file) })),
urisToOpen: macOpenFiles.map(getURIToOpenFromPathSync),
noRecentEntry,
waitMarkerFileURI,
initialStartup: true
@@ -715,7 +724,7 @@ export class CodeApplication extends Disposable {
}
const resolvedAuthorities = new Map<string, ResolvedAuthority>();
ipc.on('vscode:remoteAuthorityResolved', (event: any, data: ResolvedAuthority) => {
ipc.on('vscode:remoteAuthorityResolved', (event: Electron.Event, data: ResolvedAuthority) => {
this.logService.info('Received resolved authority', data.authority);
resolvedAuthorities.set(data.authority, data);
// Make sure to close and remove any existing connections
@@ -763,15 +772,28 @@ export class CodeApplication extends Disposable {
const channel = rawClient.getChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME);
// TODO@alex don't use call directly, wrap it around a `RemoteExtensionsFileSystemProvider`
const fileContents = await channel.call<Uint8Array>('readFile', [uri]);
callback(Buffer.from(fileContents));
const fileContents = await channel.call<VSBuffer>('readFile', [uri]);
callback(<Buffer>fileContents.buffer);
} else {
callback(undefined);
}
} catch (err) {
errors.onUnexpectedError(err);
onUnexpectedError(err);
callback(undefined);
}
});
}
}
function getURIToOpenFromPathSync(path: string): IURIToOpen {
try {
const fileStat = statSync(path);
if (fileStat.isDirectory()) {
return { folderUri: URI.file(path) };
} else if (hasWorkspaceFileExtension(path)) {
return { workspaceUri: URI.file(path) };
}
} catch (error) {
}
return { fileUri: URI.file(path) };
}

View File

@@ -125,7 +125,7 @@ function setupIPC(accessor: ServicesAccessor): Promise<Server> {
// Show a warning dialog after some timeout if it takes long to talk to the other instance
// Skip this if we are running with --wait where it is expected that we wait for a while.
// Also skip when gathering diagnostics (--status) which can take a longer time.
let startupWarningDialogHandle: any;
let startupWarningDialogHandle: NodeJS.Timeout;
if (!environmentService.wait && !environmentService.status && !environmentService.args['upload-logs']) {
startupWarningDialogHandle = setTimeout(() => {
showStartupWarningDialog(
@@ -140,12 +140,10 @@ function setupIPC(accessor: ServicesAccessor): Promise<Server> {
// Process Info
if (environmentService.args.status) {
return service.getMainProcessInfo().then(info => {
return instantiationService.invokeFunction(accessor => {
return accessor.get(IDiagnosticsService).getDiagnostics(info).then(diagnostics => {
console.log(diagnostics);
return Promise.reject(new ExpectedError());
});
return instantiationService.invokeFunction(accessor => {
return accessor.get(IDiagnosticsService).getDiagnostics(service).then(diagnostics => {
console.log(diagnostics);
return Promise.reject(new ExpectedError());
});
});
}
@@ -316,17 +314,17 @@ function createServices(args: ParsedArgs, bufferLogService: BufferLogService): I
return new InstantiationService(services, true);
}
function initServices(environmentService: IEnvironmentService, stateService: StateService): Promise<any> {
function initServices(environmentService: IEnvironmentService, stateService: StateService): Promise<unknown> {
// Ensure paths for environment service exist
const environmentServiceInitialization = Promise.all<boolean | undefined>([
const environmentServiceInitialization = Promise.all<void | undefined>([
environmentService.extensionsPath,
environmentService.nodeCachedDataDir,
environmentService.logsPath,
environmentService.globalStorageHome,
environmentService.workspaceStorageHome,
environmentService.backupHome
].map((path): undefined | Promise<boolean> => path ? mkdirp(path) : undefined));
].map((path): undefined | Promise<void> => path ? mkdirp(path) : undefined));
// State service
const stateServiceInitialization = stateService.init();

View File

@@ -26,6 +26,7 @@ import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/node/
import { getBackgroundColor } from 'vs/code/electron-main/theme';
import { RunOnceScheduler } from 'vs/base/common/async';
import { withNullAsUndefined } from 'vs/base/common/types';
import { endsWith } from 'vs/base/common/strings';
export interface IWindowCreationOptions {
state: IWindowState;
@@ -61,7 +62,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
private static readonly MAX_URL_LENGTH = 2 * 1024 * 1024; // https://cs.chromium.org/chromium/src/url/url_constants.cc?l=32
private hiddenTitleBarStyle: boolean;
private showTimeoutHandle: any;
private showTimeoutHandle: NodeJS.Timeout;
private _id: number;
private _win: Electron.BrowserWindow;
private _lastFocusTime: number;
@@ -314,7 +315,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
// Inject headers when requests are incoming
const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*'];
this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details: any, cb: any) => {
this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) => {
this.marketplaceHeadersPromise.then(headers => {
const requestHeaders = objects.assign(details.requestHeaders, headers);
cb({ cancel: false, requestHeaders });
@@ -328,7 +329,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
this._win.webContents.session.webRequest.onBeforeRequest(null!, (details, callback) => {
if (details.url.indexOf('.svg') > 0) {
const uri = URI.parse(details.url);
if (uri && !uri.scheme.match(/file/i) && (uri.path as any).endsWith('.svg')) {
if (uri && !uri.scheme.match(/file/i) && endsWith(uri.path, '.svg')) {
return callback({ cancel: true });
}
}
@@ -336,8 +337,8 @@ export class CodeWindow extends Disposable implements ICodeWindow {
return callback({});
});
this._win.webContents.session.webRequest.onHeadersReceived(null!, (details: any, callback: any) => {
const contentType: string[] = (details.responseHeaders['content-type'] || details.responseHeaders['Content-Type']) as any;
this._win.webContents.session.webRequest.onHeadersReceived(null!, (details, callback) => {
const contentType: string[] = (details.responseHeaders['content-type'] || details.responseHeaders['Content-Type']);
if (contentType && Array.isArray(contentType) && contentType.some(x => x.toLowerCase().indexOf('image/svg') >= 0)) {
return callback({ cancel: true });
}
@@ -1048,9 +1049,16 @@ export class CodeWindow extends Disposable implements ICodeWindow {
}
}
let title: string;
if (typeof item.title === 'string') {
title = item.title;
} else {
title = item.title.value;
}
return {
id: item.id,
label: !icon ? item.title as string : undefined,
label: !icon ? title : undefined,
icon
};
});

View File

@@ -7,33 +7,33 @@ import * as fs from 'fs';
import { basename, normalize, join, dirname } from 'vs/base/common/path';
import { localize } from 'vs/nls';
import * as arrays from 'vs/base/common/arrays';
import { assign, mixin, equals } from 'vs/base/common/objects';
import { assign, mixin } from 'vs/base/common/objects';
import { IBackupMainService, IEmptyWindowBackupInfo } from 'vs/platform/backup/common/backup';
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
import { IStateService } from 'vs/platform/state/common/state';
import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window';
import { hasArgs, asArray } from 'vs/platform/environment/node/argv';
import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences } from 'electron';
import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences, FileFilter } from 'electron';
import { parseLineAndColumnAware } from 'vs/code/node/paths';
import { ILifecycleService, UnloadReason, LifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, INativeOpenDialogOptions, IPathsToWaitFor, IEnterWorkspaceResult, IMessageBoxResult, INewWindowOptions, IURIToOpen, URIType, OpenDialogOptions } from 'vs/platform/windows/common/windows';
import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, INativeOpenDialogOptions, IPathsToWaitFor, IEnterWorkspaceResult, IMessageBoxResult, INewWindowOptions, IURIToOpen, isFileToOpen, isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/windows';
import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri } from 'vs/code/node/windowsFinder';
import { Event as CommonEvent, Emitter } from 'vs/base/common/event';
import product from 'vs/platform/product/node/product';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ITelemetryService, ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows';
import { IHistoryMainService, IRecent } from 'vs/platform/history/common/history';
import { IProcessEnvironment, isMacintosh, isWindows } from 'vs/base/common/platform';
import { IWorkspacesMainService, IWorkspaceIdentifier, WORKSPACE_FILTER, isSingleFolderWorkspaceIdentifier, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspacesMainService, IWorkspaceIdentifier, WORKSPACE_FILTER, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { mnemonicButtonLabel } from 'vs/base/common/labels';
import { Schemas } from 'vs/base/common/network';
import { normalizeNFC } from 'vs/base/common/normalization';
import { URI } from 'vs/base/common/uri';
import { Queue } from 'vs/base/common/async';
import { exists } from 'vs/base/node/pfs';
import { exists, dirExists } from 'vs/base/node/pfs';
import { getComparisonKey, isEqual, normalizePath, basename as resourcesBasename, originalFSPath, hasTrailingPathSeparator, removeTrailingPathSeparator } from 'vs/base/common/resources';
import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
import { restoreWindowsState, WindowsStateStorageData, getWindowsStateStoreData } from 'vs/code/electron-main/windowsStateStorage';
@@ -87,7 +87,6 @@ interface IOpenBrowserWindowOptions {
interface IPathParseOptions {
ignoreFileNotFound?: boolean;
gotoLineMode?: boolean;
forceOpenWorkspaceAsFile?: boolean;
remoteAuthority?: string;
}
@@ -206,7 +205,7 @@ export class WindowsManager implements IWindowsMainService {
this.windowsState.openedWindows = [];
}
this.dialogs = new Dialogs(environmentService, telemetryService, stateService, this);
this.dialogs = new Dialogs(stateService, this);
this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, this);
}
@@ -219,7 +218,7 @@ export class WindowsManager implements IWindowsMainService {
private registerListeners(): void {
// React to workbench ready events from windows
ipc.on('vscode:workbenchReady', (event: any, windowId: number) => {
ipc.on('vscode:workbenchReady', (event: Electron.Event, windowId: number) => {
this.logService.trace('IPC#vscode-workbenchReady');
const win = this.getWindowById(windowId);
@@ -843,26 +842,26 @@ export class WindowsManager implements IWindowsMainService {
private doExtractPathsFromAPI(openConfig: IOpenConfiguration): IPathToOpen[] {
const pathsToOpen: IPathToOpen[] = [];
const cli = openConfig.cli;
const parseOptions: IPathParseOptions = { gotoLineMode: cli && cli.goto, forceOpenWorkspaceAsFile: openConfig.forceOpenWorkspaceAsFile };
const parseOptions: IPathParseOptions = { gotoLineMode: cli && cli.goto };
for (const pathToOpen of openConfig.urisToOpen || []) {
if (!pathToOpen) {
continue;
}
const path = this.parseUri(pathToOpen.uri, pathToOpen.typeHint, parseOptions);
const path = this.parseUri(pathToOpen, parseOptions);
if (path) {
path.label = pathToOpen.label;
pathsToOpen.push(path);
} else {
const uri = resourceFromURIToOpen(pathToOpen);
// Warn about the invalid URI or path
let message, detail;
if (pathToOpen.uri.scheme === Schemas.file) {
if (uri.scheme === Schemas.file) {
message = localize('pathNotExistTitle', "Path does not exist");
detail = localize('pathNotExistDetail', "The path '{0}' does not seem to exist anymore on disk.", pathToOpen.uri.fsPath);
detail = localize('pathNotExistDetail', "The path '{0}' does not seem to exist anymore on disk.", uri.fsPath);
} else {
message = localize('uriInvalidTitle', "URI can not be opened");
detail = localize('uriInvalidDetail', "The URI '{0}' is not valid and can not be opened.", pathToOpen.uri.toString());
detail = localize('uriInvalidDetail', "The URI '{0}' is not valid and can not be opened.", uri.toString());
}
const options: Electron.MessageBoxOptions = {
title: product.nameLong,
@@ -885,19 +884,25 @@ export class WindowsManager implements IWindowsMainService {
// folder uris
const folderUris = asArray(cli['folder-uri']);
for (let folderUri of folderUris) {
const path = this.parseUri(this.argToUri(folderUri), 'folder', parseOptions);
if (path) {
pathsToOpen.push(path);
for (let f of folderUris) {
const folderUri = this.argToUri(f);
if (folderUri) {
const path = this.parseUri({ folderUri }, parseOptions);
if (path) {
pathsToOpen.push(path);
}
}
}
// file uris
const fileUris = asArray(cli['file-uri']);
for (let fileUri of fileUris) {
const path = this.parseUri(this.argToUri(fileUri), 'file');
if (path) {
pathsToOpen.push(path);
for (let f of fileUris) {
const fileUri = this.argToUri(f);
if (fileUri) {
const path = this.parseUri({ fileUri }, parseOptions);
if (path) {
pathsToOpen.push(path);
}
}
}
@@ -944,12 +949,12 @@ export class WindowsManager implements IWindowsMainService {
const windowsToOpen: IPathToOpen[] = [];
for (const openedWindow of openedWindows) {
if (openedWindow.workspace) { // Workspaces
const pathToOpen = this.parseUri(openedWindow.workspace.configPath, 'file', { remoteAuthority: openedWindow.remoteAuthority });
const pathToOpen = this.parseUri({ workspaceUri: openedWindow.workspace.configPath }, { remoteAuthority: openedWindow.remoteAuthority });
if (pathToOpen && pathToOpen.workspace) {
windowsToOpen.push(pathToOpen);
}
} else if (openedWindow.folderUri) { // Folders
const pathToOpen = this.parseUri(openedWindow.folderUri, 'folder', { remoteAuthority: openedWindow.remoteAuthority });
const pathToOpen = this.parseUri({ folderUri: openedWindow.folderUri }, { remoteAuthority: openedWindow.remoteAuthority });
if (pathToOpen && pathToOpen.folderUri) {
windowsToOpen.push(pathToOpen);
}
@@ -975,7 +980,7 @@ export class WindowsManager implements IWindowsMainService {
restoreWindows = 'all'; // always reopen all windows when an update was applied
} else {
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
restoreWindows = ((windowConfig && windowConfig.restoreWindows) || 'one') as RestoreWindowsSetting;
restoreWindows = ((windowConfig && windowConfig.restoreWindows) || 'one');
if (['all', 'folders', 'one', 'none'].indexOf(restoreWindows) === -1) {
restoreWindows = 'one';
@@ -999,12 +1004,13 @@ export class WindowsManager implements IWindowsMainService {
return undefined;
}
private parseUri(uri: URI | undefined, typeHint?: URIType, options: IPathParseOptions = {}): IPathToOpen | undefined {
if (!uri || !uri.scheme) {
private parseUri(uriToOpen: IURIToOpen, options: IPathParseOptions = {}): IPathToOpen | undefined {
if (!uriToOpen) {
return undefined;
}
let uri = resourceFromURIToOpen(uriToOpen);
if (uri.scheme === Schemas.file) {
return this.parsePath(uri.fsPath, options);
return this.parsePath(uri.fsPath, options, isFileToOpen(uriToOpen));
}
// open remote if either specified in the cli or if it's a remotehost URI
@@ -1013,21 +1019,12 @@ export class WindowsManager implements IWindowsMainService {
// normalize URI
uri = normalizePath(uri);
// remove trailing slash
if (hasTrailingPathSeparator(uri)) {
uri = removeTrailingPathSeparator(uri);
if (!typeHint) {
typeHint = 'folder';
}
}
// if there's no type hint
if (!typeHint && (hasWorkspaceFileExtension(uri.path) || options.gotoLineMode)) {
typeHint = 'file';
}
if (typeHint === 'file') {
if (isFileToOpen(uriToOpen)) {
if (options.gotoLineMode) {
const parsedPath = parseLineAndColumnAware(uri.path);
return {
@@ -1037,16 +1034,15 @@ export class WindowsManager implements IWindowsMainService {
remoteAuthority
};
}
if (hasWorkspaceFileExtension(uri.path) && !options.forceOpenWorkspaceAsFile) {
return {
workspace: getWorkspaceIdentifier(uri),
remoteAuthority
};
}
return {
fileUri: uri,
remoteAuthority
};
} else if (isWorkspaceToOpen(uriToOpen)) {
return {
workspace: getWorkspaceIdentifier(uri),
remoteAuthority
};
}
return {
folderUri: uri,
@@ -1054,7 +1050,7 @@ export class WindowsManager implements IWindowsMainService {
};
}
private parsePath(anyPath: string, options: IPathParseOptions): IPathToOpen | undefined {
private parsePath(anyPath: string, options: IPathParseOptions, forceOpenWorkspaceAsFile?: boolean): IPathToOpen | undefined {
if (!anyPath) {
return undefined;
}
@@ -1079,7 +1075,7 @@ export class WindowsManager implements IWindowsMainService {
if (candidateStat.isFile()) {
// Workspace (unless disabled via flag)
if (!options.forceOpenWorkspaceAsFile) {
if (!forceOpenWorkspaceAsFile) {
const workspace = this.workspacesMainService.resolveLocalWorkspaceSync(URI.file(candidate));
if (workspace) {
return { workspace: { id: workspace.id, configPath: workspace.configPath }, remoteAuthority: workspace.remoteAuthority };
@@ -1452,7 +1448,7 @@ export class WindowsManager implements IWindowsMainService {
// Compute x/y based on display bounds
// Note: important to use Math.round() because Electron does not seem to be too happy about
// display coordinates that are not absolute numbers.
let state = defaultWindowState() as INewWindowState;
let state = defaultWindowState();
state.x = Math.round(displayToUse.bounds.x + (displayToUse.bounds.width / 2) - (state.width! / 2));
state.y = Math.round(displayToUse.bounds.y + (displayToUse.bounds.height / 2) - (state.height! / 2));
@@ -1482,7 +1478,7 @@ export class WindowsManager implements IWindowsMainService {
state = this.ensureNoOverlap(state);
}
state.hasDefaultState = true; // flag as default state
(state as INewWindowState).hasDefaultState = true; // flag as default state
return state;
}
@@ -1537,10 +1533,6 @@ export class WindowsManager implements IWindowsMainService {
return result;
}
pickWorkspaceAndOpen(options: INativeOpenDialogOptions): void {
this.workspacesManager.pickWorkspaceAndOpen(options);
}
focusLastActive(cli: ParsedArgs, context: OpenContext): ICodeWindow {
const lastActive = this.getLastActiveWindow();
if (lastActive) {
@@ -1717,49 +1709,83 @@ export class WindowsManager implements IWindowsMainService {
this._onWindowClose.fire(win.id);
}
pickFileFolderAndOpen(options: INativeOpenDialogOptions): void {
this.doPickAndOpen(options, true /* pick folders */, true /* pick files */);
async pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise<void> {
const title = localize('open', "Open");
const paths = await this.dialogs.pick({ ...options, pickFolders: true, pickFiles: true, title });
if (paths) {
this.sendPickerTelemetry(paths, options.telemetryEventName || 'openFileFolder', options.telemetryExtraData);
const urisToOpen = await Promise.all(paths.map(path => {
return dirExists(path).then(isDir => isDir ? { folderUri: URI.file(path) } : { fileUri: URI.file(path) });
}));
this.open({
context: OpenContext.DIALOG,
contextWindowId: options.windowId,
cli: this.environmentService.args,
urisToOpen,
forceNewWindow: options.forceNewWindow
});
}
}
pickFolderAndOpen(options: INativeOpenDialogOptions): void {
this.doPickAndOpen(options, true /* pick folders */, false /* pick files */);
async pickFolderAndOpen(options: INativeOpenDialogOptions): Promise<void> {
const title = localize('openFolder', "Open Folder");
const paths = await this.dialogs.pick({ ...options, pickFolders: true, title });
if (paths) {
this.sendPickerTelemetry(paths, options.telemetryEventName || 'openFolder', options.telemetryExtraData);
this.open({
context: OpenContext.DIALOG,
contextWindowId: options.windowId,
cli: this.environmentService.args,
urisToOpen: paths.map(path => ({ folderUri: URI.file(path) })),
forceNewWindow: options.forceNewWindow
});
}
}
pickFileAndOpen(options: INativeOpenDialogOptions): void {
this.doPickAndOpen(options, false /* pick folders */, true /* pick files */);
async pickFileAndOpen(options: INativeOpenDialogOptions): Promise<void> {
const title = localize('openFile', "Open File");
const paths = await this.dialogs.pick({ ...options, pickFiles: true, title });
if (paths) {
this.sendPickerTelemetry(paths, options.telemetryEventName || 'openFile', options.telemetryExtraData);
this.open({
context: OpenContext.DIALOG,
contextWindowId: options.windowId,
cli: this.environmentService.args,
urisToOpen: paths.map(path => ({ fileUri: URI.file(path) })),
forceNewWindow: options.forceNewWindow
});
}
}
private doPickAndOpen(options: INativeOpenDialogOptions, pickFolders: boolean, pickFiles: boolean): void {
const internalOptions = options as IInternalNativeOpenDialogOptions;
internalOptions.pickFolders = pickFolders;
internalOptions.pickFiles = pickFiles;
const dialogOptions: OpenDialogOptions = internalOptions.dialogOptions || Object.create(null);
internalOptions.dialogOptions = dialogOptions;
if (!internalOptions.dialogOptions.title) {
if (pickFolders && pickFiles) {
internalOptions.dialogOptions.title = localize('open', "Open");
} else if (pickFolders) {
internalOptions.dialogOptions.title = localize('openFolder', "Open Folder");
} else {
internalOptions.dialogOptions.title = localize('openFile', "Open File");
}
async pickWorkspaceAndOpen(options: INativeOpenDialogOptions): Promise<void> {
const title = localize('openWorkspaceTitle', "Open Workspace");
const buttonLabel = mnemonicButtonLabel(localize({ key: 'openWorkspace', comment: ['&& denotes a mnemonic'] }, "&&Open"));
const filters = WORKSPACE_FILTER;
const paths = await this.dialogs.pick({ ...options, pickFiles: true, title, filters, buttonLabel });
if (paths) {
this.sendPickerTelemetry(paths, options.telemetryEventName || 'openWorkspace', options.telemetryExtraData);
this.open({
context: OpenContext.DIALOG,
contextWindowId: options.windowId,
cli: this.environmentService.args,
urisToOpen: paths.map(path => ({ workspaceUri: URI.file(path) })),
forceNewWindow: options.forceNewWindow
});
}
if (!internalOptions.telemetryEventName) {
if (pickFolders && pickFiles) {
// __GDPR__TODO__ classify event
internalOptions.telemetryEventName = 'openFileFolder';
} else if (pickFolders) {
internalOptions.telemetryEventName = 'openFolder';
} else {
internalOptions.telemetryEventName = 'openFile';
}
}
}
this.dialogs.pickAndOpen(internalOptions);
private sendPickerTelemetry(paths: string[], telemetryEventName: string, telemetryExtraData?: ITelemetryData) {
const numberOfPaths = paths ? paths.length : 0;
// Telemetry
// __GDPR__TODO__ Dynamic event names and dynamic properties. Can not be registered statically.
this.telemetryService.publicLog(telemetryEventName, {
...telemetryExtraData,
outcome: numberOfPaths ? 'success' : 'canceled',
numberOfPaths
});
}
showMessageBox(options: Electron.MessageBoxOptions, win?: ICodeWindow): Promise<IMessageBoxResult> {
@@ -1793,65 +1819,42 @@ export class WindowsManager implements IWindowsMainService {
}
interface IInternalNativeOpenDialogOptions extends INativeOpenDialogOptions {
pickFolders?: boolean;
pickFiles?: boolean;
title: string;
buttonLabel?: string;
filters?: FileFilter[];
}
class Dialogs {
private static readonly workingDirPickerStorageKey = 'pickerWorkingDir';
private readonly mapWindowToDialogQueue: Map<number, Queue<any>>;
private readonly noWindowDialogQueue: Queue<any>;
private readonly mapWindowToDialogQueue: Map<number, Queue<void>>;
private readonly noWindowDialogQueue: Queue<void>;
constructor(
private readonly environmentService: IEnvironmentService,
private readonly telemetryService: ITelemetryService,
private readonly stateService: IStateService,
private readonly windowsMainService: IWindowsMainService,
private readonly windowsMainService: IWindowsMainService
) {
this.mapWindowToDialogQueue = new Map<number, Queue<any>>();
this.noWindowDialogQueue = new Queue<any>();
this.mapWindowToDialogQueue = new Map<number, Queue<void>>();
this.noWindowDialogQueue = new Queue<void>();
}
pickAndOpen(options: INativeOpenDialogOptions): void {
this.getFileOrFolderUris(options).then(paths => {
const numberOfPaths = paths ? paths.length : 0;
// Telemetry
if (options.telemetryEventName) {
// __GDPR__TODO__ Dynamic event names and dynamic properties. Can not be registered statically.
this.telemetryService.publicLog(options.telemetryEventName, {
...options.telemetryExtraData,
outcome: numberOfPaths ? 'success' : 'canceled',
numberOfPaths
});
}
// Open
if (numberOfPaths) {
this.windowsMainService.open({
context: OpenContext.DIALOG,
contextWindowId: options.windowId,
cli: this.environmentService.args,
urisToOpen: paths,
forceNewWindow: options.forceNewWindow,
forceOpenWorkspaceAsFile: options.dialogOptions && !equals(options.dialogOptions.filters, WORKSPACE_FILTER)
});
}
});
}
private getFileOrFolderUris(options: IInternalNativeOpenDialogOptions): Promise<IURIToOpen[] | undefined> {
pick(options: IInternalNativeOpenDialogOptions): Promise<string[] | undefined> {
// Ensure dialog options
const dialogOptions = options.dialogOptions || Object.create(null);
options.dialogOptions = dialogOptions;
const dialogOptions: Electron.OpenDialogOptions = {
title: options.title,
buttonLabel: options.buttonLabel,
filters: options.filters
};
// Ensure defaultPath
if (!dialogOptions.defaultPath) {
dialogOptions.defaultPath = this.stateService.getItem<string>(Dialogs.workingDirPickerStorageKey);
}
dialogOptions.defaultPath = options.defaultPath || this.stateService.getItem<string>(Dialogs.workingDirPickerStorageKey);
// Ensure properties
if (typeof options.pickFiles === 'boolean' || typeof options.pickFolders === 'boolean') {
@@ -1878,13 +1881,7 @@ class Dialogs {
// Remember path in storage for next time
this.stateService.setItem(Dialogs.workingDirPickerStorageKey, dirname(paths[0]));
const result: IURIToOpen[] = [];
for (const path of paths) {
result.push({ uri: URI.file(path) });
}
return result;
return paths;
}
return undefined;
@@ -2057,21 +2054,13 @@ class WorkspacesManager {
return { workspace, backupPath };
}
pickWorkspaceAndOpen(options: INativeOpenDialogOptions): void {
const window = (typeof options.windowId === 'number' ? this.windowsMainService.getWindowById(options.windowId) : undefined) || this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow();
this.windowsMainService.pickFileAndOpen({
windowId: window ? window.id : undefined,
dialogOptions: {
buttonLabel: mnemonicButtonLabel(localize({ key: 'openWorkspace', comment: ['&& denotes a mnemonic'] }, "&&Open")),
title: localize('openWorkspaceTitle', "Open Workspace"),
filters: WORKSPACE_FILTER,
properties: ['openFile'],
defaultPath: options.dialogOptions && options.dialogOptions.defaultPath
},
forceNewWindow: options.forceNewWindow,
telemetryEventName: options.telemetryEventName,
telemetryExtraData: options.telemetryExtraData
});
}
}
function resourceFromURIToOpen(u: IURIToOpen) {
if (isWorkspaceToOpen(u)) {
return u.workspaceUri;
} else if (isFolderToOpen(u)) {
return u.folderUri;
}
return u.fileUri;
}

View File

@@ -13,11 +13,10 @@ import pkg from 'vs/platform/product/node/package';
import * as paths from 'vs/base/common/path';
import * as os from 'os';
import * as fs from 'fs';
import { whenDeleted } from 'vs/base/node/pfs';
import { whenDeleted, writeFileSync } from 'vs/base/node/pfs';
import { findFreePort, randomPort } from 'vs/base/node/ports';
import { resolveTerminalEncoding } from 'vs/base/node/encoding';
import * as iconv from 'iconv-lite';
import { writeFileAndFlushSync } from 'vs/base/node/extfs';
import { isWindows } from 'vs/base/common/platform';
import { ProfilingSession, Target } from 'v8-inspect-profiler';
@@ -98,9 +97,9 @@ export async function main(argv: string[]): Promise<any> {
// prevent removing alternate data streams
// (see https://github.com/Microsoft/vscode/issues/6363)
fs.truncateSync(target, 0);
writeFileAndFlushSync(target, data, { flag: 'r+' });
writeFileSync(target, data, { flag: 'r+' });
} else {
writeFileAndFlushSync(target, data);
writeFileSync(target, data);
}
// Restore previous mode as needed

View File

@@ -76,7 +76,7 @@ export class Main {
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService
) { }
async run(argv: ParsedArgs): Promise<any> {
async run(argv: ParsedArgs): Promise<void> {
if (argv['install-source']) {
await this.setInstallSource(argv['install-source']);
@@ -95,11 +95,11 @@ export class Main {
}
}
private setInstallSource(installSource: string): Promise<any> {
private setInstallSource(installSource: string): Promise<void> {
return writeFile(this.environmentService.installSourcePath, installSource.slice(0, 30));
}
private async listExtensions(showVersions: boolean): Promise<any> {
private async listExtensions(showVersions: boolean): Promise<void> {
const extensions = await this.extensionManagementService.getInstalled(ExtensionType.User);
extensions.forEach(e => console.log(getId(e.manifest, showVersions)));
}
@@ -227,7 +227,7 @@ export class Main {
}
}
private async uninstallExtension(extensions: string[]): Promise<any> {
private async uninstallExtension(extensions: string[]): Promise<void> {
async function getExtensionId(extensionDescription: string): Promise<string> {
if (!/\.vsix$/i.test(extensionDescription)) {
return extensionDescription;

View File

@@ -10,7 +10,6 @@ import * as extpath from 'vs/base/common/extpath';
import * as platform from 'vs/base/common/platform';
import * as types from 'vs/base/common/types';
import { ParsedArgs } from 'vs/platform/environment/common/environment';
import { sanitizeFilePath } from 'vs/base/node/extfs';
export function validatePaths(args: ParsedArgs): ParsedArgs {
@@ -45,7 +44,7 @@ function doValidatePaths(args: string[], gotoLineMode?: boolean): string[] {
pathCandidate = preparePath(cwd, pathCandidate);
}
const sanitizedFilePath = sanitizeFilePath(pathCandidate, cwd);
const sanitizedFilePath = extpath.sanitizeFilePath(pathCandidate, cwd);
const basename = path.basename(sanitizedFilePath);
if (basename /* can be empty if code is opened on root */ && !extpath.isValidBasename(basename)) {

View File

@@ -29,9 +29,9 @@ function getUnixShellEnvironment(): Promise<typeof process.env> {
const buffers: Buffer[] = [];
child.on('error', () => resolve({}));
child.stdout.on('data', b => buffers.push(b as Buffer));
child.stdout.on('data', b => buffers.push(b));
child.on('close', (code: number, signal: any) => {
child.on('close', code => {
if (code !== 0) {
return reject(new Error('Failed to get environment'));
}