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

@@ -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;
}