Refresh master with initial release/0.24 snapshot (#332)

* Initial port of release/0.24 source code

* Fix additional headers

* Fix a typo in launch.json
This commit is contained in:
Karl Burtram
2017-12-15 15:38:57 -08:00
committed by GitHub
parent 271b3a0b82
commit 6ad0df0e3e
7118 changed files with 107999 additions and 56466 deletions

View File

@@ -0,0 +1,13 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { NodeCachedDataCleaner } from 'vs/code/electron-browser/contrib/nodeCachedDataCleaner';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export function createSharedProcessContributions(service: IInstantiationService): void {
service.createInstance(NodeCachedDataCleaner);
}

View File

@@ -0,0 +1,88 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { onUnexpectedError } from 'vs/base/common/errors';
import { TPromise } from 'vs/base/common/winjs.base';
import { join, basename, dirname } from 'path';
import { readdir, rimraf, stat } from 'vs/base/node/pfs';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import product from 'vs/platform/node/product';
declare type OnNodeCachedDataArgs = [{ errorCode: string, path: string, detail?: string }, { path: string, length: number }];
declare const MonacoEnvironment: { onNodeCachedData: OnNodeCachedDataArgs[] };
export class NodeCachedDataCleaner {
private static _DataMaxAge = product.nameLong.indexOf('Insiders') >= 0
? 1000 * 60 * 60 * 24 * 7 // roughly 1 week
: 1000 * 60 * 60 * 24 * 30 * 3; // roughly 3 months
private _disposables: IDisposable[] = [];
constructor(
@IEnvironmentService private readonly _environmentService: IEnvironmentService
) {
this._manageCachedDataSoon();
}
dispose(): void {
this._disposables = dispose(this._disposables);
}
private _manageCachedDataSoon(): void {
// Cached data is stored as user data and we run a cleanup task everytime
// the editor starts. The strategy is to delete all files that are older than
// 3 months (1 week respectively)
if (!this._environmentService.nodeCachedDataDir) {
return;
}
// The folder which contains folders of cached data. Each of these folder is per
// version
const nodeCachedDataRootDir = dirname(this._environmentService.nodeCachedDataDir);
const nodeCachedDataCurrent = basename(this._environmentService.nodeCachedDataDir);
let handle = setTimeout(() => {
handle = undefined;
readdir(nodeCachedDataRootDir).then(entries => {
const now = Date.now();
const deletes: TPromise<any>[] = [];
entries.forEach(entry => {
// name check
// * not the current cached data folder
if (entry !== nodeCachedDataCurrent) {
const path = join(nodeCachedDataRootDir, entry);
deletes.push(stat(path).then(stats => {
// stat check
// * only directories
// * only when old enough
if (stats.isDirectory()) {
const diff = now - stats.mtime.getTime();
if (diff > NodeCachedDataCleaner._DataMaxAge) {
return rimraf(path);
}
}
return undefined;
}));
}
});
return TPromise.join(deletes);
}).done(undefined, onUnexpectedError);
}, 30 * 1000);
this._disposables.push({
dispose() { clearTimeout(handle); }
});
}
}

View File

@@ -35,6 +35,7 @@ import { WindowsChannelClient } from 'vs/platform/windows/common/windowsIpc';
import { ipcRenderer } from 'electron';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { StorageService, inMemoryLocalStorageInstance } from 'vs/platform/storage/common/storageService';
import { createSharedProcessContributions } from 'vs/code/electron-browser/contrib/contributions';
interface ISharedProcessInitData {
sharedIPCHandle: string;
@@ -98,16 +99,18 @@ function main(server: Server, initData: ISharedProcessInitData): void {
server.registerChannel('telemetryAppender', new TelemetryAppenderChannel(appender));
const services = new ServiceCollection();
const { appRoot, extensionsPath, extensionDevelopmentPath, isBuilt, extensionTestsPath } = accessor.get(IEnvironmentService);
const environmentService = accessor.get(IEnvironmentService);
const { appRoot, extensionsPath, extensionDevelopmentPath, isBuilt, extensionTestsPath, installSource } = environmentService;
if (isBuilt && !extensionDevelopmentPath && product.enableTelemetry) {
if (isBuilt && !extensionDevelopmentPath && !environmentService.args['disable-telemetry'] && product.enableTelemetry) {
const disableStorage = !!extensionTestsPath; // never keep any state when running extension tests!
const storage = disableStorage ? inMemoryLocalStorageInstance : window.localStorage;
const storageService = new StorageService(storage, storage);
const config: ITelemetryServiceConfig = {
appender,
commonProperties: resolveCommonProperties(product.commit, pkg.version)
commonProperties: resolveCommonProperties(product.commit, pkg.version, installSource)
// __GDPR__COMMON__ "common.machineId" : { "classification": "EndUserPseudonymizedInformation", "purpose": "FeatureInsight" }
.then(result => Object.defineProperty(result, 'common.machineId', {
get: () => storageService.get(machineIdStorageKey),
enumerable: true
@@ -132,6 +135,8 @@ function main(server: Server, initData: ISharedProcessInitData): void {
// clean up deprecated extensions
(extensionManagementService as ExtensionManagementService).removeDeprecatedExtensions();
createSharedProcessContributions(instantiationService2);
});
});
}

View File

@@ -5,7 +5,7 @@
'use strict';
import { app, ipcMain as ipc, BrowserWindow } from 'electron';
import { app, ipcMain as ipc, BrowserWindow, dialog } from 'electron';
import * as platform from 'vs/base/common/platform';
import { WindowsManager } from 'vs/code/electron-main/windows';
import { IWindowsService, OpenContext } from 'vs/platform/windows/common/windows';
@@ -57,12 +57,9 @@ import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces
import { dirname, join } from 'path';
import { touch } from 'vs/base/node/pfs';
// {{SQL CARBON EDIT}}
import { ProxyOAuthHandler } from 'sql/code/electron-main/oauth';
export class CodeApplication {
private static APP_ICON_REFRESH_KEY = 'macOSAppIconRefresh';
private static APP_ICON_REFRESH_KEY = 'macOSAppIconRefresh3';
private toDispose: IDisposable[];
private windowsMainService: IWindowsMainService;
@@ -79,7 +76,7 @@ export class CodeApplication {
@ILogService private logService: ILogService,
@IEnvironmentService private environmentService: IEnvironmentService,
@ILifecycleService private lifecycleService: ILifecycleService,
@IConfigurationService private configurationService: ConfigurationService<any>,
@IConfigurationService private configurationService: ConfigurationService,
@IStorageService private storageService: IStorageService,
@IHistoryMainService private historyService: IHistoryMainService
) {
@@ -137,7 +134,7 @@ export class CodeApplication {
!source || (URI.parse(source.toLowerCase()).toString() as any).startsWith(URI.file(this.environmentService.appRoot.toLowerCase()).toString());
app.on('web-contents-created', (event, contents) => {
contents.on('will-attach-webview', (event, webPreferences, params) => {
contents.on('will-attach-webview', (event: Electron.Event, webPreferences, params) => {
delete webPreferences.preload;
webPreferences.nodeIntegration = false;
@@ -231,9 +228,9 @@ export class CodeApplication {
});
// Keyboard layout changes
KeyboardLayoutMonitor.INSTANCE.onDidChangeKeyboardLayout(isISOKeyboard => {
KeyboardLayoutMonitor.INSTANCE.onDidChangeKeyboardLayout(() => {
if (this.windowsMainService) {
this.windowsMainService.sendToAll('vscode:keyboardLayoutChanged', isISOKeyboard);
this.windowsMainService.sendToAll('vscode:keyboardLayoutChanged', false);
}
});
}
@@ -277,11 +274,6 @@ export class CodeApplication {
const authHandler = appInstantiationService.createInstance(ProxyAuthHandler);
this.toDispose.push(authHandler);
// {{SQL CARBON EDIT}}
// Setup OAuth Handler
const oauthHandler = appInstantiationService.createInstance(ProxyOAuthHandler);
this.toDispose.push(oauthHandler);
// Open Windows
appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor));
@@ -299,10 +291,11 @@ export class CodeApplication {
services.set(ICredentialsService, new SyncDescriptor(CredentialsService));
// Telemtry
if (this.environmentService.isBuilt && !this.environmentService.isExtensionDevelopment && !!product.enableTelemetry) {
if (this.environmentService.isBuilt && !this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
const channel = getDelayedChannel<ITelemetryAppenderChannel>(this.sharedProcessClient.then(c => c.getChannel('telemetryAppender')));
const appender = new TelemetryAppenderClient(channel);
const commonProperties = resolveCommonProperties(product.commit, pkg.version)
const commonProperties = resolveCommonProperties(product.commit, pkg.version, this.environmentService.installSource)
// __GDPR__COMMON__ "common.machineId" : { "classification": "EndUserPseudonymizedInformation", "purpose": "FeatureInsight" }
.then(result => Object.defineProperty(result, 'common.machineId', {
get: () => this.storageService.getItem(machineIdStorageKey),
enumerable: true
@@ -378,15 +371,39 @@ export class CodeApplication {
private afterWindowOpen(accessor: ServicesAccessor): void {
const appInstantiationService = accessor.get(IInstantiationService);
// Setup Windows mutex
let windowsMutex: Mutex = null;
if (platform.isWindows) {
// Setup Windows mutex
try {
const Mutex = (require.__$__nodeRequire('windows-mutex') as any).Mutex;
windowsMutex = new Mutex(product.win32MutexName);
this.toDispose.push({ dispose: () => windowsMutex.release() });
} catch (e) {
// noop
if (!this.environmentService.isBuilt) {
dialog.showMessageBox({
title: product.nameLong,
type: 'warning',
message: 'Failed to load windows-mutex!',
detail: e.toString(),
noLink: true
});
}
}
// Ensure Windows foreground love module
try {
<any>require.__$__nodeRequire('windows-foreground-love');
} catch (e) {
if (!this.environmentService.isBuilt) {
dialog.showMessageBox({
title: product.nameLong,
type: 'warning',
message: 'Failed to load windows-foreground-love!',
detail: e.toString(),
noLink: true
});
}
}
}

View File

@@ -79,6 +79,7 @@ export class ProxyAuthHandler {
const onWindowClose = () => cb('', '');
win.on('close', onWindowClose);
win.setMenu(null);
win.loadURL(url);
win.webContents.executeJavaScript(javascript, true).then(({ username, password }: Credentials) => {
cb(username, password);

View File

@@ -7,7 +7,6 @@
import * as nativeKeymap from 'native-keymap';
import { IDisposable } from 'vs/base/common/lifecycle';
import { isMacintosh } from 'vs/base/common/platform';
import { IStorageService } from 'vs/platform/storage/node/storage';
import Event, { Emitter, once } from 'vs/base/common/event';
import { ConfigWatcher } from 'vs/base/node/config';
@@ -21,59 +20,24 @@ export class KeyboardLayoutMonitor {
public static readonly INSTANCE = new KeyboardLayoutMonitor();
private _emitter: Emitter<boolean>;
private _emitter: Emitter<void>;
private _registered: boolean;
private _isISOKeyboard: boolean;
private constructor() {
this._emitter = new Emitter<boolean>();
this._emitter = new Emitter<void>();
this._registered = false;
this._isISOKeyboard = this._readIsISOKeyboard();
}
public onDidChangeKeyboardLayout(callback: (isISOKeyboard: boolean) => void): IDisposable {
public onDidChangeKeyboardLayout(callback: () => void): IDisposable {
if (!this._registered) {
this._registered = true;
nativeKeymap.onDidChangeKeyboardLayout(() => {
this._emitter.fire(this._isISOKeyboard);
this._emitter.fire();
});
if (isMacintosh) {
// See https://github.com/Microsoft/vscode/issues/24153
// On OSX, on ISO keyboards, Chromium swaps the scan codes
// of IntlBackslash and Backquote.
//
// The C++ methods can give the current keyboard type (ISO or not)
// only after a NSEvent was handled.
//
// We therefore poll.
setInterval(() => {
let newValue = this._readIsISOKeyboard();
if (this._isISOKeyboard === newValue) {
// no change
return;
}
this._isISOKeyboard = newValue;
this._emitter.fire(this._isISOKeyboard);
}, 3000);
}
}
return this._emitter.event(callback);
}
private _readIsISOKeyboard(): boolean {
if (isMacintosh) {
return nativeKeymap.isISOKeyboard();
}
return false;
}
public isISOKeyboard(): boolean {
return this._isISOKeyboard;
}
}
export interface IKeybinding {
@@ -157,7 +121,7 @@ export class KeybindingsResolver {
private resolveKeybindings(win = this.windowsService.getLastActiveWindow()): void {
if (this.commandIds.size && win) {
const commandIds = [];
const commandIds: string[] = [];
this.commandIds.forEach(id => commandIds.push(id));
win.sendWhenReady('vscode:resolveKeybindings', JSON.stringify(commandIds));
}
@@ -174,4 +138,9 @@ export class KeybindingsResolver {
return this.keybindings[commandId];
}
public dispose(): void {
this._onKeybindingsChanged.dispose();
this.keybindingsWatcher.dispose();
}
}

View File

@@ -14,6 +14,7 @@ import { ParsedArgs } from 'vs/platform/environment/common/environment';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { OpenContext } from 'vs/platform/windows/common/windows';
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
import { whenDeleted } from 'vs/base/node/pfs';
export const ID = 'launchService';
export const ILaunchService = createDecorator<ILaunchService>(ID);
@@ -104,8 +105,8 @@ export class LaunchService implements ILaunchService {
context,
cli: args,
userEnv,
forceNewWindow: args.wait || args['new-window'],
preferNewWindow: !args['reuse-window'],
forceNewWindow: args['new-window'],
preferNewWindow: !args['reuse-window'] && !args.wait,
forceReuseWindow: args['reuse-window'],
diffMode: args.diff,
addMode: args.add
@@ -113,9 +114,13 @@ export class LaunchService implements ILaunchService {
}
// If the other instance is waiting to be killed, we hook up a window listener if one window
// is being used and only then resolve the startup promise which will kill this second instance
// is being used and only then resolve the startup promise which will kill this second instance.
// In addition, we poll for the wait marker file to be deleted to return.
if (args.wait && usedWindows.length === 1 && usedWindows[0]) {
return this.windowsService.waitForWindowClose(usedWindows[0].id);
return TPromise.any([
this.windowsService.waitForWindowCloseOrLoad(usedWindows[0].id),
whenDeleted(args.waitMarkerFilePath)
]).then(() => void 0, () => void 0);
}
return TPromise.as(null);

View File

@@ -138,7 +138,7 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
// it happens on Linux and OS X that the pipe is left behind
// let's delete it, since we can't connect to it
// and the retry the whole thing
// and then retry the whole thing
try {
fs.unlinkSync(environmentService.mainIPCHandle);
} catch (e) {
@@ -214,4 +214,4 @@ function main() {
}).done(null, err => instantiationService.invokeFunction(quit, err));
}
main();
main();

View File

@@ -10,9 +10,9 @@ import { isMacintosh, isLinux, isWindows, language } from 'vs/base/common/platfo
import * as arrays from 'vs/base/common/arrays';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ipcMain as ipc, app, shell, dialog, Menu, MenuItem, BrowserWindow } from 'electron';
import { OpenContext } from 'vs/platform/windows/common/windows';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IFilesConfiguration, AutoSaveConfiguration } from 'vs/platform/files/common/files';
import { OpenContext, IRunActionInWindowRequest } from 'vs/platform/windows/common/windows';
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { AutoSaveConfiguration } from 'vs/platform/files/common/files';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IUpdateService, State as UpdateState } from 'vs/platform/update/common/update';
import product from 'vs/platform/node/product';
@@ -29,24 +29,9 @@ interface IExtensionViewlet {
label: string;
}
interface IConfiguration extends IFilesConfiguration {
window: {
enableMenuBarMnemonics: boolean;
};
workbench: {
sideBar: {
location: 'left' | 'right';
},
statusBar: {
visible: boolean;
},
activityBar: {
visible: boolean;
}
};
editor: {
multiCursorModifier: 'ctrlCmd' | 'alt'
};
interface IMenuItemClickHandler {
inDevTools: (contents: Electron.WebContents) => void;
inNoWindow: () => void;
}
const telemetryFrom = 'menu';
@@ -55,12 +40,15 @@ export class CodeMenu {
private static MAX_MENU_RECENT_ENTRIES = 10;
private currentAutoSaveSetting: string;
private currentMultiCursorModifierSetting: string;
private currentSidebarLocation: 'left' | 'right';
private currentStatusbarVisible: boolean;
private currentActivityBarVisible: boolean;
private currentEnableMenuBarMnemonics: boolean;
private keys = [
'files.autoSave',
'editor.multiCursorModifier',
'workbench.sideBar.location',
'workbench.statusBar.visible',
'workbench.activityBar.visible',
'window.enableMenuBarMnemonics',
'window.nativeTabs'
];
private isQuitting: boolean;
private appMenuInstalled: boolean;
@@ -73,7 +61,8 @@ export class CodeMenu {
private closeFolder: Electron.MenuItem;
private closeWorkspace: Electron.MenuItem;
private saveWorkspaceAs: Electron.MenuItem;
private nativeTabMenuItems: Electron.MenuItem[];
constructor(
@IUpdateService private updateService: IUpdateService,
@@ -86,12 +75,11 @@ export class CodeMenu {
@IWorkspacesMainService private workspacesService: IWorkspacesMainService
) {
this.extensionViewlets = [];
this.nativeTabMenuItems = [];
this.menuUpdater = new RunOnceScheduler(() => this.doUpdateMenu(), 0);
this.keybindingsResolver = instantiationService.createInstance(KeybindingsResolver);
this.onConfigurationUpdated(this.configurationService.getConfiguration<IConfiguration>());
this.install();
this.registerListeners();
@@ -127,7 +115,7 @@ export class CodeMenu {
});
// Update when auto save config changes
this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(this.configurationService.getConfiguration<IConfiguration>(), true /* update menu if changed */));
this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e));
// Listen to update service
this.updateService.onStateChange(() => this.updateMenu());
@@ -136,58 +124,56 @@ export class CodeMenu {
this.keybindingsResolver.onKeybindingsChanged(() => this.updateMenu());
}
private onConfigurationUpdated(config: IConfiguration, handleMenu?: boolean): void {
let updateMenu = false;
const newAutoSaveSetting = config && config.files && config.files.autoSave;
if (newAutoSaveSetting !== this.currentAutoSaveSetting) {
this.currentAutoSaveSetting = newAutoSaveSetting;
updateMenu = true;
}
const newMultiCursorModifierSetting = config && config.editor && config.editor.multiCursorModifier;
if (newMultiCursorModifierSetting !== this.currentMultiCursorModifierSetting) {
this.currentMultiCursorModifierSetting = newMultiCursorModifierSetting;
updateMenu = true;
}
const newSidebarLocation = config && config.workbench && config.workbench.sideBar && config.workbench.sideBar.location || 'left';
if (newSidebarLocation !== this.currentSidebarLocation) {
this.currentSidebarLocation = newSidebarLocation;
updateMenu = true;
}
let newStatusbarVisible = config && config.workbench && config.workbench.statusBar && config.workbench.statusBar.visible;
if (typeof newStatusbarVisible !== 'boolean') {
newStatusbarVisible = true;
}
if (newStatusbarVisible !== this.currentStatusbarVisible) {
this.currentStatusbarVisible = newStatusbarVisible;
updateMenu = true;
}
let newActivityBarVisible = config && config.workbench && config.workbench.activityBar && config.workbench.activityBar.visible;
if (typeof newActivityBarVisible !== 'boolean') {
newActivityBarVisible = true;
}
if (newActivityBarVisible !== this.currentActivityBarVisible) {
this.currentActivityBarVisible = newActivityBarVisible;
updateMenu = true;
}
let newEnableMenuBarMnemonics = config && config.window && config.window.enableMenuBarMnemonics;
if (typeof newEnableMenuBarMnemonics !== 'boolean') {
newEnableMenuBarMnemonics = true;
}
if (newEnableMenuBarMnemonics !== this.currentEnableMenuBarMnemonics) {
this.currentEnableMenuBarMnemonics = newEnableMenuBarMnemonics;
updateMenu = true;
}
if (handleMenu && updateMenu) {
private onConfigurationUpdated(event: IConfigurationChangeEvent): void {
if (this.keys.some(key => event.affectsConfiguration(key))) {
this.updateMenu();
}
}
private get currentAutoSaveSetting(): string {
return this.configurationService.getValue<string>('files.autoSave');
}
private get currentMultiCursorModifierSetting(): string {
return this.configurationService.getValue<string>('editor.multiCursorModifier');
}
private get currentSidebarLocation(): string {
return this.configurationService.getValue<string>('workbench.sideBar.location') || 'left';
}
private get currentStatusbarVisible(): boolean {
let statusbarVisible = this.configurationService.getValue<boolean>('workbench.statusBar.visible');
if (typeof statusbarVisible !== 'boolean') {
statusbarVisible = true;
}
return statusbarVisible;
}
private get currentActivityBarVisible(): boolean {
let activityBarVisible = this.configurationService.getValue<boolean>('workbench.activityBar.visible');
if (typeof activityBarVisible !== 'boolean') {
activityBarVisible = true;
}
return activityBarVisible;
}
private get currentEnableMenuBarMnemonics(): boolean {
let enableMenuBarMnemonics = this.configurationService.getValue<boolean>('window.enableMenuBarMnemonics');
if (typeof enableMenuBarMnemonics !== 'boolean') {
enableMenuBarMnemonics = true;
}
return enableMenuBarMnemonics;
}
private get currentEnableNativeTabs(): boolean {
let enableNativeTabs = this.configurationService.getValue<boolean>('window.nativeTabs');
if (typeof enableNativeTabs !== 'boolean') {
enableNativeTabs = false;
}
return enableNativeTabs;
}
private updateMenu(): void {
this.menuUpdater.schedule(); // buffer multiple attempts to update the menu
}
@@ -217,6 +203,15 @@ export class CodeMenu {
if ((e.oldCount === 0 && e.newCount > 0) || (e.oldCount > 0 && e.newCount === 0)) {
this.updateMenu();
}
// Update specific items that are dependent on window count
else if (this.currentEnableNativeTabs) {
this.nativeTabMenuItems.forEach(item => {
if (item) {
item.enabled = e.newCount > 1;
}
});
}
}
private updateWorkspaceMenuItems(): void {
@@ -226,8 +221,7 @@ export class CodeMenu {
this.closeWorkspace.visible = isInWorkspaceContext;
this.closeFolder.visible = !isInWorkspaceContext;
this.closeFolder.enabled = isInFolderContext;
this.saveWorkspaceAs.enabled = isInFolderContext || isInWorkspaceContext;
this.closeFolder.enabled = isInFolderContext || isLinux /* https://github.com/Microsoft/vscode/issues/36431 */;
}
private install(): void {
@@ -256,7 +250,7 @@ export class CodeMenu {
// {{SQL CARBON EDIT}}
// Selection
// const selectionMenu = new Menu();
// const selectionMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'mSelection', comment: ['&& denotes a mnemonic'] }, "&&Selection")), submenu: selectionMenu });
// const selectionMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mSelection', comment: ['&& denotes a mnemonic'] }, "&&Selection")), submenu: selectionMenu });
// this.setSelectionMenu(selectionMenu);
// View
@@ -272,10 +266,9 @@ export class CodeMenu {
// {{SQL CARBON EDIT}}
// Debug
// const debugMenu = new Menu();
// const debugMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'mDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug")), submenu: debugMenu });
// const debugMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug")), submenu: debugMenu });
// this.setDebugMenu(debugMenu);
// Mac: Window
let macWindowMenuItem: Electron.MenuItem;
if (isMacintosh) {
@@ -291,23 +284,24 @@ export class CodeMenu {
// {{SQL CARBON EDIT}}
// Tasks
//const taskMenu = new Menu();
//const taskMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mTask', comment: ['&& denotes a mnemonic'] }, "&&Tasks")), submenu: taskMenu });
//this.setTaskMenu(taskMenu);
// const taskMenu = new Menu();
// const taskMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mTask', comment: ['&& denotes a mnemonic'] }, "&&Tasks")), submenu: taskMenu });
// this.setTaskMenu(taskMenu);
// Menu Structure
if (macApplicationMenuItem) {
menubar.append(macApplicationMenuItem);
}
// {{SQL CARBON EDIT}}
menubar.append(fileMenuItem);
menubar.append(editMenuItem);
// {{SQL CARBON EDIT}}
//menubar.append(selectionMenuItem);
menubar.append(viewMenuItem);
menubar.append(gotoMenuItem);
//menubar.append(debugMenuItem);
//menubar.append(taskMenuItem);
// {{SQL CARBON EDIT}}
// menubar.append(debugMenuItem);
// menubar.append(taskMenuItem);
if (macWindowMenuItem) {
menubar.append(macWindowMenuItem);
@@ -368,9 +362,26 @@ export class CodeMenu {
newFile = this.createMenuItem(nls.localize({ key: 'miNewFile', comment: ['&& denotes a mnemonic'] }, "&&New Query"), 'workbench.action.files.newUntitledFile');
}
const open = new MenuItem(this.likeAction('workbench.action.files.openFileFolder', { label: this.mnemonicLabel(nls.localize({ key: 'miOpen', comment: ['&& denotes a mnemonic'] }, "&&Open...")), click: (menuItem, win, event) => this.windowsService.pickFileFolderAndOpen({ forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }) }));
const openWorkspace = new MenuItem(this.likeAction('workbench.action.openWorkspace', { label: this.mnemonicLabel(nls.localize({ key: 'miOpenWorkspace', comment: ['&& denotes a mnemonic'] }, "&&Open Workspace...")), click: () => this.windowsService.openWorkspace() }));
const openFolder = new MenuItem(this.likeAction('workbench.action.files.openFolder', { label: this.mnemonicLabel(nls.localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder...")), click: (menuItem, win, event) => this.windowsService.pickFolderAndOpen({ forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }) }));
let open: Electron.MenuItem;
if (hasNoWindows) {
open = new MenuItem(this.likeAction('workbench.action.files.openFileFolder', { label: this.mnemonicLabel(nls.localize({ key: 'miOpen', comment: ['&& denotes a mnemonic'] }, "&&Open...")), click: (menuItem, win, event) => this.windowsService.pickFileFolderAndOpen({ forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }) }));
} else {
open = this.createMenuItem(nls.localize({ key: 'miOpen', comment: ['&& denotes a mnemonic'] }, "&&Open..."), ['workbench.action.files.openFileFolder', 'workbench.action.files.openFileFolderInNewWindow']);
}
let openWorkspace: Electron.MenuItem;
if (hasNoWindows) {
openWorkspace = new MenuItem(this.likeAction('workbench.action.openWorkspace', { label: this.mnemonicLabel(nls.localize({ key: 'miOpenWorkspace', comment: ['&& denotes a mnemonic'] }, "&&Open Workspace...")), click: (menuItem, win, event) => this.windowsService.pickWorkspaceAndOpen({ forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }) }));
} else {
openWorkspace = this.createMenuItem(nls.localize({ key: 'miOpenWorkspace', comment: ['&& denotes a mnemonic'] }, "&&Open Workspace..."), ['workbench.action.openWorkspace', 'workbench.action.openWorkspaceInNewWindow']);
}
let openFolder: Electron.MenuItem;
if (hasNoWindows) {
openFolder = new MenuItem(this.likeAction('workbench.action.files.openFolder', { label: this.mnemonicLabel(nls.localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder...")), click: (menuItem, win, event) => this.windowsService.pickFolderAndOpen({ forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }) }));
} else {
openFolder = this.createMenuItem(nls.localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder..."), ['workbench.action.files.openFolder', 'workbench.action.files.openFolderInNewWindow']);
}
let openFile: Electron.MenuItem;
if (hasNoWindows) {
@@ -383,9 +394,7 @@ export class CodeMenu {
this.setOpenRecentMenu(openRecentMenu);
const openRecent = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miOpenRecent', comment: ['&& denotes a mnemonic'] }, "Open &&Recent")), submenu: openRecentMenu, enabled: openRecentMenu.items.length > 0 });
const isMultiRootEnabled = (product.quality !== 'stable'); // TODO@Ben multi root
this.saveWorkspaceAs = this.createMenuItem(nls.localize({ key: 'miSaveWorkspaceAs', comment: ['&& denotes a mnemonic'] }, "&&Save Workspace As..."), 'workbench.action.saveWorkspaceAs');
const saveWorkspaceAs = this.createMenuItem(nls.localize({ key: 'miSaveWorkspaceAs', comment: ['&& denotes a mnemonic'] }, "Sa&&ve Workspace As..."), 'workbench.action.saveWorkspaceAs');
const addFolder = this.createMenuItem(nls.localize({ key: 'miAddFolderToWorkspace', comment: ['&& denotes a mnemonic'] }, "&&Add Folder to Workspace..."), 'workbench.action.addRootFolder');
const saveFile = this.createMenuItem(nls.localize({ key: 'miSave', comment: ['&& denotes a mnemonic'] }, "&&Save"), 'workbench.action.files.save');
@@ -394,6 +403,7 @@ export class CodeMenu {
const autoSaveEnabled = [AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE].some(s => this.currentAutoSaveSetting === s);
const autoSave = new MenuItem(this.likeAction('vscode.toggleAutoSave', { label: this.mnemonicLabel(nls.localize('miAutoSave', "Auto Save")), type: 'checkbox', checked: autoSaveEnabled, enabled: this.windowsService.getWindowCount() > 0, click: () => this.windowsService.sendToFocused('vscode.toggleAutoSave') }, false));
const installVsixExtension = this.createMenuItem(nls.localize({ key: 'miinstallVsix', comment: ['&& denotes a mnemonic'] }, "Install Extension from VSIX Package"), 'workbench.extensions.action.installVSIX');
const preferences = this.getPreferencesMenu();
@@ -417,11 +427,11 @@ export class CodeMenu {
isMacintosh ? open : null,
!isMacintosh ? openFile : null,
!isMacintosh ? openFolder : null,
isMultiRootEnabled ? openWorkspace : null,
openWorkspace,
openRecent,
isMultiRootEnabled ? __separator__() : null,
isMultiRootEnabled ? addFolder : null,
isMultiRootEnabled ? this.saveWorkspaceAs : null,
__separator__(),
addFolder,
saveWorkspaceAs,
__separator__(),
saveFile,
saveFileAs,
@@ -429,6 +439,8 @@ export class CodeMenu {
__separator__(),
autoSave,
__separator__(),
installVsixExtension,
__separator__(),
!isMacintosh ? preferences : null,
!isMacintosh ? __separator__() : null,
revertFile,
@@ -525,12 +537,12 @@ export class CodeMenu {
}, false));
}
private isOptionClick(event: Electron.Event & Electron.Modifiers): boolean {
private isOptionClick(event: Electron.Event): boolean {
return event && ((!isMacintosh && (event.ctrlKey || event.shiftKey)) || (isMacintosh && (event.metaKey || event.altKey)));
}
private createRoleMenuItem(label: string, commandId: string, role: Electron.MenuItemRole): Electron.MenuItem {
const options: Electron.MenuItemOptions = {
const options: Electron.MenuItemConstructorOptions = {
label: this.mnemonicLabel(label),
role,
enabled: true
@@ -547,8 +559,14 @@ export class CodeMenu {
let paste: Electron.MenuItem;
if (isMacintosh) {
undo = this.createDevToolsAwareMenuItem(nls.localize({ key: 'miUndo', comment: ['&& denotes a mnemonic'] }, "&&Undo"), 'undo', devTools => devTools.undo());
redo = this.createDevToolsAwareMenuItem(nls.localize({ key: 'miRedo', comment: ['&& denotes a mnemonic'] }, "&&Redo"), 'redo', devTools => devTools.redo());
undo = this.createContextAwareMenuItem(nls.localize({ key: 'miUndo', comment: ['&& denotes a mnemonic'] }, "&&Undo"), 'undo', {
inDevTools: devTools => devTools.undo(),
inNoWindow: () => Menu.sendActionToFirstResponder('undo:')
});
redo = this.createContextAwareMenuItem(nls.localize({ key: 'miRedo', comment: ['&& denotes a mnemonic'] }, "&&Redo"), 'redo', {
inDevTools: devTools => devTools.redo(),
inNoWindow: () => Menu.sendActionToFirstResponder('redo:')
});
cut = this.createRoleMenuItem(nls.localize({ key: 'miCut', comment: ['&& denotes a mnemonic'] }, "Cu&&t"), 'editor.action.clipboardCutAction', 'cut');
copy = this.createRoleMenuItem(nls.localize({ key: 'miCopy', comment: ['&& denotes a mnemonic'] }, "&&Copy"), 'editor.action.clipboardCopyAction', 'copy');
paste = this.createRoleMenuItem(nls.localize({ key: 'miPaste', comment: ['&& denotes a mnemonic'] }, "&&Paste"), 'editor.action.clipboardPasteAction', 'paste');
@@ -623,7 +641,10 @@ export class CodeMenu {
let selectAll: Electron.MenuItem;
if (isMacintosh) {
selectAll = this.createDevToolsAwareMenuItem(nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"), 'editor.action.selectAll', (devTools) => devTools.selectAll());
selectAll = this.createContextAwareMenuItem(nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"), 'editor.action.selectAll', {
inDevTools: devTools => devTools.selectAll(),
inNoWindow: () => Menu.sendActionToFirstResponder('selectAll:')
});
} else {
selectAll = this.createMenuItem(nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"), 'editor.action.selectAll');
}
@@ -892,10 +913,26 @@ export class CodeMenu {
const bringAllToFront = new MenuItem({ label: nls.localize('mBringToFront', "Bring All to Front"), role: 'front', enabled: this.windowsService.getWindowCount() > 0 });
const switchWindow = this.createMenuItem(nls.localize({ key: 'miSwitchWindow', comment: ['&& denotes a mnemonic'] }, "Switch &&Window..."), 'workbench.action.switchWindow');
this.nativeTabMenuItems = [];
const nativeTabMenuItems: Electron.MenuItem[] = [];
if (this.currentEnableNativeTabs) {
const hasMultipleWindows = this.windowsService.getWindowCount() > 1;
this.nativeTabMenuItems.push(this.createMenuItem(nls.localize('mShowPreviousTab', "Show Previous Tab"), 'workbench.action.showPreviousWindowTab', hasMultipleWindows));
this.nativeTabMenuItems.push(this.createMenuItem(nls.localize('mShowNextTab', "Show Next Tab"), 'workbench.action.showNextWindowTab', hasMultipleWindows));
this.nativeTabMenuItems.push(this.createMenuItem(nls.localize('mMoveTabToNewWindow', "Move Tab to New Window"), 'workbench.action.moveWindowTabToNewWindow', hasMultipleWindows));
this.nativeTabMenuItems.push(this.createMenuItem(nls.localize('mMergeAllWindows', "Merge All Windows"), 'workbench.action.mergeAllWindowTabs', hasMultipleWindows));
nativeTabMenuItems.push(__separator__(), ...this.nativeTabMenuItems);
} else {
this.nativeTabMenuItems = [];
}
[
minimize,
zoom,
switchWindow,
...nativeTabMenuItems,
__separator__(),
bringAllToFront
].forEach(item => macWindowMenu.append(item));
@@ -1001,8 +1038,8 @@ export class CodeMenu {
const terminateTask = this.createMenuItem(nls.localize({ key: 'miTerminateTask', comment: ['&& denotes a mnemonic'] }, "&&Terminate Task..."), 'workbench.action.tasks.terminate');
// const testTask = this.createMenuItem(nls.localize({ key: 'miTestTask', comment: ['&& denotes a mnemonic'] }, "Run Test T&&ask..."), 'workbench.action.tasks.test');
// const showTaskLog = this.createMenuItem(nls.localize({ key: 'miShowTaskLog', comment: ['&& denotes a mnemonic'] }, "&&Show Task Log"), 'workbench.action.tasks.showLog');
const configureTask = this.createMenuItem(nls.localize({ key: 'miConfigureTask', comment: ['&& denotes a mnemonic'] }, "&&Configure Tasks"), 'workbench.action.tasks.configureTaskRunner');
const configureBuildTask = this.createMenuItem(nls.localize({ key: 'miConfigureBuildTask', comment: ['&& denotes a mnemonic'] }, "Configure De&&fault Build Task"), 'workbench.action.tasks.configureDefaultBuildTask');
const configureTask = this.createMenuItem(nls.localize({ key: 'miConfigureTask', comment: ['&& denotes a mnemonic'] }, "&&Configure Tasks..."), 'workbench.action.tasks.configureTaskRunner');
const configureBuildTask = this.createMenuItem(nls.localize({ key: 'miConfigureBuildTask', comment: ['&& denotes a mnemonic'] }, "Configure De&&fault Build Task..."), 'workbench.action.tasks.configureDefaultBuildTask');
// const configureTestTask = this.createMenuItem(nls.localize({ key: 'miConfigureTestTask', comment: ['&& denotes a mnemonic'] }, "Configure Defau&&lt Test Task"), 'workbench.action.tasks.configureDefaultTestTask');
[
@@ -1086,13 +1123,13 @@ export class CodeMenu {
private createMenuItem(label: string, click: () => void, enabled?: boolean, checked?: boolean): Electron.MenuItem;
private createMenuItem(arg1: string, arg2: any, arg3?: boolean, arg4?: boolean): Electron.MenuItem {
const label = this.mnemonicLabel(arg1);
const click: () => void = (typeof arg2 === 'function') ? arg2 : (menuItem, win, event) => {
const click: () => void = (typeof arg2 === 'function') ? arg2 : (menuItem: Electron.MenuItem, win: Electron.BrowserWindow, event: Electron.Event) => {
let commandId = arg2;
if (Array.isArray(arg2)) {
commandId = this.isOptionClick(event) ? arg2[1] : arg2[0]; // support alternative action if we got multiple action Ids and the option key was pressed while invoking
}
this.windowsService.sendToFocused('vscode:runAction', commandId);
this.runActionInRenderer(commandId);
};
const enabled = typeof arg3 === 'boolean' ? arg3 : this.windowsService.getWindowCount() > 0;
const checked = typeof arg4 === 'boolean' ? arg4 : false;
@@ -1102,7 +1139,7 @@ export class CodeMenu {
commandId = arg2;
}
const options: Electron.MenuItemOptions = {
const options: Electron.MenuItemConstructorOptions = {
label,
click,
enabled
@@ -1116,26 +1153,34 @@ export class CodeMenu {
return new MenuItem(this.withKeybinding(commandId, options));
}
private createDevToolsAwareMenuItem(label: string, commandId: string, devToolsFocusedFn: (contents: Electron.WebContents) => void): Electron.MenuItem {
private createContextAwareMenuItem(label: string, commandId: string, clickHandler: IMenuItemClickHandler): Electron.MenuItem {
return new MenuItem(this.withKeybinding(commandId, {
label: this.mnemonicLabel(label),
enabled: this.windowsService.getWindowCount() > 0,
click: () => {
const windowInFocus = this.windowsService.getFocusedWindow();
if (!windowInFocus) {
return;
// No Active Window
const activeWindow = this.windowsService.getFocusedWindow();
if (!activeWindow) {
return clickHandler.inNoWindow();
}
if (windowInFocus.win.webContents.isDevToolsFocused()) {
devToolsFocusedFn(windowInFocus.win.webContents.devToolsWebContents);
} else {
this.windowsService.sendToFocused('vscode:runAction', commandId);
// DevTools focused
if (activeWindow.win.webContents.isDevToolsFocused()) {
return clickHandler.inDevTools(activeWindow.win.webContents.devToolsWebContents);
}
// Finally execute command in Window
this.runActionInRenderer(commandId);
}
}));
}
private withKeybinding(commandId: string, options: Electron.MenuItemOptions): Electron.MenuItemOptions {
private runActionInRenderer(id: string): void {
this.windowsService.sendToFocused('vscode:runAction', { id, from: 'menu' } as IRunActionInWindowRequest);
}
private withKeybinding(commandId: string, options: Electron.MenuItemConstructorOptions): Electron.MenuItemConstructorOptions {
const binding = this.keybindingsResolver.getKeybinding(commandId);
// Apply binding if there is one
@@ -1166,7 +1211,7 @@ export class CodeMenu {
return options;
}
private likeAction(commandId: string, options: Electron.MenuItemOptions, setAccelerator = !options.accelerator): Electron.MenuItemOptions {
private likeAction(commandId: string, options: Electron.MenuItemConstructorOptions, setAccelerator = !options.accelerator): Electron.MenuItemConstructorOptions {
if (setAccelerator) {
options = this.withKeybinding(commandId, options);
}
@@ -1212,6 +1257,12 @@ export class CodeMenu {
}
private reportMenuActionTelemetry(id: string): void {
/* __GDPR__
"workbencActionExecuted" : {
"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"from": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('workbenchActionExecuted', { id, from: telemetryFrom });
}

View File

@@ -40,7 +40,7 @@ export class SharedProcess implements ISharedProcess {
this.window.loadURL(url);
// Prevent the window from dying
const onClose = e => {
const onClose = (e: Event) => {
if (this.window.isVisible()) {
e.preventDefault();
this.window.hide();

View File

@@ -11,7 +11,7 @@ import { stopProfiling } from 'vs/base/node/profiler';
import nls = require('vs/nls');
import URI from 'vs/base/common/uri';
import { IStorageService } from 'vs/platform/storage/node/storage';
import { shell, screen, BrowserWindow, systemPreferences, app } from 'electron';
import { shell, screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage } from 'electron';
import { TPromise, TValueCallback } from 'vs/base/common/winjs.base';
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
import { ILogService } from 'vs/platform/log/common/log';
@@ -19,13 +19,13 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { parseArgs } from 'vs/platform/environment/node/argv';
import product from 'vs/platform/node/product';
import pkg from 'vs/platform/node/package';
import { IWindowSettings, MenuBarVisibility, IWindowConfiguration, ReadyState } from 'vs/platform/windows/common/windows';
import { IWindowSettings, MenuBarVisibility, IWindowConfiguration, ReadyState, IRunActionInWindowRequest } from 'vs/platform/windows/common/windows';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { KeyboardLayoutMonitor } from 'vs/code/electron-main/keyboard';
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import { ICodeWindow } from 'vs/platform/windows/electron-main/windows';
import { IWorkspaceIdentifier, IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces';
import { IBackupMainService } from 'vs/platform/backup/common/backup';
import { ICommandAction } from 'vs/platform/actions/common/actions';
export interface IWindowState {
width?: number;
@@ -65,11 +65,19 @@ interface IWorkbenchEditorConfiguration {
};
}
interface ITouchBarSegment extends Electron.SegmentedControlSegment {
id: string;
}
export class CodeWindow implements ICodeWindow {
public static themeStorageKey = 'theme';
public static themeBackgroundStorageKey = 'themeBackground';
private static DEFAULT_BG_LIGHT = '#FFFFFF';
private static DEFAULT_BG_DARK = '#1E1E1E';
private static DEFAULT_BG_HC_BLACK = '#000000';
private static MIN_WIDTH = 200;
private static MIN_HEIGHT = 120;
@@ -89,6 +97,8 @@ export class CodeWindow implements ICodeWindow {
private currentConfig: IWindowConfiguration;
private pendingLoadConfig: IWindowConfiguration;
private touchBarGroups: Electron.TouchBarSegmentedControl[];
constructor(
config: IWindowCreationOptions,
@ILogService private logService: ILogService,
@@ -98,6 +108,7 @@ export class CodeWindow implements ICodeWindow {
@IWorkspacesMainService private workspaceService: IWorkspacesMainService,
@IBackupMainService private backupService: IBackupMainService
) {
this.touchBarGroups = [];
this._lastFocusTime = -1;
this._readyState = ReadyState.NONE;
this.whenReadyCallbacks = [];
@@ -109,6 +120,9 @@ export class CodeWindow implements ICodeWindow {
// respect configured menu bar visibility
this.onConfigurationUpdated();
// macOS: touch bar support
this.createTouchBar();
// Eventing
this.registerListeners();
}
@@ -121,12 +135,17 @@ export class CodeWindow implements ICodeWindow {
// in case we are maximized or fullscreen, only show later after the call to maximize/fullscreen (see below)
const isFullscreenOrMaximized = (this.windowState.mode === WindowMode.Maximized || this.windowState.mode === WindowMode.Fullscreen);
const options: Electron.BrowserWindowOptions = {
let backgroundColor = this.getBackgroundColor();
if (isMacintosh && backgroundColor.toUpperCase() === CodeWindow.DEFAULT_BG_DARK) {
backgroundColor = '#171717'; // https://github.com/electron/electron/issues/5150
}
const options: Electron.BrowserWindowConstructorOptions = {
width: this.windowState.width,
height: this.windowState.height,
x: this.windowState.x,
y: this.windowState.y,
backgroundColor: this.getBackgroundColor(),
backgroundColor,
minWidth: CodeWindow.MIN_WIDTH,
minHeight: CodeWindow.MIN_HEIGHT,
show: !isFullscreenOrMaximized,
@@ -150,17 +169,12 @@ export class CodeWindow implements ICodeWindow {
}
let useCustomTitleStyle = false;
// {{SQL CARBON EDIT}}
// turn-off custom menus to avoid bug calculating size of SQL editor
/*
if (isMacintosh && (!windowConfig || !windowConfig.titleBarStyle || windowConfig.titleBarStyle === 'custom')) {
const isDev = !this.environmentService.isBuilt || !!config.extensionDevelopmentPath;
if (!isDev) {
useCustomTitleStyle = true; // not enabled when developing due to https://github.com/electron/electron/issues/3647
}
}
*/
if (useNativeTabs) {
useCustomTitleStyle = false; // native tabs on sierra do not work with custom title style
@@ -175,6 +189,23 @@ export class CodeWindow implements ICodeWindow {
this._win = new BrowserWindow(options);
this._id = this._win.id;
// TODO@Ben Bug in Electron (https://github.com/electron/electron/issues/10862). On multi-monitor setups,
// it can happen that the position we set to the window is not the correct one on the display.
// To workaround, we ask the window for its position and set it again if not matching.
// This only applies if the window is not fullscreen or maximized and multiple monitors are used.
if (isWindows && !isFullscreenOrMaximized) {
try {
if (screen.getAllDisplays().length > 1) {
const [x, y] = this._win.getPosition();
if (x !== this.windowState.x || y !== this.windowState.y) {
this._win.setPosition(this.windowState.x, this.windowState.y, false);
}
}
} catch (err) {
this.logService.log(`Unexpected error fixing window position on windows with multiple windows: ${err}\n${err.stack}`);
}
}
if (useCustomTitleStyle) {
this._win.setSheetOffset(22); // offset dialogs by the height of the custom title bar if we have any
}
@@ -275,10 +306,6 @@ export class CodeWindow implements ICodeWindow {
return this.currentConfig ? this.currentConfig.folderPath : void 0;
}
public get openedFilePath(): string {
return this.currentConfig && this.currentConfig.filesToOpen && this.currentConfig.filesToOpen[0] && this.currentConfig.filesToOpen[0].filePath;
}
public setReady(): void {
this._readyState = ReadyState.READY;
@@ -316,7 +343,7 @@ export class CodeWindow implements ICodeWindow {
});
// Prevent loading of svgs
this._win.webContents.session.webRequest.onBeforeRequest((details, callback) => {
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')) {
@@ -327,7 +354,7 @@ export class CodeWindow implements ICodeWindow {
return callback({});
});
this._win.webContents.session.webRequest.onHeadersReceived((details, callback) => {
this._win.webContents.session.webRequest.onHeadersReceived(null, (details, callback) => {
const contentType: string[] = (details.responseHeaders['content-type'] || details.responseHeaders['Content-Type']) as any;
if (contentType && Array.isArray(contentType) && contentType.some(x => x.toLowerCase().indexOf('image/svg') >= 0)) {
return callback({ cancel: true });
@@ -384,7 +411,7 @@ export class CodeWindow implements ICodeWindow {
});
// Window Failed to load
this._win.webContents.on('did-fail-load', (event: Event, errorCode: string, errorDescription: string) => {
this._win.webContents.on('did-fail-load', (event: Electron.Event, errorCode: number, errorDescription: string, validatedURL: string, isMainFrame: boolean) => {
this.logService.warn('[electron event]: fail to load, ', errorDescription);
});
@@ -399,7 +426,7 @@ export class CodeWindow implements ICodeWindow {
}
// Handle configuration changes
this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated()));
this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated()));
// Handle Workspace events
this.toDispose.push(this.workspaceService.onUntitledWorkspaceDeleted(e => this.onUntitledWorkspaceDeleted(e)));
@@ -433,15 +460,15 @@ export class CodeWindow implements ICodeWindow {
};
private registerNavigationListenerOn(command: 'swipe' | 'app-command', back: 'left' | 'browser-backward', forward: 'right' | 'browser-forward', acrossEditors: boolean) {
this._win.on(command, (e, cmd) => {
this._win.on(command as 'swipe' /* | 'app-command' */, (e: Electron.Event, cmd: string) => {
if (this.readyState !== ReadyState.READY) {
return; // window must be ready
}
if (cmd === back) {
this.send('vscode:runAction', acrossEditors ? 'workbench.action.openPreviousRecentlyUsedEditor' : 'workbench.action.navigateBack');
this.send('vscode:runAction', { id: acrossEditors ? 'workbench.action.openPreviousRecentlyUsedEditor' : 'workbench.action.navigateBack', from: 'mouse' } as IRunActionInWindowRequest);
} else if (cmd === forward) {
this.send('vscode:runAction', acrossEditors ? 'workbench.action.openNextRecentlyUsedEditor' : 'workbench.action.navigateForward');
this.send('vscode:runAction', { id: acrossEditors ? 'workbench.action.openNextRecentlyUsedEditor' : 'workbench.action.navigateForward', from: 'mouse' } as IRunActionInWindowRequest);
}
});
}
@@ -512,6 +539,7 @@ export class CodeWindow implements ICodeWindow {
delete configuration.filesToOpen;
delete configuration.filesToCreate;
delete configuration.filesToDiff;
delete configuration.filesToWait;
// Some configuration things get inherited if the window is being reloaded and we are
// in extension development mode. These options are all development related.
@@ -545,9 +573,6 @@ export class CodeWindow implements ICodeWindow {
windowConfiguration.highContrast = isWindows && systemPreferences.isInvertedColorScheme() && (!windowConfig || windowConfig.autoDetectHighContrast);
windowConfiguration.accessibilitySupport = app.isAccessibilitySupportEnabled();
// Set Keyboard Config
windowConfiguration.isISOKeyboard = KeyboardLayoutMonitor.INSTANCE.isISOKeyboard();
// Theme
windowConfiguration.baseTheme = this.getBaseTheme();
windowConfiguration.backgroundColor = this.getBackgroundColor();
@@ -581,14 +606,14 @@ export class CodeWindow implements ICodeWindow {
private getBackgroundColor(): string {
if (isWindows && systemPreferences.isInvertedColorScheme()) {
return '#000000';
return CodeWindow.DEFAULT_BG_HC_BLACK;
}
const background = this.storageService.getItem<string>(CodeWindow.themeBackgroundStorageKey, null);
if (!background) {
const baseTheme = this.getBaseTheme();
return baseTheme === 'hc-black' ? '#000000' : (baseTheme === 'vs' ? '#FFFFFF' : (isMacintosh ? '#171717' : '#1E1E1E')); // https://github.com/electron/electron/issues/5150
return baseTheme === 'hc-black' ? CodeWindow.DEFAULT_BG_HC_BLACK : (baseTheme === 'vs' ? CodeWindow.DEFAULT_BG_LIGHT : CodeWindow.DEFAULT_BG_DARK);
}
return background;
@@ -726,7 +751,13 @@ export class CodeWindow implements ICodeWindow {
// Multi Monitor (non-fullscreen): be less strict because metrics can be crazy
const bounds = { x: state.x, y: state.y, width: state.width, height: state.height };
const display = screen.getDisplayMatching(bounds);
if (display && display.bounds.x + display.bounds.width > bounds.x && display.bounds.y + display.bounds.height > bounds.y) {
if (
display && // we have a display matching the desired bounds
bounds.x < display.bounds.x + display.bounds.width && // prevent window from falling out of the screen to the right
bounds.y < display.bounds.y + display.bounds.height && // prevent window from falling out of the screen to the bottom
bounds.x + bounds.width > display.bounds.x && // prevent window from falling out of the screen to the left
bounds.y + bounds.height > display.bounds.y // prevent window from falling out of the scree nto the top
) {
if (state.mode === WindowMode.Maximized) {
const defaults = defaultWindowState(WindowMode.Maximized); // when maximized, make sure we have good values when the user restores the window
defaults.x = state.x; // carefull to keep x/y position so that the window ends up on the correct monitor
@@ -856,6 +887,78 @@ export class CodeWindow implements ICodeWindow {
this._win.webContents.send(channel, ...args);
}
public updateTouchBar(groups: ICommandAction[][]): void {
if (!isMacintosh) {
return; // only supported on macOS
}
// Update segments for all groups. Setting the segments property
// of the group directly prevents ugly flickering from happening
this.touchBarGroups.forEach((touchBarGroup, index) => {
const commands = groups[index];
touchBarGroup.segments = this.createTouchBarGroupSegments(commands);
});
}
private createTouchBar(): void {
if (!isMacintosh) {
return; // only supported on macOS
}
// To avoid flickering, we try to reuse the touch bar group
// as much as possible by creating a large number of groups
// for reusing later.
for (let i = 0; i < 10; i++) {
const groupTouchBar = this.createTouchBarGroup();
this.touchBarGroups.push(groupTouchBar);
}
// Ugly workaround for native crash on macOS 10.12.1. We are not
// leveraging the API for changing the ESC touch bar item.
// See https://github.com/electron/electron/issues/10442
(<any>this._win)._setEscapeTouchBarItem = () => { };
this._win.setTouchBar(new TouchBar({ items: this.touchBarGroups }));
}
private createTouchBarGroup(items: ICommandAction[] = []): Electron.TouchBarSegmentedControl {
// Group Segments
const segments = this.createTouchBarGroupSegments(items);
// Group Control
const control = new TouchBar.TouchBarSegmentedControl({
segments,
mode: 'buttons',
segmentStyle: 'automatic',
change: (selectedIndex) => {
this.sendWhenReady('vscode:runAction', { id: (control.segments[selectedIndex] as ITouchBarSegment).id, from: 'touchbar' });
}
});
return control;
}
private createTouchBarGroupSegments(items: ICommandAction[] = []): ITouchBarSegment[] {
const segments: ITouchBarSegment[] = items.map(item => {
let icon: Electron.NativeImage;
if (item.iconPath) {
icon = nativeImage.createFromPath(item.iconPath);
if (icon.isEmpty()) {
icon = void 0;
}
}
return {
id: item.id,
label: !icon ? item.title as string : void 0,
icon
};
});
return segments;
}
public dispose(): void {
if (this.showTimeoutHandle) {
clearTimeout(this.showTimeoutHandle);

View File

@@ -5,7 +5,7 @@
'use strict';
import * as path from 'path';
import { basename, normalize, join, dirname } from 'path';
import * as fs from 'original-fs';
import { localize } from 'vs/nls';
import * as arrays from 'vs/base/common/arrays';
@@ -19,7 +19,7 @@ import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/node/pa
import { ILifecycleService, UnloadReason, IWindowUnloadEvent } 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, ReadyState } from 'vs/platform/windows/common/windows';
import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, INativeOpenDialogOptions, ReadyState, IPathsToWaitFor, IEnterWorkspaceResult } from 'vs/platform/windows/common/windows';
import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderPath } from 'vs/code/node/windowsFinder';
import CommonEvent, { Emitter } from 'vs/base/common/event';
import product from 'vs/platform/node/product';
@@ -29,9 +29,12 @@ import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent } fr
import { IHistoryMainService } from 'vs/platform/history/common/history';
import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import { TPromise } from 'vs/base/common/winjs.base';
import { IWorkspacesMainService, IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, WORKSPACE_FILTER, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspacesMainService, IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, WORKSPACE_FILTER, isSingleFolderWorkspaceIdentifier, IWorkspaceFolderCreationData } 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/strings';
import URI from 'vs/base/common/uri';
enum WindowError {
UNRESPONSIVE,
@@ -77,6 +80,7 @@ interface IOpenBrowserWindowOptions {
filesToOpen?: IPath[];
filesToCreate?: IPath[];
filesToDiff?: IPath[];
filesToWait?: IPathsToWaitFor;
forceNewWindow?: boolean;
windowToUse?: CodeWindow;
@@ -113,6 +117,7 @@ export class WindowsManager implements IWindowsMainService {
private lastClosedWindowState: IWindowState;
private fileDialog: FileDialog;
private workspacesManager: WorkspacesManager;
private _onWindowReady = new Emitter<CodeWindow>();
onWindowReady: CommonEvent<CodeWindow> = this._onWindowReady.event;
@@ -120,6 +125,9 @@ export class WindowsManager implements IWindowsMainService {
private _onWindowClose = new Emitter<number>();
onWindowClose: CommonEvent<number> = this._onWindowClose.event;
private _onWindowLoad = new Emitter<number>();
onWindowLoad: CommonEvent<number> = this._onWindowLoad.event;
private _onActiveWindowChanged = new Emitter<CodeWindow>();
onActiveWindowChanged: CommonEvent<CodeWindow> = this._onActiveWindowChanged.event;
@@ -142,7 +150,9 @@ export class WindowsManager implements IWindowsMainService {
@IInstantiationService private instantiationService: IInstantiationService
) {
this.windowsState = this.storageService.getItem<IWindowsState>(WindowsManager.windowsStateStorageKey) || { openedWindows: [] };
this.fileDialog = new FileDialog(environmentService, telemetryService, storageService, this);
this.workspacesManager = new WorkspacesManager(workspacesService, lifecycleService, backupService, environmentService, this);
this.migrateLegacyWindowState();
}
@@ -172,11 +182,6 @@ export class WindowsManager implements IWindowsMainService {
state.folderPath = state.workspacePath;
state.workspacePath = void 0;
}
// TODO@Ben migration to new workspace ID
if (state.workspace) {
state.workspace.id = this.workspacesService.getWorkspaceId(state.workspace.configPath);
}
});
}
@@ -273,7 +278,6 @@ export class WindowsManager implements IWindowsMainService {
private onBeforeQuit(): void {
const currentWindowsState: ILegacyWindowsState = {
openedWindows: [],
openedFolders: [], // TODO@Ben migration so that old clients do not fail over data (prevents NPEs)
lastPluginDevelopmentHostWindow: this.windowsState.lastPluginDevelopmentHostWindow,
lastActiveWindow: this.lastClosedWindowState
};
@@ -358,8 +362,8 @@ export class WindowsManager implements IWindowsMainService {
// When run with --add, take the folders that are to be opened as
// folders that should be added to the currently active window.
let foldersToAdd = [];
if (openConfig.addMode && product.quality !== 'stable') { // TODO@Ben multi root
let foldersToAdd: IPath[] = [];
if (openConfig.addMode) {
foldersToAdd = pathsToOpen.filter(path => !!path.folderPath).map(path => ({ filePath: path.folderPath }));
pathsToOpen = pathsToOpen.filter(path => !path.folderPath);
}
@@ -376,6 +380,12 @@ export class WindowsManager implements IWindowsMainService {
filesToCreate = []; // diff ignores other files that do not exist
}
// When run with --wait, make sure we keep the paths to wait for
let filesToWait: IPathsToWaitFor;
if (openConfig.cli.wait && openConfig.cli.waitMarkerFilePath) {
filesToWait = { paths: [...filesToDiff, ...filesToOpen, ...filesToCreate], waitMarkerFilePath: openConfig.cli.waitMarkerFilePath };
}
//
// These are windows to open to show workspaces
//
@@ -399,7 +409,7 @@ export class WindowsManager implements IWindowsMainService {
workspacesToRestore.push(...this.workspacesService.getUntitledWorkspacesSync()); // collect from previous window session
emptyToRestore = this.backupService.getEmptyWindowBackupPaths();
emptyToRestore.push(...pathsToOpen.filter(w => !w.workspace && !w.folderPath && w.backupPath).map(w => path.basename(w.backupPath))); // add empty windows with backupPath
emptyToRestore.push(...pathsToOpen.filter(w => !w.workspace && !w.folderPath && w.backupPath).map(w => basename(w.backupPath))); // add empty windows with backupPath
emptyToRestore = arrays.distinct(emptyToRestore); // prevent duplicates
}
@@ -409,13 +419,45 @@ export class WindowsManager implements IWindowsMainService {
const emptyToOpen = pathsToOpen.filter(win => !win.workspace && !win.folderPath && !win.filePath && !win.backupPath).length;
// Open based on config
const usedWindows = this.doOpen(openConfig, workspacesToOpen, workspacesToRestore, foldersToOpen, foldersToRestore, emptyToRestore, emptyToOpen, filesToOpen, filesToCreate, filesToDiff, foldersToAdd);
const usedWindows = this.doOpen(openConfig, workspacesToOpen, workspacesToRestore, foldersToOpen, foldersToRestore, emptyToRestore, emptyToOpen, filesToOpen, filesToCreate, filesToDiff, filesToWait, foldersToAdd);
// Make sure the last active window gets focus if we opened multiple
if (usedWindows.length > 1 && this.windowsState.lastActiveWindow) {
let lastActiveWindw = usedWindows.filter(w => w.backupPath === this.windowsState.lastActiveWindow.backupPath);
if (lastActiveWindw.length) {
lastActiveWindw[0].focus();
// Make sure to pass focus to the most relevant of the windows if we open multiple
if (usedWindows.length > 1) {
let focusLastActive = this.windowsState.lastActiveWindow && !openConfig.forceEmpty && !openConfig.cli._.length && (!openConfig.pathsToOpen || !openConfig.pathsToOpen.length);
let focusLastOpened = true;
let focusLastWindow = true;
// 1.) focus last active window if we are not instructed to open any paths
if (focusLastActive) {
const lastActiveWindw = usedWindows.filter(w => w.backupPath === this.windowsState.lastActiveWindow.backupPath);
if (lastActiveWindw.length) {
lastActiveWindw[0].focus();
focusLastOpened = false;
focusLastWindow = false;
}
}
// 2.) if instructed to open paths, focus last window which is not restored
if (focusLastOpened) {
for (let i = usedWindows.length - 1; i >= 0; i--) {
const usedWindow = usedWindows[i];
if (
(usedWindow.openedWorkspace && workspacesToRestore.some(workspace => workspace.id === usedWindow.openedWorkspace.id)) || // skip over restored workspace
(usedWindow.openedFolderPath && foldersToRestore.some(folder => folder === usedWindow.openedFolderPath)) || // skip over restored folder
(usedWindow.backupPath && emptyToRestore.some(empty => empty === basename(usedWindow.backupPath))) // skip over restored empty window
) {
continue;
}
usedWindow.focus();
focusLastWindow = false;
break;
}
}
// 3.) finally, always ensure to have at least last used window focused
if (focusLastWindow) {
usedWindows[usedWindows.length - 1].focus();
}
}
@@ -437,10 +479,10 @@ export class WindowsManager implements IWindowsMainService {
}
// If we got started with --wait from the CLI, we need to signal to the outside when the window
// used for the edit operation is closed so that the waiting process can continue. We do this by
// deleting the waitMarkerFilePath.
// used for the edit operation is closed or loaded to a different folder so that the waiting
// process can continue. We do this by deleting the waitMarkerFilePath.
if (openConfig.context === OpenContext.CLI && openConfig.cli.wait && openConfig.cli.waitMarkerFilePath && usedWindows.length === 1 && usedWindows[0]) {
this.waitForWindowClose(usedWindows[0].id).done(() => fs.unlink(openConfig.cli.waitMarkerFilePath, error => void 0));
this.waitForWindowCloseOrLoad(usedWindows[0].id).done(() => fs.unlink(openConfig.cli.waitMarkerFilePath, error => void 0));
}
return usedWindows;
@@ -467,6 +509,7 @@ export class WindowsManager implements IWindowsMainService {
filesToOpen: IPath[],
filesToCreate: IPath[],
filesToDiff: IPath[],
filesToWait: IPathsToWaitFor,
foldersToAdd: IPath[]
) {
const usedWindows: CodeWindow[] = [];
@@ -491,7 +534,7 @@ export class WindowsManager implements IWindowsMainService {
// Find suitable window or folder path to open files in
const fileToCheck = filesToOpen[0] || filesToCreate[0] || filesToDiff[0];
const bestWindowOrFolder = findBestWindowOrFolderForFile({
let bestWindowOrFolder = findBestWindowOrFolderForFile({
windows: WindowsManager.WINDOWS,
newWindow: openFilesInNewWindow,
reuseWindow: openConfig.forceReuseWindow,
@@ -501,6 +544,12 @@ export class WindowsManager implements IWindowsMainService {
workspaceResolver: workspace => this.workspacesService.resolveWorkspaceSync(workspace.configPath)
});
// Special case: we started with --wait and we got back a folder to open. In this case
// we actually prefer to not open the folder but operate purely on the file.
if (typeof bestWindowOrFolder === 'string' && filesToWait) {
bestWindowOrFolder = !openFilesInNewWindow ? this.getLastActiveWindow() : null;
}
// We found a window to open the files in
if (bestWindowOrFolder instanceof CodeWindow) {
@@ -518,12 +567,13 @@ export class WindowsManager implements IWindowsMainService {
else {
// Do open files
usedWindows.push(this.doOpenFilesInExistingWindow(bestWindowOrFolder, filesToOpen, filesToCreate, filesToDiff));
usedWindows.push(this.doOpenFilesInExistingWindow(bestWindowOrFolder, filesToOpen, filesToCreate, filesToDiff, filesToWait));
// Reset these because we handled them
filesToOpen = [];
filesToCreate = [];
filesToDiff = [];
filesToWait = void 0;
}
}
@@ -541,6 +591,7 @@ export class WindowsManager implements IWindowsMainService {
filesToOpen,
filesToCreate,
filesToDiff,
filesToWait,
forceNewWindow: true
}));
@@ -548,11 +599,12 @@ export class WindowsManager implements IWindowsMainService {
filesToOpen = [];
filesToCreate = [];
filesToDiff = [];
filesToWait = void 0;
}
}
// Handle workspaces to open (instructed and to restore)
const allWorkspacesToOpen = arrays.distinct([...workspacesToOpen, ...workspacesToRestore], workspace => workspace.id); // prevent duplicates
const allWorkspacesToOpen = arrays.distinct([...workspacesToRestore, ...workspacesToOpen], workspace => workspace.id); // prevent duplicates
if (allWorkspacesToOpen.length > 0) {
// Check for existing instances
@@ -561,12 +613,13 @@ export class WindowsManager implements IWindowsMainService {
const windowOnWorkspace = windowsOnWorkspace[0];
// Do open files
usedWindows.push(this.doOpenFilesInExistingWindow(windowOnWorkspace, filesToOpen, filesToCreate, filesToDiff));
usedWindows.push(this.doOpenFilesInExistingWindow(windowOnWorkspace, filesToOpen, filesToCreate, filesToDiff, filesToWait));
// Reset these because we handled them
filesToOpen = [];
filesToCreate = [];
filesToDiff = [];
filesToWait = void 0;
openFolderInNewWindow = true; // any other folders to open must open in new window then
}
@@ -578,19 +631,20 @@ export class WindowsManager implements IWindowsMainService {
}
// Do open folder
usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { workspace: workspaceToOpen }, openFolderInNewWindow, filesToOpen, filesToCreate, filesToDiff));
usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { workspace: workspaceToOpen }, openFolderInNewWindow, filesToOpen, filesToCreate, filesToDiff, filesToWait));
// Reset these because we handled them
filesToOpen = [];
filesToCreate = [];
filesToDiff = [];
filesToWait = void 0;
openFolderInNewWindow = true; // any other folders to open must open in new window then
});
}
// Handle folders to open (instructed and to restore)
const allFoldersToOpen = arrays.distinct([...foldersToOpen, ...foldersToRestore], folder => isLinux ? folder : folder.toLowerCase()); // prevent duplicates
const allFoldersToOpen = arrays.distinct([...foldersToRestore, ...foldersToOpen], folder => isLinux ? folder : folder.toLowerCase()); // prevent duplicates
if (allFoldersToOpen.length > 0) {
// Check for existing instances
@@ -599,12 +653,13 @@ export class WindowsManager implements IWindowsMainService {
const windowOnFolderPath = windowsOnFolderPath[0];
// Do open files
usedWindows.push(this.doOpenFilesInExistingWindow(windowOnFolderPath, filesToOpen, filesToCreate, filesToDiff));
usedWindows.push(this.doOpenFilesInExistingWindow(windowOnFolderPath, filesToOpen, filesToCreate, filesToDiff, filesToWait));
// Reset these because we handled them
filesToOpen = [];
filesToCreate = [];
filesToDiff = [];
filesToWait = void 0;
openFolderInNewWindow = true; // any other folders to open must open in new window then
}
@@ -616,12 +671,13 @@ export class WindowsManager implements IWindowsMainService {
}
// Do open folder
usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { folderPath: folderToOpen }, openFolderInNewWindow, filesToOpen, filesToCreate, filesToDiff));
usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { folderPath: folderToOpen }, openFolderInNewWindow, filesToOpen, filesToCreate, filesToDiff, filesToWait));
// Reset these because we handled them
filesToOpen = [];
filesToCreate = [];
filesToDiff = [];
filesToWait = void 0;
openFolderInNewWindow = true; // any other folders to open must open in new window then
});
@@ -637,6 +693,7 @@ export class WindowsManager implements IWindowsMainService {
filesToOpen,
filesToCreate,
filesToDiff,
filesToWait,
forceNewWindow: true,
emptyWindowBackupFolder
}));
@@ -645,6 +702,7 @@ export class WindowsManager implements IWindowsMainService {
filesToOpen = [];
filesToCreate = [];
filesToDiff = [];
filesToWait = void 0;
openFolderInNewWindow = true; // any other folders to open must open in new window then
});
@@ -667,11 +725,11 @@ export class WindowsManager implements IWindowsMainService {
return arrays.distinct(usedWindows);
}
private doOpenFilesInExistingWindow(window: CodeWindow, filesToOpen: IPath[], filesToCreate: IPath[], filesToDiff: IPath[]): CodeWindow {
private doOpenFilesInExistingWindow(window: CodeWindow, filesToOpen: IPath[], filesToCreate: IPath[], filesToDiff: IPath[], filesToWait: IPathsToWaitFor): CodeWindow {
window.focus(); // make sure window has focus
window.ready().then(readyWindow => {
readyWindow.send('vscode:openFiles', { filesToOpen, filesToCreate, filesToDiff });
readyWindow.send('vscode:openFiles', { filesToOpen, filesToCreate, filesToDiff, filesToWait });
});
return window;
@@ -687,7 +745,7 @@ export class WindowsManager implements IWindowsMainService {
return window;
}
private doOpenFolderOrWorkspace(openConfig: IOpenConfiguration, folderOrWorkspace: IPathToOpen, openInNewWindow: boolean, filesToOpen: IPath[], filesToCreate: IPath[], filesToDiff: IPath[], windowToUse?: CodeWindow): CodeWindow {
private doOpenFolderOrWorkspace(openConfig: IOpenConfiguration, folderOrWorkspace: IPathToOpen, openInNewWindow: boolean, filesToOpen: IPath[], filesToCreate: IPath[], filesToDiff: IPath[], filesToWait: IPathsToWaitFor, windowToUse?: CodeWindow): CodeWindow {
const browserWindow = this.openInBrowserWindow({
userEnv: openConfig.userEnv,
cli: openConfig.cli,
@@ -697,6 +755,7 @@ export class WindowsManager implements IWindowsMainService {
filesToOpen,
filesToCreate,
filesToDiff,
filesToWait,
forceNewWindow: openInNewWindow,
windowToUse
});
@@ -734,10 +793,10 @@ export class WindowsManager implements IWindowsMainService {
// This will ensure to open these folders in one window instead of multiple
// If we are in addMode, we should not do this because in that case all
// folders should be added to the existing window.
if (!openConfig.addMode && isCommandLineOrAPICall && product.quality !== 'stable') { // TODO@Ben multi root
if (!openConfig.addMode && isCommandLineOrAPICall) {
const foldersToOpen = windowsToOpen.filter(path => !!path.folderPath);
if (foldersToOpen.length > 1) {
const workspace = this.workspacesService.createWorkspaceSync(foldersToOpen.map(folder => folder.folderPath));
const workspace = this.workspacesService.createWorkspaceSync(foldersToOpen.map(folder => ({ uri: URI.file(folder.folderPath) })));
// Add workspace and remove folders thereby
windowsToOpen.push({ workspace });
@@ -754,7 +813,7 @@ export class WindowsManager implements IWindowsMainService {
// Warn if the requested path to open does not exist
if (!path) {
const options: Electron.ShowMessageBoxOptions = {
const options: Electron.MessageBoxOptions = {
title: product.nameLong,
type: 'info',
buttons: [localize('ok', "OK")],
@@ -879,7 +938,7 @@ export class WindowsManager implements IWindowsMainService {
restoreWindows = ((windowConfig && windowConfig.restoreWindows) || 'one') as RestoreWindowsSetting;
if (restoreWindows === 'one' /* default */ && windowConfig && windowConfig.reopenFolders) {
restoreWindows = windowConfig.reopenFolders; // TODO@Ben migration
restoreWindows = windowConfig.reopenFolders; // TODO@Ben migration from deprecated window.reopenFolders setting
}
if (['all', 'folders', 'one', 'none'].indexOf(restoreWindows) === -1) {
@@ -903,7 +962,7 @@ export class WindowsManager implements IWindowsMainService {
anyPath = parsedPath.path;
}
const candidate = path.normalize(anyPath);
const candidate = normalize(anyPath);
try {
const candidateStat = fs.statSync(candidate);
if (candidateStat) {
@@ -1014,6 +1073,7 @@ export class WindowsManager implements IWindowsMainService {
configuration.filesToOpen = options.filesToOpen;
configuration.filesToCreate = options.filesToCreate;
configuration.filesToDiff = options.filesToDiff;
configuration.filesToWait = options.filesToWait;
configuration.nodeCachedDataDir = this.environmentService.nodeCachedDataDir;
// if we know the backup folder upfront (for empty windows to restore), we can set it
@@ -1021,7 +1081,7 @@ export class WindowsManager implements IWindowsMainService {
// For all other cases we first call into registerEmptyWindowBackupSync() to set it before
// loading the window.
if (options.emptyWindowBackupFolder) {
configuration.backupPath = path.join(this.environmentService.backupHome, options.emptyWindowBackupFolder);
configuration.backupPath = join(this.environmentService.backupHome, options.emptyWindowBackupFolder);
}
let window: CodeWindow;
@@ -1108,6 +1168,9 @@ export class WindowsManager implements IWindowsMainService {
// Load it
window.load(configuration);
// Signal event
this._onWindowLoad.fire(window.id);
}
});
@@ -1258,97 +1321,32 @@ export class WindowsManager implements IWindowsMainService {
});
}
public saveAndOpenWorkspace(window: CodeWindow, path: string): TPromise<void> {
if (!window || !window.win || window.readyState !== ReadyState.READY || !window.openedWorkspace || !path) {
return TPromise.as(null); // return early if the window is not ready or disposed or does not have a workspace
}
return this.doSaveAndOpenWorkspace(window, window.openedWorkspace, path);
public saveAndEnterWorkspace(win: CodeWindow, path: string): TPromise<IEnterWorkspaceResult> {
return this.workspacesManager.saveAndEnterWorkspace(win, path).then(result => this.doEnterWorkspace(win, result));
}
public createAndOpenWorkspace(window: CodeWindow, folders?: string[], path?: string): TPromise<void> {
if (!window || !window.win || window.readyState !== ReadyState.READY) {
return TPromise.as(null); // return early if the window is not ready or disposed
}
return this.workspacesService.createWorkspace(folders).then(workspace => {
return this.doSaveAndOpenWorkspace(window, workspace, path);
});
public createAndEnterWorkspace(win: CodeWindow, folders?: IWorkspaceFolderCreationData[], path?: string): TPromise<IEnterWorkspaceResult> {
return this.workspacesManager.createAndEnterWorkspace(win, folders, path).then(result => this.doEnterWorkspace(win, result));
}
private doSaveAndOpenWorkspace(window: CodeWindow, workspace: IWorkspaceIdentifier, path?: string): TPromise<void> {
let savePromise: TPromise<IWorkspaceIdentifier>;
if (path) {
savePromise = this.workspacesService.saveWorkspace(workspace, path);
} else {
savePromise = TPromise.as(workspace);
}
private doEnterWorkspace(win: CodeWindow, result: IEnterWorkspaceResult): IEnterWorkspaceResult {
return savePromise.then(workspace => {
window.focus();
// Mark as recently opened
this.historyService.addRecentlyOpened([result.workspace], []);
// Only open workspace when the window has not vetoed this
return this.lifecycleService.unload(window, UnloadReason.RELOAD, workspace).done(veto => {
if (!veto) {
// Trigger Eevent to indicate load of workspace into window
this._onWindowReady.fire(win);
// Register window for backups and migrate current backups over
let backupPath: string;
if (window.config && !window.config.extensionDevelopmentPath) {
backupPath = this.backupService.registerWorkspaceBackupSync(workspace, window.config.backupPath);
}
// Craft a new window configuration to use for the transition
const configuration: IWindowConfiguration = mixin({}, window.config);
configuration.folderPath = void 0;
configuration.workspace = workspace;
configuration.backupPath = backupPath;
// Reload
window.reload(configuration);
}
});
});
return result;
}
public openWorkspace(window: CodeWindow = this.getLastActiveWindow()): void {
let defaultPath: string;
if (window && window.openedWorkspace && !this.workspacesService.isUntitledWorkspace(window.openedWorkspace)) {
defaultPath = path.dirname(window.openedWorkspace.configPath);
} else {
defaultPath = this.getWorkspaceDialogDefaultPath(window ? (window.openedWorkspace || window.openedFolderPath) : void 0);
}
this.pickFileAndOpen({
windowId: window ? window.id : void 0,
dialogOptions: {
buttonLabel: mnemonicButtonLabel(localize({ key: 'openWorkspace', comment: ['&& denotes a mnemonic'] }, "&&Open")),
title: localize('openWorkspaceTitle', "Open Workspace"),
filters: WORKSPACE_FILTER,
properties: ['openFile'],
defaultPath
}
});
}
private getWorkspaceDialogDefaultPath(workspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier): string {
let defaultPath: string;
if (workspace) {
if (isSingleFolderWorkspaceIdentifier(workspace)) {
defaultPath = path.dirname(workspace);
} else {
const resolvedWorkspace = this.workspacesService.resolveWorkspaceSync(workspace.configPath);
if (resolvedWorkspace && resolvedWorkspace.folders.length > 0) {
defaultPath = path.dirname(resolvedWorkspace.folders[0].path);
}
}
}
return defaultPath;
public pickWorkspaceAndOpen(options: INativeOpenDialogOptions): void {
this.workspacesManager.pickWorkspaceAndOpen(options);
}
private onBeforeWindowUnload(e: IWindowUnloadEvent): void {
const windowClosing = e.reason === UnloadReason.CLOSE;
const windowLoading = e.reason === UnloadReason.LOAD;
const windowClosing = (e.reason === UnloadReason.CLOSE);
const windowLoading = (e.reason === UnloadReason.LOAD);
if (!windowClosing && !windowLoading) {
return; // only interested when window is closing or loading
}
@@ -1358,78 +1356,16 @@ export class WindowsManager implements IWindowsMainService {
return; // only care about untitled workspaces to ask for saving
}
if (e.window.config && !!e.window.config.extensionDevelopmentPath) {
return; // do not ask to save workspace when doing extension development
}
if (windowClosing && !isMacintosh && this.getWindowCount() === 1) {
return; // Windows/Linux: quits when last window is closed, so do not ask then
}
this.promptToSaveUntitledWorkspace(e, workspace);
}
private promptToSaveUntitledWorkspace(e: IWindowUnloadEvent, workspace: IWorkspaceIdentifier): void {
enum ConfirmResult {
SAVE,
DONT_SAVE,
CANCEL
}
const save = { label: mnemonicButtonLabel(localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")), result: ConfirmResult.SAVE };
const dontSave = { label: mnemonicButtonLabel(localize({ key: 'doNotSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save")), result: ConfirmResult.DONT_SAVE };
const cancel = { label: localize('cancel', "Cancel"), result: ConfirmResult.CANCEL };
const buttons: { label: string; result: ConfirmResult; }[] = [];
if (isWindows) {
buttons.push(save, dontSave, cancel);
} else if (isLinux) {
buttons.push(dontSave, cancel, save);
} else {
buttons.push(save, cancel, dontSave);
}
const options: Electron.ShowMessageBoxOptions = {
title: this.environmentService.appNameLong,
message: localize('saveWorkspaceMessage', "Do you want to save your workspace configuration as a file?"),
detail: localize('saveWorkspaceDetail', "Save your workspace if you plan to open it again."),
noLink: true,
type: 'warning',
buttons: buttons.map(button => button.label),
cancelId: buttons.indexOf(cancel)
};
if (isLinux) {
options.defaultId = 2;
}
const res = dialog.showMessageBox(e.window.win, options);
switch (buttons[res].result) {
// Cancel: veto unload
case ConfirmResult.CANCEL:
e.veto(true);
break;
// Don't Save: delete workspace
case ConfirmResult.DONT_SAVE:
this.workspacesService.deleteUntitledWorkspaceSync(workspace);
e.veto(false);
break;
// Save: save workspace, but do not veto unload
case ConfirmResult.SAVE: {
const target = dialog.showSaveDialog(e.window.win, {
buttonLabel: mnemonicButtonLabel(localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")),
title: localize('saveWorkspace', "Save Workspace"),
filters: WORKSPACE_FILTER,
defaultPath: this.getWorkspaceDialogDefaultPath(workspace)
});
if (target) {
e.veto(this.workspacesService.saveWorkspace(workspace, target).then(() => false, () => false));
} else {
e.veto(true); // keep veto if no target was provided
}
}
}
// Handle untitled workspaces with prompt as needed
this.workspacesManager.promptToSaveUntitledWorkspace(e, workspace);
}
public focusLastActive(cli: ParsedArgs, context: OpenContext): CodeWindow {
@@ -1452,14 +1388,19 @@ export class WindowsManager implements IWindowsMainService {
this.open({ context, cli: this.environmentService.args, forceNewWindow: true, forceEmpty: true });
}
public waitForWindowClose(windowId: number): TPromise<void> {
public waitForWindowCloseOrLoad(windowId: number): TPromise<void> {
return new TPromise<void>(c => {
const toDispose = this.onWindowClose(id => {
function handler(id: number) {
if (id === windowId) {
toDispose.dispose();
closeListener.dispose();
loadListener.dispose();
c(null);
}
});
}
const closeListener = this.onWindowClose(id => handler(id));
const loadListener = this.onWindowLoad(id => handler(id));
});
}
@@ -1515,7 +1456,7 @@ export class WindowsManager implements IWindowsMainService {
dialog.showMessageBox(window.win, {
title: product.nameLong,
type: 'warning',
buttons: [localize('reopen', "Reopen"), localize('wait', "Keep Waiting"), localize('close', "Close")],
buttons: [mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'wait', comment: ['&& denotes a mnemonic'] }, "&&Keep Waiting")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
message: localize('appStalled', "The window is no longer responding"),
detail: localize('appStalledDetail', "You can reopen or close the window or keep waiting."),
noLink: true
@@ -1538,7 +1479,7 @@ export class WindowsManager implements IWindowsMainService {
dialog.showMessageBox(window.win, {
title: product.nameLong,
type: 'warning',
buttons: [localize('reopen', "Reopen"), localize('close', "Close")],
buttons: [mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
message: localize('appCrashed', "The window has crashed"),
detail: localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."),
noLink: true
@@ -1657,6 +1598,7 @@ class FileDialog {
// 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',
@@ -1677,7 +1619,7 @@ class FileDialog {
});
}
public getFileOrFolderPaths(options: IInternalNativeOpenDialogOptions, clb: (paths: string[]) => void): void {
private getFileOrFolderPaths(options: IInternalNativeOpenDialogOptions, clb: (paths: string[]) => void): void {
// Ensure dialog options
if (!options.dialogOptions) {
@@ -1702,13 +1644,20 @@ class FileDialog {
options.dialogOptions.properties = ['multiSelections', options.pickFolders ? 'openDirectory' : 'openFile', 'createDirectory'];
}
if (isMacintosh) {
options.dialogOptions.properties.push('treatPackageAsDirectory'); // always drill into .app files
}
// Show Dialog
const focusedWindow = this.windowsMainService.getWindowById(options.windowId) || this.windowsMainService.getFocusedWindow();
dialog.showOpenDialog(focusedWindow && focusedWindow.win, options.dialogOptions, paths => {
if (paths && paths.length > 0) {
if (isMacintosh) {
paths = paths.map(path => normalizeNFC(path)); // normalize paths returned from the OS
}
// Remember path in storage for next time
this.storageService.setItem(FileDialog.workingDirPickerStorageKey, path.dirname(paths[0]));
this.storageService.setItem(FileDialog.workingDirPickerStorageKey, dirname(paths[0]));
// Return
return clb(paths);
@@ -1717,4 +1666,201 @@ class FileDialog {
return clb(void (0));
});
}
}
}
class WorkspacesManager {
constructor(
private workspacesService: IWorkspacesMainService,
private lifecycleService: ILifecycleService,
private backupService: IBackupMainService,
private environmentService: IEnvironmentService,
private windowsMainService: IWindowsMainService
) {
}
public saveAndEnterWorkspace(window: CodeWindow, path: string): TPromise<IEnterWorkspaceResult> {
if (!window || !window.win || window.readyState !== ReadyState.READY || !window.openedWorkspace || !path || !this.isValidTargetWorkspacePath(window, path)) {
return TPromise.as(null); // return early if the window is not ready or disposed or does not have a workspace
}
return this.doSaveAndOpenWorkspace(window, window.openedWorkspace, path);
}
public createAndEnterWorkspace(window: CodeWindow, folders?: IWorkspaceFolderCreationData[], path?: string): TPromise<IEnterWorkspaceResult> {
if (!window || !window.win || window.readyState !== ReadyState.READY || !this.isValidTargetWorkspacePath(window, path)) {
return TPromise.as(null); // return early if the window is not ready or disposed
}
return this.workspacesService.createWorkspace(folders).then(workspace => {
return this.doSaveAndOpenWorkspace(window, workspace, path);
});
}
private isValidTargetWorkspacePath(window: CodeWindow, path?: string): boolean {
if (!path) {
return true;
}
if (window.openedWorkspace && window.openedWorkspace.configPath === path) {
return false; // window is already opened on a workspace with that path
}
// Prevent overwriting a workspace that is currently opened in another window
if (findWindowOnWorkspace(this.windowsMainService.getWindows(), { id: this.workspacesService.getWorkspaceId(path), configPath: path })) {
const options: Electron.MessageBoxOptions = {
title: product.nameLong,
type: 'info',
buttons: [localize('ok', "OK")],
message: localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(path)),
detail: localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again."),
noLink: true
};
const activeWindow = BrowserWindow.getFocusedWindow();
if (activeWindow) {
dialog.showMessageBox(activeWindow, options);
} else {
dialog.showMessageBox(options);
}
return false;
}
return true; // OK
}
private doSaveAndOpenWorkspace(window: CodeWindow, workspace: IWorkspaceIdentifier, path?: string): TPromise<IEnterWorkspaceResult> {
let savePromise: TPromise<IWorkspaceIdentifier>;
if (path) {
savePromise = this.workspacesService.saveWorkspace(workspace, path);
} else {
savePromise = TPromise.as(workspace);
}
return savePromise.then(workspace => {
window.focus();
// Register window for backups and migrate current backups over
let backupPath: string;
if (!window.config.extensionDevelopmentPath) {
backupPath = this.backupService.registerWorkspaceBackupSync(workspace, window.config.backupPath);
}
// Update window configuration properly based on transition to workspace
window.config.folderPath = void 0;
window.config.workspace = workspace;
window.config.backupPath = backupPath;
return { workspace, backupPath };
});
}
public pickWorkspaceAndOpen(options: INativeOpenDialogOptions): void {
const window = this.windowsMainService.getWindowById(options.windowId) || this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow();
this.windowsMainService.pickFileAndOpen({
windowId: window ? window.id : void 0,
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
});
}
public promptToSaveUntitledWorkspace(e: IWindowUnloadEvent, workspace: IWorkspaceIdentifier): void {
enum ConfirmResult {
SAVE,
DONT_SAVE,
CANCEL
}
const save = { label: mnemonicButtonLabel(localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")), result: ConfirmResult.SAVE };
const dontSave = { label: mnemonicButtonLabel(localize({ key: 'doNotSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save")), result: ConfirmResult.DONT_SAVE };
const cancel = { label: localize('cancel', "Cancel"), result: ConfirmResult.CANCEL };
const buttons: { label: string; result: ConfirmResult; }[] = [];
if (isWindows) {
buttons.push(save, dontSave, cancel);
} else if (isLinux) {
buttons.push(dontSave, cancel, save);
} else {
buttons.push(save, cancel, dontSave);
}
const options: Electron.MessageBoxOptions = {
title: this.environmentService.appNameLong,
message: localize('saveWorkspaceMessage', "Do you want to save your workspace configuration as a file?"),
detail: localize('saveWorkspaceDetail', "Save your workspace if you plan to open it again."),
noLink: true,
type: 'warning',
buttons: buttons.map(button => button.label),
cancelId: buttons.indexOf(cancel)
};
if (isLinux) {
options.defaultId = 2;
}
const res = dialog.showMessageBox(e.window.win, options);
switch (buttons[res].result) {
// Cancel: veto unload
case ConfirmResult.CANCEL:
e.veto(true);
break;
// Don't Save: delete workspace
case ConfirmResult.DONT_SAVE:
this.workspacesService.deleteUntitledWorkspaceSync(workspace);
e.veto(false);
break;
// Save: save workspace, but do not veto unload
case ConfirmResult.SAVE: {
let target = dialog.showSaveDialog(e.window.win, {
buttonLabel: mnemonicButtonLabel(localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")),
title: localize('saveWorkspace', "Save Workspace"),
filters: WORKSPACE_FILTER,
defaultPath: this.getUntitledWorkspaceSaveDialogDefaultPath(workspace)
});
if (target) {
if (isMacintosh) {
target = normalizeNFC(target); // normalize paths returned from the OS
}
e.veto(this.workspacesService.saveWorkspace(workspace, target).then(() => false, () => false));
} else {
e.veto(true); // keep veto if no target was provided
}
}
}
}
private getUntitledWorkspaceSaveDialogDefaultPath(workspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier): string {
if (workspace) {
if (isSingleFolderWorkspaceIdentifier(workspace)) {
return dirname(workspace);
}
const resolvedWorkspace = this.workspacesService.resolveWorkspaceSync(workspace.configPath);
if (resolvedWorkspace && resolvedWorkspace.folders.length > 0) {
for (const folder of resolvedWorkspace.folders) {
if (folder.uri.scheme === Schemas.file) {
return dirname(folder.uri.fsPath);
}
}
}
}
return void 0;
}
}

View File

@@ -14,9 +14,13 @@ import pkg from 'vs/platform/node/package';
import * as fs from 'fs';
import * as paths from 'path';
import * as os from 'os';
import { whenDeleted } from 'vs/base/node/pfs';
function shouldSpawnCliProcess(argv: ParsedArgs): boolean {
return argv['list-extensions'] || !!argv['install-extension'] || !!argv['uninstall-extension'];
return !!argv['install-source']
|| !!argv['list-extensions']
|| !!argv['install-extension']
|| !!argv['uninstall-extension'];
}
interface IMainCli {
@@ -105,14 +109,7 @@ export function main(argv: string[]): TPromise<void> {
child.once('exit', () => c(null));
// Complete when wait marker file is deleted
const interval = setInterval(() => {
fs.exists(waitMarkerFilePath, exists => {
if (!exists) {
clearInterval(interval);
c(null);
}
});
}, 1000);
whenDeleted(waitMarkerFilePath).done(c, c);
});
}
}

View File

@@ -7,6 +7,7 @@ import { localize } from 'vs/nls';
import product from 'vs/platform/node/product';
import pkg from 'vs/platform/node/package';
import * as path from 'path';
import * as fs from 'fs';
import { TPromise } from 'vs/base/common/winjs.base';
import { sequence } from 'vs/base/common/async';
@@ -16,7 +17,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { EnvironmentService, getInstallSourcePath } from 'vs/platform/environment/node/environmentService';
import { IExtensionManagementService, IExtensionGalleryService, IExtensionManifest, IGalleryExtension, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService';
@@ -33,8 +34,8 @@ import { mkdirp } from 'vs/base/node/pfs';
import { IChoiceService } from 'vs/platform/message/common/message';
import { ChoiceCliService } from 'vs/platform/message/node/messageCli';
const notFound = id => localize('notFound', "Extension '{0}' not found.", id);
const notInstalled = id => localize('notInstalled', "Extension '{0}' is not installed.", id);
const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id);
const notInstalled = (id: string) => localize('notInstalled', "Extension '{0}' is not installed.", id);
const useId = localize('useId', "Make sure you use the full extension ID, including the publisher, eg: {0}", 'ms-vscode.csharp');
function getId(manifest: IExtensionManifest, withVersion?: boolean): string {
@@ -50,6 +51,7 @@ type Task = { (): TPromise<void> };
class Main {
constructor(
@IEnvironmentService private environmentService: IEnvironmentService,
@IExtensionManagementService private extensionManagementService: IExtensionManagementService,
@IExtensionGalleryService private extensionGalleryService: IExtensionGalleryService
) { }
@@ -57,7 +59,9 @@ class Main {
run(argv: ParsedArgs): TPromise<any> {
// TODO@joao - make this contributable
if (argv['list-extensions']) {
if (argv['install-source']) {
return this.setInstallSource(argv['install-source']);
} else if (argv['list-extensions']) {
return this.listExtensions(argv['show-versions']);
} else if (argv['install-extension']) {
const arg = argv['install-extension'];
@@ -71,6 +75,13 @@ class Main {
return undefined;
}
private setInstallSource(installSource: string): TPromise<any> {
return new TPromise<void>((c, e) => {
const path = getInstallSourcePath(this.environmentService.userDataPath);
fs.writeFile(path, installSource.slice(0, 30), 'utf8', err => err ? e(err) : c(null));
});
}
private listExtensions(showVersions: boolean): TPromise<any> {
return this.extensionManagementService.getInstalled(LocalExtensionType.User).then(extensions => {
extensions.forEach(e => console.log(getId(e.manifest, showVersions)));
@@ -99,7 +110,7 @@ class Main {
return TPromise.as(null);
}
return this.extensionGalleryService.query({ names: [id] })
return this.extensionGalleryService.query({ names: [id], source: 'cli' })
.then<IPager<IGalleryExtension>>(null, err => {
if (err.responseText) {
try {
@@ -122,7 +133,7 @@ class Main {
console.log(localize('foundExtension', "Found '{0}' in the marketplace.", id));
console.log(localize('installing', "Installing..."));
return this.extensionManagementService.installFromGallery(extension, false)
return this.extensionManagementService.installFromGallery(extension)
.then(() => console.log(localize('successInstall', "Extension '{0}' v{1} was successfully installed!", id, extension.version)));
});
});
@@ -161,7 +172,7 @@ export function main(argv: ParsedArgs): TPromise<void> {
const envService = accessor.get(IEnvironmentService);
return TPromise.join([envService.appSettingsHome, envService.extensionsPath].map(p => mkdirp(p))).then(() => {
const { appRoot, extensionsPath, extensionDevelopmentPath, isBuilt } = envService;
const { appRoot, extensionsPath, extensionDevelopmentPath, isBuilt, installSource } = envService;
const services = new ServiceCollection();
services.set(IConfigurationService, new SyncDescriptor(ConfigurationService));
@@ -170,7 +181,7 @@ export function main(argv: ParsedArgs): TPromise<void> {
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
services.set(IChoiceService, new SyncDescriptor(ChoiceCliService));
if (isBuilt && !extensionDevelopmentPath && product.enableTelemetry) {
if (isBuilt && !extensionDevelopmentPath && !envService.args['disable-telemetry'] && product.enableTelemetry) {
const appenders: AppInsightsAppender[] = [];
if (product.aiConfig && product.aiConfig.asimovKey) {
@@ -183,7 +194,7 @@ export function main(argv: ParsedArgs): TPromise<void> {
const config: ITelemetryServiceConfig = {
appender: combinedAppender(...appenders),
commonProperties: resolveCommonProperties(product.commit, pkg.version),
commonProperties: resolveCommonProperties(product.commit, pkg.version, installSource),
piiPaths: [appRoot, extensionsPath]
};

View File

@@ -58,6 +58,9 @@ function getUnixShellEnvironment(): TPromise<typeof process.env> {
delete env['ELECTRON_NO_ATTACH_CONSOLE'];
}
// https://github.com/Microsoft/vscode/issues/22593#issuecomment-336050758
delete env['XDG_RUNTIME_DIR'];
c(env);
} catch (err) {
e(err);

View File

@@ -11,6 +11,7 @@ import * as platform from 'vs/base/common/platform';
import * as paths from 'vs/base/common/paths';
import { OpenContext } from 'vs/platform/windows/common/windows';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IResolvedWorkspace } from 'vs/platform/workspaces/common/workspaces';
import { Schemas } from 'vs/base/common/network';
export interface ISimpleWindow {
openedWorkspace?: IWorkspaceIdentifier;
@@ -62,7 +63,7 @@ function findWindowOnFilePath<W extends ISimpleWindow>(windows: W[], filePath: s
for (let i = 0; i < workspaceWindows.length; i++) {
const window = workspaceWindows[i];
const resolvedWorkspace = workspaceResolver(window.openedWorkspace);
if (resolvedWorkspace && resolvedWorkspace.folders.some(folder => paths.isEqualOrParent(filePath, folder.path, !platform.isLinux /* ignorecase */))) {
if (resolvedWorkspace && resolvedWorkspace.folders.some(folder => folder.uri.scheme === Schemas.file && paths.isEqualOrParent(filePath, folder.uri.fsPath, !platform.isLinux /* ignorecase */))) {
return window;
}
}
@@ -165,4 +166,4 @@ export function findWindowOnWorkspaceOrFolderPath<W extends ISimpleWindow>(windo
return false;
})[0];
}
}

View File

@@ -9,6 +9,7 @@ import path = require('path');
import { findBestWindowOrFolderForFile, ISimpleWindow, IBestWindowOrFolderOptions } from 'vs/code/node/windowsFinder';
import { OpenContext } from 'vs/platform/windows/common/windows';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { toWorkspaceFolders } from 'vs/platform/workspace/common/workspace';
const fixturesFolder = require.toUrl('./fixtures');
@@ -24,7 +25,7 @@ function options(custom?: Partial<IBestWindowOrFolderOptions<ISimpleWindow>>): I
reuseWindow: false,
context: OpenContext.CLI,
codeSettingsFolder: '_vscode',
workspaceResolver: workspace => { return workspace === testWorkspace ? { id: testWorkspace.id, configPath: workspace.configPath, folders: [{ path: path.join(fixturesFolder, 'vscode_workspace_1_folder') }, { path: path.join(fixturesFolder, 'vscode_workspace_2_folder') }] } : null; },
workspaceResolver: workspace => { return workspace === testWorkspace ? { id: testWorkspace.id, configPath: workspace.configPath, folders: toWorkspaceFolders([{ path: path.join(fixturesFolder, 'vscode_workspace_1_folder') }, { path: path.join(fixturesFolder, 'vscode_workspace_2_folder') }]) } : null; },
...custom
};
}