mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-06 01:25:38 -05:00
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:
13
src/vs/code/electron-browser/contrib/contributions.ts
Normal file
13
src/vs/code/electron-browser/contrib/contributions.ts
Normal 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);
|
||||
}
|
||||
@@ -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); }
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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&< 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 });
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user