Initial VS Code 1.19 source merge (#571)

* Initial 1.19 xcopy

* Fix yarn build

* Fix numerous build breaks

* Next batch of build break fixes

* More build break fixes

* Runtime breaks

* Additional post merge fixes

* Fix windows setup file

* Fix test failures.

* Update license header blocks to refer to source eula
This commit is contained in:
Karl Burtram
2018-01-28 23:37:17 -08:00
committed by GitHub
parent 9a1ac20710
commit 251ae01c3e
8009 changed files with 93378 additions and 35634 deletions

View File

@@ -12,12 +12,9 @@ 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
private static readonly _DataMaxAge = product.nameLong.indexOf('Insiders') >= 0
? 1000 * 60 * 60 * 24 * 7 // roughly 1 week
: 1000 * 60 * 60 * 24 * 30 * 3; // roughly 3 months

View File

@@ -89,7 +89,11 @@ function main() {
});
}
require(['vs/code/electron-browser/sharedProcessMain'], function () { });
require(['vs/code/electron-browser/sharedProcessMain'], function (sharedProcess) {
sharedProcess.startup({
machineId: configuration.machineId
});
});
});
}

View File

@@ -24,7 +24,7 @@ import { IRequestService } from 'vs/platform/request/node/request';
import { RequestService } from 'vs/platform/request/electron-browser/requestService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { combinedAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { resolveCommonProperties, machineIdStorageKey } from 'vs/platform/telemetry/node/commonProperties';
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc';
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
@@ -34,8 +34,17 @@ import { IWindowsService } from 'vs/platform/windows/common/windows';
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';
import { createLogService } from 'vs/platform/log/node/spdlogService';
import { ILogService } from 'vs/platform/log/common/log';
export interface ISharedProcessConfiguration {
readonly machineId: string;
}
export function startup(configuration: ISharedProcessConfiguration) {
handshake(configuration);
}
interface ISharedProcessInitData {
sharedIPCHandle: string;
@@ -66,10 +75,17 @@ class ActiveWindowManager implements IDisposable {
const eventPrefix = 'monacoworkbench';
function main(server: Server, initData: ISharedProcessInitData): void {
function main(server: Server, initData: ISharedProcessInitData, configuration: ISharedProcessConfiguration): void {
const services = new ServiceCollection();
services.set(IEnvironmentService, new SyncDescriptor(EnvironmentService, initData.args, process.execPath));
const environmentService = new EnvironmentService(initData.args, process.execPath);
const logService = createLogService('sharedprocess', environmentService);
process.once('exit', () => logService.dispose());
logService.info('main', JSON.stringify(configuration));
services.set(IEnvironmentService, environmentService);
services.set(ILogService, logService);
services.set(IConfigurationService, new SyncDescriptor(ConfigurationService));
services.set(IRequestService, new SyncDescriptor(RequestService));
@@ -100,21 +116,12 @@ function main(server: Server, initData: ISharedProcessInitData): void {
const services = new ServiceCollection();
const environmentService = accessor.get(IEnvironmentService);
const { appRoot, extensionsPath, extensionDevelopmentPath, isBuilt, extensionTestsPath, installSource } = environmentService;
const { appRoot, extensionsPath, extensionDevelopmentPath, isBuilt, installSourcePath } = environmentService;
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, installSource)
// __GDPR__COMMON__ "common.machineId" : { "classification": "EndUserPseudonymizedInformation", "purpose": "FeatureInsight" }
.then(result => Object.defineProperty(result, 'common.machineId', {
get: () => storageService.get(machineIdStorageKey),
enumerable: true
})),
commonProperties: resolveCommonProperties(product.commit, pkg.version, configuration.machineId, installSourcePath),
piiPaths: [appRoot, extensionsPath]
};
@@ -177,15 +184,13 @@ function setupIPC(hook: string): TPromise<Server> {
function startHandshake(): TPromise<ISharedProcessInitData> {
return new TPromise<ISharedProcessInitData>((c, e) => {
ipcRenderer.once('handshake:hey there', (_, r) => c(r));
ipcRenderer.once('handshake:hey there', (_: any, r: ISharedProcessInitData) => c(r));
ipcRenderer.send('handshake:hello');
});
}
function handshake(): TPromise<void> {
function handshake(configuration: ISharedProcessConfiguration): TPromise<void> {
return startHandshake()
.then((data) => setupIPC(data.sharedIPCHandle).then(server => main(server, data)))
.then(data => setupIPC(data.sharedIPCHandle).then(server => main(server, data, configuration)))
.then(() => ipcRenderer.send('handshake:im ready'));
}
handshake();
}

View File

@@ -26,7 +26,7 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ILogService } from 'vs/platform/log/common/log';
import { IStorageService } from 'vs/platform/storage/node/storage';
import { IStateService } from 'vs/platform/state/common/state';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IURLService } from 'vs/platform/url/common/url';
@@ -35,10 +35,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { ITelemetryAppenderChannel, TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc';
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
import { CredentialsService } from 'vs/platform/credentials/node/credentialsService';
import { CredentialsChannel } from 'vs/platform/credentials/node/credentialsIpc';
import { resolveCommonProperties, machineIdStorageKey, machineIdIpcChannel } from 'vs/platform/telemetry/node/commonProperties';
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
import { getDelayedChannel } from 'vs/base/parts/ipc/common/ipc';
import product from 'vs/platform/node/product';
import pkg from 'vs/platform/node/package';
@@ -56,10 +53,12 @@ import { WorkspacesChannel } from 'vs/platform/workspaces/common/workspacesIpc';
import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces';
import { dirname, join } from 'path';
import { touch } from 'vs/base/node/pfs';
import { getMachineId } from 'vs/base/node/id';
export class CodeApplication {
private static APP_ICON_REFRESH_KEY = 'macOSAppIconRefresh3';
private static readonly APP_ICON_REFRESH_KEY = 'macOSAppIconRefresh3';
private static readonly MACHINE_ID_KEY = 'telemetry.machineId';
private toDispose: IDisposable[];
private windowsMainService: IWindowsMainService;
@@ -76,9 +75,9 @@ export class CodeApplication {
@ILogService private logService: ILogService,
@IEnvironmentService private environmentService: IEnvironmentService,
@ILifecycleService private lifecycleService: ILifecycleService,
@IConfigurationService private configurationService: ConfigurationService,
@IStorageService private storageService: IStorageService,
@IHistoryMainService private historyService: IHistoryMainService
@IConfigurationService configurationService: ConfigurationService,
@IStateService private stateService: IStateService,
@IHistoryMainService private historyMainService: IHistoryMainService
) {
this.toDispose = [mainIpcServer, configurationService];
@@ -110,7 +109,7 @@ export class CodeApplication {
});
app.on('will-quit', () => {
this.logService.log('App#will-quit: disposing resources');
this.logService.trace('App#will-quit: disposing resources');
this.dispose();
});
@@ -122,7 +121,7 @@ export class CodeApplication {
});
app.on('activate', (event: Event, hasVisibleWindows: boolean) => {
this.logService.log('App#activate');
this.logService.trace('App#activate');
// Mac only event: open new window when we get activated
if (!hasVisibleWindows && this.windowsMainService) {
@@ -133,7 +132,7 @@ export class CodeApplication {
const isValidWebviewSource = (source: string) =>
!source || (URI.parse(source.toLowerCase()).toString() as any).startsWith(URI.file(this.environmentService.appRoot.toLowerCase()).toString());
app.on('web-contents-created', (event, contents) => {
app.on('web-contents-created', (_event: any, contents) => {
contents.on('will-attach-webview', (event: Electron.Event, webPreferences, params) => {
delete webPreferences.preload;
webPreferences.nodeIntegration = false;
@@ -157,7 +156,7 @@ export class CodeApplication {
let macOpenFiles: string[] = [];
let runningTimeout: number = null;
app.on('open-file', (event: Event, path: string) => {
this.logService.log('App#open-file: ', path);
this.logService.trace('App#open-file: ', path);
event.preventDefault();
// Keep in array because more might come!
@@ -188,19 +187,14 @@ export class CodeApplication {
this.windowsMainService.openNewWindow(OpenContext.DESKTOP); //macOS native tab "+" button
});
ipc.on('vscode:exit', (event, code: number) => {
this.logService.log('IPC#vscode:exit', code);
ipc.on('vscode:exit', (_event: any, code: number) => {
this.logService.trace('IPC#vscode:exit', code);
this.dispose();
this.lifecycleService.kill(code);
});
ipc.on(machineIdIpcChannel, (event, machineId: string) => {
this.logService.log('IPC#vscode-machineId');
this.storageService.setItem(machineIdStorageKey, machineId);
});
ipc.on('vscode:fetchShellEnv', (event, windowId) => {
ipc.on('vscode:fetchShellEnv', (_event: any, windowId: number) => {
const { webContents } = BrowserWindow.fromId(windowId);
getShellEnvironment().then(shellEnv => {
if (!webContents.isDestroyed()) {
@@ -215,9 +209,9 @@ export class CodeApplication {
});
});
ipc.on('vscode:broadcast', (event, windowId: number, broadcast: { channel: string; payload: any; }) => {
ipc.on('vscode:broadcast', (_event: any, windowId: number, broadcast: { channel: string; payload: any; }) => {
if (this.windowsMainService && broadcast.channel && !isUndefinedOrNull(broadcast.payload)) {
this.logService.log('IPC#vscode:broadcast', broadcast.channel, broadcast.payload);
this.logService.trace('IPC#vscode:broadcast', broadcast.channel, broadcast.payload);
// Handle specific events on main side
this.onBroadcast(broadcast.channel, broadcast.payload);
@@ -241,15 +235,15 @@ export class CodeApplication {
if (event === 'vscode:changeColorTheme' && typeof payload === 'string') {
let data = JSON.parse(payload);
this.storageService.setItem(CodeWindow.themeStorageKey, data.id);
this.storageService.setItem(CodeWindow.themeBackgroundStorageKey, data.background);
this.stateService.setItem(CodeWindow.themeStorageKey, data.id);
this.stateService.setItem(CodeWindow.themeBackgroundStorageKey, data.background);
}
}
public startup(): void {
this.logService.log('Starting VS Code in verbose mode');
this.logService.log(`from: ${this.environmentService.appRoot}`);
this.logService.log('args:', this.environmentService.args);
public startup(): TPromise<void> {
this.logService.debug('Starting VS Code');
this.logService.debug(`from: ${this.environmentService.appRoot}`);
this.logService.debug('args:', this.environmentService.args);
// Make sure we associate the program with the app user model id
// This will help Windows to associate the running program with
@@ -262,46 +256,62 @@ export class CodeApplication {
// Create Electron IPC Server
this.electronIpcServer = new ElectronIPCServer();
// Spawn shared process
this.sharedProcess = new SharedProcess(this.environmentService, this.userEnv);
this.toDispose.push(this.sharedProcess);
this.sharedProcessClient = this.sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main'));
// Resolve unique machine ID
this.logService.trace('Resolving machine identifier...');
return this.resolveMachineId().then(machineId => {
this.logService.trace(`Resolved machine identifier: ${machineId}`);
// Services
const appInstantiationService = this.initServices();
// Spawn shared process
this.sharedProcess = new SharedProcess(this.environmentService, machineId, this.userEnv);
this.toDispose.push(this.sharedProcess);
this.sharedProcessClient = this.sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main'));
// Setup Auth Handler
const authHandler = appInstantiationService.createInstance(ProxyAuthHandler);
this.toDispose.push(authHandler);
// Services
const appInstantiationService = this.initServices(machineId);
// Open Windows
appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor));
// Setup Auth Handler
const authHandler = appInstantiationService.createInstance(ProxyAuthHandler);
this.toDispose.push(authHandler);
// Post Open Windows Tasks
appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor));
// Open Windows
appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor));
// Post Open Windows Tasks
appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor));
});
}
private initServices(): IInstantiationService {
private resolveMachineId(): TPromise<string> {
const machineId = this.stateService.getItem<string>(CodeApplication.MACHINE_ID_KEY);
if (machineId) {
return TPromise.wrap(machineId);
}
return getMachineId().then(machineId => {
// Remember in global storage
this.stateService.setItem(CodeApplication.MACHINE_ID_KEY, machineId);
return machineId;
});
}
private initServices(machineId: string): IInstantiationService {
const services = new ServiceCollection();
services.set(IUpdateService, new SyncDescriptor(UpdateService));
services.set(IWindowsMainService, new SyncDescriptor(WindowsManager));
services.set(IWindowsMainService, new SyncDescriptor(WindowsManager, machineId));
services.set(IWindowsService, new SyncDescriptor(WindowsService, this.sharedProcess));
services.set(ILaunchService, new SyncDescriptor(LaunchService));
services.set(ICredentialsService, new SyncDescriptor(CredentialsService));
// Telemtry
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, 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
}));
const commonProperties = resolveCommonProperties(product.commit, pkg.version, machineId, this.environmentService.installSourcePath);
const piiPaths = [this.environmentService.appRoot, this.environmentService.extensionsPath];
const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths };
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, config));
} else {
services.set(ITelemetryService, NullTelemetryService);
@@ -346,10 +356,6 @@ export class CodeApplication {
this.electronIpcServer.registerChannel('windows', windowsChannel);
this.sharedProcessClient.done(client => client.registerChannel('windows', windowsChannel));
const credentialsService = accessor.get(ICredentialsService);
const credentialsChannel = new CredentialsChannel(credentialsService);
this.electronIpcServer.registerChannel('credentials', credentialsChannel);
// Lifecycle
this.lifecycleService.ready();
@@ -393,6 +399,7 @@ export class CodeApplication {
// Ensure Windows foreground love module
try {
// tslint:disable-next-line:no-unused-expression
<any>require.__$__nodeRequire('windows-foreground-love');
} catch (e) {
if (!this.environmentService.isBuilt) {
@@ -411,8 +418,8 @@ export class CodeApplication {
appInstantiationService.createInstance(CodeMenu);
// Jump List
this.historyService.updateWindowsJumpList();
this.historyService.onRecentlyOpenedChange(() => this.historyService.updateWindowsJumpList());
this.historyMainService.updateWindowsJumpList();
this.historyMainService.onRecentlyOpenedChange(() => this.historyMainService.updateWindowsJumpList());
// Start shared process here
this.sharedProcess.spawn();
@@ -420,8 +427,8 @@ export class CodeApplication {
// Helps application icon refresh after an update with new icon is installed (macOS)
// TODO@Ben remove after a couple of releases
if (platform.isMacintosh) {
if (!this.storageService.getItem(CodeApplication.APP_ICON_REFRESH_KEY)) {
this.storageService.setItem(CodeApplication.APP_ICON_REFRESH_KEY, true);
if (!this.stateService.getItem(CodeApplication.APP_ICON_REFRESH_KEY)) {
this.stateService.setItem(CodeApplication.APP_ICON_REFRESH_KEY, true);
// 'exe' => /Applications/Visual Studio Code - Insiders.app/Contents/MacOS/Electron
const appPath = dirname(dirname(dirname(app.getPath('exe'))));

View File

@@ -8,7 +8,7 @@
import { localize } from 'vs/nls';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
import { fromEventEmitter } from 'vs/base/node/event';
import { fromNodeEventEmitter } from 'vs/base/common/event';
import { BrowserWindow, app } from 'electron';
type LoginEvent = {
@@ -32,9 +32,9 @@ export class ProxyAuthHandler {
private disposables: IDisposable[] = [];
constructor(
@IWindowsMainService private windowsService: IWindowsMainService
@IWindowsMainService private windowsMainService: IWindowsMainService
) {
const onLogin = fromEventEmitter<LoginEvent>(app, 'login', (event, webContents, req, authInfo, cb) => ({ event, webContents, req, authInfo, cb }));
const onLogin = fromNodeEventEmitter<LoginEvent>(app, 'login', (event, webContents, req, authInfo, cb) => ({ event, webContents, req, authInfo, cb }));
onLogin(this.onLogin, this, this.disposables);
}
@@ -59,7 +59,7 @@ export class ProxyAuthHandler {
title: 'VS Code'
};
const focusedWindow = this.windowsService.getFocusedWindow();
const focusedWindow = this.windowsMainService.getFocusedWindow();
if (focusedWindow) {
opts.parent = focusedWindow.win;

View File

@@ -0,0 +1,178 @@
/*---------------------------------------------------------------------------------------------
* 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 { WorkspaceStats, collectWorkspaceStats, collectLaunchConfigs, WorkspaceStatItem } from 'vs/base/node/stats';
import { IMainProcessInfo } from 'vs/code/electron-main/launch';
import { ProcessItem, listProcesses } from 'vs/base/node/ps';
import product from 'vs/platform/node/product';
import pkg from 'vs/platform/node/package';
import * as os from 'os';
import { virtualMachineHint } from 'vs/base/node/id';
import { repeat, pad } from 'vs/base/common/strings';
import { isWindows } from 'vs/base/common/platform';
import { app } from 'electron';
import { basename } from 'path';
export function printDiagnostics(info: IMainProcessInfo): Promise<any> {
return listProcesses(info.mainPID).then(rootProcess => {
// Environment Info
console.log('');
console.log(formatEnvironment(info));
// Process List
console.log('');
console.log(formatProcessList(info, rootProcess));
// Workspace Stats
if (info.windows.some(window => window.folders && window.folders.length > 0)) {
console.log('');
console.log('Workspace Stats: ');
info.windows.forEach(window => {
if (window.folders.length === 0) {
return;
}
console.log(`| Window (${window.title})`);
window.folders.forEach(folder => {
try {
const stats = collectWorkspaceStats(folder, ['node_modules', '.git']);
let countMessage = `${stats.fileCount} files`;
if (stats.maxFilesReached) {
countMessage = `more than ${countMessage}`;
}
console.log(`| Folder (${basename(folder)}): ${countMessage}`);
console.log(formatWorkspaceStats(stats));
const launchConfigs = collectLaunchConfigs(folder);
if (launchConfigs.length > 0) {
console.log(formatLaunchConfigs(launchConfigs));
}
} catch (error) {
console.log(`| Error: Unable to collect workpsace stats for folder ${folder} (${error.toString()})`);
}
});
});
}
console.log('');
console.log('');
});
}
function formatWorkspaceStats(workspaceStats: WorkspaceStats): string {
const output: string[] = [];
const lineLength = 60;
let col = 0;
const appendAndWrap = (name: string, count: number) => {
const item = ` ${name}(${count})`;
if (col + item.length > lineLength) {
output.push(line);
line = '| ';
col = line.length;
}
else {
col += item.length;
}
line += item;
};
// File Types
let line = '| File types:';
const maxShown = 10;
let max = workspaceStats.fileTypes.length > maxShown ? maxShown : workspaceStats.fileTypes.length;
for (let i = 0; i < max; i++) {
const item = workspaceStats.fileTypes[i];
appendAndWrap(item.name, item.count);
}
output.push(line);
// Conf Files
if (workspaceStats.configFiles.length >= 0) {
line = '| Conf files:';
col = 0;
workspaceStats.configFiles.forEach((item) => {
appendAndWrap(item.name, item.count);
});
output.push(line);
}
return output.join('\n');
}
function formatLaunchConfigs(configs: WorkspaceStatItem[]): string {
const output: string[] = [];
let line = '| Launch Configs:';
configs.forEach(each => {
const item = each.count > 1 ? ` ${each.name}(${each.count})` : ` ${each.name}`;
line += item;
});
output.push(line);
return output.join('\n');
}
function formatEnvironment(info: IMainProcessInfo): string {
const MB = 1024 * 1024;
const GB = 1024 * MB;
const output: string[] = [];
output.push(`Version: ${pkg.name} ${pkg.version} (${product.commit || 'Commit unknown'}, ${product.date || 'Date unknown'})`);
output.push(`OS Version: ${os.type()} ${os.arch()} ${os.release()})`);
const cpus = os.cpus();
if (cpus && cpus.length > 0) {
output.push(`CPUs: ${cpus[0].model} (${cpus.length} x ${cpus[0].speed})`);
}
output.push(`Memory (System): ${(os.totalmem() / GB).toFixed(2)}GB (${(os.freemem() / GB).toFixed(2)}GB free)`);
if (!isWindows) {
output.push(`Load (avg): ${os.loadavg().map(l => Math.round(l)).join(', ')}`); // only provided on Linux/macOS
}
output.push(`VM: ${Math.round((virtualMachineHint.value() * 100))}%`);
output.push(`Screen Reader: ${app.isAccessibilitySupportEnabled() ? 'yes' : 'no'}`);
return output.join('\n');
}
function formatProcessList(info: IMainProcessInfo, rootProcess: ProcessItem): string {
const mapPidToWindowTitle = new Map<number, string>();
info.windows.forEach(window => mapPidToWindowTitle.set(window.pid, window.title));
const output: string[] = [];
output.push('CPU %\tMem MB\tProcess');
formatProcessItem(mapPidToWindowTitle, output, rootProcess, 0);
return output.join('\n');
}
function formatProcessItem(mapPidToWindowTitle: Map<number, string>, output: string[], item: ProcessItem, indent: number): void {
const isRoot = (indent === 0);
const MB = 1024 * 1024;
// Format name with indent
let name: string;
if (isRoot) {
name = `${product.applicationName} main`;
} else {
name = `${repeat(' ', indent)} ${item.name}`;
if (item.name === 'window') {
name = `${name} (${mapPidToWindowTitle.get(item.pid)})`;
}
}
const memory = process.platform === 'win32' ? item.mem : (os.totalmem() * (item.mem / 100));
output.push(`${pad(Number(item.load.toFixed(0)), 5, ' ')}\t${pad(Number((memory / MB).toFixed(0)), 6, ' ')}\t${name}`);
// Recurse into children if any
if (Array.isArray(item.children)) {
item.children.forEach(child => formatProcessItem(mapPidToWindowTitle, output, child, indent + 1));
}
}

View File

@@ -7,7 +7,7 @@
import * as nativeKeymap from 'native-keymap';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IStorageService } from 'vs/platform/storage/node/storage';
import { IStateService } from 'vs/platform/state/common/state';
import Event, { Emitter, once } from 'vs/base/common/event';
import { ConfigWatcher } from 'vs/base/node/config';
import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding';
@@ -48,7 +48,7 @@ export interface IKeybinding {
export class KeybindingsResolver {
private static lastKnownKeybindingsMapStorageKey = 'lastKnownKeybindings';
private static readonly lastKnownKeybindingsMapStorageKey = 'lastKnownKeybindings';
private commandIds: Set<string>;
private keybindings: { [commandId: string]: IKeybinding };
@@ -58,13 +58,13 @@ export class KeybindingsResolver {
onKeybindingsChanged: Event<void> = this._onKeybindingsChanged.event;
constructor(
@IStorageService private storageService: IStorageService,
@IStateService private stateService: IStateService,
@IEnvironmentService environmentService: IEnvironmentService,
@IWindowsMainService private windowsService: IWindowsMainService,
@IWindowsMainService private windowsMainService: IWindowsMainService,
@ILogService private logService: ILogService
) {
this.commandIds = new Set<string>();
this.keybindings = this.storageService.getItem<{ [id: string]: string; }>(KeybindingsResolver.lastKnownKeybindingsMapStorageKey) || Object.create(null);
this.keybindings = this.stateService.getItem<{ [id: string]: string; }>(KeybindingsResolver.lastKnownKeybindingsMapStorageKey) || Object.create(null);
this.keybindingsWatcher = new ConfigWatcher<IUserFriendlyKeybinding[]>(environmentService.appKeybindingsPath, { changeBufferDelay: 100, onError: error => this.logService.error(error) });
this.registerListeners();
@@ -102,24 +102,24 @@ export class KeybindingsResolver {
if (keybindingsChanged) {
this.keybindings = resolvedKeybindings;
this.storageService.setItem(KeybindingsResolver.lastKnownKeybindingsMapStorageKey, this.keybindings); // keep to restore instantly after restart
this.stateService.setItem(KeybindingsResolver.lastKnownKeybindingsMapStorageKey, this.keybindings); // keep to restore instantly after restart
this._onKeybindingsChanged.fire();
}
});
// Resolve keybindings when any first window is loaded
const onceOnWindowReady = once(this.windowsService.onWindowReady);
const onceOnWindowReady = once(this.windowsMainService.onWindowReady);
onceOnWindowReady(win => this.resolveKeybindings(win));
// Resolve keybindings again when keybindings.json changes
this.keybindingsWatcher.onDidUpdateConfiguration(() => this.resolveKeybindings());
// Resolve keybindings when window reloads because an installed extension could have an impact
this.windowsService.onWindowReload(() => this.resolveKeybindings());
this.windowsMainService.onWindowReload(() => this.resolveKeybindings());
}
private resolveKeybindings(win = this.windowsService.getLastActiveWindow()): void {
private resolveKeybindings(win = this.windowsMainService.getLastActiveWindow()): void {
if (this.commandIds.size && win) {
const commandIds: string[] = [];
this.commandIds.forEach(id => commandIds.push(id));

View File

@@ -15,6 +15,7 @@ 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';
import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces';
export const ID = 'launchService';
export const ILaunchService = createDecorator<ILaunchService>(ID);
@@ -24,15 +25,28 @@ export interface IStartArguments {
userEnv: IProcessEnvironment;
}
export interface IWindowInfo {
pid: number;
title: string;
folders: string[];
}
export interface IMainProcessInfo {
mainPID: number;
windows: IWindowInfo[];
}
export interface ILaunchService {
_serviceBrand: any;
start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void>;
getMainProcessId(): TPromise<number>;
getMainProcessInfo(): TPromise<IMainProcessInfo>;
}
export interface ILaunchChannel extends IChannel {
call(command: 'start', arg: IStartArguments): TPromise<void>;
call(command: 'get-main-process-id', arg: null): TPromise<any>;
call(command: 'get-main-process-info', arg: null): TPromise<any>;
call(command: string, arg: any): TPromise<any>;
}
@@ -48,6 +62,9 @@ export class LaunchChannel implements ILaunchChannel {
case 'get-main-process-id':
return this.service.getMainProcessId();
case 'get-main-process-info':
return this.service.getMainProcessInfo();
}
return undefined;
@@ -67,6 +84,10 @@ export class LaunchChannelClient implements ILaunchService {
public getMainProcessId(): TPromise<number> {
return this.channel.call('get-main-process-id', null);
}
public getMainProcessInfo(): TPromise<IMainProcessInfo> {
return this.channel.call('get-main-process-info', null);
}
}
export class LaunchService implements ILaunchService {
@@ -75,18 +96,18 @@ export class LaunchService implements ILaunchService {
constructor(
@ILogService private logService: ILogService,
@IWindowsMainService private windowsService: IWindowsMainService,
@IURLService private urlService: IURLService
@IWindowsMainService private windowsMainService: IWindowsMainService,
@IURLService private urlService: IURLService,
@IWorkspacesMainService private workspacesMainService: IWorkspacesMainService
) { }
public start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void> {
this.logService.log('Received data from other instance: ', args, userEnv);
this.logService.trace('Received data from other instance: ', args, userEnv);
// Check early for open-url which is handled in URL service
if (args['open-url'] && args._urls && args._urls.length > 0) {
// --open-url must contain -- followed by the url(s)
// process.argv is used over args._ as args._ are resolved to file paths at this point
args._urls.forEach(url => this.urlService.open(url));
const openUrl = (args['open-url'] ? args._urls : []) || [];
if (openUrl.length > 0) {
openUrl.forEach(url => this.urlService.open(url));
return TPromise.as(null);
}
@@ -95,13 +116,13 @@ export class LaunchService implements ILaunchService {
const context = !!userEnv['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP;
let usedWindows: ICodeWindow[];
if (!!args.extensionDevelopmentPath) {
this.windowsService.openExtensionDevelopmentHostWindow({ context, cli: args, userEnv });
this.windowsMainService.openExtensionDevelopmentHostWindow({ context, cli: args, userEnv });
} else if (args._.length === 0 && (args['new-window'] || args['unity-launch'])) {
usedWindows = this.windowsService.open({ context, cli: args, userEnv, forceNewWindow: true, forceEmpty: true });
usedWindows = this.windowsMainService.open({ context, cli: args, userEnv, forceNewWindow: true, forceEmpty: true });
} else if (args._.length === 0) {
usedWindows = [this.windowsService.focusLastActive(args, context)];
usedWindows = [this.windowsMainService.focusLastActive(args, context)];
} else {
usedWindows = this.windowsService.open({
usedWindows = this.windowsMainService.open({
context,
cli: args,
userEnv,
@@ -118,7 +139,7 @@ export class LaunchService implements ILaunchService {
// In addition, we poll for the wait marker file to be deleted to return.
if (args.wait && usedWindows.length === 1 && usedWindows[0]) {
return TPromise.any([
this.windowsService.waitForWindowCloseOrLoad(usedWindows[0].id),
this.windowsMainService.waitForWindowCloseOrLoad(usedWindows[0].id),
whenDeleted(args.waitMarkerFilePath)
]).then(() => void 0, () => void 0);
}
@@ -127,8 +148,40 @@ export class LaunchService implements ILaunchService {
}
public getMainProcessId(): TPromise<number> {
this.logService.log('Received request for process ID from other instance.');
this.logService.trace('Received request for process ID from other instance.');
return TPromise.as(process.pid);
}
public getMainProcessInfo(): TPromise<IMainProcessInfo> {
this.logService.trace('Received request for main process info from other instance.');
return TPromise.wrap({
mainPID: process.pid,
windows: this.windowsMainService.getWindows().map(window => {
return this.getWindowInfo(window);
})
} as IMainProcessInfo);
}
private getWindowInfo(window: ICodeWindow): IWindowInfo {
const folders: string[] = [];
if (window.openedFolderPath) {
folders.push(window.openedFolderPath);
} else if (window.openedWorkspace) {
const rootFolders = this.workspacesMainService.resolveWorkspaceSync(window.openedWorkspace.configPath).folders;
rootFolders.forEach(root => {
if (root.uri.scheme === 'file') {
folders.push(root.uri.fsPath);
}
});
}
return {
pid: window.win.webContents.getOSProcessId(),
title: window.win.getTitle(),
folders
} as IWindowInfo;
}
}

View File

@@ -5,22 +5,25 @@
'use strict';
import { app } from 'electron';
import { app, dialog } from 'electron';
import { assign } from 'vs/base/common/objects';
import * as platform from 'vs/base/common/platform';
import product from 'vs/platform/node/product';
import * as path from 'path';
import { parseMainProcessArgv } from 'vs/platform/environment/node/argv';
import { mkdirp } from 'vs/base/node/pfs';
import { mkdirp, readdir, rimraf } from 'vs/base/node/pfs';
import { validatePaths } from 'vs/code/node/paths';
import { LifecycleService, ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
import { Server, serve, connect } from 'vs/base/parts/ipc/node/ipc.net';
import { TPromise } from 'vs/base/common/winjs.base';
import { ILaunchChannel, LaunchChannelClient } from './launch';
import { ILaunchChannel, LaunchChannelClient } from 'vs/code/electron-main/launch';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ILogService, LogMainService } from 'vs/platform/log/common/log';
import { IStorageService, StorageService } from 'vs/platform/storage/node/storage';
import { ILogService, ConsoleLogMainService, MultiplexLogService } from 'vs/platform/log/common/log';
import { StateService } from 'vs/platform/state/node/stateService';
import { IStateService } from 'vs/platform/state/common/state';
import { IBackupMainService } from 'vs/platform/backup/common/backup';
import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService';
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
@@ -37,16 +40,30 @@ import { HistoryMainService } from 'vs/platform/history/electron-main/historyMai
import { IHistoryMainService } from 'vs/platform/history/common/history';
import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces';
import { localize } from 'vs/nls';
import { mnemonicButtonLabel } from 'vs/base/common/labels';
import { createLogService } from 'vs/platform/log/node/spdlogService';
import { printDiagnostics } from 'vs/code/electron-main/diagnostics';
function createServices(args: ParsedArgs): IInstantiationService {
const services = new ServiceCollection();
services.set(IEnvironmentService, new SyncDescriptor(EnvironmentService, args, process.execPath));
services.set(ILogService, new SyncDescriptor(LogMainService));
const environmentService = new EnvironmentService(args, process.execPath);
const spdlogService = createLogService('main', environmentService);
const consoleLogService = new ConsoleLogMainService(environmentService);
const logService = new MultiplexLogService([consoleLogService, spdlogService]);
process.once('exit', () => logService.dispose());
// Eventually cleanup
setTimeout(() => cleanupOlderLogs(environmentService).then(null, err => console.error(err)), 10000);
services.set(IEnvironmentService, environmentService);
services.set(ILogService, logService);
services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService));
services.set(IHistoryMainService, new SyncDescriptor(HistoryMainService));
services.set(ILifecycleService, new SyncDescriptor(LifecycleService));
services.set(IStorageService, new SyncDescriptor(StorageService));
services.set(IStateService, new SyncDescriptor(StateService));
services.set(IConfigurationService, new SyncDescriptor(ConfigurationService));
services.set(IRequestService, new SyncDescriptor(RequestService));
services.set(IURLService, new SyncDescriptor(URLService, args['open-url'] ? args._urls : []));
@@ -55,12 +72,28 @@ function createServices(args: ParsedArgs): IInstantiationService {
return new InstantiationService(services, true);
}
/**
* Cleans up older logs, while keeping the 10 most recent ones.
*/
async function cleanupOlderLogs(environmentService: EnvironmentService): TPromise<void> {
const currentLog = path.basename(environmentService.logsPath);
const logsRoot = path.dirname(environmentService.logsPath);
const children = await readdir(logsRoot);
const allSessions = children.filter(name => /^\d{8}T\d{6}$/.test(name));
const oldSessions = allSessions.sort().filter((d, i) => d !== currentLog);
const toDelete = oldSessions.slice(0, Math.max(0, oldSessions.length - 9));
await TPromise.join(toDelete.map(name => rimraf(path.join(logsRoot, name))));
}
function createPaths(environmentService: IEnvironmentService): TPromise<any> {
const paths = [
environmentService.appSettingsHome,
environmentService.extensionsPath,
environmentService.nodeCachedDataDir
environmentService.nodeCachedDataDir,
environmentService.logsPath
];
return TPromise.join(paths.map(p => p && mkdirp(p))) as TPromise<any>;
}
@@ -73,11 +106,11 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
const environmentService = accessor.get(IEnvironmentService);
function allowSetForegroundWindow(service: LaunchChannelClient): TPromise<void> {
let promise = TPromise.as<void>(void 0);
let promise = TPromise.wrap<void>(void 0);
if (platform.isWindows) {
promise = service.getMainProcessId()
.then(processId => {
logService.log('Sending some foreground love to the running instance:', processId);
logService.trace('Sending some foreground love to the running instance:', processId);
try {
const { allowSetForegroundWindow } = <any>require.__$__nodeRequire('windows-foreground-love');
@@ -93,10 +126,22 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
function setup(retry: boolean): TPromise<Server> {
return serve(environmentService.mainIPCHandle).then(server => {
if (platform.isMacintosh) {
app.dock.show(); // dock might be hidden at this case due to a retry
// Print --status usage info
if (environmentService.args.status) {
logService.warn('Warning: The --status argument can only be used if Code is already running. Please run it again after Code has started.');
throw new ExpectedError('Terminating...');
}
// dock might be hidden at this case due to a retry
if (platform.isMacintosh) {
app.dock.show();
}
// Set the VSCODE_PID variable here when we are sure we are the first
// instance to startup. Otherwise we would wrongly overwrite the PID
process.env['VSCODE_PID'] = String(process.pid);
return server;
}, err => {
if (err.code !== 'EADDRINUSE') {
@@ -121,18 +166,53 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
return TPromise.wrapError<Server>(new Error(msg));
}
logService.log('Sending env to running instance...');
// Show a warning dialog after some timeout if it takes long to talk to the other instance
// Skip this if we are running with --wait where it is expected that we wait for a while.
// Also skip when gathering diagnostics (--status) which can take a longer time.
let startupWarningDialogHandle: number;
if (!environmentService.wait && !environmentService.status) {
startupWarningDialogHandle = setTimeout(() => {
showStartupWarningDialog(
localize('secondInstanceNoResponse', "Another instance of {0} is running but not responding", product.nameShort),
localize('secondInstanceNoResponseDetail', "Please close all other instances and try again.")
);
}, 10000);
}
const channel = client.getChannel<ILaunchChannel>('launch');
const service = new LaunchChannelClient(channel);
// Process Info
if (environmentService.args.status) {
return service.getMainProcessInfo().then(info => {
return printDiagnostics(info).then(() => TPromise.wrapError(new ExpectedError()));
});
}
logService.trace('Sending env to running instance...');
return allowSetForegroundWindow(service)
.then(() => service.start(environmentService.args, process.env))
.then(() => client.dispose())
.then(() => TPromise.wrapError(new ExpectedError('Sent env to running instance. Terminating...')));
.then(() => {
// Now that we started, make sure the warning dialog is prevented
if (startupWarningDialogHandle) {
clearTimeout(startupWarningDialogHandle);
}
return TPromise.wrapError(new ExpectedError('Sent env to running instance. Terminating...'));
});
},
err => {
if (!retry || platform.isWindows || err.code !== 'ECONNREFUSED') {
if (err.code === 'EPERM') {
showStartupWarningDialog(
localize('secondInstanceAdmin', "A second instance of {0} is already running as administrator.", product.nameShort),
localize('secondInstanceAdminDetail', "Please close the other instance and try again.")
);
}
return TPromise.wrapError<Server>(err);
}
@@ -142,7 +222,7 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
try {
fs.unlinkSync(environmentService.mainIPCHandle);
} catch (e) {
logService.log('Fatal error deleting obsolete instance handle', e);
logService.warn('Could not delete obsolete instance handle', e);
return TPromise.wrapError<Server>(e);
}
@@ -155,6 +235,17 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
return setup(true);
}
function showStartupWarningDialog(message: string, detail: string): void {
dialog.showMessageBox(null, {
title: product.nameLong,
type: 'warning',
buttons: [mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
message,
detail,
noLink: true
});
}
function quit(accessor: ServicesAccessor, reason?: ExpectedError | Error): void {
const logService = accessor.get(ILogService);
const lifecycleService = accessor.get(ILifecycleService);
@@ -163,14 +254,16 @@ function quit(accessor: ServicesAccessor, reason?: ExpectedError | Error): void
if (reason) {
if ((reason as ExpectedError).isExpected) {
logService.log(reason.message);
if (reason.message) {
logService.trace(reason.message);
}
} else {
exitCode = 1; // signal error to the outside
if (reason.stack) {
console.error(reason.stack);
logService.error(reason.stack);
} else {
console.error(`Startup error: ${reason.toString()}`);
logService.error(`Startup error: ${reason.toString()}`);
}
}
}
@@ -198,19 +291,16 @@ function main() {
// Patch `process.env` with the instance's environment
const environmentService = accessor.get(IEnvironmentService);
const instanceEnv: typeof process.env = {
VSCODE_PID: String(process.pid),
VSCODE_IPC_HOOK: environmentService.mainIPCHandle,
VSCODE_NLS_CONFIG: process.env['VSCODE_NLS_CONFIG']
VSCODE_NLS_CONFIG: process.env['VSCODE_NLS_CONFIG'],
VSCODE_LOGS: process.env['VSCODE_LOGS']
};
assign(process.env, instanceEnv);
// Startup
return instantiationService.invokeFunction(a => createPaths(a.get(IEnvironmentService)))
.then(() => instantiationService.invokeFunction(setupIPC))
.then(mainIpcServer => {
const app = instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnv);
app.startup();
});
.then(mainIpcServer => instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnv).startup());
}).done(null, err => instantiationService.invokeFunction(quit, err));
}

View File

@@ -9,7 +9,7 @@ import * as nls from 'vs/nls';
import { isMacintosh, isLinux, isWindows, language } from 'vs/base/common/platform';
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 { ipcMain as ipc, app, shell, dialog, Menu, MenuItem, BrowserWindow, clipboard } from 'electron';
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';
@@ -18,11 +18,11 @@ import { IUpdateService, State as UpdateState } from 'vs/platform/update/common/
import product from 'vs/platform/node/product';
import { RunOnceScheduler } from 'vs/base/common/async';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { mnemonicMenuLabel as baseMnemonicLabel, unmnemonicLabel, getPathLabel } from 'vs/base/common/labels';
import { mnemonicMenuLabel as baseMnemonicLabel, unmnemonicLabel, getPathLabel, mnemonicButtonLabel } from 'vs/base/common/labels';
import { KeybindingsResolver } from 'vs/code/electron-main/keyboard';
import { IWindowsMainService, IWindowsCountChangedEvent } from 'vs/platform/windows/electron-main/windows';
import { IHistoryMainService } from 'vs/platform/history/common/history';
import { IWorkspaceIdentifier, IWorkspacesMainService, getWorkspaceLabel, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspaceIdentifier, getWorkspaceLabel, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
interface IExtensionViewlet {
id: string;
@@ -38,7 +38,7 @@ const telemetryFrom = 'menu';
export class CodeMenu {
private static MAX_MENU_RECENT_ENTRIES = 10;
private static readonly MAX_MENU_RECENT_ENTRIES = 10;
private keys = [
'files.autoSave',
@@ -68,11 +68,10 @@ export class CodeMenu {
@IUpdateService private updateService: IUpdateService,
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService private configurationService: IConfigurationService,
@IWindowsMainService private windowsService: IWindowsMainService,
@IWindowsMainService private windowsMainService: IWindowsMainService,
@IEnvironmentService private environmentService: IEnvironmentService,
@ITelemetryService private telemetryService: ITelemetryService,
@IHistoryMainService private historyService: IHistoryMainService,
@IWorkspacesMainService private workspacesService: IWorkspacesMainService
@IHistoryMainService private historyMainService: IHistoryMainService
) {
this.extensionViewlets = [];
this.nativeTabMenuItems = [];
@@ -93,14 +92,14 @@ export class CodeMenu {
});
// Listen to some events from window service to update menu
this.historyService.onRecentlyOpenedChange(() => this.updateMenu());
this.windowsService.onWindowsCountChanged(e => this.onWindowsCountChanged(e));
this.windowsService.onActiveWindowChanged(() => this.updateWorkspaceMenuItems());
this.windowsService.onWindowReady(() => this.updateWorkspaceMenuItems());
this.windowsService.onWindowClose(() => this.updateWorkspaceMenuItems());
this.historyMainService.onRecentlyOpenedChange(() => this.updateMenu());
this.windowsMainService.onWindowsCountChanged(e => this.onWindowsCountChanged(e));
this.windowsMainService.onActiveWindowChanged(() => this.updateWorkspaceMenuItems());
this.windowsMainService.onWindowReady(() => this.updateWorkspaceMenuItems());
this.windowsMainService.onWindowClose(() => this.updateWorkspaceMenuItems());
// Listen to extension viewlets
ipc.on('vscode:extensionViewlets', (event, rawExtensionViewlets) => {
ipc.on('vscode:extensionViewlets', (_event: any, rawExtensionViewlets: string) => {
let extensionViewlets: IExtensionViewlet[] = [];
try {
extensionViewlets = JSON.parse(rawExtensionViewlets);
@@ -215,7 +214,7 @@ export class CodeMenu {
}
private updateWorkspaceMenuItems(): void {
const window = this.windowsService.getLastActiveWindow();
const window = this.windowsMainService.getLastActiveWindow();
const isInWorkspaceContext = window && !!window.openedWorkspace;
const isInFolderContext = window && !!window.openedFolderPath;
@@ -258,10 +257,11 @@ export class CodeMenu {
const viewMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mView', comment: ['&& denotes a mnemonic'] }, "&&View")), submenu: viewMenu });
this.setViewMenu(viewMenu);
// {{SQL CARBON EDIT}}
// Goto
const gotoMenu = new Menu();
const gotoMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go")), submenu: gotoMenu });
this.setGotoMenu(gotoMenu);
// const gotoMenu = new Menu();
// const gotoMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go")), submenu: gotoMenu });
// this.setGotoMenu(gotoMenu);
// {{SQL CARBON EDIT}}
// Debug
@@ -298,8 +298,8 @@ export class CodeMenu {
// {{SQL CARBON EDIT}}
//menubar.append(selectionMenuItem);
menubar.append(viewMenuItem);
menubar.append(gotoMenuItem);
// {{SQL CARBON EDIT}}
//menubar.append(gotoMenuItem);
// menubar.append(debugMenuItem);
// menubar.append(taskMenuItem);
@@ -316,7 +316,7 @@ export class CodeMenu {
this.appMenuInstalled = true;
const dockMenu = new Menu();
dockMenu.append(new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window")), click: () => this.windowsService.openNewWindow(OpenContext.DOCK) }));
dockMenu.append(new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window")), click: () => this.windowsMainService.openNewWindow(OpenContext.DOCK) }));
app.dock.setMenu(dockMenu);
}
@@ -331,7 +331,13 @@ export class CodeMenu {
const hide = new MenuItem({ label: nls.localize('mHide', "Hide {0}", product.nameLong), role: 'hide', accelerator: 'Command+H' });
const hideOthers = new MenuItem({ label: nls.localize('mHideOthers', "Hide Others"), role: 'hideothers', accelerator: 'Command+Alt+H' });
const showAll = new MenuItem({ label: nls.localize('mShowAll', "Show All"), role: 'unhide' });
const quit = new MenuItem(this.likeAction('workbench.action.quit', { label: nls.localize('miQuit', "Quit {0}", product.nameLong), click: () => this.windowsService.quit() }));
const quit = new MenuItem(this.likeAction('workbench.action.quit', {
label: nls.localize('miQuit', "Quit {0}", product.nameLong), click: () => {
if (this.windowsMainService.getWindowCount() === 0 || !!this.windowsMainService.getFocusedWindow()) {
this.windowsMainService.quit(); // fix for https://github.com/Microsoft/vscode/issues/39191
}
}
}));
const actions = [about];
actions.push(...checkForUpdates);
@@ -352,40 +358,40 @@ export class CodeMenu {
}
private setFileMenu(fileMenu: Electron.Menu): void {
const hasNoWindows = (this.windowsService.getWindowCount() === 0);
const hasNoWindows = (this.windowsMainService.getWindowCount() === 0);
// {{SQL CARBON EDIT}}
let newFile: Electron.MenuItem;
if (hasNoWindows) {
newFile = new MenuItem(this.likeAction('workbench.action.files.newUntitledFile', { label: this.mnemonicLabel(nls.localize({ key: 'miNewFile', comment: ['&& denotes a mnemonic'] }, "&&New Query")), click: () => this.windowsService.openNewWindow(OpenContext.MENU) }));
newFile = new MenuItem(this.likeAction('workbench.action.files.newUntitledFile', { label: this.mnemonicLabel(nls.localize({ key: 'miNewFile', comment: ['&& denotes a mnemonic'] }, "&&New Query")), click: () => this.windowsMainService.openNewWindow(OpenContext.MENU) }));
} else {
newFile = this.createMenuItem(nls.localize({ key: 'miNewFile', comment: ['&& denotes a mnemonic'] }, "&&New Query"), 'workbench.action.files.newUntitledFile');
}
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 } }) }));
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.windowsMainService.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 } }) }));
openWorkspace = new MenuItem(this.likeAction('workbench.action.openWorkspace', { label: this.mnemonicLabel(nls.localize({ key: 'miOpenWorkspace', comment: ['&& denotes a mnemonic'] }, "Open Wor&&kspace...")), click: (menuItem, win, event) => this.windowsMainService.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']);
openWorkspace = this.createMenuItem(nls.localize({ key: 'miOpenWorkspace', comment: ['&& denotes a mnemonic'] }, "Open Wor&&kspace..."), ['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 } }) }));
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.windowsMainService.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) {
openFile = new MenuItem(this.likeAction('workbench.action.files.openFile', { label: this.mnemonicLabel(nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File...")), click: (menuItem, win, event) => this.windowsService.pickFileAndOpen({ forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }) }));
openFile = new MenuItem(this.likeAction('workbench.action.files.openFile', { label: this.mnemonicLabel(nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File...")), click: (menuItem, win, event) => this.windowsMainService.pickFileAndOpen({ forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }) }));
} else {
openFile = this.createMenuItem(nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File..."), ['workbench.action.files.openFile', 'workbench.action.files.openFileInNewWindow']);
}
@@ -394,29 +400,28 @@ 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 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 saveWorkspaceAs = this.createMenuItem(nls.localize('miSaveWorkspaceAs', "Save Workspace As..."), 'workbench.action.saveWorkspaceAs');
const addFolder = this.createMenuItem(nls.localize({ key: 'miAddFolderToWorkspace', comment: ['&& denotes a mnemonic'] }, "A&&dd Folder to Workspace..."), 'workbench.action.addRootFolder');
const saveFile = this.createMenuItem(nls.localize({ key: 'miSave', comment: ['&& denotes a mnemonic'] }, "&&Save"), 'workbench.action.files.save');
const saveFileAs = this.createMenuItem(nls.localize({ key: 'miSaveAs', comment: ['&& denotes a mnemonic'] }, "Save &&As..."), 'workbench.action.files.saveAs');
const saveAllFiles = this.createMenuItem(nls.localize({ key: 'miSaveAll', comment: ['&& denotes a mnemonic'] }, "Save A&&ll"), 'workbench.action.files.saveAll');
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 autoSave = new MenuItem(this.likeAction('vscode.toggleAutoSave', { label: this.mnemonicLabel(nls.localize('miAutoSave', "Auto Save")), type: 'checkbox', checked: autoSaveEnabled, enabled: this.windowsMainService.getWindowCount() > 0, click: () => this.windowsMainService.sendToFocused('vscode.toggleAutoSave') }, false));
const preferences = this.getPreferencesMenu();
const newWindow = new MenuItem(this.likeAction('workbench.action.newWindow', { label: this.mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window")), click: () => this.windowsService.openNewWindow(OpenContext.MENU) }));
const newWindow = new MenuItem(this.likeAction('workbench.action.newWindow', { label: this.mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window")), click: () => this.windowsMainService.openNewWindow(OpenContext.MENU) }));
const revertFile = this.createMenuItem(nls.localize({ key: 'miRevert', comment: ['&& denotes a mnemonic'] }, "Re&&vert File"), 'workbench.action.files.revert');
const closeWindow = new MenuItem(this.likeAction('workbench.action.closeWindow', { label: this.mnemonicLabel(nls.localize({ key: 'miCloseWindow', comment: ['&& denotes a mnemonic'] }, "Clos&&e Window")), click: () => this.windowsService.getLastActiveWindow().win.close(), enabled: this.windowsService.getWindowCount() > 0 }));
const closeWindow = new MenuItem(this.likeAction('workbench.action.closeWindow', { label: this.mnemonicLabel(nls.localize({ key: 'miCloseWindow', comment: ['&& denotes a mnemonic'] }, "Clos&&e Window")), click: () => this.windowsMainService.getLastActiveWindow().win.close(), enabled: this.windowsMainService.getWindowCount() > 0 }));
this.closeWorkspace = this.createMenuItem(nls.localize({ key: 'miCloseWorkspace', comment: ['&& denotes a mnemonic'] }, "Close &&Workspace"), 'workbench.action.closeFolder');
this.closeFolder = this.createMenuItem(nls.localize({ key: 'miCloseFolder', comment: ['&& denotes a mnemonic'] }, "Close &&Folder"), 'workbench.action.closeFolder');
const closeEditor = this.createMenuItem(nls.localize({ key: 'miCloseEditor', comment: ['&& denotes a mnemonic'] }, "&&Close Editor"), 'workbench.action.closeActiveEditor');
const exit = new MenuItem(this.likeAction('workbench.action.quit', { label: this.mnemonicLabel(nls.localize({ key: 'miExit', comment: ['&& denotes a mnemonic'] }, "E&&xit")), click: () => this.windowsService.quit() }));
const exit = new MenuItem(this.likeAction('workbench.action.quit', { label: this.mnemonicLabel(nls.localize({ key: 'miExit', comment: ['&& denotes a mnemonic'] }, "E&&xit")), click: () => this.windowsMainService.quit() }));
this.updateWorkspaceMenuItems();
@@ -439,8 +444,6 @@ export class CodeMenu {
__separator__(),
autoSave,
__separator__(),
installVsixExtension,
__separator__(),
!isMacintosh ? preferences : null,
!isMacintosh ? __separator__() : null,
revertFile,
@@ -480,7 +483,7 @@ export class CodeMenu {
private setOpenRecentMenu(openRecentMenu: Electron.Menu): void {
openRecentMenu.append(this.createMenuItem(nls.localize({ key: 'miReopenClosedEditor', comment: ['&& denotes a mnemonic'] }, "&&Reopen Closed Editor"), 'workbench.action.reopenClosedEditor'));
const { workspaces, files } = this.historyService.getRecentlyOpened();
const { workspaces, files } = this.historyMainService.getRecentlyOpened();
// Workspaces
if (workspaces.length > 0) {
@@ -504,7 +507,7 @@ export class CodeMenu {
openRecentMenu.append(__separator__());
openRecentMenu.append(this.createMenuItem(nls.localize({ key: 'miMore', comment: ['&& denotes a mnemonic'] }, "&&More..."), 'workbench.action.openRecent'));
openRecentMenu.append(__separator__());
openRecentMenu.append(new MenuItem(this.likeAction('workbench.action.clearRecentFiles', { label: this.mnemonicLabel(nls.localize({ key: 'miClearRecentOpen', comment: ['&& denotes a mnemonic'] }, "&&Clear Recently Opened")), click: () => this.historyService.clearRecentlyOpened() })));
openRecentMenu.append(new MenuItem(this.likeAction('workbench.action.clearRecentFiles', { label: this.mnemonicLabel(nls.localize({ key: 'miClearRecentOpen', comment: ['&& denotes a mnemonic'] }, "&&Clear Recently Opened")), click: () => this.historyMainService.clearRecentlyOpened() })));
}
}
@@ -523,7 +526,7 @@ export class CodeMenu {
label,
click: (menuItem, win, event) => {
const openInNewWindow = this.isOptionClick(event);
const success = this.windowsService.open({
const success = this.windowsMainService.open({
context: OpenContext.MENU,
cli: this.environmentService.args,
pathsToOpen: [path], forceNewWindow: openInNewWindow,
@@ -531,7 +534,7 @@ export class CodeMenu {
}).length > 0;
if (!success) {
this.historyService.removeFromRecentlyOpened([isSingleFolderWorkspaceIdentifier(workspace) ? workspace : workspace.configPath]);
this.historyMainService.removeFromRecentlyOpened([isSingleFolderWorkspaceIdentifier(workspace) ? workspace : workspace.configPath]);
}
}
}, false));
@@ -616,8 +619,7 @@ export class CodeMenu {
private setSelectionMenu(winLinuxEditMenu: Electron.Menu): void {
let multiCursorModifierLabel: string;
if (this.currentMultiCursorModifierSetting === 'ctrlCmd') {
// The default has been overwritten
multiCursorModifierLabel = nls.localize('miMultiCursorAlt', "Switch to Alt+Click for Multi-Cursor");
multiCursorModifierLabel = nls.localize('miMultiCursorAlt', "Switch to Alt+Click for Multi-Cursor"); // The default has been overwritten
} else {
multiCursorModifierLabel = (
isMacintosh
@@ -670,6 +672,7 @@ export class CodeMenu {
selectHighlights,
].forEach(item => winLinuxEditMenu.append(item));
}
// {{SQL CARBON EDIT}}
*/
private setViewMenu(viewMenu: Electron.Menu): void {
@@ -701,7 +704,7 @@ export class CodeMenu {
const commands = this.createMenuItem(nls.localize({ key: 'miCommandPalette', comment: ['&& denotes a mnemonic'] }, "&&Command Palette..."), 'workbench.action.showCommands');
const fullscreen = new MenuItem(this.withKeybinding('workbench.action.toggleFullScreen', { label: this.mnemonicLabel(nls.localize({ key: 'miToggleFullScreen', comment: ['&& denotes a mnemonic'] }, "Toggle &&Full Screen")), click: () => this.windowsService.getLastActiveWindow().toggleFullScreen(), enabled: this.windowsService.getWindowCount() > 0 }));
const fullscreen = new MenuItem(this.withKeybinding('workbench.action.toggleFullScreen', { label: this.mnemonicLabel(nls.localize({ key: 'miToggleFullScreen', comment: ['&& denotes a mnemonic'] }, "Toggle &&Full Screen")), click: () => this.windowsMainService.getLastActiveWindow().toggleFullScreen(), enabled: this.windowsMainService.getWindowCount() > 0 }));
const toggleZenMode = this.createMenuItem(nls.localize('miToggleZenMode', "Toggle Zen Mode"), 'workbench.action.toggleZenMode');
const toggleMenuBar = this.createMenuItem(nls.localize({ key: 'miToggleMenuBar', comment: ['&& denotes a mnemonic'] }, "Toggle Menu &&Bar"), 'workbench.action.toggleMenuBar');
// {{SQL CARBON EDIT}}
@@ -908,15 +911,15 @@ export class CodeMenu {
}
private setMacWindowMenu(macWindowMenu: Electron.Menu): void {
const minimize = new MenuItem({ label: nls.localize('mMinimize', "Minimize"), role: 'minimize', accelerator: 'Command+M', enabled: this.windowsService.getWindowCount() > 0 });
const zoom = new MenuItem({ label: nls.localize('mZoom', "Zoom"), role: 'zoom', enabled: this.windowsService.getWindowCount() > 0 });
const bringAllToFront = new MenuItem({ label: nls.localize('mBringToFront', "Bring All to Front"), role: 'front', enabled: this.windowsService.getWindowCount() > 0 });
const minimize = new MenuItem({ label: nls.localize('mMinimize', "Minimize"), role: 'minimize', accelerator: 'Command+M', enabled: this.windowsMainService.getWindowCount() > 0 });
const zoom = new MenuItem({ label: nls.localize('mZoom', "Zoom"), role: 'zoom', enabled: this.windowsMainService.getWindowCount() > 0 });
const bringAllToFront = new MenuItem({ label: nls.localize('mBringToFront', "Bring All to Front"), role: 'front', enabled: this.windowsMainService.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;
const hasMultipleWindows = this.windowsMainService.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));
@@ -939,7 +942,7 @@ export class CodeMenu {
}
private toggleDevTools(): void {
const w = this.windowsService.getFocusedWindow();
const w = this.windowsMainService.getFocusedWindow();
if (w && w.win) {
const contents = w.win.webContents;
if (w.hasHiddenTitleBarStyle() && !w.win.isFullScreen() && !contents.isDevToolsOpened()) {
@@ -954,7 +957,7 @@ export class CodeMenu {
const toggleDevToolsItem = new MenuItem(this.likeAction('workbench.action.toggleDevTools', {
label: this.mnemonicLabel(nls.localize({ key: 'miToggleDevTools', comment: ['&& denotes a mnemonic'] }, "&&Toggle Developer Tools")),
click: () => this.toggleDevTools(),
enabled: (this.windowsService.getWindowCount() > 0)
enabled: (this.windowsMainService.getWindowCount() > 0)
}));
const showAccessibilityOptions = new MenuItem(this.likeAction('accessibilityOptions', {
@@ -967,9 +970,9 @@ export class CodeMenu {
let reportIssuesItem: Electron.MenuItem = null;
if (product.reportIssueUrl) {
const label = nls.localize({ key: 'miReportIssues', comment: ['&& denotes a mnemonic'] }, "Report &&Issues");
const label = nls.localize({ key: 'miReportIssue', comment: ['&& denotes a mnemonic', 'Translate this to "Report Issue in English" in all languages please!'] }, "Report &&Issue");
if (this.windowsService.getWindowCount() > 0) {
if (this.windowsMainService.getWindowCount() > 0) {
reportIssuesItem = this.createMenuItem(label, 'workbench.action.reportIssues');
} else {
reportIssuesItem = new MenuItem({ label: this.mnemonicLabel(label), click: () => this.openUrl(product.reportIssueUrl, 'openReportIssues') });
@@ -1040,27 +1043,22 @@ export class CodeMenu {
const showTasks = this.createMenuItem(nls.localize({ key: 'miRunningTask', comment: ['&& denotes a mnemonic'] }, "Show Runnin&&g Tasks..."), 'workbench.action.tasks.showTasks');
const restartTask = this.createMenuItem(nls.localize({ key: 'miRestartTask', comment: ['&& denotes a mnemonic'] }, "R&&estart Running Task..."), 'workbench.action.tasks.restartTask');
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 configureTestTask = this.createMenuItem(nls.localize({ key: 'miConfigureTestTask', comment: ['&& denotes a mnemonic'] }, "Configure Defau&&lt Test Task"), 'workbench.action.tasks.configureDefaultTestTask');
[
//__separator__(),
runTask,
buildTask,
// testTask,
__separator__(),
terminateTask,
restartTask,
showTasks,
__separator__(),
//showTaskLog,
configureTask,
configureBuildTask
// configureTestTask
].forEach(item => taskMenu.append(item));
// {{SQL CARBON EDIT}}
*/
}
@@ -1135,7 +1133,7 @@ export class CodeMenu {
this.runActionInRenderer(commandId);
};
const enabled = typeof arg3 === 'boolean' ? arg3 : this.windowsService.getWindowCount() > 0;
const enabled = typeof arg3 === 'boolean' ? arg3 : this.windowsMainService.getWindowCount() > 0;
const checked = typeof arg4 === 'boolean' ? arg4 : false;
let commandId: string;
@@ -1160,11 +1158,11 @@ export class CodeMenu {
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,
enabled: this.windowsMainService.getWindowCount() > 0,
click: () => {
// No Active Window
const activeWindow = this.windowsService.getFocusedWindow();
const activeWindow = this.windowsMainService.getFocusedWindow();
if (!activeWindow) {
return clickHandler.inNoWindow();
}
@@ -1181,7 +1179,13 @@ export class CodeMenu {
}
private runActionInRenderer(id: string): void {
this.windowsService.sendToFocused('vscode:runAction', { id, from: 'menu' } as IRunActionInWindowRequest);
// We make sure to not run actions when the window has no focus, this helps
// for https://github.com/Microsoft/vscode/issues/25907 and specifically for
// https://github.com/Microsoft/vscode/issues/11928
const activeWindow = this.windowsMainService.getFocusedWindow();
if (activeWindow) {
this.windowsMainService.sendToFocused('vscode:runAction', { id, from: 'menu' } as IRunActionInWindowRequest);
}
}
private withKeybinding(commandId: string, options: Electron.MenuItemConstructorOptions): Electron.MenuItemConstructorOptions {
@@ -1232,25 +1236,36 @@ export class CodeMenu {
}
private openAboutDialog(): void {
const lastActiveWindow = this.windowsService.getFocusedWindow() || this.windowsService.getLastActiveWindow();
const lastActiveWindow = this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow();
dialog.showMessageBox(lastActiveWindow && lastActiveWindow.win, {
const detail = nls.localize('aboutDetail',
"Version {0}\nCommit {1}\nDate {2}\nShell {3}\nRenderer {4}\nNode {5}\nArchitecture {6}",
app.getVersion(),
product.commit || 'Unknown',
product.date || 'Unknown',
process.versions['electron'],
process.versions['chrome'],
process.versions['node'],
process.arch
);
const buttons = [nls.localize('okButton', "OK")];
if (isWindows) {
buttons.push(mnemonicButtonLabel(nls.localize({ key: 'copy', comment: ['&& denotes a mnemonic'] }, "&&Copy"))); // https://github.com/Microsoft/vscode/issues/37608
}
const result = dialog.showMessageBox(lastActiveWindow && lastActiveWindow.win, {
title: product.nameLong,
type: 'info',
message: product.nameLong,
detail: nls.localize('aboutDetail',
"\nVersion {0}\nCommit {1}\nDate {2}\nShell {3}\nRenderer {4}\nNode {5}\nArchitecture {6}",
app.getVersion(),
product.commit || 'Unknown',
product.date || 'Unknown',
process.versions['electron'],
process.versions['chrome'],
process.versions['node'],
process.arch
),
buttons: [nls.localize('okButton', "OK")],
detail: `\n${detail}`,
buttons,
noLink: true
}, result => null);
});
if (isWindows && result === 1) {
clipboard.writeText(detail);
}
this.reportMenuActionTelemetry('showAboutDialog');
}
@@ -1262,7 +1277,7 @@ export class CodeMenu {
private reportMenuActionTelemetry(id: string): void {
/* __GDPR__
"workbencActionExecuted" : {
"workbenchActionExecuted" : {
"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"from": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}

View File

@@ -10,16 +10,22 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
import { TPromise } from 'vs/base/common/winjs.base';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { BrowserWindow, ipcMain } from 'electron';
import { PromiseSource } from 'vs/base/common/async';
import { ISharedProcess } from 'vs/platform/windows/electron-main/windows';
import { Barrier } from 'vs/base/common/async';
export class SharedProcess implements ISharedProcess {
private spawnPromiseSource: PromiseSource<void>;
private barrier = new Barrier();
private window: Electron.BrowserWindow;
private disposables: IDisposable[] = [];
constructor(
private environmentService: IEnvironmentService,
private readonly machineId: string,
private readonly userEnv: IProcessEnvironment
) { }
@memoize
private get _whenReady(): TPromise<void> {
this.window = new BrowserWindow({
@@ -32,6 +38,7 @@ export class SharedProcess implements ISharedProcess {
});
const config = assign({
appRoot: this.environmentService.appRoot,
machineId: this.machineId,
nodeCachedDataDir: this.environmentService.nodeCachedDataDir,
userEnv: this.userEnv
});
@@ -65,7 +72,7 @@ export class SharedProcess implements ISharedProcess {
}));
return new TPromise<void>((c, e) => {
ipcMain.once('handshake:hello', ({ sender }) => {
ipcMain.once('handshake:hello', ({ sender }: { sender: any }) => {
sender.send('handshake:hey there', {
sharedIPCHandle: this.environmentService.sharedIPCHandle,
args: this.environmentService.args
@@ -76,22 +83,15 @@ export class SharedProcess implements ISharedProcess {
});
}
constructor(
private environmentService: IEnvironmentService,
private userEnv: IProcessEnvironment
) {
this.spawnPromiseSource = new PromiseSource<void>();
spawn(): void {
this.barrier.open();
}
public spawn(): void {
this.spawnPromiseSource.complete();
whenReady(): TPromise<void> {
return this.barrier.wait().then(() => this._whenReady);
}
public whenReady(): TPromise<void> {
return this.spawnPromiseSource.value.then(() => this._whenReady);
}
public toggle(): void {
toggle(): void {
if (this.window.isVisible()) {
this.hide();
} else {
@@ -99,17 +99,17 @@ export class SharedProcess implements ISharedProcess {
}
}
public show(): void {
show(): void {
this.window.show();
this.window.webContents.openDevTools();
}
public hide(): void {
hide(): void {
this.window.webContents.closeDevTools();
this.window.hide();
}
public dispose(): void {
dispose(): void {
this.disposables = dispose(this.disposables);
}
}

View File

@@ -7,10 +7,9 @@
import * as path from 'path';
import * as objects from 'vs/base/common/objects';
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 { IStateService } from 'vs/platform/state/common/state';
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';
@@ -18,7 +17,6 @@ import { ILogService } from 'vs/platform/log/common/log';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
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, IRunActionInWindowRequest } from 'vs/platform/windows/common/windows';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
@@ -26,6 +24,8 @@ 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';
import { mark, exportEntries } from 'vs/base/common/performance';
import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/node/extensionGalleryService';
export interface IWindowState {
width?: number;
@@ -71,15 +71,15 @@ interface ITouchBarSegment extends Electron.SegmentedControlSegment {
export class CodeWindow implements ICodeWindow {
public static themeStorageKey = 'theme';
public static themeBackgroundStorageKey = 'themeBackground';
public static readonly themeStorageKey = 'theme';
public static readonly themeBackgroundStorageKey = 'themeBackground';
private static DEFAULT_BG_LIGHT = '#FFFFFF';
private static DEFAULT_BG_DARK = '#1E1E1E';
private static DEFAULT_BG_HC_BLACK = '#000000';
private static readonly DEFAULT_BG_LIGHT = '#FFFFFF';
private static readonly DEFAULT_BG_DARK = '#1E1E1E';
private static readonly DEFAULT_BG_HC_BLACK = '#000000';
private static MIN_WIDTH = 200;
private static MIN_HEIGHT = 120;
private static readonly MIN_WIDTH = 200;
private static readonly MIN_HEIGHT = 120;
private hiddenTitleBarStyle: boolean;
private showTimeoutHandle: any;
@@ -97,6 +97,8 @@ export class CodeWindow implements ICodeWindow {
private currentConfig: IWindowConfiguration;
private pendingLoadConfig: IWindowConfiguration;
private marketplaceHeadersPromise: TPromise<object>;
private touchBarGroups: Electron.TouchBarSegmentedControl[];
constructor(
@@ -104,9 +106,9 @@ export class CodeWindow implements ICodeWindow {
@ILogService private logService: ILogService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IConfigurationService private configurationService: IConfigurationService,
@IStorageService private storageService: IStorageService,
@IWorkspacesMainService private workspaceService: IWorkspacesMainService,
@IBackupMainService private backupService: IBackupMainService
@IStateService private stateService: IStateService,
@IWorkspacesMainService private workspacesMainService: IWorkspacesMainService,
@IBackupMainService private backupMainService: IBackupMainService
) {
this.touchBarGroups = [];
this._lastFocusTime = -1;
@@ -123,6 +125,9 @@ export class CodeWindow implements ICodeWindow {
// macOS: touch bar support
this.createTouchBar();
// Request handling
this.handleMarketplaceRequests();
// Eventing
this.registerListeners();
}
@@ -160,7 +165,7 @@ export class CodeWindow implements ICodeWindow {
options.icon = path.join(this.environmentService.appRoot, 'resources/linux/code.png'); // Windows and Mac are better off using the embedded icon(s)
}
const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
let useNativeTabs = false;
if (windowConfig && windowConfig.nativeTabs) {
@@ -206,7 +211,7 @@ export class CodeWindow implements ICodeWindow {
}
}
} catch (err) {
this.logService.log(`Unexpected error fixing window position on windows with multiple windows: ${err}\n${err.stack}`);
this.logService.warn(`Unexpected error fixing window position on windows with multiple windows: ${err}\n${err.stack}`);
}
}
@@ -334,17 +339,21 @@ export class CodeWindow implements ICodeWindow {
return this._readyState;
}
private registerListeners(): void {
const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*'];
const headers = {
'X-Market-Client-Id': `VSCode ${pkg.version}`,
'User-Agent': `VSCode ${pkg.version}`,
'X-Market-User-Id': this.environmentService.machineUUID
};
private handleMarketplaceRequests(): void {
this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) => {
cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) });
// Resolve marketplace headers
this.marketplaceHeadersPromise = resolveMarketplaceHeaders(this.environmentService);
// Inject headers when requests are incoming
const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*'];
this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details: any, cb: any) => {
this.marketplaceHeadersPromise.done(headers => {
cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) });
});
});
}
private registerListeners(): void {
// Prevent loading of svgs
this._win.webContents.session.webRequest.onBeforeRequest(null, (details, callback) => {
@@ -358,7 +367,7 @@ export class CodeWindow implements ICodeWindow {
return callback({});
});
this._win.webContents.session.webRequest.onHeadersReceived(null, (details, callback) => {
this._win.webContents.session.webRequest.onHeadersReceived(null, (details: any, callback: any) => {
const contentType: string[] = (details.responseHeaders['content-type'] || details.responseHeaders['Content-Type']) as any;
if (contentType && Array.isArray(contentType) && contentType.some(x => x.toLowerCase().indexOf('image/svg') >= 0)) {
return callback({ cancel: true });
@@ -433,7 +442,7 @@ export class CodeWindow implements ICodeWindow {
this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated()));
// Handle Workspace events
this.toDispose.push(this.workspaceService.onUntitledWorkspaceDeleted(e => this.onUntitledWorkspaceDeleted(e)));
this.toDispose.push(this.workspacesMainService.onUntitledWorkspaceDeleted(e => this.onUntitledWorkspaceDeleted(e)));
}
private onUntitledWorkspaceDeleted(workspace: IWorkspaceIdentifier): void {
@@ -454,14 +463,14 @@ export class CodeWindow implements ICodeWindow {
// Swipe command support (macOS)
if (isMacintosh) {
const config = this.configurationService.getConfiguration<IWorkbenchEditorConfiguration>();
const config = this.configurationService.getValue<IWorkbenchEditorConfiguration>();
if (config && config.workbench && config.workbench.editor && config.workbench.editor.swipeToNavigate) {
this.registerNavigationListenerOn('swipe', 'left', 'right', true);
} else {
this._win.removeAllListeners('swipe');
}
}
};
}
private registerNavigationListenerOn(command: 'swipe' | 'app-command', back: 'left' | 'browser-backward', forward: 'right' | 'browser-forward', acrossEditors: boolean) {
this._win.on(command as 'swipe' /* | 'app-command' */, (e: Electron.Event, cmd: string) => {
@@ -496,7 +505,7 @@ export class CodeWindow implements ICodeWindow {
// Clear Document Edited if needed
if (isMacintosh && this._win.isDocumentEdited()) {
if (!isReload || !this.backupService.isHotExitEnabled()) {
if (!isReload || !this.backupMainService.isHotExitEnabled()) {
this._win.setDocumentEdited(false);
}
}
@@ -511,6 +520,7 @@ export class CodeWindow implements ICodeWindow {
}
// Load URL
mark('main:loadWindow');
this._win.loadURL(this.getUrl(config));
// Make window visible if it did not open in N seconds because this indicates an error
@@ -524,12 +534,6 @@ export class CodeWindow implements ICodeWindow {
}
}, 10000);
}
// (--prof-startup) save profile to disk
const { profileStartup } = this.environmentService;
if (profileStartup) {
stopProfiling(profileStartup.dir, profileStartup.prefix).done(undefined, err => this.logService.error(err));
}
}
public reload(configuration?: IWindowConfiguration, cli?: ParsedArgs): void {
@@ -564,7 +568,7 @@ export class CodeWindow implements ICodeWindow {
private getUrl(windowConfiguration: IWindowConfiguration): string {
// Set zoomlevel
const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
const zoomLevel = windowConfig && windowConfig.zoomLevel;
if (typeof zoomLevel === 'number') {
windowConfiguration.zoomLevel = zoomLevel;
@@ -582,6 +586,7 @@ export class CodeWindow implements ICodeWindow {
windowConfiguration.backgroundColor = this.getBackgroundColor();
// Perf Counters
windowConfiguration.perfEntries = exportEntries();
windowConfiguration.perfStartTime = global.perfStartTime;
windowConfiguration.perfAppReady = global.perfAppReady;
windowConfiguration.perfWindowLoadTime = Date.now();
@@ -603,7 +608,7 @@ export class CodeWindow implements ICodeWindow {
return 'hc-black';
}
const theme = this.storageService.getItem<string>(CodeWindow.themeStorageKey, 'vs-dark');
const theme = this.stateService.getItem<string>(CodeWindow.themeStorageKey, 'vs-dark');
return theme.split(' ')[0];
}
@@ -613,7 +618,7 @@ export class CodeWindow implements ICodeWindow {
return CodeWindow.DEFAULT_BG_HC_BLACK;
}
const background = this.storageService.getItem<string>(CodeWindow.themeBackgroundStorageKey, null);
const background = this.stateService.getItem<string>(CodeWindow.themeBackgroundStorageKey, null);
if (!background) {
const baseTheme = this.getBaseTheme();
@@ -624,6 +629,9 @@ export class CodeWindow implements ICodeWindow {
}
public serializeWindowState(): IWindowState {
if (!this._win) {
return defaultWindowState();
}
// fullscreen gets special treatment
if (this._win.isFullScreen()) {
@@ -676,7 +684,7 @@ export class CodeWindow implements ICodeWindow {
try {
state = this.validateWindowState(state);
} catch (err) {
this.logService.log(`Unexpected error validating window state: ${err}\n${err.stack}`); // somehow display API can be picky about the state to validate
this.logService.warn(`Unexpected error validating window state: ${err}\n${err.stack}`); // somehow display API can be picky about the state to validate
}
}
@@ -794,7 +802,7 @@ export class CodeWindow implements ICodeWindow {
}
private getMenuBarVisibility(): MenuBarVisibility {
const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
if (!windowConfig || !windowConfig.menuBarVisibility) {
return 'default';
}
@@ -831,7 +839,7 @@ export class CodeWindow implements ICodeWindow {
if (notify) {
this.send('vscode:showInfoMessage', nls.localize('hiddenMenuBar', "You can still access the menu bar by pressing the **Alt** key."));
};
}
break;
case ('hidden'):
@@ -845,7 +853,7 @@ export class CodeWindow implements ICodeWindow {
this._win.setAutoHideMenuBar(false);
});
break;
};
}
}
public onWindowTitleDoubleClick(): void {
@@ -972,4 +980,4 @@ export class CodeWindow implements ICodeWindow {
this._win = null; // Important to dereference the window object to allow for GC
}
}
}

View File

@@ -12,7 +12,7 @@ import * as arrays from 'vs/base/common/arrays';
import { assign, mixin, equals } from 'vs/base/common/objects';
import { IBackupMainService } from 'vs/platform/backup/common/backup';
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
import { IStorageService } from 'vs/platform/storage/node/storage';
import { IStateService } from 'vs/platform/state/common/state';
import { CodeWindow, IWindowState as ISingleWindowState, defaultWindowState, WindowMode } from 'vs/code/electron-main/window';
import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences, app } from 'electron';
import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/node/paths';
@@ -107,7 +107,7 @@ export class WindowsManager implements IWindowsMainService {
_serviceBrand: any;
private static windowsStateStorageKey = 'windowsState';
private static readonly windowsStateStorageKey = 'windowsState';
private static WINDOWS: CodeWindow[] = [];
@@ -138,21 +138,22 @@ export class WindowsManager implements IWindowsMainService {
onWindowsCountChanged: CommonEvent<IWindowsCountChangedEvent> = this._onWindowsCountChanged.event;
constructor(
private readonly machineId: string,
@ILogService private logService: ILogService,
@IStorageService private storageService: IStorageService,
@IStateService private stateService: IStateService,
@IEnvironmentService private environmentService: IEnvironmentService,
@ILifecycleService private lifecycleService: ILifecycleService,
@IBackupMainService private backupService: IBackupMainService,
@ITelemetryService private telemetryService: ITelemetryService,
@IBackupMainService private backupMainService: IBackupMainService,
@ITelemetryService telemetryService: ITelemetryService,
@IConfigurationService private configurationService: IConfigurationService,
@IHistoryMainService private historyService: IHistoryMainService,
@IWorkspacesMainService private workspacesService: IWorkspacesMainService,
@IHistoryMainService private historyMainService: IHistoryMainService,
@IWorkspacesMainService private workspacesMainService: IWorkspacesMainService,
@IInstantiationService private instantiationService: IInstantiationService
) {
this.windowsState = this.storageService.getItem<IWindowsState>(WindowsManager.windowsStateStorageKey) || { openedWindows: [] };
this.windowsState = this.stateService.getItem<IWindowsState>(WindowsManager.windowsStateStorageKey) || { openedWindows: [] };
this.fileDialog = new FileDialog(environmentService, telemetryService, storageService, this);
this.workspacesManager = new WorkspacesManager(workspacesService, lifecycleService, backupService, environmentService, this);
this.fileDialog = new FileDialog(environmentService, telemetryService, stateService, this);
this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, environmentService, this);
this.migrateLegacyWindowState();
}
@@ -201,8 +202,8 @@ export class WindowsManager implements IWindowsMainService {
});
// React to workbench loaded events from windows
ipc.on('vscode:workbenchLoaded', (event, windowId: number) => {
this.logService.log('IPC#vscode-workbenchLoaded');
ipc.on('vscode:workbenchLoaded', (_event: any, windowId: number) => {
this.logService.trace('IPC#vscode-workbenchLoaded');
const win = this.getWindowById(windowId);
if (win) {
@@ -310,7 +311,7 @@ export class WindowsManager implements IWindowsMainService {
}
// Persist
this.storageService.setItem(WindowsManager.windowsStateStorageKey, currentWindowsState);
this.stateService.setItem(WindowsManager.windowsStateStorageKey, currentWindowsState);
}
// See note on #onBeforeQuit() for details how these events are flowing
@@ -403,12 +404,12 @@ export class WindowsManager implements IWindowsMainService {
let workspacesToRestore: IWorkspaceIdentifier[] = [];
let emptyToRestore: string[] = [];
if (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath) {
foldersToRestore = this.backupService.getFolderBackupPaths();
foldersToRestore = this.backupMainService.getFolderBackupPaths();
workspacesToRestore = this.backupService.getWorkspaceBackups(); // collect from workspaces with hot-exit backups
workspacesToRestore.push(...this.workspacesService.getUntitledWorkspacesSync()); // collect from previous window session
workspacesToRestore = this.backupMainService.getWorkspaceBackups(); // collect from workspaces with hot-exit backups
workspacesToRestore.push(...this.workspacesMainService.getUntitledWorkspacesSync()); // collect from previous window session
emptyToRestore = this.backupService.getEmptyWindowBackupPaths();
emptyToRestore = this.backupMainService.getEmptyWindowBackupPaths();
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
}
@@ -475,7 +476,9 @@ export class WindowsManager implements IWindowsMainService {
}
});
this.historyService.addRecentlyOpened(recentlyOpenedWorkspaces, recentlyOpenedFiles);
if (!this.environmentService.skipAddToRecentlyOpened) {
this.historyMainService.addRecentlyOpened(recentlyOpenedWorkspaces, recentlyOpenedFiles);
}
}
// If we got started with --wait from the CLI, we need to signal to the outside when the window
@@ -541,7 +544,7 @@ export class WindowsManager implements IWindowsMainService {
context: openConfig.context,
filePath: fileToCheck && fileToCheck.filePath,
userHome: this.environmentService.userHome,
workspaceResolver: workspace => this.workspacesService.resolveWorkspaceSync(workspace.configPath)
workspaceResolver: workspace => this.workspacesMainService.resolveWorkspaceSync(workspace.configPath)
});
// Special case: we started with --wait and we got back a folder to open. In this case
@@ -796,7 +799,7 @@ export class WindowsManager implements IWindowsMainService {
if (!openConfig.addMode && isCommandLineOrAPICall) {
const foldersToOpen = windowsToOpen.filter(path => !!path.folderPath);
if (foldersToOpen.length > 1) {
const workspace = this.workspacesService.createWorkspaceSync(foldersToOpen.map(folder => ({ uri: URI.file(folder.folderPath) })));
const workspace = this.workspacesMainService.createWorkspaceSync(foldersToOpen.map(folder => ({ uri: URI.file(folder.folderPath) })));
// Add workspace and remove folders thereby
windowsToOpen.push({ workspace });
@@ -934,7 +937,7 @@ export class WindowsManager implements IWindowsMainService {
if (this.lifecycleService.wasRestarted) {
restoreWindows = 'all'; // always reopen all windows when an update was applied
} else {
const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
restoreWindows = ((windowConfig && windowConfig.restoreWindows) || 'one') as RestoreWindowsSetting;
if (restoreWindows === 'one' /* default */ && windowConfig && windowConfig.reopenFolders) {
@@ -970,7 +973,7 @@ export class WindowsManager implements IWindowsMainService {
// Workspace (unless disabled via flag)
if (!options || !options.forceOpenWorkspaceAsFile) {
const workspace = this.workspacesService.resolveWorkspaceSync(candidate);
const workspace = this.workspacesMainService.resolveWorkspaceSync(candidate);
if (workspace) {
return { workspace: { id: workspace.id, configPath: workspace.configPath } };
}
@@ -990,7 +993,7 @@ export class WindowsManager implements IWindowsMainService {
};
}
} catch (error) {
this.historyService.removeFromRecentlyOpened([candidate]); // since file does not seem to exist anymore, remove from recent
this.historyMainService.removeFromRecentlyOpened([candidate]); // since file does not seem to exist anymore, remove from recent
if (options && options.ignoreFileNotFound) {
return { filePath: candidate, createFilePath: true }; // assume this is a file that does not yet exist
@@ -1003,7 +1006,7 @@ export class WindowsManager implements IWindowsMainService {
private shouldOpenNewWindow(openConfig: IOpenConfiguration): { openFolderInNewWindow: boolean; openFilesInNewWindow: boolean; } {
// let the user settings override how folders are open in a new window or same window unless we are forced
const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
const openFolderInNewWindowConfig = (windowConfig && windowConfig.openFoldersInNewWindow) || 'default' /* default */;
const openFilesInNewWindowConfig = (windowConfig && windowConfig.openFilesInNewWindow) || 'off' /* default */;
@@ -1065,6 +1068,7 @@ export class WindowsManager implements IWindowsMainService {
// Build IWindowConfiguration from config and options
const configuration: IWindowConfiguration = mixin({}, options.cli); // inherit all properties from CLI
configuration.appRoot = this.environmentService.appRoot;
configuration.machineId = this.machineId;
configuration.execPath = process.execPath;
configuration.userEnv = assign({}, this.initialUserEnv, options.userEnv || {});
configuration.isInitialStartup = options.initialStartup;
@@ -1094,7 +1098,7 @@ export class WindowsManager implements IWindowsMainService {
// New window
if (!window) {
const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
const state = this.getNewWindowState(configuration);
// Window state is not from a previous session: only allow fullscreen if we inherit it or user wants fullscreen
@@ -1158,11 +1162,11 @@ export class WindowsManager implements IWindowsMainService {
// Register window for backups
if (!configuration.extensionDevelopmentPath) {
if (configuration.workspace) {
configuration.backupPath = this.backupService.registerWorkspaceBackupSync(configuration.workspace);
configuration.backupPath = this.backupMainService.registerWorkspaceBackupSync(configuration.workspace);
} else if (configuration.folderPath) {
configuration.backupPath = this.backupService.registerFolderBackupSync(configuration.folderPath);
configuration.backupPath = this.backupMainService.registerFolderBackupSync(configuration.folderPath);
} else {
configuration.backupPath = this.backupService.registerEmptyWindowBackupSync(options.emptyWindowBackupFolder);
configuration.backupPath = this.backupMainService.registerEmptyWindowBackupSync(options.emptyWindowBackupFolder);
}
}
@@ -1257,7 +1261,7 @@ export class WindowsManager implements IWindowsMainService {
state.y = displayToUse.bounds.y + (displayToUse.bounds.height / 2) - (state.height / 2);
// Check for newWindowDimensions setting and adjust accordingly
const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
let ensureNoOverlap = true;
if (windowConfig && windowConfig.newWindowDimensions) {
if (windowConfig.newWindowDimensions === 'maximized') {
@@ -1332,7 +1336,7 @@ export class WindowsManager implements IWindowsMainService {
private doEnterWorkspace(win: CodeWindow, result: IEnterWorkspaceResult): IEnterWorkspaceResult {
// Mark as recently opened
this.historyService.addRecentlyOpened([result.workspace], []);
this.historyMainService.addRecentlyOpened([result.workspace], []);
// Trigger Eevent to indicate load of workspace into window
this._onWindowReady.fire(win);
@@ -1352,7 +1356,7 @@ export class WindowsManager implements IWindowsMainService {
}
const workspace = e.window.openedWorkspace;
if (!workspace || !this.workspacesService.isUntitledWorkspace(workspace)) {
if (!workspace || !this.workspacesMainService.isUntitledWorkspace(workspace)) {
return; // only care about untitled workspaces to ask for saving
}
@@ -1453,48 +1457,48 @@ export class WindowsManager implements IWindowsMainService {
// Unresponsive
if (error === WindowError.UNRESPONSIVE) {
dialog.showMessageBox(window.win, {
const result = dialog.showMessageBox(window.win, {
title: product.nameLong,
type: 'warning',
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
}, result => {
if (!window.win) {
return; // Return early if the window has been going down already
}
if (result === 0) {
window.reload();
} else if (result === 2) {
this.onBeforeWindowClose(window); // 'close' event will not be fired on destroy(), so run it manually
window.win.destroy(); // make sure to destroy the window as it is unresponsive
}
});
if (!window.win) {
return; // Return early if the window has been going down already
}
if (result === 0) {
window.reload();
} else if (result === 2) {
this.onBeforeWindowClose(window); // 'close' event will not be fired on destroy(), so run it manually
window.win.destroy(); // make sure to destroy the window as it is unresponsive
}
}
// Crashed
else {
dialog.showMessageBox(window.win, {
const result = dialog.showMessageBox(window.win, {
title: product.nameLong,
type: 'warning',
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
}, result => {
if (!window.win) {
return; // Return early if the window has been going down already
}
if (result === 0) {
window.reload();
} else if (result === 1) {
this.onBeforeWindowClose(window); // 'close' event will not be fired on destroy(), so run it manually
window.win.destroy(); // make sure to destroy the window as it has crashed
}
});
if (!window.win) {
return; // Return early if the window has been going down already
}
if (result === 0) {
window.reload();
} else if (result === 1) {
this.onBeforeWindowClose(window); // 'close' event will not be fired on destroy(), so run it manually
window.win.destroy(); // make sure to destroy the window as it has crashed
}
}
}
@@ -1582,12 +1586,12 @@ interface IInternalNativeOpenDialogOptions extends INativeOpenDialogOptions {
class FileDialog {
private static workingDirPickerStorageKey = 'pickerWorkingDir';
private static readonly workingDirPickerStorageKey = 'pickerWorkingDir';
constructor(
private environmentService: IEnvironmentService,
private telemetryService: ITelemetryService,
private storageService: IStorageService,
private stateService: IStateService,
private windowsMainService: IWindowsMainService
) {
}
@@ -1628,7 +1632,7 @@ class FileDialog {
// Ensure defaultPath
if (!options.dialogOptions.defaultPath) {
options.dialogOptions.defaultPath = this.storageService.getItem<string>(FileDialog.workingDirPickerStorageKey);
options.dialogOptions.defaultPath = this.stateService.getItem<string>(FileDialog.workingDirPickerStorageKey);
}
// Ensure properties
@@ -1650,21 +1654,20 @@ class FileDialog {
// 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, dirname(paths[0]));
// Return
return clb(paths);
let paths = dialog.showOpenDialog(focusedWindow && focusedWindow.win, options.dialogOptions);
if (paths && paths.length > 0) {
if (isMacintosh) {
paths = paths.map(path => normalizeNFC(path)); // normalize paths returned from the OS
}
return clb(void (0));
});
// Remember path in storage for next time
this.stateService.setItem(FileDialog.workingDirPickerStorageKey, dirname(paths[0]));
// Return
return clb(paths);
}
return clb(void (0));
}
}
@@ -1672,7 +1675,6 @@ class WorkspacesManager {
constructor(
private workspacesService: IWorkspacesMainService,
private lifecycleService: ILifecycleService,
private backupService: IBackupMainService,
private environmentService: IEnvironmentService,
private windowsMainService: IWindowsMainService

View File

@@ -3,18 +3,21 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { spawn } from 'child_process';
import { spawn, ChildProcess } from 'child_process';
import { TPromise } from 'vs/base/common/winjs.base';
import { assign } from 'vs/base/common/objects';
import { parseCLIProcessArgv, buildHelpMessage } from 'vs/platform/environment/node/argv';
import { ParsedArgs } from 'vs/platform/environment/common/environment';
import product from 'vs/platform/node/product';
import pkg from 'vs/platform/node/package';
import * as fs from 'fs';
import * as paths from 'path';
import * as os from 'os';
import * as fs from 'fs';
import { whenDeleted } from 'vs/base/node/pfs';
import { findFreePort } from 'vs/base/node/ports';
import { resolveTerminalEncoding } from 'vs/base/node/encoding';
import * as iconv from 'iconv-lite';
import { isWindows } from 'vs/base/common/platform';
function shouldSpawnCliProcess(argv: ParsedArgs): boolean {
return !!argv['install-source']
@@ -27,7 +30,7 @@ interface IMainCli {
main: (argv: ParsedArgs) => TPromise<void>;
}
export function main(argv: string[]): TPromise<void> {
export async function main(argv: string[]): TPromise<any> {
let args: ParsedArgs;
try {
@@ -37,24 +40,127 @@ export function main(argv: string[]): TPromise<void> {
return TPromise.as(null);
}
// Help
if (args.help) {
console.log(buildHelpMessage(product.nameLong, product.applicationName, pkg.version));
} else if (args.version) {
console.log(`${pkg.version}\n${product.commit}`);
} else if (shouldSpawnCliProcess(args)) {
}
// Version Info
else if (args.version) {
console.log(`${pkg.version}\n${product.commit}\n${process.arch}`);
}
// Extensions Management
else if (shouldSpawnCliProcess(args)) {
const mainCli = new TPromise<IMainCli>(c => require(['vs/code/node/cliProcessMain'], c));
return mainCli.then(cli => cli.main(args));
} else {
}
// Just Code
else {
const env = assign({}, process.env, {
// this will signal Code that it was spawned from this module
'VSCODE_CLI': '1',
'VSCODE_CLI': '1', // this will signal Code that it was spawned from this module
'ELECTRON_NO_ATTACH_CONSOLE': '1'
});
delete env['ELECTRON_RUN_AS_NODE'];
if (args.verbose) {
const processCallbacks: ((child: ChildProcess) => Thenable<any>)[] = [];
const verbose = args.verbose || args.status;
if (verbose) {
env['ELECTRON_ENABLE_LOGGING'] = '1';
processCallbacks.push(child => {
child.stdout.on('data', (data: Buffer) => console.log(data.toString('utf8').trim()));
child.stderr.on('data', (data: Buffer) => console.log(data.toString('utf8').trim()));
return new TPromise<void>(c => child.once('exit', () => c(null)));
});
}
let stdinWithoutTty: boolean;
try {
stdinWithoutTty = !process.stdin.isTTY; // Via https://twitter.com/MylesBorins/status/782009479382626304
} catch (error) {
// Windows workaround for https://github.com/nodejs/node/issues/11656
}
let stdinFilePath: string;
if (stdinWithoutTty) {
// Read from stdin: we require a single "-" argument to be passed in order to start reading from
// stdin. We do this because there is no reliable way to find out if data is piped to stdin. Just
// checking for stdin being connected to a TTY is not enough (https://github.com/Microsoft/vscode/issues/40351)
if (args._.length === 1 && args._[0] === '-') {
// remove the "-" argument when we read from stdin
args._ = [];
argv = argv.filter(a => a !== '-');
// prepare temp file to read stdin to
stdinFilePath = paths.join(os.tmpdir(), `code-stdin-${Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 3)}.txt`);
// open tmp file for writing
let stdinFileError: Error;
let stdinFileStream: fs.WriteStream;
try {
stdinFileStream = fs.createWriteStream(stdinFilePath);
} catch (error) {
stdinFileError = error;
}
if (!stdinFileError) {
// Pipe into tmp file using terminals encoding
resolveTerminalEncoding(verbose).done(encoding => {
const converterStream = iconv.decodeStream(encoding);
process.stdin.pipe(converterStream).pipe(stdinFileStream);
});
// Make sure to open tmp file
argv.push(stdinFilePath);
// Enable --wait to get all data and ignore adding this to history
argv.push('--wait');
argv.push('--skip-add-to-recently-opened');
args.wait = true;
}
if (verbose) {
if (stdinFileError) {
console.error(`Failed to create file to read via stdin: ${stdinFileError.toString()}`);
} else {
console.log(`Reading from stdin via: ${stdinFilePath}`);
}
}
}
// If the user pipes data via stdin but forgot to add the "-" argument, help by printing a message
// if we detect that data flows into via stdin after a certain timeout.
else if (args._.length === 0) {
processCallbacks.push(child => new TPromise(c => {
const dataListener = () => {
if (isWindows) {
console.log(`Run with '${product.applicationName} -' to read output from another program (e.g. 'echo Hello World | ${product.applicationName} -').`);
} else {
console.log(`Run with '${product.applicationName} -' to read from stdin (e.g. 'ps aux | grep code | ${product.applicationName} -').`);
}
c(void 0);
};
// wait for 1s maximum...
setTimeout(() => {
process.stdin.removeListener('data', dataListener);
c(void 0);
}, 1000);
// ...but finish early if we detect data
process.stdin.once('data', dataListener);
}));
}
}
// If we are started with --wait create a random temporary file
@@ -73,7 +179,7 @@ export function main(argv: string[]): TPromise<void> {
waitMarkerError = error;
}
if (args.verbose) {
if (verbose) {
if (waitMarkerError) {
console.error(`Failed to create marker file for --wait: ${waitMarkerError.toString()}`);
} else {
@@ -82,26 +188,76 @@ export function main(argv: string[]): TPromise<void> {
}
}
// If we have been started with `--prof-startup` we need to find free ports to profile
// the main process, the renderer, and the extension host. We also disable v8 cached data
// to get better profile traces. Last, we listen on stdout for a signal that tells us to
// stop profiling.
if (args['prof-startup']) {
const portMain = await findFreePort(9222, 10, 6000);
const portRenderer = await findFreePort(portMain + 1, 10, 6000);
const portExthost = await findFreePort(portRenderer + 1, 10, 6000);
if (!portMain || !portRenderer || !portExthost) {
console.error('Failed to find free ports for profiler to connect to do.');
return;
}
const filenamePrefix = paths.join(os.homedir(), Math.random().toString(16).slice(-4));
argv.push(`--inspect-brk=${portMain}`);
argv.push(`--remote-debugging-port=${portRenderer}`);
argv.push(`--inspect-brk-extensions=${portExthost}`);
argv.push(`--prof-startup-prefix`, filenamePrefix);
argv.push(`--no-cached-data`);
fs.writeFileSync(filenamePrefix, argv.slice(-6).join('|'));
processCallbacks.push(async child => {
// load and start profiler
const profiler = await import('v8-inspect-profiler');
const main = await profiler.startProfiling({ port: portMain });
const renderer = await profiler.startProfiling({ port: portRenderer, tries: 200 });
const extHost = await profiler.startProfiling({ port: portExthost, tries: 300 });
// wait for the renderer to delete the
// marker file
whenDeleted(filenamePrefix);
let profileMain = await main.stop();
let profileRenderer = await renderer.stop();
let profileExtHost = await extHost.stop();
let suffix = '';
if (!process.env['VSCODE_DEV']) {
// when running from a not-development-build we remove
// absolute filenames because we don't want to reveal anything
// about users. We also append the `.txt` suffix to make it
// easier to attach these files to GH issues
profileMain = profiler.rewriteAbsolutePaths(profileMain, 'piiRemoved');
profileRenderer = profiler.rewriteAbsolutePaths(profileRenderer, 'piiRemoved');
profileExtHost = profiler.rewriteAbsolutePaths(profileExtHost, 'piiRemoved');
suffix = '.txt';
}
// finally stop profiling and save profiles to disk
await profiler.writeProfile(profileMain, `${filenamePrefix}-main.cpuprofile${suffix}`);
await profiler.writeProfile(profileRenderer, `${filenamePrefix}-renderer.cpuprofile${suffix}`);
await profiler.writeProfile(profileExtHost, `${filenamePrefix}-exthost.cpuprofile${suffix}`);
});
}
const options = {
detached: true,
env
};
if (!args.verbose) {
if (!verbose) {
options['stdio'] = 'ignore';
}
const child = spawn(process.execPath, argv.slice(2), options);
if (args.verbose) {
child.stdout.on('data', (data: Buffer) => console.log(data.toString('utf8').trim()));
child.stderr.on('data', (data: Buffer) => console.log(data.toString('utf8').trim()));
}
if (args.verbose) {
return new TPromise<void>(c => child.once('exit', () => c(null)));
}
if (args.wait && waitMarkerFilePath) {
return new TPromise<void>(c => {
@@ -110,8 +266,16 @@ export function main(argv: string[]): TPromise<void> {
// Complete when wait marker file is deleted
whenDeleted(waitMarkerFilePath).done(c, c);
}).then(() => {
// Make sure to delete the tmp stdin file if we have any
if (stdinFilePath) {
fs.unlinkSync(stdinFilePath);
}
});
}
return TPromise.join(processCallbacks.map(callback => callback(child)));
}
return TPromise.as(null);

View File

@@ -7,7 +7,6 @@ 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';
@@ -17,9 +16,9 @@ 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, getInstallSourcePath } from 'vs/platform/environment/node/environmentService';
import { EnvironmentService } 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 { ExtensionManagementService, validateLocalExtension } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { combinedAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
@@ -30,9 +29,15 @@ import { RequestService } from 'vs/platform/request/node/requestService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
import { mkdirp } from 'vs/base/node/pfs';
import { mkdirp, writeFile } from 'vs/base/node/pfs';
import { IChoiceService } from 'vs/platform/message/common/message';
import { ChoiceCliService } from 'vs/platform/message/node/messageCli';
import { getBaseLabel } from 'vs/base/common/labels';
import { IStateService } from 'vs/platform/state/common/state';
import { StateService } from 'vs/platform/state/node/stateService';
import { createLogService } from 'vs/platform/log/node/spdlogService';
import { ILogService } from 'vs/platform/log/common/log';
import { isPromiseCanceledError } from 'vs/base/common/errors';
const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id);
const notInstalled = (id: string) => localize('notInstalled', "Extension '{0}' is not installed.", id);
@@ -76,10 +81,7 @@ class Main {
}
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));
});
return writeFile(this.environmentService.installSourcePath, installSource.slice(0, 30));
}
private listExtensions(showVersions: boolean): TPromise<any> {
@@ -95,7 +97,14 @@ class Main {
const extension = path.isAbsolute(id) ? id : path.join(process.cwd(), id);
return this.extensionManagementService.install(extension).then(() => {
console.log(localize('successVsixInstall', "Extension '{0}' was successfully installed!", path.basename(extension)));
console.log(localize('successVsixInstall', "Extension '{0}' was successfully installed!", getBaseLabel(extension)));
}, error => {
if (isPromiseCanceledError(error)) {
console.log(localize('cancelVsixInstall', "Cancelled installing Extension '{0}'.", getBaseLabel(extension)));
return null;
} else {
return TPromise.wrapError(error);
}
});
});
@@ -134,7 +143,16 @@ class Main {
console.log(localize('installing', "Installing..."));
return this.extensionManagementService.installFromGallery(extension)
.then(() => console.log(localize('successInstall', "Extension '{0}' v{1} was successfully installed!", id, extension.version)));
.then(
() => console.log(localize('successInstall', "Extension '{0}' v{1} was successfully installed!", id, extension.version)),
error => {
if (isPromiseCanceledError(error)) {
console.log(localize('cancelVsixInstall', "Cancelled installing Extension '{0}'.", id));
return null;
} else {
return TPromise.wrapError(error);
}
});
});
});
});
@@ -142,19 +160,31 @@ class Main {
return sequence([...vsixTasks, ...galleryTasks]);
}
private uninstallExtension(ids: string[]): TPromise<any> {
return sequence(ids.map(id => () => {
return this.extensionManagementService.getInstalled(LocalExtensionType.User).then(installed => {
const [extension] = installed.filter(e => getId(e.manifest) === id);
private uninstallExtension(extensions: string[]): TPromise<any> {
async function getExtensionId(extensionDescription: string): TPromise<string> {
if (!/\.vsix$/i.test(extensionDescription)) {
return extensionDescription;
}
if (!extension) {
return TPromise.wrapError(new Error(`${notInstalled(id)}\n${useId}`));
}
const zipPath = path.isAbsolute(extensionDescription) ? extensionDescription : path.join(process.cwd(), extensionDescription);
const manifest = await validateLocalExtension(zipPath);
return getId(manifest);
}
console.log(localize('uninstalling', "Uninstalling {0}...", id));
return sequence(extensions.map(extension => () => {
return getExtensionId(extension).then(id => {
return this.extensionManagementService.getInstalled(LocalExtensionType.User).then(installed => {
const [extension] = installed.filter(e => getId(e.manifest) === id);
return this.extensionManagementService.uninstall(extension, true)
.then(() => console.log(localize('successUninstall', "Extension '{0}' was successfully uninstalled!", id)));
if (!extension) {
return TPromise.wrapError(new Error(`${notInstalled(id)}\n${useId}`));
}
console.log(localize('uninstalling', "Uninstalling {0}...", id));
return this.extensionManagementService.uninstall(extension, true)
.then(() => console.log(localize('successUninstall', "Extension '{0}' was successfully uninstalled!", id)));
});
});
}));
}
@@ -164,15 +194,25 @@ const eventPrefix = 'monacoworkbench';
export function main(argv: ParsedArgs): TPromise<void> {
const services = new ServiceCollection();
services.set(IEnvironmentService, new SyncDescriptor(EnvironmentService, argv, process.execPath));
const environmentService = new EnvironmentService(argv, process.execPath);
const logService = createLogService('cli', environmentService);
process.once('exit', () => logService.dispose());
logService.info('main', argv);
services.set(IEnvironmentService, environmentService);
services.set(ILogService, logService);
services.set(IStateService, new SyncDescriptor(StateService));
const instantiationService: IInstantiationService = new InstantiationService(services);
return instantiationService.invokeFunction(accessor => {
const envService = accessor.get(IEnvironmentService);
const stateService = accessor.get(IStateService);
return TPromise.join([envService.appSettingsHome, envService.extensionsPath].map(p => mkdirp(p))).then(() => {
const { appRoot, extensionsPath, extensionDevelopmentPath, isBuilt, installSource } = envService;
const { appRoot, extensionsPath, extensionDevelopmentPath, isBuilt, installSourcePath } = envService;
const services = new ServiceCollection();
services.set(IConfigurationService, new SyncDescriptor(ConfigurationService));
@@ -194,7 +234,7 @@ export function main(argv: ParsedArgs): TPromise<void> {
const config: ITelemetryServiceConfig = {
appender: combinedAppender(...appenders),
commonProperties: resolveCommonProperties(product.commit, pkg.version, installSource),
commonProperties: resolveCommonProperties(product.commit, pkg.version, stateService.getItem('telemetry.machineId'), installSourcePath),
piiPaths: [appRoot, extensionsPath]
};