Merge from master

This commit is contained in:
Raj Musuku
2019-02-21 17:56:04 -08:00
parent 5a146e34fa
commit 666ae11639
11482 changed files with 119352 additions and 255574 deletions

View File

@@ -3,6 +3,4 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/platform/update/node/update.config.contribution';

View File

@@ -3,182 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
//@ts-check
'use strict';
const path = require('path');
const fs = require('fs');
const remote = require('electron').remote;
const bootstrapWindow = require('../../../../bootstrap-window');
function assign(destination, source) {
return Object.keys(source)
.reduce(function (r, key) { r[key] = source[key]; return r; }, destination);
}
function parseURLQueryArgs() {
const search = window.location.search || '';
return search.split(/[?&]/)
.filter(function (param) { return !!param; })
.map(function (param) { return param.split('='); })
.filter(function (param) { return param.length === 2; })
.reduce(function (r, param) { r[param[0]] = decodeURIComponent(param[1]); return r; }, {});
}
function uriFromPath(_path) {
var pathName = path.resolve(_path).replace(/\\/g, '/');
if (pathName.length > 0 && pathName.charAt(0) !== '/') {
pathName = '/' + pathName;
}
return encodeURI('file://' + pathName);
}
function readFile(file) {
return new Promise(function(resolve, reject) {
fs.readFile(file, 'utf8', function(err, data) {
if (err) {
reject(err);
return;
}
resolve(data);
});
});
}
const writeFile = (file, content) => new Promise((c, e) => fs.writeFile(file, content, 'utf8', err => err ? e(err) : c()));
function main() {
const args = parseURLQueryArgs();
const configuration = JSON.parse(args['config'] || '{}') || {};
assign(process.env, configuration.userEnv);
//#region Add support for using node_modules.asar
(function () {
const path = require('path');
const Module = require('module');
let NODE_MODULES_PATH = path.join(configuration.appRoot, 'node_modules');
if (/[a-z]\:/.test(NODE_MODULES_PATH)) {
// Make drive letter uppercase
NODE_MODULES_PATH = NODE_MODULES_PATH.charAt(0).toUpperCase() + NODE_MODULES_PATH.substr(1);
}
const NODE_MODULES_ASAR_PATH = NODE_MODULES_PATH + '.asar';
const originalResolveLookupPaths = Module._resolveLookupPaths;
Module._resolveLookupPaths = function (request, parent, newReturn) {
const result = originalResolveLookupPaths(request, parent, newReturn);
const paths = newReturn ? result : result[1];
for (let i = 0, len = paths.length; i < len; i++) {
if (paths[i] === NODE_MODULES_PATH) {
paths.splice(i, 0, NODE_MODULES_ASAR_PATH);
break;
}
}
return result;
};
})();
//#endregion
const extractKey = function (e) {
return [
e.ctrlKey ? 'ctrl-' : '',
e.metaKey ? 'meta-' : '',
e.altKey ? 'alt-' : '',
e.shiftKey ? 'shift-' : '',
e.keyCode
].join('');
};
const TOGGLE_DEV_TOOLS_KB = (process.platform === 'darwin' ? 'meta-alt-73' : 'ctrl-shift-73'); // mac: Cmd-Alt-I, rest: Ctrl-Shift-I
const RELOAD_KB = (process.platform === 'darwin' ? 'meta-82' : 'ctrl-82'); // mac: Cmd-R, rest: Ctrl-R
window.addEventListener('keydown', function (e) {
const key = extractKey(e);
if (key === TOGGLE_DEV_TOOLS_KB) {
remote.getCurrentWebContents().toggleDevTools();
} else if (key === RELOAD_KB) {
remote.getCurrentWindow().reload();
}
});
// Load the loader and start loading the workbench
const rootUrl = uriFromPath(configuration.appRoot) + '/out';
// Get the nls configuration into the process.env as early as possible.
var nlsConfig = { availableLanguages: {} };
const config = process.env['VSCODE_NLS_CONFIG'];
if (config) {
process.env['VSCODE_NLS_CONFIG'] = config;
try {
nlsConfig = JSON.parse(config);
} catch (e) { /*noop*/ }
}
if (nlsConfig._resolvedLanguagePackCoreLocation) {
let bundles = Object.create(null);
nlsConfig.loadBundle = function(bundle, language, cb) {
let result = bundles[bundle];
if (result) {
cb(undefined, result);
return;
}
let bundleFile = path.join(nlsConfig._resolvedLanguagePackCoreLocation, bundle.replace(/\//g, '!') + '.nls.json');
readFile(bundleFile).then(function (content) {
let json = JSON.parse(content);
bundles[bundle] = json;
cb(undefined, json);
}).catch((error) => {
try {
if (nlsConfig._corruptedFile) {
writeFile(nlsConfig._corruptedFile, 'corrupted').catch(function (error) { console.error(error); });
}
} finally {
cb(error, undefined);
}
});
};
}
var locale = nlsConfig.availableLanguages['*'] || 'en';
if (locale === 'zh-tw') {
locale = 'zh-Hant';
} else if (locale === 'zh-cn') {
locale = 'zh-Hans';
}
window.document.documentElement.setAttribute('lang', locale);
// Load the loader
const loaderFilename = configuration.appRoot + '/out/vs/loader.js';
const loaderSource = fs.readFileSync(loaderFilename);
require('vm').runInThisContext(loaderSource, { filename: loaderFilename });
var define = global.define;
global.define = undefined;
window.nodeRequire = require.__$__nodeRequire;
define('fs', ['original-fs'], function (originalFS) { return originalFS; }); // replace the patched electron fs with the original node fs for all AMD code
window.MonacoEnvironment = {};
require.config({
baseUrl: rootUrl,
'vs/nls': nlsConfig,
nodeCachedDataDir: configuration.nodeCachedDataDir,
nodeModules: [/*BUILD->INSERT_NODE_MODULES*/]
});
if (nlsConfig.pseudo) {
require(['vs/nls'], function (nlsPlugin) {
nlsPlugin.setPseudoTranslation(nlsConfig.pseudo);
});
}
require(['vs/code/electron-browser/issue/issueReporterMain'], (issueReporter) => {
issueReporter.startup(configuration);
});
}
main();
bootstrapWindow.load(['vs/code/electron-browser/issue/issueReporterMain'], function (issueReporter, configuration) {
issueReporter.startup(configuration);
}, { forceEnableDeveloperKeybindings: true });

View File

@@ -3,10 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./media/issueReporter';
import { shell, ipcRenderer, webFrame, remote, clipboard } from 'electron';
import { shell, ipcRenderer, webFrame, clipboard } from 'electron';
import { localize } from 'vs/nls';
import { $ } from 'vs/base/browser/dom';
import * as collections from 'vs/base/common/collections';
@@ -19,25 +17,24 @@ import { debounce } from 'vs/base/common/decorators';
import * as platform from 'vs/base/common/platform';
import { Disposable } from 'vs/base/common/lifecycle';
import { Client as ElectronIPCClient } from 'vs/base/parts/ipc/electron-browser/ipc.electron-browser';
import { getDelayedChannel } from 'vs/base/parts/ipc/common/ipc';
import { getDelayedChannel } from 'vs/base/parts/ipc/node/ipc';
import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IWindowConfiguration, IWindowsService } from 'vs/platform/windows/common/windows';
import { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform/telemetry/common/telemetryUtils';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
import { ITelemetryAppenderChannel, TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc';
import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
import { WindowsChannelClient } from 'vs/platform/windows/common/windowsIpc';
import { WindowsChannelClient } from 'vs/platform/windows/node/windowsIpc';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel';
import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssueReporterData, IssueReporterFeatures } from 'vs/platform/issue/common/issue';
import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssueReporterData, IssueReporterFeatures, IssueReporterExtensionData } from 'vs/platform/issue/common/issue';
import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage';
import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc';
import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/node/logIpc';
import { ILogService, getLogLevel } from 'vs/platform/log/common/log';
import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
import { normalizeGitHubUrl } from 'vs/code/electron-browser/issue/issueReporterUtil';
@@ -72,6 +69,7 @@ export class IssueReporter extends Disposable {
private receivedSystemInfo = false;
private receivedPerformanceInfo = false;
private shouldQueueSearch = false;
private hasBeenSubmitted = false;
private previewButton: Button;
@@ -91,7 +89,7 @@ export class IssueReporter extends Disposable {
this.previewButton = new Button(document.getElementById('issue-reporter'));
ipcRenderer.on('issuePerformanceInfoResponse', (event, info) => {
ipcRenderer.on('vscode:issuePerformanceInfoResponse', (event, info) => {
this.logService.trace('issueReporter: Received performance data');
this.issueReporterModel.update(info);
this.receivedPerformanceInfo = true;
@@ -102,7 +100,7 @@ export class IssueReporter extends Disposable {
this.updatePreviewButtonState();
});
ipcRenderer.on('issueSystemInfoResponse', (event, info) => {
ipcRenderer.on('vscode:issueSystemInfoResponse', (event, info) => {
this.logService.trace('issueReporter: Received system data');
this.issueReporterModel.update({ systemInfo: info });
this.receivedSystemInfo = true;
@@ -111,9 +109,9 @@ export class IssueReporter extends Disposable {
this.updatePreviewButtonState();
});
ipcRenderer.send('issueSystemInfoRequest');
ipcRenderer.send('vscode:issueSystemInfoRequest');
if (configuration.data.issueType === IssueType.PerformanceIssue) {
ipcRenderer.send('issuePerformanceInfoRequest');
ipcRenderer.send('vscode:issuePerformanceInfoRequest');
}
this.logService.trace('issueReporter: Sent data requests');
@@ -213,11 +211,9 @@ export class IssueReporter extends Disposable {
document.body.style.color = styles.color;
}
private handleExtensionData(extensions: ILocalExtension[]) {
private handleExtensionData(extensions: IssueReporterExtensionData[]) {
const { nonThemes, themes } = collections.groupBy(extensions, ext => {
const manifestKeys = ext.manifest.contributes ? Object.keys(ext.manifest.contributes) : [];
const onlyTheme = !ext.manifest.activationEvents && manifestKeys.length === 1 && manifestKeys[0] === 'themes';
return onlyTheme ? 'themes' : 'nonThemes';
return ext.isTheme ? 'themes' : 'nonThemes';
});
const numberOfThemeExtesions = themes && themes.length;
@@ -288,7 +284,7 @@ export class IssueReporter extends Disposable {
const instantiationService = new InstantiationService(serviceCollection, true);
if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
const channel = getDelayedChannel<ITelemetryAppenderChannel>(sharedProcess.then(c => c.getChannel('telemetryAppender')));
const channel = getDelayedChannel(sharedProcess.then(c => c.getChannel('telemetryAppender')));
const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(logService));
const commonProperties = resolveCommonProperties(product.commit, pkg.version, configuration.machineId, this.environmentService.installSourcePath);
const piiPaths = [this.environmentService.appRoot, this.environmentService.extensionsPath];
@@ -308,7 +304,7 @@ export class IssueReporter extends Disposable {
const issueType = parseInt((<HTMLInputElement>event.target).value);
this.issueReporterModel.update({ issueType: issueType });
if (issueType === IssueType.PerformanceIssue && !this.receivedPerformanceInfo) {
ipcRenderer.send('issuePerformanceInfoRequest');
ipcRenderer.send('vscode:issuePerformanceInfoRequest');
}
this.updatePreviewButtonState();
this.render();
@@ -383,15 +379,19 @@ export class IssueReporter extends Disposable {
this.previewButton.onDidClick(() => this.createIssue());
function sendWorkbenchCommand(commandId: string) {
ipcRenderer.send('vscode:workbenchCommand', { id: commandId, from: 'issueReporter' });
}
this.addEventListener('disableExtensions', 'click', () => {
ipcRenderer.send('workbenchCommand', 'workbench.action.reloadWindowWithExtensionsDisabled');
sendWorkbenchCommand('workbench.action.reloadWindowWithExtensionsDisabled');
});
this.addEventListener('disableExtensions', 'keydown', (e: KeyboardEvent) => {
e.stopPropagation();
if (e.keyCode === 13 || e.keyCode === 32) {
ipcRenderer.send('workbenchCommand', 'workbench.extensions.action.disableAll');
ipcRenderer.send('workbenchCommand', 'workbench.action.reloadWindow');
sendWorkbenchCommand('workbench.extensions.action.disableAll');
sendWorkbenchCommand('workbench.action.reloadWindow');
}
});
@@ -400,7 +400,21 @@ export class IssueReporter extends Disposable {
// Cmd/Ctrl+Enter previews issue and closes window
if (cmdOrCtrlKey && e.keyCode === 13) {
if (this.createIssue()) {
remote.getCurrentWindow().close();
ipcRenderer.send('vscode:closeIssueReporter');
}
}
// Cmd/Ctrl + w closes issue window
if (cmdOrCtrlKey && e.keyCode === 87) {
e.stopPropagation();
e.preventDefault();
const issueTitle = (<HTMLInputElement>document.getElementById('issue-title'))!.value;
const { issueDescription } = this.issueReporterModel.getData();
if (!this.hasBeenSubmitted && (issueTitle || issueDescription)) {
ipcRenderer.send('vscode:issueReporterConfirmClose');
} else {
ipcRenderer.send('vscode:closeIssueReporter');
}
}
@@ -459,12 +473,12 @@ export class IssueReporter extends Disposable {
private getExtensionRepositoryUrl(): string {
const selectedExtension = this.issueReporterModel.getData().selectedExtension;
return selectedExtension && selectedExtension.manifest && selectedExtension.manifest.repository && selectedExtension.manifest.repository.url;
return selectedExtension && selectedExtension.repositoryUrl;
}
private getExtensionBugsUrl(): string {
const selectedExtension = this.issueReporterModel.getData().selectedExtension;
return selectedExtension && selectedExtension.manifest && selectedExtension.manifest.bugs && selectedExtension.manifest.bugs.url;
return selectedExtension && selectedExtension.bugsUrl;
}
private searchVSCodeIssues(title: string, issueDescription: string): void {
@@ -775,6 +789,7 @@ export class IssueReporter extends Disposable {
}
*/
this.telemetryService.publicLog('issueReporterSubmit', { issueType: this.issueReporterModel.getData().issueType, numSimilarIssuesDisplayed: this.numberOfSearchResultsDisplayed });
this.hasBeenSubmitted = true;
const baseUrl = this.getIssueUrlWithTitle((<HTMLInputElement>document.getElementById('issue-title')).value);
const issueBody = this.issueReporterModel.serialize();
@@ -785,7 +800,7 @@ export class IssueReporter extends Disposable {
url = baseUrl + `&body=${encodeURIComponent(localize('pasteData', "We have written the needed data into your clipboard because it was too large to send. Please paste."))}`;
}
shell.openExternal(url);
ipcRenderer.send('vscode:openExternal', url);
return true;
}
@@ -834,7 +849,7 @@ export class IssueReporter extends Disposable {
target.innerHTML = `<table>${tableHtml}</table>`;
}
private updateExtensionSelector(extensions: ILocalExtension[]): void {
private updateExtensionSelector(extensions: IssueReporterExtensionData[]): void {
interface IOption {
name: string;
id: string;
@@ -842,8 +857,8 @@ export class IssueReporter extends Disposable {
const extensionOptions: IOption[] = extensions.map(extension => {
return {
name: extension.manifest.displayName || extension.manifest.name || '',
id: extension.identifier.id
name: extension.displayName || extension.name || '',
id: extension.id
};
});
@@ -869,7 +884,7 @@ export class IssueReporter extends Disposable {
this.addEventListener('extension-selector', 'change', (e: Event) => {
const selectedExtensionId = (<HTMLInputElement>e.target).value;
const extensions = this.issueReporterModel.getData().allExtensions;
const matches = extensions.filter(extension => extension.identifier.id === selectedExtensionId);
const matches = extensions.filter(extension => extension.id === selectedExtensionId);
if (matches.length) {
this.issueReporterModel.update({ selectedExtension: matches[0] });
@@ -891,7 +906,7 @@ export class IssueReporter extends Disposable {
document.querySelector('.block-workspace .block-info code').textContent = '\n' + state.workspaceInfo;
}
private updateExtensionTable(extensions: ILocalExtension[], numThemeExtensions: number): void {
private updateExtensionTable(extensions: IssueReporterExtensionData[], numThemeExtensions: number): void {
const target = document.querySelector('.block-extensions .block-info');
if (this.environmentService.disableExtensions) {
@@ -911,7 +926,7 @@ export class IssueReporter extends Disposable {
target.innerHTML = `<table>${table}</table>${themeExclusionStr}`;
}
private updateSearchedExtensionTable(extensions: ILocalExtension[]): void {
private updateSearchedExtensionTable(extensions: IssueReporterExtensionData[]): void {
const target = document.querySelector('.block-searchedExtensions .block-info');
if (!extensions.length) {
@@ -923,7 +938,7 @@ export class IssueReporter extends Disposable {
target.innerHTML = `<table>${table}</table>`;
}
private getExtensionTableHtml(extensions: ILocalExtension[]): string {
private getExtensionTableHtml(extensions: IssueReporterExtensionData[]): string {
let table = `
<tr>
<th>Extension</th>
@@ -934,9 +949,9 @@ export class IssueReporter extends Disposable {
table += extensions.map(extension => {
return `
<tr>
<td>${extension.manifest.name}</td>
<td>${extension.manifest.publisher.substr(0, 3)}</td>
<td>${extension.manifest.version}</td>
<td>${extension.name}</td>
<td>${extension.publisher.substr(0, 3)}</td>
<td>${extension.version}</td>
</tr>`;
}).join('');

View File

@@ -3,11 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { assign } from 'vs/base/common/objects';
import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IssueType, ISettingSearchResult } from 'vs/platform/issue/common/issue';
import { IssueType, ISettingSearchResult, IssueReporterExtensionData } from 'vs/platform/issue/common/issue';
export interface IssueReporterData {
issueType?: IssueType;
@@ -26,11 +23,11 @@ export interface IssueReporterData {
includeSettingsSearchDetails?: boolean;
numberOfThemeExtesions?: number;
allExtensions?: ILocalExtension[];
enabledNonThemeExtesions?: ILocalExtension[];
allExtensions?: IssueReporterExtensionData[];
enabledNonThemeExtesions?: IssueReporterExtensionData[];
extensionsDisabled?: boolean;
fileOnExtension?: boolean;
selectedExtension?: ILocalExtension;
selectedExtension?: IssueReporterExtensionData;
actualSearchResults?: ISettingSearchResult[];
query?: string;
filterResultCount?: number;
@@ -79,12 +76,12 @@ ${this.getInfos()}
|| this._data.issueType === IssueType.PerformanceIssue
|| this._data.issueType === IssueType.FeatureRequest;
return fileOnExtensionSupported && this._data.fileOnExtension;
return !!(fileOnExtensionSupported && this._data.fileOnExtension);
}
private getExtensionVersion(): string {
if (this.fileOnExtension()) {
return `\nExtension version: ${this._data.selectedExtension.manifest.version}`;
if (this.fileOnExtension() && this._data.selectedExtension) {
return `\nExtension version: ${this._data.selectedExtension.version}`;
} else {
return '';
}
@@ -201,7 +198,7 @@ ${this._data.workspaceInfo};
let tableHeader = `Extension|Author (truncated)|Version
---|---|---`;
const table = this._data.enabledNonThemeExtesions.map(e => {
return `${e.manifest.name}|${e.manifest.publisher.substr(0, 3)}|${e.manifest.version}`;
return `${e.name}|${e.publisher.substr(0, 3)}|${e.version}`;
}).join('\n');
return `<details><summary>Extensions (${this._data.enabledNonThemeExtesions.length})</summary>

View File

@@ -2,7 +2,6 @@
* 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 { escape } from 'vs/base/common/strings';
import { localize } from 'vs/nls';

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { endsWith, rtrim } from 'vs/base/common/strings';
export function normalizeGitHubUrl(url: string): string {

View File

@@ -24,6 +24,10 @@ td {
vertical-align: top;
}
label {
user-select: none;
}
.block-settingsSearchResults-details {
padding-bottom: .5rem;
}

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import { IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel';
import { normalizeGitHubUrl } from 'vs/code/electron-browser/issue/issueReporterUtil';

View File

@@ -8,7 +8,7 @@
</head>
<body aria-label="">
<div id="process-list"></div>
<table id="process-list" aria-live="polite"></table>
</body>
<!-- Startup via processExplorer.js -->

View File

@@ -3,182 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
//@ts-check
'use strict';
const path = require('path');
const fs = require('fs');
const remote = require('electron').remote;
const bootstrapWindow = require('../../../../bootstrap-window');
function assign(destination, source) {
return Object.keys(source)
.reduce(function (r, key) { r[key] = source[key]; return r; }, destination);
}
function parseURLQueryArgs() {
const search = window.location.search || '';
return search.split(/[?&]/)
.filter(function (param) { return !!param; })
.map(function (param) { return param.split('='); })
.filter(function (param) { return param.length === 2; })
.reduce(function (r, param) { r[param[0]] = decodeURIComponent(param[1]); return r; }, {});
}
function uriFromPath(_path) {
var pathName = path.resolve(_path).replace(/\\/g, '/');
if (pathName.length > 0 && pathName.charAt(0) !== '/') {
pathName = '/' + pathName;
}
return encodeURI('file://' + pathName);
}
function readFile(file) {
return new Promise(function(resolve, reject) {
fs.readFile(file, 'utf8', function(err, data) {
if (err) {
reject(err);
return;
}
resolve(data);
});
});
}
const writeFile = (file, content) => new Promise((c, e) => fs.writeFile(file, content, 'utf8', err => err ? e(err) : c()));
function main() {
const args = parseURLQueryArgs();
const configuration = JSON.parse(args['config'] || '{}') || {};
assign(process.env, configuration.userEnv);
//#region Add support for using node_modules.asar
(function () {
const path = require('path');
const Module = require('module');
let NODE_MODULES_PATH = path.join(configuration.appRoot, 'node_modules');
if (/[a-z]\:/.test(NODE_MODULES_PATH)) {
// Make drive letter uppercase
NODE_MODULES_PATH = NODE_MODULES_PATH.charAt(0).toUpperCase() + NODE_MODULES_PATH.substr(1);
}
const NODE_MODULES_ASAR_PATH = NODE_MODULES_PATH + '.asar';
const originalResolveLookupPaths = Module._resolveLookupPaths;
Module._resolveLookupPaths = function (request, parent, newReturn) {
const result = originalResolveLookupPaths(request, parent, newReturn);
const paths = newReturn ? result : result[1];
for (let i = 0, len = paths.length; i < len; i++) {
if (paths[i] === NODE_MODULES_PATH) {
paths.splice(i, 0, NODE_MODULES_ASAR_PATH);
break;
}
}
return result;
};
})();
//#endregion
// Get the nls configuration into the process.env as early as possible.
var nlsConfig = { availableLanguages: {} };
const config = process.env['VSCODE_NLS_CONFIG'];
if (config) {
process.env['VSCODE_NLS_CONFIG'] = config;
try {
nlsConfig = JSON.parse(config);
} catch (e) { /*noop*/ }
}
if (nlsConfig._resolvedLanguagePackCoreLocation) {
let bundles = Object.create(null);
nlsConfig.loadBundle = function(bundle, language, cb) {
let result = bundles[bundle];
if (result) {
cb(undefined, result);
return;
}
let bundleFile = path.join(nlsConfig._resolvedLanguagePackCoreLocation, bundle.replace(/\//g, '!') + '.nls.json');
readFile(bundleFile).then(function (content) {
let json = JSON.parse(content);
bundles[bundle] = json;
cb(undefined, json);
}).catch((error) => {
try {
if (nlsConfig._corruptedFile) {
writeFile(nlsConfig._corruptedFile, 'corrupted').catch(function (error) { console.error(error); });
}
} finally {
cb(error, undefined);
}
});
};
}
var locale = nlsConfig.availableLanguages['*'] || 'en';
if (locale === 'zh-tw') {
locale = 'zh-Hant';
} else if (locale === 'zh-cn') {
locale = 'zh-Hans';
}
window.document.documentElement.setAttribute('lang', locale);
const extractKey = function (e) {
return [
e.ctrlKey ? 'ctrl-' : '',
e.metaKey ? 'meta-' : '',
e.altKey ? 'alt-' : '',
e.shiftKey ? 'shift-' : '',
e.keyCode
].join('');
};
const TOGGLE_DEV_TOOLS_KB = (process.platform === 'darwin' ? 'meta-alt-73' : 'ctrl-shift-73'); // mac: Cmd-Alt-I, rest: Ctrl-Shift-I
const RELOAD_KB = (process.platform === 'darwin' ? 'meta-82' : 'ctrl-82'); // mac: Cmd-R, rest: Ctrl-R
window.addEventListener('keydown', function (e) {
const key = extractKey(e);
if (key === TOGGLE_DEV_TOOLS_KB) {
remote.getCurrentWebContents().toggleDevTools();
} else if (key === RELOAD_KB) {
remote.getCurrentWindow().reload();
}
});
// Load the loader
const loaderFilename = configuration.appRoot + '/out/vs/loader.js';
const loaderSource = fs.readFileSync(loaderFilename);
require('vm').runInThisContext(loaderSource, { filename: loaderFilename });
var define = global.define;
global.define = undefined;
window.nodeRequire = require.__$__nodeRequire;
define('fs', ['original-fs'], function (originalFS) { return originalFS; }); // replace the patched electron fs with the original node fs for all AMD code
window.MonacoEnvironment = {};
const rootUrl = uriFromPath(configuration.appRoot) + '/out';
require.config({
baseUrl: rootUrl,
'vs/nls': nlsConfig,
nodeCachedDataDir: configuration.nodeCachedDataDir,
nodeModules: [/*BUILD->INSERT_NODE_MODULES*/]
});
if (nlsConfig.pseudo) {
require(['vs/nls'], function (nlsPlugin) {
nlsPlugin.setPseudoTranslation(nlsConfig.pseudo);
});
}
require([
'vs/code/electron-browser/processExplorer/processExplorerMain'
], function (processExplorer) {
processExplorer.startup(configuration.data);
});
}
main();
bootstrapWindow.load(['vs/code/electron-browser/processExplorer/processExplorerMain'], function (processExplorer, configuration) {
processExplorer.startup(configuration.data);
}, { forceEnableDeveloperKeybindings: true });

View File

@@ -3,11 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./media/processExplorer';
import { listProcesses, ProcessItem } from 'vs/base/node/ps';
import { remote, webFrame, ipcRenderer, clipboard } from 'electron';
import { webFrame, ipcRenderer, clipboard } from 'electron';
import { repeat } from 'vs/base/common/strings';
import { totalmem } from 'os';
import product from 'vs/platform/node/product';
@@ -15,10 +13,15 @@ import { localize } from 'vs/nls';
import { ProcessExplorerStyles, ProcessExplorerData } from 'vs/platform/issue/common/issue';
import * as browser from 'vs/base/browser/browser';
import * as platform from 'vs/base/common/platform';
import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu';
import { popup } from 'vs/base/parts/contextmenu/electron-browser/contextmenu';
let processList: any[];
let mapPidToWindowTitle = new Map<number, string>();
const DEBUG_FLAGS_PATTERN = /\s--(inspect|debug)(-brk|port)?=(\d+)?/;
const DEBUG_PORT_PATTERN = /\s--(inspect|debug)-port=(\d+)/;
function getProcessList(rootProcess: ProcessItem) {
const processes: any[] = [];
@@ -62,6 +65,40 @@ function getProcessItem(processes: any[], item: ProcessItem, indent: number): vo
}
}
function isDebuggable(cmd: string): boolean {
const matches = DEBUG_FLAGS_PATTERN.exec(cmd);
return (matches && matches.length >= 2) || cmd.indexOf('node ') >= 0 || cmd.indexOf('node.exe') >= 0;
}
function attachTo(item: ProcessItem) {
const config: any = {
type: 'node',
request: 'attach',
name: `process ${item.pid}`
};
let matches = DEBUG_FLAGS_PATTERN.exec(item.cmd);
if (matches && matches.length >= 2) {
// attach via port
if (matches.length === 4 && matches[3]) {
config.port = parseInt(matches[3]);
}
config.protocol = matches[1] === 'debug' ? 'legacy' : 'inspector';
} else {
// no port -> try to attach via pid (send SIGUSR1)
config.processId = String(item.pid);
}
// a debug-port=n or inspect-port=n overrides the port
matches = DEBUG_PORT_PATTERN.exec(item.cmd);
if (matches && matches.length === 3) {
// override port
config.port = parseInt(matches[2]);
}
ipcRenderer.send('vscode:workbenchCommand', { id: 'workbench.action.debug.start', from: 'processExplorer', args: [config] });
}
function getProcessIdWithHighestProperty(processList, propertyName: string) {
let max = 0;
let maxProcessId;
@@ -77,16 +114,24 @@ function getProcessIdWithHighestProperty(processList, propertyName: string) {
function updateProcessInfo(processList): void {
const target = document.getElementById('process-list');
if (!target) {
return;
}
const highestCPUProcess = getProcessIdWithHighestProperty(processList, 'cpu');
const highestMemoryProcess = getProcessIdWithHighestProperty(processList, 'memory');
let tableHtml = `
<tr>
<th class="cpu">${localize('cpu', "CPU %")}</th>
<th class="memory">${localize('memory', "Memory (MB)")}</th>
<th class="pid">${localize('pid', "pid")}</th>
<th class="nameLabel">${localize('name', "Name")}</th>
</tr>`;
<thead>
<tr>
<th scope="col" class="cpu">${localize('cpu', "CPU %")}</th>
<th scope="col" class="memory">${localize('memory', "Memory (MB)")}</th>
<th scope="col" class="pid">${localize('pid', "pid")}</th>
<th scope="col" class="nameLabel">${localize('name', "Name")}</th>
</tr>
</thead>`;
tableHtml += `<tbody>`;
processList.forEach(p => {
const cpuClass = p.pid === highestCPUProcess ? 'highest' : '';
@@ -101,7 +146,9 @@ function updateProcessInfo(processList): void {
</tr>`;
});
target.innerHTML = `<table>${tableHtml}</table>`;
tableHtml += `</tbody>`;
target.innerHTML = tableHtml;
}
function applyStyles(styles: ProcessExplorerStyles): void {
@@ -121,7 +168,9 @@ function applyStyles(styles: ProcessExplorerStyles): void {
}
styleTag.innerHTML = content.join('\n');
document.head.appendChild(styleTag);
if (document.head) {
document.head.appendChild(styleTag);
}
document.body.style.color = styles.color;
}
@@ -137,29 +186,29 @@ function applyZoom(zoomLevel: number): void {
function showContextMenu(e) {
e.preventDefault();
const menu = new remote.Menu();
const items: IContextMenuItem[] = [];
const pid = parseInt(e.currentTarget.id);
if (pid && typeof pid === 'number') {
menu.append(new remote.MenuItem({
items.push({
label: localize('killProcess', "Kill Process"),
click() {
process.kill(pid, 'SIGTERM');
}
}));
});
menu.append(new remote.MenuItem({
items.push({
label: localize('forceKillProcess', "Force Kill Process"),
click() {
process.kill(pid, 'SIGKILL');
}
}));
});
menu.append(new remote.MenuItem({
items.push({
type: 'separator'
}));
});
menu.append(new remote.MenuItem({
items.push({
label: localize('copy', "Copy"),
click() {
const row = document.getElementById(pid.toString());
@@ -167,9 +216,9 @@ function showContextMenu(e) {
clipboard.writeText(row.innerText);
}
}
}));
});
menu.append(new remote.MenuItem({
items.push({
label: localize('copyAll', "Copy All"),
click() {
const processList = document.getElementById('process-list');
@@ -177,9 +226,23 @@ function showContextMenu(e) {
clipboard.writeText(processList.innerText);
}
}
}));
});
const item = processList.filter(process => process.pid === pid)[0];
if (item && isDebuggable(item.cmd)) {
items.push({
type: 'separator'
});
items.push({
label: localize('debug', "Debug"),
click() {
attachTo(item);
}
});
}
} else {
menu.append(new remote.MenuItem({
items.push({
label: localize('copyAll', "Copy All"),
click() {
const processList = document.getElementById('process-list');
@@ -187,10 +250,10 @@ function showContextMenu(e) {
clipboard.writeText(processList.innerText);
}
}
}));
});
}
menu.popup({ window: remote.getCurrentWindow() });
popup(items);
}
export function startup(data: ProcessExplorerData): void {
@@ -198,7 +261,7 @@ export function startup(data: ProcessExplorerData): void {
applyZoom(data.zoomLevel);
// Map window process pids to titles, annotate process names with this when rendering to distinguish between them
ipcRenderer.on('windowsInfoResponse', (event, windows) => {
ipcRenderer.on('vscode:windowsInfoResponse', (event, windows) => {
mapPidToWindowTitle = new Map<number, string>();
windows.forEach(window => mapPidToWindowTitle.set(window.pid, window.title));
});
@@ -206,7 +269,7 @@ export function startup(data: ProcessExplorerData): void {
setInterval(() => {
ipcRenderer.send('windowsInfoRequest');
listProcesses(remote.process.pid).then(processes => {
listProcesses(data.pid).then(processes => {
processList = getProcessList(processes);
updateProcessInfo(processList);

View File

@@ -79,11 +79,6 @@
</body>
<script>
const electron = require('electron');
const shell = electron.shell;
const ipc = electron.ipcRenderer;
const remote = electron.remote;
const currentWindow = remote.getCurrentWindow();
function promptForCredentials(data) {
return new Promise((c, e) => {

View File

@@ -2,16 +2,17 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { NodeCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner';
import { LanguagePackCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { StorageDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner';
export function createSharedProcessContributions(service: IInstantiationService): IDisposable {
return combinedDisposable([
service.createInstance(NodeCachedDataCleaner),
service.createInstance(LanguagePackCachedDataCleaner)
service.createInstance(LanguagePackCachedDataCleaner),
service.createInstance(StorageDataCleaner)
]);
}

View File

@@ -2,7 +2,6 @@
* 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 * as path from 'path';
import * as pfs from 'vs/base/node/pfs';
@@ -51,7 +50,7 @@ export class LanguagePackCachedDataCleaner {
}
private _manageCachedDataSoon(): void {
let handle = setTimeout(async () => {
let handle: any = setTimeout(async () => {
handle = undefined;
this._logService.info('Starting to clean up unused language packs.');
const maxAge = product.nameLong.indexOf('Insiders') >= 0

View File

@@ -2,12 +2,10 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { basename, dirname, join } from 'path';
import { onUnexpectedError } from 'vs/base/common/errors';
import { TPromise } from 'vs/base/common/winjs.base';
import { join, basename, dirname } from 'path';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { readdir, rimraf, stat } from 'vs/base/node/pfs';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import product from 'vs/platform/node/product';
@@ -43,13 +41,13 @@ export class NodeCachedDataCleaner {
const nodeCachedDataRootDir = dirname(this._environmentService.nodeCachedDataDir);
const nodeCachedDataCurrent = basename(this._environmentService.nodeCachedDataDir);
let handle = setTimeout(() => {
let handle: any = setTimeout(() => {
handle = undefined;
readdir(nodeCachedDataRootDir).then(entries => {
const now = Date.now();
const deletes: TPromise<any>[] = [];
const deletes: Thenable<any>[] = [];
entries.forEach(entry => {
// name check
@@ -72,9 +70,9 @@ export class NodeCachedDataCleaner {
}
});
return TPromise.join(deletes);
return Promise.all(deletes);
}).done(undefined, onUnexpectedError);
}).then(undefined, onUnexpectedError);
}, 30 * 1000);

View File

@@ -0,0 +1,57 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { join } from 'path';
import { readdir, readFile, rimraf } from 'vs/base/node/pfs';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { IBackupWorkspacesFormat } from 'vs/platform/backup/common/backup';
export class StorageDataCleaner extends Disposable {
// Workspace/Folder storage names are MD5 hashes (128bits / 4 due to hex presentation)
private static NON_EMPTY_WORKSPACE_ID_LENGTH = 128 / 4;
constructor(
@IEnvironmentService private environmentService: IEnvironmentService
) {
super();
this.cleanUpStorageSoon();
}
private cleanUpStorageSoon(): void {
let handle: any = setTimeout(() => {
handle = void 0;
// Leverage the backup workspace file to find out which empty workspace is currently in use to
// determine which empty workspace storage can safely be deleted
readFile(this.environmentService.backupWorkspacesPath, 'utf8').then(contents => {
const workspaces = JSON.parse(contents) as IBackupWorkspacesFormat;
const emptyWorkspaces = workspaces.emptyWorkspaceInfos.map(info => info.backupFolder);
// Read all workspace storage folders that exist
return readdir(this.environmentService.workspaceStorageHome).then(storageFolders => {
const deletes: Promise<void>[] = [];
storageFolders.forEach(storageFolder => {
if (storageFolder.length === StorageDataCleaner.NON_EMPTY_WORKSPACE_ID_LENGTH) {
return;
}
if (emptyWorkspaces.indexOf(storageFolder) === -1) {
deletes.push(rimraf(join(this.environmentService.workspaceStorageHome, storageFolder)));
}
});
return Promise.all(deletes);
});
}).then(null, onUnexpectedError);
}, 30 * 1000);
this._register(toDisposable(() => clearTimeout(handle)));
}
}

View File

@@ -3,168 +3,17 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
//@ts-check
'use strict';
const path = require('path');
const fs = require('fs');
const bootstrap = require('../../../../bootstrap');
const bootstrapWindow = require('../../../../bootstrap-window');
function assign(destination, source) {
return Object.keys(source)
.reduce(function (r, key) { r[key] = source[key]; return r; }, destination);
}
// Avoid Monkey Patches from Application Insights
bootstrap.avoidMonkeyPatchFromAppInsights();
function parseURLQueryArgs() {
const search = window.location.search || '';
return search.split(/[?&]/)
.filter(function (param) { return !!param; })
.map(function (param) { return param.split('='); })
.filter(function (param) { return param.length === 2; })
.reduce(function (r, param) { r[param[0]] = decodeURIComponent(param[1]); return r; }, {});
}
function createScript(src, onload) {
const script = document.createElement('script');
script.src = src;
script.addEventListener('load', onload);
const head = document.getElementsByTagName('head')[0];
head.insertBefore(script, head.lastChild);
}
function uriFromPath(_path) {
var pathName = path.resolve(_path).replace(/\\/g, '/');
if (pathName.length > 0 && pathName.charAt(0) !== '/') {
pathName = '/' + pathName;
}
return encodeURI('file://' + pathName);
}
function readFile(file) {
return new Promise(function(resolve, reject) {
fs.readFile(file, 'utf8', function(err, data) {
if (err) {
reject(err);
return;
}
resolve(data);
});
bootstrapWindow.load(['vs/code/electron-browser/sharedProcess/sharedProcessMain'], function (sharedProcess, configuration) {
sharedProcess.startup({
machineId: configuration.machineId
});
}
const writeFile = (file, content) => new Promise((c, e) => fs.writeFile(file, content, 'utf8', err => err ? e(err) : c()));
function main() {
const args = parseURLQueryArgs();
const configuration = JSON.parse(args['config'] || '{}') || {};
//#region Add support for using node_modules.asar
(function () {
const path = require('path');
const Module = require('module');
let NODE_MODULES_PATH = path.join(configuration.appRoot, 'node_modules');
if (/[a-z]\:/.test(NODE_MODULES_PATH)) {
// Make drive letter uppercase
NODE_MODULES_PATH = NODE_MODULES_PATH.charAt(0).toUpperCase() + NODE_MODULES_PATH.substr(1);
}
const NODE_MODULES_ASAR_PATH = NODE_MODULES_PATH + '.asar';
const originalResolveLookupPaths = Module._resolveLookupPaths;
Module._resolveLookupPaths = function (request, parent, newReturn) {
const result = originalResolveLookupPaths(request, parent, newReturn);
const paths = newReturn ? result : result[1];
for (let i = 0, len = paths.length; i < len; i++) {
if (paths[i] === NODE_MODULES_PATH) {
paths.splice(i, 0, NODE_MODULES_ASAR_PATH);
break;
}
}
return result;
};
})();
//#endregion
// Correctly inherit the parent's environment
assign(process.env, configuration.userEnv);
// Get the nls configuration into the process.env as early as possible.
var nlsConfig = { availableLanguages: {} };
const config = process.env['VSCODE_NLS_CONFIG'];
if (config) {
process.env['VSCODE_NLS_CONFIG'] = config;
try {
nlsConfig = JSON.parse(config);
} catch (e) { /*noop*/ }
}
if (nlsConfig._resolvedLanguagePackCoreLocation) {
let bundles = Object.create(null);
nlsConfig.loadBundle = function(bundle, language, cb) {
let result = bundles[bundle];
if (result) {
cb(undefined, result);
return;
}
let bundleFile = path.join(nlsConfig._resolvedLanguagePackCoreLocation, bundle.replace(/\//g, '!') + '.nls.json');
readFile(bundleFile).then(function (content) {
let json = JSON.parse(content);
bundles[bundle] = json;
cb(undefined, json);
}).catch((error) => {
try {
if (nlsConfig._corruptedFile) {
writeFile(nlsConfig._corruptedFile, 'corrupted').catch(function (error) { console.error(error); });
}
} finally {
cb(error, undefined);
}
});
};
}
var locale = nlsConfig.availableLanguages['*'] || 'en';
if (locale === 'zh-tw') {
locale = 'zh-Hant';
} else if (locale === 'zh-cn') {
locale = 'zh-Hans';
}
window.document.documentElement.setAttribute('lang', locale);
// Load the loader and start loading the workbench
const rootUrl = uriFromPath(configuration.appRoot) + '/out';
// In the bundled version the nls plugin is packaged with the loader so the NLS Plugins
// loads as soon as the loader loads. To be able to have pseudo translation
createScript(rootUrl + '/vs/loader.js', function () {
var define = global.define;
global.define = undefined;
define('fs', ['original-fs'], function (originalFS) { return originalFS; }); // replace the patched electron fs with the original node fs for all AMD code
window.MonacoEnvironment = {};
require.config({
baseUrl: rootUrl,
'vs/nls': nlsConfig,
nodeCachedDataDir: configuration.nodeCachedDataDir,
nodeModules: [/*BUILD->INSERT_NODE_MODULES*/]
});
if (nlsConfig.pseudo) {
require(['vs/nls'], function (nlsPlugin) {
nlsPlugin.setPseudoTranslation(nlsConfig.pseudo);
});
}
require(['vs/code/electron-browser/sharedProcess/sharedProcessMain'], function (sharedProcess) {
sharedProcess.startup({
machineId: configuration.machineId
});
});
});
}
main();
});

View File

@@ -3,20 +3,17 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as fs from 'fs';
import * as platform from 'vs/base/common/platform';
import product from 'vs/platform/node/product';
import pkg from 'vs/platform/node/package';
import { serve, Server, connect } from 'vs/base/parts/ipc/node/ipc.net';
import { TPromise } from 'vs/base/common/winjs.base';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { ExtensionManagementChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
import { ExtensionManagementChannel } from 'vs/platform/extensionManagement/node/extensionManagementIpc';
import { IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService';
@@ -27,22 +24,26 @@ import { RequestService } from 'vs/platform/request/electron-browser/requestServ
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { combinedAppender, NullTelemetryService, ITelemetryAppender, NullAppender, LogAppender } from 'vs/platform/telemetry/common/telemetryUtils';
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc';
import { TelemetryAppenderChannel } from 'vs/platform/telemetry/node/telemetryIpc';
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
import { IWindowsService, ActiveWindowManager } from 'vs/platform/windows/common/windows';
import { WindowsChannelClient } from 'vs/platform/windows/common/windowsIpc';
import { WindowsChannelClient } from 'vs/platform/windows/node/windowsIpc';
import { ipcRenderer } from 'electron';
import { createSharedProcessContributions } from 'vs/code/electron-browser/sharedProcess/contrib/contributions';
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc';
import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/node/logIpc';
import { LocalizationsService } from 'vs/platform/localizations/node/localizations';
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
import { LocalizationsChannel } from 'vs/platform/localizations/common/localizationsIpc';
import { DialogChannelClient } from 'vs/platform/dialogs/common/dialogIpc';
import { LocalizationsChannel } from 'vs/platform/localizations/node/localizationsIpc';
import { DialogChannelClient } from 'vs/platform/dialogs/node/dialogIpc';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { DownloadService } from 'vs/platform/download/node/downloadService';
import { IDownloadService } from 'vs/platform/download/common/download';
import { StaticRouter } from 'vs/base/parts/ipc/node/ipc';
import { DefaultURITransformer } from 'vs/base/common/uriIpc';
export interface ISharedProcessConfiguration {
readonly machineId: string;
@@ -62,12 +63,19 @@ const eventPrefix = 'monacoworkbench';
function main(server: Server, initData: ISharedProcessInitData, configuration: ISharedProcessConfiguration): void {
const services = new ServiceCollection();
const disposables: IDisposable[] = [];
process.once('exit', () => dispose(disposables));
const onExit = () => dispose(disposables);
process.once('exit', onExit);
ipcRenderer.once('handshake:goodbye', onExit);
disposables.push(server);
const environmentService = new EnvironmentService(initData.args, process.execPath);
const mainRoute = () => TPromise.as('main');
const logLevelClient = new LogLevelSetterChannelClient(server.getChannel('loglevel', { routeCall: mainRoute, routeEvent: mainRoute }));
const mainRouter = new StaticRouter(ctx => ctx === 'main');
const logLevelClient = new LogLevelSetterChannelClient(server.getChannel('loglevel', mainRouter));
const logService = new FollowerLogService(logLevelClient, createSpdLogService('sharedprocess', initData.logLevel, environmentService.logsPath));
disposables.push(logService);
@@ -77,14 +85,15 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
services.set(ILogService, logService);
services.set(IConfigurationService, new SyncDescriptor(ConfigurationService));
services.set(IRequestService, new SyncDescriptor(RequestService));
services.set(IDownloadService, new SyncDescriptor(DownloadService));
const windowsChannel = server.getChannel('windows', { routeCall: mainRoute, routeEvent: mainRoute });
const windowsChannel = server.getChannel('windows', mainRouter);
const windowsService = new WindowsChannelClient(windowsChannel);
services.set(IWindowsService, windowsService);
const activeWindowManager = new ActiveWindowManager(windowsService);
const route = () => activeWindowManager.getActiveClientId();
const dialogChannel = server.getChannel('dialog', { routeCall: route, routeEvent: route });
const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id));
const dialogChannel = server.getChannel('dialog', activeWindowRouter);
services.set(IDialogService, new DialogChannelClient(dialogChannel));
const instantiationService = new InstantiationService(services);
@@ -92,27 +101,28 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
instantiationService.invokeFunction(accessor => {
const services = new ServiceCollection();
const environmentService = accessor.get(IEnvironmentService);
const { appRoot, extensionsPath, extensionDevelopmentPath, isBuilt, installSourcePath } = environmentService;
const { appRoot, extensionsPath, extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService;
const telemetryLogService = new FollowerLogService(logLevelClient, createSpdLogService('telemetry', initData.logLevel, environmentService.logsPath));
telemetryLogService.info('The below are logs for every telemetry event sent from VS Code once the log level is set to trace.');
telemetryLogService.info('===========================================================');
let appInsightsAppender: ITelemetryAppender = NullAppender;
if (product.aiConfig && product.aiConfig.asimovKey && isBuilt) {
appInsightsAppender = new AppInsightsAppender(eventPrefix, null, product.aiConfig.asimovKey, telemetryLogService);
disposables.push(appInsightsAppender); // Ensure the AI appender is disposed so that it flushes remaining data
}
server.registerChannel('telemetryAppender', new TelemetryAppenderChannel(appInsightsAppender));
if (!extensionDevelopmentPath && !environmentService.args['disable-telemetry'] && product.enableTelemetry) {
let appInsightsAppender: ITelemetryAppender | null = NullAppender;
if (!extensionDevelopmentLocationURI && !environmentService.args['disable-telemetry'] && product.enableTelemetry) {
if (product.aiConfig && product.aiConfig.asimovKey && isBuilt) {
appInsightsAppender = new AppInsightsAppender(eventPrefix, null, product.aiConfig.asimovKey, telemetryLogService);
disposables.push(appInsightsAppender); // Ensure the AI appender is disposed so that it flushes remaining data
}
const config: ITelemetryServiceConfig = {
appender: combinedAppender(appInsightsAppender, new LogAppender(logService)),
commonProperties: resolveCommonProperties(product.commit, pkg.version, configuration.machineId, installSourcePath),
piiPaths: [appRoot, extensionsPath]
};
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, config));
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config]));
} else {
services.set(ITelemetryService, NullTelemetryService);
}
server.registerChannel('telemetryAppender', new TelemetryAppenderChannel(appInsightsAppender));
services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
@@ -121,8 +131,9 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
const instantiationService2 = instantiationService.createChild(services);
instantiationService2.invokeFunction(accessor => {
const extensionManagementService = accessor.get(IExtensionManagementService);
const channel = new ExtensionManagementChannel(extensionManagementService);
const channel = new ExtensionManagementChannel(extensionManagementService, () => DefaultURITransformer);
server.registerChannel('extensions', channel);
// clean up deprecated extensions
@@ -138,11 +149,11 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
});
}
function setupIPC(hook: string): TPromise<Server> {
function setup(retry: boolean): TPromise<Server> {
function setupIPC(hook: string): Thenable<Server> {
function setup(retry: boolean): Thenable<Server> {
return serve(hook).then(null, err => {
if (!retry || platform.isWindows || err.code !== 'EADDRINUSE') {
return TPromise.wrapError(err);
return Promise.reject(err);
}
// should retry, not windows and eaddrinuse
@@ -151,7 +162,7 @@ function setupIPC(hook: string): TPromise<Server> {
client => {
// we could connect to a running instance. this is not good, abort
client.dispose();
return TPromise.wrapError(new Error('There is an instance already running.'));
return Promise.reject(new Error('There is an instance already running.'));
},
err => {
// it happens on Linux and OS X that the pipe is left behind
@@ -160,7 +171,7 @@ function setupIPC(hook: string): TPromise<Server> {
try {
fs.unlinkSync(hook);
} catch (e) {
return TPromise.wrapError(new Error('Error deleting the shared ipc hook.'));
return Promise.reject(new Error('Error deleting the shared ipc hook.'));
}
return setup(false);
@@ -172,15 +183,14 @@ function setupIPC(hook: string): TPromise<Server> {
return setup(true);
}
function startHandshake(): TPromise<ISharedProcessInitData> {
return new TPromise<ISharedProcessInitData>((c, e) => {
async function handshake(configuration: ISharedProcessConfiguration): Promise<void> {
const data = await new Promise<ISharedProcessInitData>(c => {
ipcRenderer.once('handshake:hey there', (_: any, r: ISharedProcessInitData) => c(r));
ipcRenderer.send('handshake:hello');
});
}
function handshake(configuration: ISharedProcessConfiguration): TPromise<void> {
return startHandshake()
.then(data => setupIPC(data.sharedIPCHandle).then(server => main(server, data, configuration)))
.then(() => ipcRenderer.send('handshake:im ready'));
}
const server = await setupIPC(data.sharedIPCHandle);
main(server, data, configuration);
ipcRenderer.send('handshake:im ready');
}

View File

@@ -0,0 +1,19 @@
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- // {{SQL CARBON EDIT}}
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data: vscode-remote:; media-src 'none'; child-src 'self'; object-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' https:; font-src 'self' https:;">
-->
</head>
<body class="monaco-shell vs-dark" aria-label="">
</body>
<!-- // {{SQL CARBON EDIT}} -->
<script src="../../../../sql/parts/grid/load/loadJquery.js"></script>
<script src="../../../../../node_modules/slickgrid/lib/jquery.event.drag-2.3.0.js"></script>
<script src="../../../../../node_modules/slickgrid/lib/jquery-ui-1.9.2.js"></script>
<!-- Startup via workbench.js -->
<script src="workbench.js"></script>
</html>

View File

@@ -0,0 +1,161 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
//@ts-check
'use strict';
const perf = require('../../../base/common/performance');
perf.mark('renderer/started');
const bootstrapWindow = require('../../../../bootstrap-window');
// Setup shell environment
process['lazyEnv'] = getLazyEnv();
// {{SQL CARBON EDIT}}
/* eslint-disable */
// SQL global imports
const _ = require('underscore')._;
require('slickgrid/slick.core');
const Slick = window.Slick;
require('slickgrid/slick.grid');
require('slickgrid/slick.editors');
require('slickgrid/slick.dataview');
require('reflect-metadata');
require('zone.js');
// {{SQL CARBON EDIT}} - End
// Load workbench main
bootstrapWindow.load([
'vs/workbench/workbench.main',
'vs/nls!vs/workbench/workbench.main',
'vs/css!vs/workbench/workbench.main'
],
function (workbench, configuration) {
perf.mark('didLoadWorkbenchMain');
return process['lazyEnv'].then(function () {
perf.mark('main/startup');
// @ts-ignore
return require('vs/workbench/electron-browser/main').startup(configuration);
});
}, {
removeDeveloperKeybindingsAfterLoad: true,
canModifyDOM: function (windowConfig) {
showPartsSplash(windowConfig);
},
beforeLoaderConfig: function (windowConfig, loaderConfig) {
loaderConfig.recordStats = !!windowConfig.performance;
if (loaderConfig.nodeCachedData) {
const onNodeCachedData = window['MonacoEnvironment'].onNodeCachedData = [];
loaderConfig.nodeCachedData.onData = function () {
onNodeCachedData.push(arguments);
};
}
},
beforeRequire: function () {
perf.mark('willLoadWorkbenchMain');
}
});
/**
* @param {object} configuration
*/
function showPartsSplash(configuration) {
perf.mark('willShowPartsSplash');
let data;
try {
if (!process.env['VSCODE_TEST_STORAGE_MIGRATION']) {
// TODO@Ben remove me after a while
perf.mark('willReadLocalStorage');
let raw = window.localStorage.getItem('storage://global/parts-splash-data');
perf.mark('didReadLocalStorage');
data = JSON.parse(raw);
} else {
data = JSON.parse(configuration.partsSplashData);
}
} catch (e) {
// ignore
}
// high contrast mode has been turned on from the outside, e.g OS -> ignore stored colors and layouts
if (data && configuration.highContrast && data.baseTheme !== 'hc-black') {
data = undefined;
}
// developing an extension -> ignore stored layouts
if (data && configuration.extensionDevelopmentPath) {
data.layoutInfo = undefined;
}
// minimal color configuration (works with or without persisted data)
const baseTheme = data ? data.baseTheme : configuration.highContrast ? 'hc-black' : 'vs-dark';
const shellBackground = data ? data.colorInfo.editorBackground : configuration.highContrast ? '#000000' : '#1E1E1E';
const shellForeground = data ? data.colorInfo.foreground : configuration.highContrast ? '#FFFFFF' : '#CCCCCC';
const style = document.createElement('style');
style.className = 'initialShellColors';
document.head.appendChild(style);
document.body.className = `monaco-shell ${baseTheme}`;
style.innerHTML = `.monaco-shell { background-color: ${shellBackground}; color: ${shellForeground}; }`;
if (data && data.layoutInfo) {
// restore parts if possible (we might not always store layout info)
const { id, layoutInfo, colorInfo } = data;
const splash = document.createElement('div');
splash.id = id;
// ensure there is enough space
layoutInfo.sideBarWidth = Math.min(layoutInfo.sideBarWidth, window.innerWidth - (layoutInfo.activityBarWidth + layoutInfo.editorPartMinWidth));
if (configuration.folderUri || configuration.workspace) {
// folder or workspace -> status bar color, sidebar
splash.innerHTML = `
<div style="position: absolute; width: 100%; left: 0; top: 0; height: ${layoutInfo.titleBarHeight}px; background-color: ${colorInfo.titleBarBackground}; -webkit-app-region: drag;"></div>
<div style="position: absolute; height: calc(100% - ${layoutInfo.titleBarHeight}px); top: ${layoutInfo.titleBarHeight}px; ${layoutInfo.sideBarSide}: 0; width: ${layoutInfo.activityBarWidth}px; background-color: ${colorInfo.activityBarBackground};"></div>
<div style="position: absolute; height: calc(100% - ${layoutInfo.titleBarHeight}px); top: ${layoutInfo.titleBarHeight}px; ${layoutInfo.sideBarSide}: ${layoutInfo.activityBarWidth}px; width: ${layoutInfo.sideBarWidth}px; background-color: ${colorInfo.sideBarBackground};"></div>
<div style="position: absolute; width: 100%; bottom: 0; left: 0; height: ${layoutInfo.statusBarHeight}px; background-color: ${colorInfo.statusBarBackground};"></div>
`;
} else {
// empty -> speical status bar color, no sidebar
splash.innerHTML = `
<div style="position: absolute; width: 100%; left: 0; top: 0; height: ${layoutInfo.titleBarHeight}px; background-color: ${colorInfo.titleBarBackground}; -webkit-app-region: drag;"></div>
<div style="position: absolute; height: calc(100% - ${layoutInfo.titleBarHeight}px); top: ${layoutInfo.titleBarHeight}px; ${layoutInfo.sideBarSide}: 0; width: ${layoutInfo.activityBarWidth}px; background-color: ${colorInfo.activityBarBackground};"></div>
<div style="position: absolute; width: 100%; bottom: 0; left: 0; height: ${layoutInfo.statusBarHeight}px; background-color: ${colorInfo.statusBarNoFolderBackground};"></div>
`;
}
document.body.appendChild(splash);
}
perf.mark('didShowPartsSplash');
}
/**
* @returns {Promise<void>}
*/
function getLazyEnv() {
// @ts-ignore
const ipc = require('electron').ipcRenderer;
return new Promise(function (resolve) {
const handle = setTimeout(function () {
resolve();
console.warn('renderer did not receive lazyEnv in time');
}, 10000);
ipc.once('vscode:acceptShellEnv', function (event, shellEnv) {
clearTimeout(handle);
bootstrapWindow.assign(process.env, shellEnv);
// @ts-ignore
resolve(process.env);
});
ipc.send('vscode:fetchShellEnv');
});
}

View File

@@ -3,23 +3,21 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { app, ipcMain as ipc, systemPreferences } from 'electron';
import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, protocol } from 'electron';
import * as platform from 'vs/base/common/platform';
import { WindowsManager } from 'vs/code/electron-main/windows';
import { IWindowsService, OpenContext, ActiveWindowManager } from 'vs/platform/windows/common/windows';
import { WindowsChannel } from 'vs/platform/windows/common/windowsIpc';
import { WindowsChannel } from 'vs/platform/windows/node/windowsIpc';
import { WindowsService } from 'vs/platform/windows/electron-main/windowsService';
import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
import { ILifecycleService, LifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
import { getShellEnvironment } from 'vs/code/node/shellEnv';
import { IUpdateService } from 'vs/platform/update/common/update';
import { UpdateChannel } from 'vs/platform/update/common/updateIpc';
import { UpdateChannel } from 'vs/platform/update/node/updateIpc';
import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron-main';
import { Server, connect, Client } from 'vs/base/parts/ipc/node/ipc.net';
import { SharedProcess } from 'vs/code/electron-main/sharedProcess';
import { Mutex } from 'windows-mutex';
import { LaunchService, LaunchChannel, ILaunchService } from './launch';
import { LaunchService, LaunchChannel, ILaunchService } from 'vs/platform/launch/electron-main/launchService';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
@@ -28,49 +26,64 @@ 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';
import { URLHandlerChannelClient, URLServiceChannel } from 'vs/platform/url/common/urlIpc';
import { URLHandlerChannelClient, URLServiceChannel } from 'vs/platform/url/node/urlIpc';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform/telemetry/common/telemetryUtils';
import { ITelemetryAppenderChannel, TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc';
import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc';
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
import { getDelayedChannel } from 'vs/base/parts/ipc/common/ipc';
import { getDelayedChannel, StaticRouter } from 'vs/base/parts/ipc/node/ipc';
import product from 'vs/platform/node/product';
import pkg from 'vs/platform/node/package';
import { ProxyAuthHandler } from './auth';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ProxyAuthHandler } from 'vs/code/electron-main/auth';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
import { TPromise } from 'vs/base/common/winjs.base';
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
import { IHistoryMainService } from 'vs/platform/history/common/history';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { CodeWindow } from 'vs/code/electron-main/window';
import { KeyboardLayoutMonitor } from 'vs/code/electron-main/keyboard';
import URI from 'vs/base/common/uri';
import { WorkspacesChannel } from 'vs/platform/workspaces/common/workspacesIpc';
import { URI } from 'vs/base/common/uri';
import { WorkspacesChannel } from 'vs/platform/workspaces/node/workspacesIpc';
import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces';
import { getMachineId } from 'vs/base/node/id';
import { Win32UpdateService } from 'vs/platform/update/electron-main/updateService.win32';
import { LinuxUpdateService } from 'vs/platform/update/electron-main/updateService.linux';
import { DarwinUpdateService } from 'vs/platform/update/electron-main/updateService.darwin';
import { IIssueService } from 'vs/platform/issue/common/issue';
import { IssueChannel } from 'vs/platform/issue/common/issueIpc';
import { IssueChannel } from 'vs/platform/issue/node/issueIpc';
import { IssueService } from 'vs/platform/issue/electron-main/issueService';
import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc';
import { LogLevelSetterChannel } from 'vs/platform/log/node/logIpc';
import * as errors from 'vs/base/common/errors';
import { ElectronURLListener } from 'vs/platform/url/electron-main/electronUrlListener';
import { serve as serveDriver } from 'vs/platform/driver/electron-main/driver';
import { connectRemoteAgentManagement, RemoteAgentConnectionContext } from 'vs/platform/remote/node/remoteAgentConnection';
import { IMenubarService } from 'vs/platform/menubar/common/menubar';
import { MenubarService } from 'vs/platform/menubar/electron-main/menubarService';
import { MenubarChannel } from 'vs/platform/menubar/common/menubarIpc';
import { IUriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay';
import { CodeMenu } from 'vs/code/electron-main/menus';
import { MenubarChannel } from 'vs/platform/menubar/node/menubarIpc';
import { ILabelService, RegisterFormatterEvent } from 'vs/platform/label/common/label';
import { hasArgs } from 'vs/platform/environment/node/argv';
import { RunOnceScheduler } from 'vs/base/common/async';
import { registerContextMenuListener } from 'vs/base/parts/contextmenu/electron-main/contextmenu';
import { THEME_STORAGE_KEY, THEME_BG_STORAGE_KEY } from 'vs/code/electron-main/theme';
import { nativeSep, join } from 'vs/base/common/paths';
import { homedir } from 'os';
import { localize } from 'vs/nls';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from 'vs/platform/remote/node/remoteAgentFileSystemChannel';
import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { SnapUpdateService } from 'vs/platform/update/electron-main/updateService.snap';
import { IStorageMainService, StorageMainService } from 'vs/platform/storage/node/storageMainService';
import { GlobalStorageDatabaseChannel } from 'vs/platform/storage/node/storageIpc';
import { generateUuid } from 'vs/base/common/uuid';
// {{SQL CARBON EDIT}}
import { CodeMenu } from 'sql/workbench/electron-browser/menus';
// {{SQL CARBON EDIT}} - End
export class CodeApplication {
export class CodeApplication extends Disposable {
private static readonly MACHINE_ID_KEY = 'telemetry.machineId';
private toDispose: IDisposable[];
private windowsMainService: IWindowsMainService;
private electronIpcServer: ElectronIPCServer;
@@ -88,9 +101,12 @@ export class CodeApplication {
@IConfigurationService private configurationService: ConfigurationService,
@IStateService private stateService: IStateService,
@IHistoryMainService private historyMainService: IHistoryMainService,
@IUriDisplayService private uriDisplayService: IUriDisplayService
@ILabelService private labelService: ILabelService
) {
this.toDispose = [mainIpcServer, configurationService];
super();
this._register(mainIpcServer);
this._register(configurationService);
this.registerListeners();
}
@@ -102,11 +118,11 @@ export class CodeApplication {
process.on('uncaughtException', err => this.onUnexpectedError(err));
process.on('unhandledRejection', (reason: any, promise: Promise<any>) => errors.onUnexpectedError(reason));
app.on('will-quit', () => {
this.logService.trace('App#will-quit: disposing resources');
// Contextmenu via IPC support
registerContextMenuListener();
this.dispose();
});
// Dispose on shutdown
this.lifecycleService.onWillShutdown(() => this.dispose());
app.on('accessibility-support-changed', (event: Event, accessibilitySupportEnabled: boolean) => {
if (this.windowsMainService) {
@@ -123,40 +139,122 @@ export class CodeApplication {
}
});
const isValidWebviewSource = (source: string): boolean => {
if (!source) {
return false;
}
if (source === 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%09%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E') {
return true;
}
const srcUri: any = URI.parse(source.toLowerCase()).toString();
return srcUri.startsWith(URI.file(this.environmentService.appRoot.toLowerCase()).toString());
};
// Security related measures (https://electronjs.org/docs/tutorial/security)
// DO NOT CHANGE without consulting the documentation
app.on('web-contents-created', (event: any, contents) => {
contents.on('will-attach-webview', (event: Electron.Event, webPreferences, params) => {
// Ensure defaults
delete webPreferences.preload;
webPreferences.nodeIntegration = false;
// Verify URLs being loaded
if (isValidWebviewSource(params.src) && isValidWebviewSource(webPreferences.preloadURL)) {
if (this.isValidWebviewSource(params.src) && this.isValidWebviewSource(webPreferences.preloadURL)) {
return;
}
delete webPreferences.preloadUrl;
// Otherwise prevent loading
this.logService.error('webContents#web-contents-created: Prevented webview attach');
event.preventDefault();
});
contents.on('will-navigate', event => {
this.logService.error('webContents#will-navigate: Prevented webcontent navigation');
event.preventDefault();
});
contents.on('new-window', (event: Event, url: string) => {
event.preventDefault(); // prevent code that wants to open links
shell.openExternal(url);
});
});
const connectionPool: Map<string, ActiveConnection> = new Map<string, ActiveConnection>();
class ActiveConnection {
private _authority: string;
private _client: TPromise<Client<RemoteAgentConnectionContext>>;
private _disposeRunner: RunOnceScheduler;
constructor(authority: string, host: string, port: number) {
this._authority = authority;
this._client = connectRemoteAgentManagement(authority, host, port, `main`);
this._disposeRunner = new RunOnceScheduler(() => this._dispose(), 5000);
}
private _dispose(): void {
this._disposeRunner.dispose();
connectionPool.delete(this._authority);
this._client.then((connection) => {
connection.dispose();
});
}
public getClient(): TPromise<Client<RemoteAgentConnectionContext>> {
this._disposeRunner.schedule();
return this._client;
}
}
const resolvedAuthorities = new Map<string, ResolvedAuthority>();
ipc.on('vscode:remoteAuthorityResolved', (event: any, data: ResolvedAuthority) => {
resolvedAuthorities.set(data.authority, data);
});
const resolveAuthority = (authority: string): ResolvedAuthority | null => {
if (authority.indexOf('+') >= 0) {
if (resolvedAuthorities.has(authority)) {
return resolvedAuthorities.get(authority);
}
return null;
} else {
const [host, strPort] = authority.split(':');
const port = parseInt(strPort, 10);
return { authority, host, port, syncExtensions: false };
}
};
protocol.registerBufferProtocol(REMOTE_HOST_SCHEME, async (request, callback) => {
if (request.method !== 'GET') {
return callback(null);
}
const uri = URI.parse(request.url);
let activeConnection: ActiveConnection = null;
if (connectionPool.has(uri.authority)) {
activeConnection = connectionPool.get(uri.authority);
} else {
let resolvedAuthority = resolveAuthority(uri.authority);
if (!resolvedAuthority) {
callback(null);
return;
}
activeConnection = new ActiveConnection(uri.authority, resolvedAuthority.host, resolvedAuthority.port);
connectionPool.set(uri.authority, activeConnection);
}
try {
const rawClient = await activeConnection.getClient();
if (connectionPool.has(uri.authority)) { // not disposed in the meantime
const channel = rawClient.getChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME);
// TODO@alex don't use call directly, wrap it around a `RemoteExtensionsFileSystemProvider`
const fileContents = await channel.call<Uint8Array>('readFile', [uri]);
callback(Buffer.from(fileContents));
} else {
callback(null);
}
} catch (err) {
errors.onUnexpectedError(err);
callback(null);
}
});
let macOpenFileURIs: URI[] = [];
let runningTimeout: number = null;
let runningTimeout: any = null;
app.on('open-file', (event: Event, path: string) => {
this.logService.trace('App#open-file: ', path);
event.preventDefault();
@@ -189,15 +287,15 @@ export class CodeApplication {
this.windowsMainService.openNewWindow(OpenContext.DESKTOP); //macOS native tab "+" button
});
ipc.on('vscode:exit', (event: any, code: number) => {
ipc.on('vscode:exit', (event: Event, code: number) => {
this.logService.trace('IPC#vscode:exit', code);
this.dispose();
this.lifecycleService.kill(code);
});
ipc.on('vscode:fetchShellEnv', event => {
const webContents = event.sender.webContents;
ipc.on('vscode:fetchShellEnv', (event: Event) => {
const webContents = event.sender;
getShellEnvironment().then(shellEnv => {
if (!webContents.isDestroyed()) {
webContents.send('vscode:acceptShellEnv', shellEnv);
@@ -211,7 +309,7 @@ export class CodeApplication {
});
});
ipc.on('vscode:broadcast', (event: any, windowId: number, broadcast: { channel: string; payload: any; }) => {
ipc.on('vscode:broadcast', (event: Event, windowId: number, broadcast: { channel: string; payload: any; }) => {
if (this.windowsMainService && broadcast.channel && !isUndefinedOrNull(broadcast.payload)) {
this.logService.trace('IPC#vscode:broadcast', broadcast.channel, broadcast.payload);
@@ -223,8 +321,20 @@ export class CodeApplication {
}
});
ipc.on('vscode:uriDisplayRegisterFormater', (event: any, { scheme, formater }) => {
this.uriDisplayService.registerFormater(scheme, formater);
ipc.on('vscode:labelRegisterFormatter', (event: any, data: RegisterFormatterEvent) => {
this.labelService.registerFormatter(data.selector, data.formatter);
});
ipc.on('vscode:toggleDevTools', (event: Event) => {
event.sender.toggleDevTools();
});
ipc.on('vscode:openDevTools', (event: Event) => {
event.sender.openDevTools();
});
ipc.on('vscode:reloadWindow', (event: Event) => {
event.sender.reload();
});
// Keyboard layout changes
@@ -235,6 +345,20 @@ export class CodeApplication {
});
}
private isValidWebviewSource(source: string): boolean {
if (!source) {
return false;
}
if (source === 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%09%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E') {
return true;
}
const srcUri: any = URI.parse(source).fsPath.toLowerCase();
const rootUri = URI.file(this.environmentService.appRoot).fsPath.toLowerCase();
return srcUri.startsWith(rootUri + nativeSep);
}
private onUnexpectedError(err: Error): void {
if (err) {
@@ -262,8 +386,8 @@ export class CodeApplication {
if (event === 'vscode:changeColorTheme' && typeof payload === 'string') {
let data = JSON.parse(payload);
this.stateService.setItem(CodeWindow.themeStorageKey, data.id);
this.stateService.setItem(CodeWindow.themeBackgroundStorageKey, data.background);
this.stateService.setItem(THEME_STORAGE_KEY, data.baseTheme);
this.stateService.setItem(THEME_BG_STORAGE_KEY, data.background);
}
}
@@ -288,7 +412,7 @@ export class CodeApplication {
// See: https://github.com/Microsoft/vscode/issues/35361#issuecomment-399794085
try {
if (platform.isMacintosh && this.configurationService.getValue<boolean>('window.nativeTabs') === true && !systemPreferences.getUserDefault('NSUseImprovedLayoutPass', 'boolean')) {
systemPreferences.registerDefaults({ NSUseImprovedLayoutPass: true });
systemPreferences.setUserDefault('NSUseImprovedLayoutPass', 'boolean', true as any);
}
} catch (error) {
this.logService.error(error);
@@ -303,37 +427,73 @@ export class CodeApplication {
this.logService.trace(`Resolved machine identifier: ${machineId}`);
// Spawn shared process
this.sharedProcess = new SharedProcess(this.environmentService, this.lifecycleService, this.logService, machineId, this.userEnv);
this.sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv);
this.sharedProcessClient = this.sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main'));
// Services
const appInstantiationService = this.initServices(machineId);
return this.initServices(machineId).then(appInstantiationService => {
let promise: TPromise<any> = TPromise.as(null);
// Create driver
if (this.environmentService.driverHandle) {
serveDriver(this.electronIpcServer, this.environmentService.driverHandle, this.environmentService, appInstantiationService).then(server => {
this.logService.info('Driver started at:', this.environmentService.driverHandle);
this.toDispose.push(server);
});
}
return promise.then(() => {
// Create driver
if (this.environmentService.driverHandle) {
serveDriver(this.electronIpcServer, this.environmentService.driverHandle, this.environmentService, appInstantiationService).then(server => {
this.logService.info('Driver started at:', this.environmentService.driverHandle);
this._register(server);
});
}
// Setup Auth Handler
const authHandler = appInstantiationService.createInstance(ProxyAuthHandler);
this.toDispose.push(authHandler);
this._register(authHandler);
// Open Windows
appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor));
const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor));
// Post Open Windows Tasks
appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor));
// Tracing: Stop tracing after windows are ready if enabled
if (this.environmentService.args.trace) {
this.stopTracingEventually(windows);
}
});
});
}
private stopTracingEventually(windows: ICodeWindow[]): void {
this.logService.info(`Tracing: waiting for windows to get ready...`);
let recordingStopped = false;
const stopRecording = (timeout) => {
if (recordingStopped) {
return;
}
recordingStopped = true; // only once
contentTracing.stopRecording(join(homedir(), `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`), path => {
if (!timeout) {
this.windowsMainService.showMessageBox({
type: 'info',
message: localize('trace.message', "Successfully created trace."),
detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path),
buttons: [localize('trace.ok', "Ok")]
}, this.windowsMainService.getLastActiveWindow());
} else {
this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`);
}
});
};
// Wait up to 30s before creating the trace anyways
const timeoutHandle = setTimeout(() => stopRecording(true), 30000);
// Wait for all windows to get ready and stop tracing then
TPromise.join(windows.map(window => window.ready())).then(() => {
clearTimeout(timeoutHandle);
stopRecording(false);
});
}
private resolveMachineId(): TPromise<string> {
const machineId = this.stateService.getItem<string>(CodeApplication.MACHINE_ID_KEY);
if (machineId) {
@@ -349,40 +509,84 @@ export class CodeApplication {
});
}
private initServices(machineId: string): IInstantiationService {
private initServices(machineId: string): Thenable<IInstantiationService> {
const services = new ServiceCollection();
if (process.platform === 'win32') {
services.set(IUpdateService, new SyncDescriptor(Win32UpdateService));
} else if (process.platform === 'linux') {
services.set(IUpdateService, new SyncDescriptor(LinuxUpdateService));
if (process.env.SNAP && process.env.SNAP_REVISION) {
services.set(IUpdateService, new SyncDescriptor(SnapUpdateService));
} else {
services.set(IUpdateService, new SyncDescriptor(LinuxUpdateService));
}
} else if (process.platform === 'darwin') {
services.set(IUpdateService, new SyncDescriptor(DarwinUpdateService));
}
services.set(IWindowsMainService, new SyncDescriptor(WindowsManager, machineId));
services.set(IWindowsService, new SyncDescriptor(WindowsService, this.sharedProcess));
services.set(IWindowsMainService, new SyncDescriptor(WindowsManager, [machineId]));
services.set(IWindowsService, new SyncDescriptor(WindowsService, [this.sharedProcess]));
services.set(ILaunchService, new SyncDescriptor(LaunchService));
services.set(IIssueService, new SyncDescriptor(IssueService, machineId, this.userEnv));
services.set(IIssueService, new SyncDescriptor(IssueService, [machineId, this.userEnv]));
services.set(IMenubarService, new SyncDescriptor(MenubarService));
services.set(IStorageMainService, new SyncDescriptor(StorageMainService));
// Telemtry
// Telemetry
if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
const channel = getDelayedChannel<ITelemetryAppenderChannel>(this.sharedProcessClient.then(c => c.getChannel('telemetryAppender')));
const channel = getDelayedChannel(this.sharedProcessClient.then(c => c.getChannel('telemetryAppender')));
const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(this.logService));
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));
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config]));
} else {
services.set(ITelemetryService, NullTelemetryService);
}
return this.instantiationService.createChild(services);
const appInstantiationService = this.instantiationService.createChild(services);
return appInstantiationService.invokeFunction(accessor => this.initStorageService(accessor)).then(() => appInstantiationService);
}
private openFirstWindow(accessor: ServicesAccessor): void {
private initStorageService(accessor: ServicesAccessor): Thenable<void> {
const storageMainService = accessor.get(IStorageMainService) as StorageMainService;
// Ensure to close storage on shutdown
this.lifecycleService.onWillShutdown(e => e.join(storageMainService.close()));
// Initialize storage service
return storageMainService.initialize().then(void 0, error => {
errors.onUnexpectedError(error);
this.logService.error(error);
}).then(() => {
// Apply global telemetry values as part of the initialization
// These are global across all windows and thereby should be
// written from the main process once.
const telemetryInstanceId = 'telemetry.instanceId';
const instanceId = storageMainService.get(telemetryInstanceId, null);
if (instanceId === null) {
storageMainService.store(telemetryInstanceId, generateUuid());
}
const telemetryFirstSessionDate = 'telemetry.firstSessionDate';
const firstSessionDate = storageMainService.get(telemetryFirstSessionDate, null);
if (firstSessionDate === null) {
storageMainService.store(telemetryFirstSessionDate, new Date().toUTCString());
}
const telemetryCurrentSessionDate = 'telemetry.currentSessionDate';
const telemetryLastSessionDate = 'telemetry.lastSessionDate';
const lastSessionDate = storageMainService.get(telemetryCurrentSessionDate, null); // previous session date was the "current" one at that time
const currentSessionDate = new Date().toUTCString(); // current session date is "now"
storageMainService.store(telemetryLastSessionDate, lastSessionDate);
storageMainService.store(telemetryCurrentSessionDate, currentSessionDate);
});
}
private openFirstWindow(accessor: ServicesAccessor): ICodeWindow[] {
const appInstantiationService = accessor.get(IInstantiationService);
// Register more Main IPC services
@@ -406,7 +610,7 @@ export class CodeApplication {
const windowsService = accessor.get(IWindowsService);
const windowsChannel = new WindowsChannel(windowsService);
this.electronIpcServer.registerChannel('windows', windowsChannel);
this.sharedProcessClient.done(client => client.registerChannel('windows', windowsChannel));
this.sharedProcessClient.then(client => client.registerChannel('windows', windowsChannel));
const menubarService = accessor.get(IMenubarService);
const menubarChannel = new MenubarChannel(menubarService);
@@ -416,13 +620,17 @@ export class CodeApplication {
const urlChannel = new URLServiceChannel(urlService);
this.electronIpcServer.registerChannel('url', urlChannel);
const storageMainService = accessor.get(IStorageMainService);
const storageChannel = this._register(new GlobalStorageDatabaseChannel(storageMainService as StorageMainService));
this.electronIpcServer.registerChannel('storage', storageChannel);
// Log level management
const logLevelChannel = new LogLevelSetterChannel(accessor.get(ILogService));
this.electronIpcServer.registerChannel('loglevel', logLevelChannel);
this.sharedProcessClient.done(client => client.registerChannel('loglevel', logLevelChannel));
this.sharedProcessClient.then(client => client.registerChannel('loglevel', logLevelChannel));
// Lifecycle
this.lifecycleService.ready();
(this.lifecycleService as LifecycleService).ready();
// Propagate to clients
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); // TODO@Joao: unfold this
@@ -431,8 +639,8 @@ export class CodeApplication {
// Create a URL handler which forwards to the last active window
const activeWindowManager = new ActiveWindowManager(windowsService);
const route = () => activeWindowManager.getActiveClientId();
const urlHandlerChannel = this.electronIpcServer.getChannel('urlHandler', { routeCall: route, routeEvent: route });
const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id));
const urlHandlerChannel = this.electronIpcServer.getChannel('urlHandler', activeWindowRouter);
const multiplexURLHandler = new URLHandlerChannelClient(urlHandlerChannel);
// On Mac, Code can be running without any open windows, so we must create a window to handle urls,
@@ -460,33 +668,39 @@ export class CodeApplication {
// Watch Electron URLs and forward them to the UrlService
const urls = args['open-url'] ? args._urls : [];
const urlListener = new ElectronURLListener(urls, urlService, this.windowsMainService);
this.toDispose.push(urlListener);
this._register(urlListener);
this.windowsMainService.ready(this.userEnv);
// Open our first window
const macOpenFiles = (<any>global).macOpenFiles as string[];
const context = !!process.env['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP;
if (args['new-window'] && args._.length === 0 && (args['folder-uri'] || []).length === 0) {
this.windowsMainService.open({ context, cli: args, forceNewWindow: true, forceEmpty: true, initialStartup: true }); // new window if "-n" was used without paths
} else if (macOpenFiles && macOpenFiles.length && (!args._ || !args._.length || !args['folder-uri'] || !args['folder-uri'].length)) {
this.windowsMainService.open({ context: OpenContext.DOCK, cli: args, urisToOpen: macOpenFiles.map(file => URI.file(file)), initialStartup: true }); // mac: open-file event received on startup
} else {
this.windowsMainService.open({ context, cli: args, forceNewWindow: args['new-window'] || (!args._.length && args['unity-launch']), diffMode: args.diff, initialStartup: true }); // default: read paths from cli
const hasCliArgs = hasArgs(args._);
const hasFolderURIs = hasArgs(args['folder-uri']);
const hasFileURIs = hasArgs(args['file-uri']);
if (args['new-window'] && !hasCliArgs && !hasFolderURIs && !hasFileURIs) {
return this.windowsMainService.open({ context, cli: args, forceNewWindow: true, forceEmpty: true, initialStartup: true }); // new window if "-n" was used without paths
}
if (macOpenFiles && macOpenFiles.length && !hasCliArgs && !hasFolderURIs && !hasFileURIs) {
return this.windowsMainService.open({ context: OpenContext.DOCK, cli: args, urisToOpen: macOpenFiles.map(file => URI.file(file)), initialStartup: true }); // mac: open-file event received on startup
}
return this.windowsMainService.open({ context, cli: args, forceNewWindow: args['new-window'] || (!hasCliArgs && args['unity-launch']), diffMode: args.diff, initialStartup: true }); // default: read paths from cli
}
private afterWindowOpen(accessor: ServicesAccessor): void {
const windowsMainService = accessor.get(IWindowsMainService);
let windowsMutex: Mutex = null;
let windowsMutex: Mutex | null = null;
if (platform.isWindows) {
// Setup Windows mutex
try {
const Mutex = (require.__$__nodeRequire('windows-mutex') as any).Mutex;
windowsMutex = new Mutex(product.win32MutexName);
this.toDispose.push({ dispose: () => windowsMutex.release() });
this._register(toDisposable(() => windowsMutex.release()));
} catch (e) {
if (!this.environmentService.isBuilt) {
windowsMainService.showMessageBox({
@@ -516,23 +730,21 @@ export class CodeApplication {
}
}
// TODO@sbatten: Remove when switching back to dynamic menu
// {{SQL CARBON EDIT}} - Use static menu for now
// Install Menu
const instantiationService = accessor.get(IInstantiationService);
const configurationService = accessor.get(IConfigurationService);
if (platform.isMacintosh || configurationService.getValue<string>('window.titleBarStyle') !== 'custom') {
instantiationService.createInstance(CodeMenu);
}
// {{SQL CARBON EDIT}} - End
// Jump List
this.historyMainService.updateWindowsJumpList();
this.historyMainService.onRecentlyOpenedChange(() => this.historyMainService.updateWindowsJumpList());
// Start shared process after a while
TPromise.timeout(3000).then(() => this.sharedProcess.spawn());
}
private dispose(): void {
this.toDispose = dispose(this.toDispose);
const sharedProcessSpawn = this._register(new RunOnceScheduler(() => getShellEnvironment().then(userEnv => this.sharedProcess.spawn(userEnv)), 3000));
sharedProcessSpawn.schedule();
}
}

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { localize } from 'vs/nls';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
@@ -14,8 +12,8 @@ import { BrowserWindow, app } from 'electron';
type LoginEvent = {
event: Electron.Event;
webContents: Electron.WebContents;
req: Electron.LoginRequest;
authInfo: Electron.LoginAuthInfo;
req: Electron.Request;
authInfo: Electron.AuthInfo;
cb: (username: string, password: string) => void;
};
@@ -56,10 +54,7 @@ export class ProxyAuthHandler {
width: 450,
height: 220,
show: true,
title: 'VS Code',
webPreferences: {
disableBlinkFeatures: 'Auxclick'
}
title: 'VS Code'
};
const focusedWindow = this.windowsMainService.getFocusedWindow();

View File

@@ -1,304 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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';
import URI from 'vs/base/common/uri';
export interface VersionInfo {
vscodeVersion: string;
os: string;
}
export interface SystemInfo {
CPUs?: string;
'Memory (System)': string;
'Load (avg)'?: string;
VM: string;
'Screen Reader': string;
'Process Argv': string;
'GPU Status': Electron.GPUFeatureStatus;
}
export interface ProcessInfo {
cpu: number;
memory: number;
pid: number;
name: string;
}
export interface PerformanceInfo {
processInfo?: string;
workspaceInfo?: string;
}
export function getPerformanceInfo(info: IMainProcessInfo): Promise<PerformanceInfo> {
return listProcesses(info.mainPID).then(rootProcess => {
const workspaceInfoMessages = [];
// Workspace Stats
const workspaceStatPromises = [];
if (info.windows.some(window => window.folderURIs && window.folderURIs.length > 0)) {
info.windows.forEach(window => {
if (window.folderURIs.length === 0) {
return;
}
workspaceInfoMessages.push(`| Window (${window.title})`);
window.folderURIs.forEach(uriComponents => {
const folderUri = URI.revive(uriComponents);
if (folderUri.scheme === 'file') {
const folder = folderUri.fsPath;
workspaceStatPromises.push(collectWorkspaceStats(folder, ['node_modules', '.git']).then(async stats => {
let countMessage = `${stats.fileCount} files`;
if (stats.maxFilesReached) {
countMessage = `more than ${countMessage}`;
}
workspaceInfoMessages.push(`| Folder (${basename(folder)}): ${countMessage}`);
workspaceInfoMessages.push(formatWorkspaceStats(stats));
const launchConfigs = await collectLaunchConfigs(folder);
if (launchConfigs.length > 0) {
workspaceInfoMessages.push(formatLaunchConfigs(launchConfigs));
}
}));
} else {
workspaceInfoMessages.push(`| Folder (${folderUri.toString()}): RPerformance stats not available.`);
}
});
});
}
return Promise.all(workspaceStatPromises).then(() => {
return {
processInfo: formatProcessList(info, rootProcess),
workspaceInfo: workspaceInfoMessages.join('\n')
};
}).catch(error => {
return {
processInfo: formatProcessList(info, rootProcess),
workspaceInfo: `Unable to calculate workspace stats: ${error}`
};
});
});
}
export function getSystemInfo(info: IMainProcessInfo): SystemInfo {
const MB = 1024 * 1024;
const GB = 1024 * MB;
const systemInfo: SystemInfo = {
'Memory (System)': `${(os.totalmem() / GB).toFixed(2)}GB (${(os.freemem() / GB).toFixed(2)}GB free)`,
VM: `${Math.round((virtualMachineHint.value() * 100))}%`,
'Screen Reader': `${app.isAccessibilitySupportEnabled() ? 'yes' : 'no'}`,
'Process Argv': `${info.mainArguments.join(' ')}`,
'GPU Status': app.getGPUFeatureStatus()
};
const cpus = os.cpus();
if (cpus && cpus.length > 0) {
systemInfo.CPUs = `${cpus[0].model} (${cpus.length} x ${cpus[0].speed})`;
}
if (!isWindows) {
systemInfo['Load (avg)'] = `${os.loadavg().map(l => Math.round(l)).join(', ')}`;
}
return systemInfo;
}
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
const workspaceStatPromises = [];
if (info.windows.some(window => window.folderURIs && window.folderURIs.length > 0)) {
console.log('');
console.log('Workspace Stats: ');
info.windows.forEach(window => {
if (window.folderURIs.length === 0) {
return;
}
console.log(`| Window (${window.title})`);
window.folderURIs.forEach(uriComponents => {
const folderUri = URI.revive(uriComponents);
if (folderUri.scheme === 'file') {
const folder = folderUri.fsPath;
workspaceStatPromises.push(collectWorkspaceStats(folder, ['node_modules', '.git']).then(async stats => {
let countMessage = `${stats.fileCount} files`;
if (stats.maxFilesReached) {
countMessage = `more than ${countMessage}`;
}
console.log(`| Folder (${basename(folder)}): ${countMessage}`);
console.log(formatWorkspaceStats(stats));
await collectLaunchConfigs(folder).then(launchConfigs => {
if (launchConfigs.length > 0) {
console.log(formatLaunchConfigs(launchConfigs));
}
});
}).catch(error => {
console.log(`| Error: Unable to collect workspace stats for folder ${folder} (${error.toString()})`);
}));
} else {
console.log(`| Folder (${folderUri.toString()}): Workspace stats not available.`);
}
});
});
}
return Promise.all(workspaceStatPromises).then(() => {
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 expandGPUFeatures(): string {
const gpuFeatures = app.getGPUFeatureStatus();
const longestFeatureName = Math.max(...Object.keys(gpuFeatures).map(feature => feature.length));
// Make columns aligned by adding spaces after feature name
return Object.keys(gpuFeatures).map(feature => `${feature}: ${repeat(' ', longestFeatureName - feature.length)} ${gpuFeatures[feature]}`).join('\n ');
}
export 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'}`);
output.push(`Process Argv: ${info.mainArguments.join(' ')}`);
output.push(`GPU Status: ${expandGPUFeatures()}`);
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\t PID\tProcess');
if (rootProcess) {
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${pad(Number((item.pid).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

@@ -3,18 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nativeKeymap from 'native-keymap';
import { IDisposable } from 'vs/base/common/lifecycle';
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';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ipcMain as ipc } from 'electron';
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
import { ILogService } from 'vs/platform/log/common/log';
import { Emitter } from 'vs/base/common/event';
export class KeyboardLayoutMonitor {
@@ -38,109 +29,4 @@ export class KeyboardLayoutMonitor {
}
return this._emitter.event(callback);
}
}
export interface IKeybinding {
id: string;
label: string;
isNative: boolean;
}
export class KeybindingsResolver {
private static readonly lastKnownKeybindingsMapStorageKey = 'lastKnownKeybindings';
private commandIds: Set<string>;
private keybindings: { [commandId: string]: IKeybinding };
private keybindingsWatcher: ConfigWatcher<IUserFriendlyKeybinding[]>;
private _onKeybindingsChanged = new Emitter<void>();
onKeybindingsChanged: Event<void> = this._onKeybindingsChanged.event;
constructor(
@IStateService private stateService: IStateService,
@IEnvironmentService environmentService: IEnvironmentService,
@IWindowsMainService private windowsMainService: IWindowsMainService,
@ILogService private logService: ILogService
) {
this.commandIds = new Set<string>();
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();
}
private registerListeners(): void {
// Listen to resolved keybindings from window
ipc.on('vscode:keybindingsResolved', (event, rawKeybindings: string) => {
let keybindings: IKeybinding[] = [];
try {
keybindings = JSON.parse(rawKeybindings);
} catch (error) {
// Should not happen
}
// Fill hash map of resolved keybindings and check for changes
let keybindingsChanged = false;
let keybindingsCount = 0;
const resolvedKeybindings: { [commandId: string]: IKeybinding } = Object.create(null);
keybindings.forEach(keybinding => {
keybindingsCount++;
resolvedKeybindings[keybinding.id] = keybinding;
if (!this.keybindings[keybinding.id] || keybinding.label !== this.keybindings[keybinding.id].label) {
keybindingsChanged = true;
}
});
// A keybinding might have been unassigned, so we have to account for that too
if (Object.keys(this.keybindings).length !== keybindingsCount) {
keybindingsChanged = true;
}
if (keybindingsChanged) {
this.keybindings = resolvedKeybindings;
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.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.windowsMainService.onWindowReload(() => this.resolveKeybindings());
}
private resolveKeybindings(win = this.windowsMainService.getLastActiveWindow()): void {
if (this.commandIds.size && win) {
const commandIds: string[] = [];
this.commandIds.forEach(id => commandIds.push(id));
win.sendWhenReady('vscode:resolveKeybindings', JSON.stringify(commandIds));
}
}
public getKeybinding(commandId: string): IKeybinding {
if (!commandId) {
return void 0;
}
if (!this.commandIds.has(commandId)) {
this.commandIds.add(commandId);
}
return this.keybindings[commandId];
}
public dispose(): void {
this._onKeybindingsChanged.dispose();
this.keybindingsWatcher.dispose();
}
}

View File

@@ -1,307 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 { TPromise } from 'vs/base/common/winjs.base';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { ILogService } from 'vs/platform/log/common/log';
import { IURLService } from 'vs/platform/url/common/url';
import { IProcessEnvironment, isMacintosh } from 'vs/base/common/platform';
import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/environment';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { OpenContext, IWindowSettings } 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';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import URI, { UriComponents } from 'vs/base/common/uri';
import { BrowserWindow } from 'electron';
import { Event } from 'vs/base/common/event';
export const ID = 'launchService';
export const ILaunchService = createDecorator<ILaunchService>(ID);
export interface IStartArguments {
args: ParsedArgs;
userEnv: IProcessEnvironment;
}
export interface IWindowInfo {
pid: number;
title: string;
folderURIs: UriComponents[];
}
export interface IMainProcessInfo {
mainPID: number;
mainArguments: string[];
windows: IWindowInfo[];
}
function parseOpenUrl(args: ParsedArgs): URI[] {
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
return args._urls
.map(url => {
try {
return URI.parse(url);
} catch (err) {
return null;
}
})
.filter(uri => !!uri);
}
return [];
}
export interface ILaunchService {
_serviceBrand: any;
start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void>;
getMainProcessId(): TPromise<number>;
getMainProcessInfo(): TPromise<IMainProcessInfo>;
getLogsPath(): TPromise<string>;
}
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: 'get-logs-path', arg: null): TPromise<string>;
call(command: string, arg: any): TPromise<any>;
}
export class LaunchChannel implements ILaunchChannel {
constructor(private service: ILaunchService) { }
listen<T>(event: string): Event<T> {
throw new Error('No event found');
}
call(command: string, arg: any): TPromise<any> {
switch (command) {
case 'start':
const { args, userEnv } = arg as IStartArguments;
return this.service.start(args, userEnv);
case 'get-main-process-id':
return this.service.getMainProcessId();
case 'get-main-process-info':
return this.service.getMainProcessInfo();
case 'get-logs-path':
return this.service.getLogsPath();
}
return undefined;
}
}
export class LaunchChannelClient implements ILaunchService {
_serviceBrand: any;
constructor(private channel: ILaunchChannel) { }
start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void> {
return this.channel.call('start', { args, userEnv });
}
getMainProcessId(): TPromise<number> {
return this.channel.call('get-main-process-id', null);
}
getMainProcessInfo(): TPromise<IMainProcessInfo> {
return this.channel.call('get-main-process-info', null);
}
getLogsPath(): TPromise<string> {
return this.channel.call('get-logs-path', null);
}
}
export class LaunchService implements ILaunchService {
_serviceBrand: any;
constructor(
@ILogService private logService: ILogService,
@IWindowsMainService private windowsMainService: IWindowsMainService,
@IURLService private urlService: IURLService,
@IWorkspacesMainService private workspacesMainService: IWorkspacesMainService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IConfigurationService private readonly configurationService: IConfigurationService
) { }
start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void> {
this.logService.trace('Received data from other instance: ', args, userEnv);
const urlsToOpen = parseOpenUrl(args);
// Check early for open-url which is handled in URL service
if (urlsToOpen.length) {
let whenWindowReady = TPromise.as<any>(null);
// Create a window if there is none
if (this.windowsMainService.getWindowCount() === 0) {
const window = this.windowsMainService.openNewWindow(OpenContext.DESKTOP)[0];
whenWindowReady = window.ready();
}
// Make sure a window is open, ready to receive the url event
whenWindowReady.then(() => {
for (const url of urlsToOpen) {
this.urlService.open(url);
}
});
return TPromise.as(null);
}
// Otherwise handle in windows service
return this.startOpenWindow(args, userEnv);
}
private startOpenWindow(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void> {
const context = !!userEnv['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP;
let usedWindows: ICodeWindow[];
// Special case extension development
if (!!args.extensionDevelopmentPath) {
this.windowsMainService.openExtensionDevelopmentHostWindow({ context, cli: args, userEnv });
}
// Start without file/folder arguments
else if (args._.length === 0 && (args['folder-uri'] || []).length === 0) {
let openNewWindow = false;
// Force new window
if (args['new-window'] || args['unity-launch']) {
openNewWindow = true;
}
// Force reuse window
else if (args['reuse-window']) {
openNewWindow = false;
}
// Otherwise check for settings
else {
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
const openWithoutArgumentsInNewWindowConfig = (windowConfig && windowConfig.openWithoutArgumentsInNewWindow) || 'default' /* default */;
switch (openWithoutArgumentsInNewWindowConfig) {
case 'on':
openNewWindow = true;
break;
case 'off':
openNewWindow = false;
break;
default:
openNewWindow = !isMacintosh; // prefer to restore running instance on macOS
}
}
if (openNewWindow) {
usedWindows = this.windowsMainService.open({ context, cli: args, userEnv, forceNewWindow: true, forceEmpty: true });
} else {
usedWindows = [this.windowsMainService.focusLastActive(args, context)];
}
}
// Start with file/folder arguments
else {
usedWindows = this.windowsMainService.open({
context,
cli: args,
userEnv,
forceNewWindow: args['new-window'],
preferNewWindow: !args['reuse-window'] && !args.wait,
forceReuseWindow: args['reuse-window'],
diffMode: args.diff,
addMode: args.add
});
}
// {{SQL CARBON EDIT}}
// give the first used window a chance to process the other command line arguments
if (args['reuse-window'] && usedWindows.length > 0 && usedWindows[0])
{
let window = usedWindows[0];
usedWindows[0].ready().then(() => window.send('ads:processCommandLine', args));
}
// {{SQL CARBON EDIT}}
// If the other instance is waiting to be killed, we hook up a window listener if one window
// is being used and only then resolve the startup promise which will kill this second instance.
// 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.windowsMainService.waitForWindowCloseOrLoad(usedWindows[0].id),
whenDeleted(args.waitMarkerFilePath)
]).then(() => void 0, () => void 0);
}
return TPromise.as(null);
}
getMainProcessId(): TPromise<number> {
this.logService.trace('Received request for process ID from other instance.');
return TPromise.as(process.pid);
}
getMainProcessInfo(): TPromise<IMainProcessInfo> {
this.logService.trace('Received request for main process info from other instance.');
const windows: IWindowInfo[] = [];
BrowserWindow.getAllWindows().forEach(window => {
const codeWindow = this.windowsMainService.getWindowById(window.id);
if (codeWindow) {
windows.push(this.codeWindowToInfo(codeWindow));
} else {
windows.push(this.browserWindowToInfo(window));
}
});
return TPromise.wrap({
mainPID: process.pid,
mainArguments: process.argv,
windows
} as IMainProcessInfo);
}
getLogsPath(): TPromise<string> {
this.logService.trace('Received request for logs path from other instance.');
return TPromise.as(this.environmentService.logsPath);
}
private codeWindowToInfo(window: ICodeWindow): IWindowInfo {
const folderURIs: URI[] = [];
if (window.openedFolderUri) {
folderURIs.push(window.openedFolderUri);
} else if (window.openedWorkspace) {
const rootFolders = this.workspacesMainService.resolveWorkspaceSync(window.openedWorkspace.configPath).folders;
rootFolders.forEach(root => {
folderURIs.push(root.uri);
});
}
return this.browserWindowToInfo(window.win, folderURIs);
}
private browserWindowToInfo(win: BrowserWindow, folderURIs: URI[] = []): IWindowInfo {
return {
pid: win.webContents.getOSProcessId(),
title: win.getTitle(),
folderURIs
} as IWindowInfo;
}
}

View File

@@ -3,20 +3,18 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as os from 'os';
import * as cp from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import { localize } from 'vs/nls';
import { ILaunchChannel } from 'vs/code/electron-main/launch';
import { TPromise } from 'vs/base/common/winjs.base';
import product from 'vs/platform/node/product';
import { IRequestService } from 'vs/platform/request/node/request';
import { IRequestContext } from 'vs/base/node/request';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ILaunchService } from 'vs/platform/launch/electron-main/launchService';
interface PostResult {
readonly blob_id: string;
@@ -34,7 +32,7 @@ class Endpoint {
}
export async function uploadLogs(
channel: ILaunchChannel,
launchService: ILaunchService,
requestService: IRequestService,
environmentService: IEnvironmentService
): Promise<any> {
@@ -44,7 +42,7 @@ export async function uploadLogs(
return;
}
const logsPath = await channel.call('get-logs-path', null);
const logsPath = await launchService.getLogsPath();
if (await promptUserToConfirmLogUpload(logsPath, environmentService)) {
console.log(localize('beginUploading', 'Uploading...'));
@@ -86,14 +84,14 @@ async function postLogs(
headers: {
'Content-Type': 'application/zip'
}
});
}, CancellationToken.None);
} catch (e) {
clearInterval(dotter);
console.log(localize('postError', 'Error posting logs: {0}', e));
throw e;
}
return new TPromise<PostResult>((res, reject) => {
return new Promise<PostResult>((resolve, reject) => {
const parts: Buffer[] = [];
result.stream.on('data', data => {
parts.push(data);
@@ -104,7 +102,7 @@ async function postLogs(
try {
const response = Buffer.concat(parts).toString('utf-8');
if (result.res.statusCode === 200) {
res(JSON.parse(response));
resolve(JSON.parse(response));
} else {
const errorMessage = localize('responseError', 'Error posting logs. Got {0} — {1}', result.res.statusCode, response);
console.log(errorMessage);
@@ -120,13 +118,13 @@ async function postLogs(
function zipLogs(
logsPath: string
): TPromise<string> {
): Promise<string> {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'vscode-log-upload'));
const outZip = path.join(tempDir, 'logs.zip');
return new TPromise<string>((resolve, reject) => {
return new Promise<string>((resolve, reject) => {
doZip(logsPath, outZip, tempDir, (err, stdout, stderr) => {
if (err) {
console.error(localize('zipError', 'Error zipping logs: {0}', err));
console.error(localize('zipError', 'Error zipping logs: {0}', err.message));
reject(err);
} else {
resolve(outZip);

View File

@@ -3,9 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/code/electron-main/contributions';
import 'vs/code/code.main';
import { app, dialog } from 'electron';
import { assign } from 'vs/base/common/objects';
import * as platform from 'vs/base/common/platform';
@@ -16,10 +14,9 @@ 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 'vs/code/electron-main/launch';
import { LaunchChannelClient } from 'vs/platform/launch/electron-main/launchService';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { InstantiationService } from 'vs/platform/instantiation/node/instantiationService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ILogService, ConsoleLogMainService, MultiplexLogService, getLogLevel } from 'vs/platform/log/common/log';
@@ -44,29 +41,26 @@ import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces
import { localize } from 'vs/nls';
import { mnemonicButtonLabel } from 'vs/base/common/labels';
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
import { printDiagnostics } from 'vs/code/electron-main/diagnostics';
import { IDiagnosticsService, DiagnosticsService } from 'vs/platform/diagnostics/electron-main/diagnosticsService';
import { BufferLogService } from 'vs/platform/log/common/bufferLog';
import { uploadLogs } from 'vs/code/electron-main/logUploader';
import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { CommandLineDialogService } from 'vs/platform/dialogs/node/dialogService';
import { IUriDisplayService, UriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay';
import { ILabelService, LabelService } from 'vs/platform/label/common/label';
import { createWaitMarkerFile } from 'vs/code/node/wait';
function createServices(args: ParsedArgs, bufferLogService: BufferLogService): IInstantiationService {
const services = new ServiceCollection();
const environmentService = new EnvironmentService(args, process.execPath);
const consoleLogService = new ConsoleLogMainService(getLogLevel(environmentService));
const logService = new MultiplexLogService([consoleLogService, bufferLogService]);
const uriDisplayService = new UriDisplayService(environmentService, undefined);
const logService = new MultiplexLogService([new ConsoleLogMainService(getLogLevel(environmentService)), bufferLogService]);
process.once('exit', () => logService.dispose());
// Eventually cleanup
setTimeout(() => cleanupOlderLogs(environmentService).then(null, err => console.error(err)), 10000);
services.set(IEnvironmentService, environmentService);
services.set(IUriDisplayService, uriDisplayService);
services.set(ILabelService, new LabelService(environmentService, void 0, void 0));
services.set(ILogService, logService);
services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService));
services.set(IHistoryMainService, new SyncDescriptor(HistoryMainService));
@@ -77,6 +71,7 @@ function createServices(args: ParsedArgs, bufferLogService: BufferLogService): I
services.set(IURLService, new SyncDescriptor(URLService));
services.set(IBackupMainService, new SyncDescriptor(BackupMainService));
services.set(IDialogService, new SyncDescriptor(CommandLineDialogService));
services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService));
return new InstantiationService(services, true);
}
@@ -92,31 +87,33 @@ async function cleanupOlderLogs(environmentService: EnvironmentService): Promise
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))));
await Promise.all(toDelete.map(name => rimraf(path.join(logsRoot, name))));
}
function createPaths(environmentService: IEnvironmentService): TPromise<any> {
function createPaths(environmentService: IEnvironmentService): Thenable<any> {
const paths = [
environmentService.appSettingsHome,
environmentService.extensionsPath,
environmentService.nodeCachedDataDir,
environmentService.logsPath
environmentService.logsPath,
environmentService.globalStorageHome,
environmentService.workspaceStorageHome
];
return TPromise.join(paths.map(p => p && mkdirp(p))) as TPromise<any>;
return Promise.all(paths.map(path => path && mkdirp(path)));
}
class ExpectedError extends Error {
public readonly isExpected = true;
}
function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
function setupIPC(accessor: ServicesAccessor): Thenable<Server> {
const logService = accessor.get(ILogService);
const environmentService = accessor.get(IEnvironmentService);
const requestService = accessor.get(IRequestService);
const diagnosticsService = accessor.get(IDiagnosticsService);
function allowSetForegroundWindow(service: LaunchChannelClient): TPromise<void> {
let promise = TPromise.wrap<void>(void 0);
function allowSetForegroundWindow(service: LaunchChannelClient): Thenable<void> {
let promise: Thenable<void> = Promise.resolve();
if (platform.isWindows) {
promise = service.getMainProcessId()
.then(processId => {
@@ -134,7 +131,7 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
return promise;
}
function setup(retry: boolean): TPromise<Server> {
function setup(retry: boolean): Thenable<Server> {
return serve(environmentService.mainIPCHandle).then(server => {
// Print --status usage info
@@ -161,7 +158,7 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
return server;
}, err => {
if (err.code !== 'EADDRINUSE') {
return TPromise.wrapError<Server>(err);
return Promise.reject<Server>(err);
}
// Since we are the second instance, we do not want to show the dock
@@ -179,13 +176,13 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
logService.error(msg);
client.dispose();
return TPromise.wrapError<Server>(new Error(msg));
return Promise.reject(new Error(msg));
}
// 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;
let startupWarningDialogHandle: any;
if (!environmentService.wait && !environmentService.status && !environmentService.args['upload-logs']) {
startupWarningDialogHandle = setTimeout(() => {
showStartupWarningDialog(
@@ -195,20 +192,20 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
}, 10000);
}
const channel = client.getChannel<ILaunchChannel>('launch');
const channel = client.getChannel('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()));
return diagnosticsService.printDiagnostics(info).then(() => Promise.reject(new ExpectedError()));
});
}
// Log uploader
if (typeof environmentService.args['upload-logs'] !== 'undefined') {
return uploadLogs(channel, requestService, environmentService)
.then(() => TPromise.wrapError(new ExpectedError()));
return uploadLogs(service, requestService, environmentService)
.then(() => Promise.reject(new ExpectedError()));
}
logService.trace('Sending env to running instance...');
@@ -223,7 +220,7 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
clearTimeout(startupWarningDialogHandle);
}
return TPromise.wrapError(new ExpectedError('Sent env to running instance. Terminating...'));
return Promise.reject(new ExpectedError('Sent env to running instance. Terminating...'));
});
},
err => {
@@ -235,7 +232,7 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
);
}
return TPromise.wrapError<Server>(err);
return Promise.reject<Server>(err);
}
// it happens on Linux and OS X that the pipe is left behind
@@ -245,7 +242,7 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
fs.unlinkSync(environmentService.mainIPCHandle);
} catch (e) {
logService.warn('Could not delete obsolete instance handle', e);
return TPromise.wrapError<Server>(e);
return Promise.reject<Server>(e);
}
return setup(false);
@@ -293,12 +290,55 @@ function quit(accessor: ServicesAccessor, reason?: ExpectedError | Error): void
lifecycleService.kill(exitCode);
}
function main() {
function patchEnvironment(environmentService: IEnvironmentService): typeof process.env {
const instanceEnvironment: typeof process.env = {
VSCODE_IPC_HOOK: environmentService.mainIPCHandle,
VSCODE_NLS_CONFIG: process.env['VSCODE_NLS_CONFIG'],
VSCODE_LOGS: process.env['VSCODE_LOGS']
};
if (process.env['VSCODE_PORTABLE']) {
instanceEnvironment['VSCODE_PORTABLE'] = process.env['VSCODE_PORTABLE'];
}
assign(process.env, instanceEnvironment);
return instanceEnvironment;
}
function startup(args: ParsedArgs): void {
// We need to buffer the spdlog logs until we are sure
// we are the only instance running, otherwise we'll have concurrent
// log file access on Windows (https://github.com/Microsoft/vscode/issues/41218)
const bufferLogService = new BufferLogService();
const instantiationService = createServices(args, bufferLogService);
instantiationService.invokeFunction(accessor => {
const environmentService = accessor.get(IEnvironmentService);
// Patch `process.env` with the instance's environment
const instanceEnvironment = patchEnvironment(environmentService);
// Startup
return instantiationService
.invokeFunction(a => createPaths(a.get(IEnvironmentService)))
.then(() => instantiationService.invokeFunction(setupIPC))
.then(mainIpcServer => {
bufferLogService.logger = createSpdLogService('main', bufferLogService.getLevel(), environmentService.logsPath);
return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup();
});
}).then(null, err => instantiationService.invokeFunction(quit, err));
}
function main(): void {
// Set the error handler early enough so that we are not getting the
// default electron error dialog popping up
setUnexpectedErrorHandler(err => console.error(err));
// Parse arguments
let args: ParsedArgs;
try {
args = parseMainProcessArgv(process.argv);
@@ -307,40 +347,31 @@ function main() {
console.error(err.message);
app.exit(1);
return;
return void 0;
}
// We need to buffer the spdlog logs until we are sure
// we are the only instance running, otherwise we'll have concurrent
// log file access on Windows
// https://github.com/Microsoft/vscode/issues/41218
const bufferLogService = new BufferLogService();
const instantiationService = createServices(args, bufferLogService);
// If we are started with --wait create a random temporary file
// and pass it over to the starting instance. We can use this file
// to wait for it to be deleted to monitor that the edited file
// is closed and then exit the waiting process.
//
// Note: we are not doing this if the wait marker has been already
// added as argument. This can happen if Code was started from CLI.
if (args.wait && !args.waitMarkerFilePath) {
createWaitMarkerFile(args.verbose).then(waitMarkerFilePath => {
if (waitMarkerFilePath) {
process.argv.push('--waitMarkerFilePath', waitMarkerFilePath);
args.waitMarkerFilePath = waitMarkerFilePath;
}
return instantiationService.invokeFunction(accessor => {
startup(args);
});
}
// Patch `process.env` with the instance's environment
const environmentService = accessor.get(IEnvironmentService);
const instanceEnv: typeof process.env = {
VSCODE_IPC_HOOK: environmentService.mainIPCHandle,
VSCODE_NLS_CONFIG: process.env['VSCODE_NLS_CONFIG'],
VSCODE_LOGS: process.env['VSCODE_LOGS']
};
if (process.env['VSCODE_PORTABLE']) {
instanceEnv['VSCODE_PORTABLE'] = process.env['VSCODE_PORTABLE'];
}
assign(process.env, instanceEnv);
// Startup
return instantiationService.invokeFunction(a => createPaths(a.get(IEnvironmentService)))
.then(() => instantiationService.invokeFunction(setupIPC))
.then(mainIpcServer => {
bufferLogService.logger = createSpdLogService('main', bufferLogService.getLevel(), environmentService.logsPath);
return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnv).startup();
});
}).done(null, err => instantiationService.invokeFunction(quit, err));
// Otherwise just startup normally
else {
startup(args);
}
}
main();

View File

@@ -1,799 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 * as nls from 'vs/nls';
import { isMacintosh, language } from 'vs/base/common/platform';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { app, shell, Menu, MenuItem, BrowserWindow } from 'electron';
import { OpenContext, IRunActionInWindowRequest } from 'vs/platform/windows/common/windows';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IUpdateService, StateType } from 'vs/platform/update/common/update';
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 } from 'vs/base/common/labels';
import { IWindowsMainService, IWindowsCountChangedEvent } from 'vs/platform/windows/electron-main/windows';
import { IHistoryMainService } from 'vs/platform/history/common/history';
import { IWorkspaceIdentifier, getWorkspaceLabel, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IMenubarData, IMenubarKeybinding, MenubarMenuItem, isMenubarMenuItemSeparator, isMenubarMenuItemSubmenu, isMenubarMenuItemAction } from 'vs/platform/menubar/common/menubar';
import URI from 'vs/base/common/uri';
import { IUriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay';
const telemetryFrom = 'menu';
export class Menubar {
private static readonly MAX_MENU_RECENT_ENTRIES = 10;
private isQuitting: boolean;
private appMenuInstalled: boolean;
private closedLastWindow: boolean;
private menuUpdater: RunOnceScheduler;
private nativeTabMenuItems: Electron.MenuItem[];
private menubarMenus: IMenubarData;
private keybindings: { [commandId: string]: IMenubarKeybinding };
constructor(
@IUpdateService private updateService: IUpdateService,
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService private configurationService: IConfigurationService,
@IWindowsMainService private windowsMainService: IWindowsMainService,
@IEnvironmentService private environmentService: IEnvironmentService,
@ITelemetryService private telemetryService: ITelemetryService,
@IHistoryMainService private historyMainService: IHistoryMainService,
@IUriDisplayService private uriDisplayService: IUriDisplayService
) {
this.menuUpdater = new RunOnceScheduler(() => this.doUpdateMenu(), 0);
this.keybindings = Object.create(null);
this.closedLastWindow = false;
this.install();
this.registerListeners();
}
private registerListeners(): void {
// Keep flag when app quits
app.on('will-quit', () => {
this.isQuitting = true;
});
// // Listen to some events from window service to update menu
this.historyMainService.onRecentlyOpenedChange(() => this.scheduleUpdateMenu());
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: any, rawExtensionViewlets: string) => {
// let extensionViewlets: IExtensionViewlet[] = [];
// try {
// extensionViewlets = JSON.parse(rawExtensionViewlets);
// } catch (error) {
// // Should not happen
// }
// if (extensionViewlets.length) {
// this.extensionViewlets = extensionViewlets;
// this.updateMenu();
// }
// });
// Update when auto save config changes
// this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e));
// Listen to update service
// this.updateService.onStateChange(() => this.updateMenu());
}
private get currentEnableMenuBarMnemonics(): boolean {
let enableMenuBarMnemonics = this.configurationService.getValue<boolean>('window.enableMenuBarMnemonics');
if (typeof enableMenuBarMnemonics !== 'boolean') {
enableMenuBarMnemonics = true;
}
return enableMenuBarMnemonics;
}
private get currentEnableNativeTabs(): boolean {
let enableNativeTabs = this.configurationService.getValue<boolean>('window.nativeTabs');
if (typeof enableNativeTabs !== 'boolean') {
enableNativeTabs = false;
}
return enableNativeTabs;
}
updateMenu(menus: IMenubarData, windowId: number, additionalKeybindings?: Array<IMenubarKeybinding>) {
this.menubarMenus = menus;
if (additionalKeybindings) {
additionalKeybindings.forEach(keybinding => {
this.keybindings[keybinding.id] = keybinding;
});
}
this.scheduleUpdateMenu();
}
private scheduleUpdateMenu(): void {
this.menuUpdater.schedule(); // buffer multiple attempts to update the menu
}
private doUpdateMenu(): void {
// Due to limitations in Electron, it is not possible to update menu items dynamically. The suggested
// workaround from Electron is to set the application menu again.
// See also https://github.com/electron/electron/issues/846
//
// Run delayed to prevent updating menu while it is open
if (!this.isQuitting) {
setTimeout(() => {
if (!this.isQuitting) {
this.install();
}
}, 10 /* delay this because there is an issue with updating a menu when it is open */);
}
}
private onWindowsCountChanged(e: IWindowsCountChangedEvent): void {
if (!isMacintosh) {
return;
}
// Update menu if window count goes from N > 0 or 0 > N to update menu item enablement
if ((e.oldCount === 0 && e.newCount > 0) || (e.oldCount > 0 && e.newCount === 0)) {
this.closedLastWindow = e.newCount === 0;
this.scheduleUpdateMenu();
}
// Update specific items that are dependent on window count
else if (this.currentEnableNativeTabs) {
this.nativeTabMenuItems.forEach(item => {
if (item) {
item.enabled = e.newCount > 1;
}
});
}
}
private install(): void {
// Menus
const menubar = new Menu();
// Mac: Application
let macApplicationMenuItem: Electron.MenuItem;
if (isMacintosh) {
const applicationMenu = new Menu();
macApplicationMenuItem = new MenuItem({ label: product.nameShort, submenu: applicationMenu });
this.setMacApplicationMenu(applicationMenu);
menubar.append(macApplicationMenuItem);
}
// Mac: Dock
if (isMacintosh && !this.appMenuInstalled) {
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.windowsMainService.openNewWindow(OpenContext.DOCK) }));
app.dock.setMenu(dockMenu);
}
// File
const fileMenu = new Menu();
const fileMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mFile', comment: ['&& denotes a mnemonic'] }, "&&File")), submenu: fileMenu });
if (this.shouldDrawMenu('File')) {
if (this.shouldFallback('File')) {
this.setFallbackMenuById(fileMenu, 'File');
} else {
this.setMenuById(fileMenu, 'File');
}
menubar.append(fileMenuItem);
}
// Edit
const editMenu = new Menu();
const editMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mEdit', comment: ['&& denotes a mnemonic'] }, "&&Edit")), submenu: editMenu });
if (this.shouldDrawMenu('Edit')) {
this.setMenuById(editMenu, 'Edit');
menubar.append(editMenuItem);
}
// Selection
const selectionMenu = new Menu();
const selectionMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mSelection', comment: ['&& denotes a mnemonic'] }, "&&Selection")), submenu: selectionMenu });
if (this.shouldDrawMenu('Selection')) {
this.setMenuById(selectionMenu, 'Selection');
menubar.append(selectionMenuItem);
}
// View
const viewMenu = new Menu();
const viewMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mView', comment: ['&& denotes a mnemonic'] }, "&&View")), submenu: viewMenu });
if (this.shouldDrawMenu('View')) {
this.setMenuById(viewMenu, 'View');
menubar.append(viewMenuItem);
}
// Layout
const layoutMenu = new Menu();
const layoutMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mLayout', comment: ['&& denotes a mnemonic'] }, "&&Layout")), submenu: layoutMenu });
if (this.shouldDrawMenu('Layout')) {
this.setMenuById(layoutMenu, 'Layout');
menubar.append(layoutMenuItem);
}
// Go
const gotoMenu = new Menu();
const gotoMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go")), submenu: gotoMenu });
if (this.shouldDrawMenu('Go')) {
this.setMenuById(gotoMenu, 'Go');
menubar.append(gotoMenuItem);
}
// Debug
const debugMenu = new Menu();
const debugMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug")), submenu: debugMenu });
if (this.shouldDrawMenu('Debug')) {
this.setMenuById(debugMenu, 'Debug');
menubar.append(debugMenuItem);
}
// Tasks
const taskMenu = new Menu();
const taskMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mTask', comment: ['&& denotes a mnemonic'] }, "&&Tasks")), submenu: taskMenu });
if (this.shouldDrawMenu('Tasks')) {
this.setMenuById(taskMenu, 'Tasks');
menubar.append(taskMenuItem);
}
// Mac: Window
let macWindowMenuItem: Electron.MenuItem;
if (this.shouldDrawMenu('Window')) {
const windowMenu = new Menu();
macWindowMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize('mWindow', "Window")), submenu: windowMenu, role: 'window' });
this.setMacWindowMenu(windowMenu);
}
if (macWindowMenuItem) {
menubar.append(macWindowMenuItem);
}
// Preferences
if (!isMacintosh) {
const preferencesMenu = new Menu();
const preferencesMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mPreferences', comment: ['&& denotes a mnemonic'] }, "&&Preferences")), submenu: preferencesMenu });
if (this.shouldDrawMenu('Preferences')) {
if (this.shouldFallback('Preferences')) {
this.setFallbackMenuById(preferencesMenu, 'Preferences');
} else {
this.setMenuById(preferencesMenu, 'Preferences');
}
menubar.append(preferencesMenuItem);
}
}
// Help
const helpMenu = new Menu();
const helpMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mHelp', comment: ['&& denotes a mnemonic'] }, "&&Help")), submenu: helpMenu, role: 'help' });
if (this.shouldDrawMenu('Help')) {
if (this.shouldFallback('Help')) {
this.setFallbackMenuById(helpMenu, 'Help');
} else {
this.setMenuById(helpMenu, 'Help');
}
menubar.append(helpMenuItem);
}
if (menubar.items && menubar.items.length > 0) {
Menu.setApplicationMenu(menubar);
} else {
Menu.setApplicationMenu(null);
}
}
private setMacApplicationMenu(macApplicationMenu: Electron.Menu): void {
const about = new MenuItem({ label: nls.localize('mAbout', "About {0}", product.nameLong), role: 'about' });
const checkForUpdates = this.getUpdateMenuItems();
let preferences;
if (this.shouldDrawMenu('Preferences')) {
const preferencesMenu = new Menu();
this.setMenuById(preferencesMenu, 'Preferences');
preferences = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miPreferences', comment: ['&& denotes a mnemonic'] }, "&&Preferences")), submenu: preferencesMenu });
}
const servicesMenu = new Menu();
const services = new MenuItem({ label: nls.localize('mServices', "Services"), role: 'services', submenu: servicesMenu });
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: () => {
if (this.windowsMainService.getWindowCount() === 0 || !!BrowserWindow.getFocusedWindow()) {
this.windowsMainService.quit(); // fix for https://github.com/Microsoft/vscode/issues/39191
}
}
}));
const actions = [about];
actions.push(...checkForUpdates);
if (preferences) {
actions.push(...[
__separator__(),
preferences
]);
}
actions.push(...[
__separator__(),
services,
__separator__(),
hide,
hideOthers,
showAll,
__separator__(),
quit
]);
actions.forEach(i => macApplicationMenu.append(i));
}
private shouldDrawMenu(menuId: string): boolean {
// We need to draw an empty menu to override the electron default
if (!isMacintosh && this.configurationService.getValue('window.titleBarStyle') === 'custom') {
return false;
}
switch (menuId) {
case 'File':
case 'Help':
if (isMacintosh) {
return (this.windowsMainService.getWindowCount() === 0 && this.closedLastWindow) || (!!this.menubarMenus && !!this.menubarMenus[menuId]);
}
case 'Window':
if (isMacintosh) {
return (this.windowsMainService.getWindowCount() === 0 && this.closedLastWindow) || !!this.menubarMenus;
}
default:
return this.windowsMainService.getWindowCount() > 0 && (!!this.menubarMenus && !!this.menubarMenus[menuId]);
}
}
private shouldFallback(menuId: string): boolean {
return this.shouldDrawMenu(menuId) && (this.windowsMainService.getWindowCount() === 0 && this.closedLastWindow && isMacintosh);
}
private setFallbackMenuById(menu: Electron.Menu, menuId: string): void {
switch (menuId) {
case 'File':
const newFile = new MenuItem(this.likeAction('workbench.action.files.newUntitledFile', { label: this.mnemonicLabel(nls.localize({ key: 'miNewFile', comment: ['&& denotes a mnemonic'] }, "&&New File")), click: () => this.windowsMainService.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 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 } }) }));
const 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 } }) }));
const openRecentMenu = new Menu();
this.setFallbackMenuById(openRecentMenu, 'Recent');
const openRecent = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miOpenRecent', comment: ['&& denotes a mnemonic'] }, "Open &&Recent")), submenu: openRecentMenu });
menu.append(newFile);
menu.append(newWindow);
menu.append(__separator__());
menu.append(open);
menu.append(openWorkspace);
menu.append(openRecent);
break;
case 'Recent':
menu.append(this.createMenuItem(nls.localize({ key: 'miReopenClosedEditor', comment: ['&& denotes a mnemonic'] }, "&&Reopen Closed Editor"), 'workbench.action.reopenClosedEditor'));
this.insertRecentMenuItems(menu);
menu.append(__separator__());
menu.append(this.createMenuItem(nls.localize({ key: 'miMore', comment: ['&& denotes a mnemonic'] }, "&&More..."), 'workbench.action.openRecent'));
menu.append(__separator__());
menu.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() })));
break;
case 'Help':
let twitterItem: MenuItem;
if (product.twitterUrl) {
twitterItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miTwitter', comment: ['&& denotes a mnemonic'] }, "&&Join us on Twitter")), click: () => this.openUrl(product.twitterUrl, 'openTwitterUrl') });
}
let featureRequestsItem: MenuItem;
if (product.requestFeatureUrl) {
featureRequestsItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miUserVoice', comment: ['&& denotes a mnemonic'] }, "&&Search Feature Requests")), click: () => this.openUrl(product.requestFeatureUrl, 'openUserVoiceUrl') });
}
let reportIssuesItem: MenuItem;
if (product.reportIssueUrl) {
const label = nls.localize({ key: 'miReportIssue', comment: ['&& denotes a mnemonic', 'Translate this to "Report Issue in English" in all languages please!'] }, "Report &&Issue");
reportIssuesItem = new MenuItem({ label: this.mnemonicLabel(label), click: () => this.openUrl(product.reportIssueUrl, 'openReportIssues') });
}
let licenseItem: MenuItem;
if (product.privacyStatementUrl) {
licenseItem = new MenuItem({
label: this.mnemonicLabel(nls.localize({ key: 'miLicense', comment: ['&& denotes a mnemonic'] }, "View &&License")), click: () => {
if (language) {
const queryArgChar = product.licenseUrl.indexOf('?') > 0 ? '&' : '?';
this.openUrl(`${product.licenseUrl}${queryArgChar}lang=${language}`, 'openLicenseUrl');
} else {
this.openUrl(product.licenseUrl, 'openLicenseUrl');
}
}
});
}
let privacyStatementItem: MenuItem;
if (product.privacyStatementUrl) {
privacyStatementItem = new MenuItem({
label: this.mnemonicLabel(nls.localize({ key: 'miPrivacyStatement', comment: ['&& denotes a mnemonic'] }, "&&Privacy Statement")), click: () => {
if (language) {
const queryArgChar = product.licenseUrl.indexOf('?') > 0 ? '&' : '?';
this.openUrl(`${product.privacyStatementUrl}${queryArgChar}lang=${language}`, 'openPrivacyStatement');
} else {
this.openUrl(product.privacyStatementUrl, 'openPrivacyStatement');
}
}
});
}
if (twitterItem) { menu.append(twitterItem); }
if (featureRequestsItem) { menu.append(featureRequestsItem); }
if (reportIssuesItem) { menu.append(reportIssuesItem); }
if ((twitterItem || featureRequestsItem || reportIssuesItem) && (licenseItem || privacyStatementItem)) { menu.append(__separator__()); }
if (licenseItem) { menu.append(licenseItem); }
if (privacyStatementItem) { menu.append(privacyStatementItem); }
break;
}
}
private setMenu(menu: Electron.Menu, items: Array<MenubarMenuItem>) {
items.forEach((item: MenubarMenuItem) => {
if (isMenubarMenuItemSeparator(item)) {
menu.append(__separator__());
} else if (isMenubarMenuItemSubmenu(item)) {
const submenu = new Menu();
const submenuItem = new MenuItem({ label: this.mnemonicLabel(item.label), submenu: submenu });
this.setMenu(submenu, item.submenu.items);
menu.append(submenuItem);
} else if (isMenubarMenuItemAction(item)) {
if (item.id === 'workbench.action.openRecent') {
this.insertRecentMenuItems(menu);
} else if (item.id === 'workbench.action.showAboutDialog') {
this.insertCheckForUpdatesItems(menu);
}
// Store the keybinding
if (item.keybinding) {
this.keybindings[item.id] = item.keybinding;
} else if (this.keybindings[item.id]) {
this.keybindings[item.id] = undefined;
}
const menuItem = this.createMenuItem(item.label, item.id, item.enabled, item.checked);
menu.append(menuItem);
}
});
}
private setMenuById(menu: Electron.Menu, menuId: string): void {
if (this.menubarMenus && this.menubarMenus[menuId]) {
this.setMenu(menu, this.menubarMenus[menuId].items);
}
}
private insertCheckForUpdatesItems(menu: Electron.Menu) {
const updateItems = this.getUpdateMenuItems();
if (updateItems.length) {
updateItems.forEach(i => menu.append(i));
menu.append(__separator__());
}
}
private insertRecentMenuItems(menu: Electron.Menu) {
const { workspaces, files } = this.historyMainService.getRecentlyOpened();
// Workspaces
if (workspaces.length > 0) {
for (let i = 0; i < Menubar.MAX_MENU_RECENT_ENTRIES && i < workspaces.length; i++) {
menu.append(this.createOpenRecentMenuItem(workspaces[i], 'openRecentWorkspace', false));
}
menu.append(__separator__());
}
// Files
if (files.length > 0) {
for (let i = 0; i < Menubar.MAX_MENU_RECENT_ENTRIES && i < files.length; i++) {
menu.append(this.createOpenRecentMenuItem(files[i], 'openRecentFile', true));
}
menu.append(__separator__());
}
}
private createOpenRecentMenuItem(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | string, commandId: string, isFile: boolean): Electron.MenuItem {
let label: string;
let uri: URI;
if (isSingleFolderWorkspaceIdentifier(workspace)) {
label = unmnemonicLabel(getWorkspaceLabel(workspace, this.environmentService, this.uriDisplayService, { verbose: true }));
uri = workspace;
} else if (isWorkspaceIdentifier(workspace)) {
label = getWorkspaceLabel(workspace, this.environmentService, this.uriDisplayService, { verbose: true });
uri = URI.file(workspace.configPath);
} else {
uri = URI.file(workspace);
label = unmnemonicLabel(this.uriDisplayService.getLabel(uri));
}
return new MenuItem(this.likeAction(commandId, {
label,
click: (menuItem, win, event) => {
const openInNewWindow = this.isOptionClick(event);
const success = this.windowsMainService.open({
context: OpenContext.MENU,
cli: this.environmentService.args,
urisToOpen: [uri],
forceNewWindow: openInNewWindow,
forceOpenWorkspaceAsFile: isFile
}).length > 0;
if (!success) {
this.historyMainService.removeFromRecentlyOpened([workspace]);
}
}
}, false));
}
private isOptionClick(event: Electron.Event): boolean {
return event && ((!isMacintosh && (event.ctrlKey || event.shiftKey)) || (isMacintosh && (event.metaKey || event.altKey)));
}
private setMacWindowMenu(macWindowMenu: Electron.Menu): void {
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.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));
this.nativeTabMenuItems.push(this.createMenuItem(nls.localize('mMoveTabToNewWindow', "Move Tab to New Window"), 'workbench.action.moveWindowTabToNewWindow', hasMultipleWindows));
this.nativeTabMenuItems.push(this.createMenuItem(nls.localize('mMergeAllWindows', "Merge All Windows"), 'workbench.action.mergeAllWindowTabs', hasMultipleWindows));
nativeTabMenuItems.push(__separator__(), ...this.nativeTabMenuItems);
} else {
this.nativeTabMenuItems = [];
}
[
minimize,
zoom,
switchWindow,
...nativeTabMenuItems,
__separator__(),
bringAllToFront
].forEach(item => macWindowMenu.append(item));
}
private getUpdateMenuItems(): Electron.MenuItem[] {
const state = this.updateService.state;
switch (state.type) {
case StateType.Uninitialized:
return [];
case StateType.Idle:
return [new MenuItem({
label: nls.localize('miCheckForUpdates', "Check for Updates..."), click: () => setTimeout(() => {
this.reportMenuActionTelemetry('CheckForUpdate');
const focusedWindow = this.windowsMainService.getFocusedWindow();
const context = focusedWindow ? { windowId: focusedWindow.id } : null;
this.updateService.checkForUpdates(context);
}, 0)
})];
case StateType.CheckingForUpdates:
return [new MenuItem({ label: nls.localize('miCheckingForUpdates', "Checking For Updates..."), enabled: false })];
case StateType.AvailableForDownload:
return [new MenuItem({
label: nls.localize('miDownloadUpdate', "Download Available Update"), click: () => {
this.updateService.downloadUpdate();
}
})];
case StateType.Downloading:
return [new MenuItem({ label: nls.localize('miDownloadingUpdate', "Downloading Update..."), enabled: false })];
case StateType.Downloaded:
return [new MenuItem({
label: nls.localize('miInstallUpdate', "Install Update..."), click: () => {
this.reportMenuActionTelemetry('InstallUpdate');
this.updateService.applyUpdate();
}
})];
case StateType.Updating:
return [new MenuItem({ label: nls.localize('miInstallingUpdate', "Installing Update..."), enabled: false })];
case StateType.Ready:
return [new MenuItem({
label: nls.localize('miRestartToUpdate', "Restart to Update..."), click: () => {
this.reportMenuActionTelemetry('RestartToUpdate');
this.updateService.quitAndInstall();
}
})];
}
}
private createMenuItem(label: string, commandId: string | string[], enabled?: boolean, checked?: boolean): Electron.MenuItem;
private createMenuItem(label: string, click: () => void, enabled?: boolean, checked?: boolean): Electron.MenuItem;
private createMenuItem(arg1: string, arg2: any, arg3?: boolean, arg4?: boolean): Electron.MenuItem {
const label = this.mnemonicLabel(arg1);
const click: () => void = (typeof arg2 === 'function') ? arg2 : (menuItem: Electron.MenuItem, win: Electron.BrowserWindow, event: Electron.Event) => {
let commandId = arg2;
if (Array.isArray(arg2)) {
commandId = this.isOptionClick(event) ? arg2[1] : arg2[0]; // support alternative action if we got multiple action Ids and the option key was pressed while invoking
}
this.runActionInRenderer(commandId);
};
const enabled = typeof arg3 === 'boolean' ? arg3 : this.windowsMainService.getWindowCount() > 0;
const checked = typeof arg4 === 'boolean' ? arg4 : false;
const options: Electron.MenuItemConstructorOptions = {
label,
click,
enabled
};
if (checked) {
options['type'] = 'checkbox';
options['checked'] = checked;
}
let commandId: string;
if (typeof arg2 === 'string') {
commandId = arg2;
} else if (Array.isArray(arg2)) {
commandId = arg2[0];
}
// Add role for special case menu items
if (isMacintosh) {
if (commandId === 'editor.action.clipboardCutAction') {
options['role'] = 'cut';
} else if (commandId === 'editor.action.clipboardCopyAction') {
options['role'] = 'copy';
} else if (commandId === 'editor.action.clipboardPasteAction') {
options['role'] = 'paste';
}
}
return new MenuItem(this.withKeybinding(commandId, options));
}
private runActionInRenderer(id: string): void {
// 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 {
const binding = this.keybindings[commandId];
// Apply binding if there is one
if (binding && binding.label) {
// if the binding is native, we can just apply it
if (binding.isNative) {
options.accelerator = binding.label;
}
// the keybinding is not native so we cannot show it as part of the accelerator of
// the menu item. we fallback to a different strategy so that we always display it
else {
const bindingIndex = options.label.indexOf('[');
if (bindingIndex >= 0) {
options.label = `${options.label.substr(0, bindingIndex)} [${binding.label}]`;
} else {
options.label = `${options.label} [${binding.label}]`;
}
}
}
// Unset bindings if there is none
else {
options.accelerator = void 0;
}
return options;
}
private likeAction(commandId: string, options: Electron.MenuItemConstructorOptions, setAccelerator = !options.accelerator): Electron.MenuItemConstructorOptions {
if (setAccelerator) {
options = this.withKeybinding(commandId, options);
}
const originalClick = options.click;
options.click = (item, window, event) => {
this.reportMenuActionTelemetry(commandId);
if (originalClick) {
originalClick(item, window, event);
}
};
return options;
}
private openUrl(url: string, id: string): void {
shell.openExternal(url);
this.reportMenuActionTelemetry(id);
}
private reportMenuActionTelemetry(id: string): void {
/* __GDPR__
"workbenchActionExecuted" : {
"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"from": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('workbenchActionExecuted', { id, from: telemetryFrom });
}
private mnemonicLabel(label: string): string {
return baseMnemonicLabel(label, !this.currentEnableMenuBarMnemonics);
}
}
function __separator__(): Electron.MenuItem {
return new MenuItem({ type: 'separator' });
}

File diff suppressed because it is too large Load Diff

View File

@@ -6,37 +6,40 @@
import { assign } from 'vs/base/common/objects';
import { memoize } from 'vs/base/common/decorators';
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 { ISharedProcess } from 'vs/platform/windows/electron-main/windows';
import { Barrier } from 'vs/base/common/async';
import { ILogService } from 'vs/platform/log/common/log';
import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
import { IStateService } from 'vs/platform/state/common/state';
import { getBackgroundColor } from 'vs/code/electron-main/theme';
import { dispose, toDisposable, IDisposable } from 'vs/base/common/lifecycle';
export class SharedProcess implements ISharedProcess {
private barrier = new Barrier();
private window: Electron.BrowserWindow;
private window: Electron.BrowserWindow | null;
constructor(
private readonly environmentService: IEnvironmentService,
private readonly lifecycleService: ILifecycleService,
private readonly logService: ILogService,
private readonly machineId: string,
private readonly userEnv: IProcessEnvironment,
) {
}
private userEnv: NodeJS.ProcessEnv,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@ILifecycleService private readonly lifecycleService: ILifecycleService,
@IStateService private readonly stateService: IStateService,
@ILogService private readonly logService: ILogService
) { }
@memoize
private get _whenReady(): TPromise<void> {
private get _whenReady(): Promise<void> {
this.window = new BrowserWindow({
show: false,
backgroundColor: getBackgroundColor(this.stateService),
webPreferences: {
images: false,
webaudio: false,
webgl: false
webgl: false,
disableBlinkFeatures: 'Auxclick' // do NOT change, allows us to identify this window as shared-process in the process explorer
}
});
const config = assign({
@@ -57,26 +60,34 @@ export class SharedProcess implements ISharedProcess {
e.preventDefault();
// Still hide the window though if visible
if (this.window.isVisible()) {
if (this.window && this.window.isVisible()) {
this.window.hide();
}
};
this.window.on('close', onClose);
this.lifecycleService.onShutdown(() => {
const disposables: IDisposable[] = [];
this.lifecycleService.onWillShutdown(() => {
dispose(disposables);
// Shut the shared process down when we are quitting
//
// Note: because we veto the window close, we must first remove our veto.
// Otherwise the application would never quit because the shared process
// window is refusing to close!
//
this.window.removeListener('close', onClose);
if (this.window) {
this.window.removeListener('close', onClose);
}
// Electron seems to crash on Windows without this setTimeout :|
setTimeout(() => {
try {
this.window.close();
if (this.window) {
this.window.close();
}
} catch (err) {
// ignore, as electron is already shutting down
}
@@ -85,7 +96,7 @@ export class SharedProcess implements ISharedProcess {
}, 0);
});
return new TPromise<void>((c, e) => {
return new Promise<void>(c => {
ipcMain.once('handshake:hello', ({ sender }: { sender: any }) => {
sender.send('handshake:hey there', {
sharedIPCHandle: this.environmentService.sharedIPCHandle,
@@ -93,21 +104,24 @@ export class SharedProcess implements ISharedProcess {
logLevel: this.logService.getLevel()
});
ipcMain.once('handshake:im ready', () => c(null));
disposables.push(toDisposable(() => sender.send('handshake:goodbye')));
ipcMain.once('handshake:im ready', () => c(void 0));
});
});
}
spawn(): void {
spawn(userEnv: NodeJS.ProcessEnv): void {
this.userEnv = { ...this.userEnv, ...userEnv };
this.barrier.open();
}
whenReady(): TPromise<void> {
return this.barrier.wait().then(() => this._whenReady);
async whenReady(): Promise<void> {
await this.barrier.wait();
await this._whenReady;
}
toggle(): void {
if (this.window.isVisible()) {
if (!this.window || this.window.isVisible()) {
this.hide();
} else {
this.show();
@@ -115,12 +129,16 @@ export class SharedProcess implements ISharedProcess {
}
show(): void {
this.window.show();
this.window.webContents.openDevTools();
if (this.window) {
this.window.show();
this.window.webContents.openDevTools();
}
}
hide(): void {
this.window.webContents.closeDevTools();
this.window.hide();
if (this.window) {
this.window.webContents.closeDevTools();
this.window.hide();
}
}
}

View File

@@ -0,0 +1,39 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isWindows, isMacintosh } from 'vs/base/common/platform';
import { systemPreferences } from 'electron';
import { IStateService } from 'vs/platform/state/common/state';
export const DEFAULT_BG_LIGHT = '#FFFFFF';
export const DEFAULT_BG_DARK = '#1E1E1E';
export const DEFAULT_BG_HC_BLACK = '#000000';
export const THEME_STORAGE_KEY = 'theme';
export const THEME_BG_STORAGE_KEY = 'themeBackground';
export function getBackgroundColor(stateService: IStateService): string {
if (isWindows && systemPreferences.isInvertedColorScheme()) {
return DEFAULT_BG_HC_BLACK;
}
let background = stateService.getItem<string | null>(THEME_BG_STORAGE_KEY, null);
if (!background) {
let baseTheme: string;
if (isWindows && systemPreferences.isInvertedColorScheme()) {
baseTheme = 'hc-black';
} else {
baseTheme = stateService.getItem<string>(THEME_STORAGE_KEY, 'vs-dark').split(' ')[0];
}
background = (baseTheme === 'hc-black') ? DEFAULT_BG_HC_BLACK : (baseTheme === 'vs' ? DEFAULT_BG_LIGHT : DEFAULT_BG_DARK);
}
if (isMacintosh && background.toUpperCase() === DEFAULT_BG_DARK) {
background = '#171717'; // https://github.com/electron/electron/issues/5150
}
return background;
}

View File

@@ -3,29 +3,28 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as path from 'path';
import * as objects from 'vs/base/common/objects';
import * as nls from 'vs/nls';
import URI from 'vs/base/common/uri';
import { URI } from 'vs/base/common/uri';
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 { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage } from 'electron';
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
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 { IWindowSettings, MenuBarVisibility, IWindowConfiguration, ReadyState, IRunActionInWindowRequest } from 'vs/platform/windows/common/windows';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IWindowSettings, MenuBarVisibility, IWindowConfiguration, ReadyState, IRunActionInWindowRequest, getTitleBarStyle } from 'vs/platform/windows/common/windows';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import { ICodeWindow, IWindowState, WindowMode } 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 { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
import { mark, exportEntries } from 'vs/base/common/performance';
import * as perf from 'vs/base/common/performance';
import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/node/extensionGalleryService';
import { getBackgroundColor } from 'vs/code/electron-main/theme';
import { IStorageMainService } from 'vs/platform/storage/node/storageMainService';
export interface IWindowCreationOptions {
state: IWindowState;
@@ -53,18 +52,13 @@ interface ITouchBarSegment extends Electron.SegmentedControlSegment {
id: string;
}
export class CodeWindow implements ICodeWindow {
static readonly themeStorageKey = 'theme';
static readonly themeBackgroundStorageKey = 'themeBackground';
private static readonly DEFAULT_BG_LIGHT = '#FFFFFF';
private static readonly DEFAULT_BG_DARK = '#1E1E1E';
private static readonly DEFAULT_BG_HC_BLACK = '#000000';
export class CodeWindow extends Disposable implements ICodeWindow {
private static readonly MIN_WIDTH = 200;
private static readonly MIN_HEIGHT = 120;
private static readonly MAX_URL_LENGTH = 2 * 1024 * 1024; // https://cs.chromium.org/chromium/src/url/url_constants.cc?l=32
private hiddenTitleBarStyle: boolean;
private showTimeoutHandle: any;
private _id: number;
@@ -73,15 +67,14 @@ export class CodeWindow implements ICodeWindow {
private _readyState: ReadyState;
private windowState: IWindowState;
private currentMenuBarVisibility: MenuBarVisibility;
private toDispose: IDisposable[];
private representedFilename: string;
private whenReadyCallbacks: TValueCallback<ICodeWindow>[];
private whenReadyCallbacks: { (window: ICodeWindow): void }[];
private currentConfig: IWindowConfiguration;
private pendingLoadConfig: IWindowConfiguration;
private marketplaceHeadersPromise: TPromise<object>;
private marketplaceHeadersPromise: Thenable<object>;
private touchBarGroups: Electron.TouchBarSegmentedControl[];
@@ -92,13 +85,15 @@ export class CodeWindow implements ICodeWindow {
@IConfigurationService private configurationService: IConfigurationService,
@IStateService private stateService: IStateService,
@IWorkspacesMainService private workspacesMainService: IWorkspacesMainService,
@IBackupMainService private backupMainService: IBackupMainService
@IBackupMainService private backupMainService: IBackupMainService,
@IStorageMainService private storageMainService: IStorageMainService
) {
super();
this.touchBarGroups = [];
this._lastFocusTime = -1;
this._readyState = ReadyState.NONE;
this.whenReadyCallbacks = [];
this.toDispose = [];
// create browser window
this.createBrowserWindow(config);
@@ -124,24 +119,22 @@ export class CodeWindow implements ICodeWindow {
// in case we are maximized or fullscreen, only show later after the call to maximize/fullscreen (see below)
const isFullscreenOrMaximized = (this.windowState.mode === WindowMode.Maximized || this.windowState.mode === WindowMode.Fullscreen);
let backgroundColor = this.getBackgroundColor();
if (isMacintosh && backgroundColor.toUpperCase() === CodeWindow.DEFAULT_BG_DARK) {
backgroundColor = '#171717'; // https://github.com/electron/electron/issues/5150
}
const options: Electron.BrowserWindowConstructorOptions = {
width: this.windowState.width,
height: this.windowState.height,
x: this.windowState.x,
y: this.windowState.y,
backgroundColor,
backgroundColor: getBackgroundColor(this.stateService),
minWidth: CodeWindow.MIN_WIDTH,
minHeight: CodeWindow.MIN_HEIGHT,
show: !isFullscreenOrMaximized,
title: product.nameLong,
webPreferences: {
'backgroundThrottling': false, // by default if Code is in the background, intervals and timeouts get throttled,
disableBlinkFeatures: 'Auxclick' // disable auxclick events (see https://developers.google.com/web/updates/2016/10/auxclick)
// By default if Code is in the background, intervals and timeouts get throttled, so we
// want to enforce that Code stays in the foreground. This triggers a disable_hidden_
// flag that Electron provides via patch:
// https://github.com/electron/libchromiumcontent/blob/master/patches/common/chromium/disable_hidden.patch
'backgroundThrottling': false
}
};
@@ -151,6 +144,10 @@ export class CodeWindow implements ICodeWindow {
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
if (isMacintosh && !this.useNativeFullScreen()) {
options.fullscreenable = false; // enables simple fullscreen mode
}
if (isMacintosh) {
options.acceptFirstMouse = true; // enabled by default
@@ -159,28 +156,14 @@ export class CodeWindow implements ICodeWindow {
}
}
let useNativeTabs = false;
if (isMacintosh && windowConfig && windowConfig.nativeTabs === true) {
options.tabbingIdentifier = product.nameShort; // this opts in to sierra tabs
useNativeTabs = true;
}
let useCustomTitleStyle = false;
// {{SQL CARBON EDIT}}
// if (isMacintosh) {
// turn-off custom menus to avoid bug calculating size of SQL editor
//
// if (isMacintosh && (!windowConfig || !windowConfig.titleBarStyle || windowConfig.titleBarStyle === 'custom')) {
// const isDev = !this.environmentService.isBuilt || !!config.extensionDevelopmentPath;
// if (!isDev) {
// useCustomTitleStyle = true; // not enabled when developing due to https://github.com/electron/electron/issues/3647
// }
// }
if (useNativeTabs) {
useCustomTitleStyle = false; // native tabs on sierra do not work with custom title style
}
// const useCustomTitleStyle = getTitleBarStyle(this.configurationService, this.environmentService, !!config.extensionDevelopmentPath) === 'custom';
const useCustomTitleStyle = false;
if (useCustomTitleStyle) {
options.titleBarStyle = 'hidden';
this.hiddenTitleBarStyle = true;
@@ -193,7 +176,7 @@ export class CodeWindow implements ICodeWindow {
this._win = new BrowserWindow(options);
this._id = this._win.id;
if (useCustomTitleStyle) {
if (isMacintosh && useCustomTitleStyle) {
this._win.setSheetOffset(22); // offset dialogs by the height of the custom title bar if we have any
}
@@ -201,7 +184,7 @@ export class CodeWindow implements ICodeWindow {
this._win.maximize();
if (this.windowState.mode === WindowMode.Fullscreen) {
this._win.setFullScreen(true);
this.setFullScreen(true);
}
if (!this._win.isVisible()) {
@@ -284,6 +267,10 @@ export class CodeWindow implements ICodeWindow {
return this.currentConfig ? this.currentConfig.folderUri : void 0;
}
get remoteAuthority(): string {
return this.currentConfig ? this.currentConfig.remoteAuthority : void 0;
}
setReady(): void {
this._readyState = ReadyState.READY;
@@ -293,19 +280,19 @@ export class CodeWindow implements ICodeWindow {
}
}
ready(): TPromise<ICodeWindow> {
return new TPromise<ICodeWindow>((c) => {
if (this._readyState === ReadyState.READY) {
return c(this);
ready(): Thenable<ICodeWindow> {
return new Promise<ICodeWindow>(resolve => {
if (this.isReady) {
return resolve(this);
}
// otherwise keep and call later when we are ready
this.whenReadyCallbacks.push(c);
this.whenReadyCallbacks.push(resolve);
});
}
get readyState(): ReadyState {
return this._readyState;
get isReady(): boolean {
return this._readyState === ReadyState.READY;
}
private handleMarketplaceRequests(): void {
@@ -316,7 +303,7 @@ export class CodeWindow implements ICodeWindow {
// Inject headers when requests are incoming
const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*'];
this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details: any, cb: any) => {
this.marketplaceHeadersPromise.done(headers => {
this.marketplaceHeadersPromise.then(headers => {
cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) });
});
});
@@ -371,20 +358,26 @@ export class CodeWindow implements ICodeWindow {
// App commands support
this.registerNavigationListenerOn('app-command', 'browser-backward', 'browser-forward', false);
// Handle code that wants to open links
this._win.webContents.on('new-window', (event: Event, url: string) => {
event.preventDefault();
shell.openExternal(url);
});
// Window Focus
this._win.on('focus', () => {
this._lastFocusTime = Date.now();
});
// Simple fullscreen doesn't resize automatically when the resolution changes
if (isMacintosh) {
const displayMetricsChangedListener = () => {
if (this.isFullScreen() && !this.useNativeFullScreen()) {
this.setFullScreen(false);
this.setFullScreen(true);
}
};
screen.addListener('display-metrics-changed', displayMetricsChangedListener);
this._register(toDisposable(() => screen.removeListener('display-metrics-changed', displayMetricsChangedListener)));
}
// Window (Un)Maximize
this._win.on('maximize', (e) => {
this._win.on('maximize', e => {
if (this.currentConfig) {
this.currentConfig.maximized = true;
}
@@ -392,7 +385,7 @@ export class CodeWindow implements ICodeWindow {
app.emit('browser-window-maximize', e, this._win);
});
this._win.on('unmaximize', (e) => {
this._win.on('unmaximize', e => {
if (this.currentConfig) {
this.currentConfig.maximized = false;
}
@@ -414,21 +407,11 @@ export class CodeWindow implements ICodeWindow {
this.logService.warn('[electron event]: fail to load, ', errorDescription);
});
// Prevent any kind of navigation triggered by the user!
// But do not touch this in dev version because it will prevent "Reload" from dev tools
if (this.environmentService.isBuilt) {
this._win.webContents.on('will-navigate', (event: Event) => {
if (event) {
event.preventDefault();
}
});
}
// Handle configuration changes
this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated()));
this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated()));
// Handle Workspace events
this.toDispose.push(this.workspacesMainService.onUntitledWorkspaceDeleted(e => this.onUntitledWorkspaceDeleted(e)));
this._register(this.workspacesMainService.onUntitledWorkspaceDeleted(e => this.onUntitledWorkspaceDeleted(e)));
// TODO@Ben workaround for https://github.com/Microsoft/vscode/issues/13612
// It looks like smooth scrolling disappears as soon as the window is minimized
@@ -488,7 +471,7 @@ export class CodeWindow implements ICodeWindow {
private registerNavigationListenerOn(command: 'swipe' | 'app-command', back: 'left' | 'browser-backward', forward: 'right' | 'browser-forward', acrossEditors: boolean) {
this._win.on(command as 'swipe' /* | 'app-command' */, (e: Electron.Event, cmd: string) => {
if (this.readyState !== ReadyState.READY) {
if (!this.isReady) {
return; // window must be ready
}
@@ -500,11 +483,17 @@ export class CodeWindow implements ICodeWindow {
});
}
addTabbedWindow(window: ICodeWindow): void {
if (isMacintosh) {
this._win.addTabbedWindow(window.win);
}
}
load(config: IWindowConfiguration, isReload?: boolean, disableExtensions?: boolean): void {
// If this is the first time the window is loaded, we associate the paths
// directly with the window because we assume the loading will just work
if (this.readyState === ReadyState.NONE) {
if (this._readyState === ReadyState.NONE) {
this.currentConfig = config;
}
@@ -541,7 +530,7 @@ export class CodeWindow implements ICodeWindow {
}
// Load URL
mark('main:loadWindow');
perf.mark('main:loadWindow');
this._win.loadURL(this.getUrl(configuration));
// Make window visible if it did not open in N seconds because this indicates an error
@@ -601,7 +590,7 @@ export class CodeWindow implements ICodeWindow {
}
// Set fullscreen state
windowConfiguration.fullscreen = this._win.isFullScreen();
windowConfiguration.fullscreen = this.isFullScreen();
// Set Accessibility Config
let autoDetectHighContrast = true;
@@ -611,54 +600,45 @@ export class CodeWindow implements ICodeWindow {
windowConfiguration.highContrast = isWindows && autoDetectHighContrast && systemPreferences.isInvertedColorScheme();
windowConfiguration.accessibilitySupport = app.isAccessibilitySupportEnabled();
// Theme
windowConfiguration.baseTheme = this.getBaseTheme();
windowConfiguration.backgroundColor = this.getBackgroundColor();
// Title style related
windowConfiguration.maximized = this._win.isMaximized();
windowConfiguration.frameless = this.hasHiddenTitleBarStyle() && !isMacintosh;
// Perf Counters
windowConfiguration.perfEntries = exportEntries();
windowConfiguration.perfStartTime = (<any>global).perfStartTime;
windowConfiguration.perfWindowLoadTime = Date.now();
// Dump Perf Counters
windowConfiguration.perfEntries = perf.exportEntries();
// Parts splash
windowConfiguration.partsSplashData = this.storageMainService.get('parts-splash-data', void 0);
// Config (combination of process.argv and window configuration)
const environment = parseArgs(process.argv);
const config = objects.assign(environment, windowConfiguration);
for (let key in config) {
if (config[key] === void 0 || config[key] === null || config[key] === '') {
if (config[key] === void 0 || config[key] === null || config[key] === '' || config[key] === false) {
delete config[key]; // only send over properties that have a true value
}
}
return `${require.toUrl('vs/workbench/electron-browser/bootstrap/index.html')}?config=${encodeURIComponent(JSON.stringify(config))}`;
// In the unlikely event of the URL becoming larger than 2MB, remove parts of
// it that are not under our control. Mainly, the user environment can be very
// large depending on user configuration, so we can only remove it in that case.
let configUrl = this.doGetUrl(config);
if (configUrl.length > CodeWindow.MAX_URL_LENGTH) {
delete config.userEnv;
this.logService.warn('Application URL exceeds maximum of 2MB and was shortened.');
configUrl = this.doGetUrl(config);
if (configUrl.length > CodeWindow.MAX_URL_LENGTH) {
this.logService.error('Application URL exceeds maximum of 2MB and cannot be loaded.');
}
}
return configUrl;
}
private getBaseTheme(): string {
if (isWindows && systemPreferences.isInvertedColorScheme()) {
return 'hc-black';
}
const theme = this.stateService.getItem<string>(CodeWindow.themeStorageKey, 'vs-dark');
return theme.split(' ')[0];
}
private getBackgroundColor(): string {
if (isWindows && systemPreferences.isInvertedColorScheme()) {
return CodeWindow.DEFAULT_BG_HC_BLACK;
}
const background = this.stateService.getItem<string>(CodeWindow.themeBackgroundStorageKey, null);
if (!background) {
const baseTheme = this.getBaseTheme();
return baseTheme === 'hc-black' ? CodeWindow.DEFAULT_BG_HC_BLACK : (baseTheme === 'vs' ? CodeWindow.DEFAULT_BG_LIGHT : CodeWindow.DEFAULT_BG_DARK);
}
return background;
private doGetUrl(config: object): string {
return `${require.toUrl('vs/code/electron-browser/workbench/workbench.html')}?config=${encodeURIComponent(JSON.stringify(config))}`;
}
serializeWindowState(): IWindowState {
@@ -667,19 +647,27 @@ export class CodeWindow implements ICodeWindow {
}
// fullscreen gets special treatment
if (this._win.isFullScreen()) {
if (this.isFullScreen()) {
const display = screen.getDisplayMatching(this.getBounds());
return {
const defaultState = defaultWindowState();
const res = {
mode: WindowMode.Fullscreen,
display: display ? display.id : void 0,
// still carry over window dimensions from previous sessions!
width: this.windowState.width,
height: this.windowState.height,
x: this.windowState.x,
y: this.windowState.y
// Still carry over window dimensions from previous sessions
// if we can compute it in fullscreen state.
// does not seem possible in all cases on Linux for example
// (https://github.com/Microsoft/vscode/issues/58218) so we
// fallback to the defaults in that case.
width: this.windowState.width || defaultState.width,
height: this.windowState.height || defaultState.height,
x: this.windowState.x || 0,
y: this.windowState.y || 0
};
return res;
}
const state: IWindowState = Object.create(null);
@@ -825,15 +813,63 @@ export class CodeWindow implements ICodeWindow {
}
toggleFullScreen(): void {
const willBeFullScreen = !this._win.isFullScreen();
this.setFullScreen(!this.isFullScreen());
}
// set fullscreen flag on window
this._win.setFullScreen(willBeFullScreen);
private setFullScreen(fullscreen: boolean): void {
// respect configured menu bar visibility or default to toggle if not set
// Set fullscreen state
if (this.useNativeFullScreen()) {
this.setNativeFullScreen(fullscreen);
} else {
this.setSimpleFullScreen(fullscreen);
}
// Events
this.sendWhenReady(fullscreen ? 'vscode:enterFullScreen' : 'vscode:leaveFullScreen');
// Respect configured menu bar visibility or default to toggle if not set
this.setMenuBarVisibility(this.currentMenuBarVisibility, false);
}
isFullScreen(): boolean {
return this._win.isFullScreen() || this._win.isSimpleFullScreen();
}
private setNativeFullScreen(fullscreen: boolean): void {
if (this._win.isSimpleFullScreen()) {
this._win.setSimpleFullScreen(false);
}
this._win.setFullScreen(fullscreen);
}
private setSimpleFullScreen(fullscreen: boolean): void {
if (this._win.isFullScreen()) {
this._win.setFullScreen(false);
}
this._win.setSimpleFullScreen(fullscreen);
this._win.webContents.focus(); // workaround issue where focus is not going into window
}
private useNativeFullScreen(): boolean {
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
if (!windowConfig || typeof windowConfig.nativeFullScreen !== 'boolean') {
return true; // default
}
if (windowConfig.nativeTabs) {
return true; // https://github.com/electron/electron/issues/16142
}
return windowConfig.nativeFullScreen !== false;
}
isMinimized(): boolean {
return this._win.isMinimized();
}
private getMenuBarVisibility(): MenuBarVisibility {
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
if (!windowConfig || !windowConfig.menuBarVisibility) {
@@ -874,7 +910,7 @@ export class CodeWindow implements ICodeWindow {
}
private doSetMenuBarVisibility(visibility: MenuBarVisibility): void {
const isFullscreen = this._win.isFullScreen();
const isFullscreen = this.isFullScreen();
switch (visibility) {
case ('default'):
@@ -912,7 +948,11 @@ export class CodeWindow implements ICodeWindow {
break;
case 'Maximize':
default:
this.win.maximize();
if (this.win.isMaximized()) {
this.win.unmaximize();
} else {
this.win.maximize();
}
}
}
@@ -933,9 +973,11 @@ export class CodeWindow implements ICodeWindow {
}
sendWhenReady(channel: string, ...args: any[]): void {
this.ready().then(() => {
if (this.isReady) {
this.send(channel, ...args);
});
} else {
this.ready().then(() => this.send(channel, ...args));
}
}
send(channel: string, ...args: any[]): void {
@@ -1012,12 +1054,12 @@ export class CodeWindow implements ICodeWindow {
}
dispose(): void {
super.dispose();
if (this.showTimeoutHandle) {
clearTimeout(this.showTimeoutHandle);
}
this.toDispose = dispose(this.toDispose);
this._win = null; // Important to dereference the window object to allow for GC
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
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';
@@ -14,11 +13,13 @@ 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 { findFreePort, randomPort } from 'vs/base/node/ports';
import { resolveTerminalEncoding } from 'vs/base/node/encoding';
import * as iconv from 'iconv-lite';
import { writeFileAndFlushSync } from 'vs/base/node/extfs';
import { isWindows } from 'vs/base/common/platform';
import { ProfilingSession, Target } from 'v8-inspect-profiler';
import { createWaitMarkerFile } from 'vs/code/node/wait';
function shouldSpawnCliProcess(argv: ParsedArgs): boolean {
return !!argv['install-source']
@@ -28,7 +29,7 @@ function shouldSpawnCliProcess(argv: ParsedArgs): boolean {
}
interface IMainCli {
main: (argv: ParsedArgs) => TPromise<void>;
main: (argv: ParsedArgs) => Thenable<void>;
}
export async function main(argv: string[]): Promise<any> {
@@ -38,7 +39,7 @@ export async function main(argv: string[]): Promise<any> {
args = parseCLIProcessArgv(argv);
} catch (err) {
console.error(err.message);
return TPromise.as(null);
return;
}
// Help
@@ -53,8 +54,9 @@ export async function main(argv: string[]): Promise<any> {
// 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));
const cli = await new Promise<IMainCli>((c, e) => require(['vs/code/node/cliProcessMain'], c, e));
await cli.main(args);
return;
}
// Write File
@@ -69,13 +71,13 @@ export async function main(argv: string[]): Promise<any> {
!fs.existsSync(source) || !fs.statSync(source).isFile() || // make sure source exists as file
!fs.existsSync(target) || !fs.statSync(target).isFile() // make sure target exists as file
) {
return TPromise.wrapError(new Error('Using --file-write with invalid arguments.'));
throw new Error('Using --file-write with invalid arguments.');
}
try {
// Check for readonly status and chmod if so if we are told so
let targetMode: number;
let targetMode: number = 0;
let restoreMode = false;
if (!!args['file-chmod']) {
targetMode = fs.statSync(target).mode;
@@ -87,18 +89,17 @@ export async function main(argv: string[]): Promise<any> {
// Write source to target
const data = fs.readFileSync(source);
try {
if (isWindows) {
// On Windows we use a different strategy of saving the file
// by first truncating the file and then writing with r+ mode.
// This helps to save hidden files on Windows
// (see https://github.com/Microsoft/vscode/issues/931) and
// prevent removing alternate data streams
// (see https://github.com/Microsoft/vscode/issues/6363)
fs.truncateSync(target, 0);
writeFileAndFlushSync(target, data, { flag: 'r+' });
} else {
writeFileAndFlushSync(target, data);
} catch (error) {
// On Windows and if the file exists with an EPERM error, we try a different strategy of saving the file
// by first truncating the file and then writing with r+ mode. This helps to save hidden files on Windows
// (see https://github.com/Microsoft/vscode/issues/931)
if (isWindows && error.code === 'EPERM') {
fs.truncateSync(target, 0);
writeFileAndFlushSync(target, data, { flag: 'r+' });
} else {
throw error;
}
}
// Restore previous mode as needed
@@ -106,10 +107,9 @@ export async function main(argv: string[]): Promise<any> {
fs.chmodSync(target, targetMode);
}
} catch (error) {
return TPromise.wrapError(new Error(`Using --file-write resulted in an error: ${error}`));
error.message = `Error using --file-write: ${error.message}`;
throw error;
}
return TPromise.as(null);
}
// Just Code
@@ -127,15 +127,15 @@ export async function main(argv: string[]): Promise<any> {
if (verbose) {
env['ELECTRON_ENABLE_LOGGING'] = '1';
processCallbacks.push(child => {
processCallbacks.push(async 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)));
await new Promise(c => child.once('exit', () => c()));
});
}
let stdinWithoutTty: boolean;
let stdinWithoutTty: boolean = false;
try {
stdinWithoutTty = !process.stdin.isTTY; // Via https://twitter.com/MylesBorins/status/782009479382626304
} catch (error) {
@@ -161,7 +161,7 @@ export async function main(argv: string[]): Promise<any> {
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 stdinFileError: Error | undefined;
let stdinFileStream: fs.WriteStream;
try {
stdinFileStream = fs.createWriteStream(stdinFilePath);
@@ -172,7 +172,7 @@ export async function main(argv: string[]): Promise<any> {
if (!stdinFileError) {
// Pipe into tmp file using terminals encoding
resolveTerminalEncoding(verbose).done(encoding => {
resolveTerminalEncoding(verbose).then(encoding => {
const converterStream = iconv.decodeStream(encoding);
process.stdin.pipe(converterStream).pipe(stdinFileStream);
});
@@ -198,7 +198,7 @@ export async function main(argv: string[]): Promise<any> {
// 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 => {
processCallbacks.push(child => new Promise(c => {
const dataListener = () => {
if (isWindows) {
console.log(`Run with '${product.applicationName} -' to read output from another program (e.g. 'echo Hello World | ${product.applicationName} -').`);
@@ -226,24 +226,11 @@ export async function main(argv: string[]): Promise<any> {
// and pass it over to the starting instance. We can use this file
// to wait for it to be deleted to monitor that the edited file
// is closed and then exit the waiting process.
let waitMarkerFilePath: string;
let waitMarkerFilePath: string | undefined;
if (args.wait) {
let waitMarkerError: Error;
const randomTmpFile = paths.join(os.tmpdir(), Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10));
try {
fs.writeFileSync(randomTmpFile, '');
waitMarkerFilePath = randomTmpFile;
waitMarkerFilePath = await createWaitMarkerFile(verbose);
if (waitMarkerFilePath) {
argv.push('--waitMarkerFilePath', waitMarkerFilePath);
} catch (error) {
waitMarkerError = error;
}
if (verbose) {
if (waitMarkerError) {
console.error(`Failed to create marker file for --wait: ${waitMarkerError.toString()}`);
} else {
console.log(`Marker file for --wait created: ${waitMarkerFilePath}`);
}
}
}
@@ -252,16 +239,16 @@ export async function main(argv: string[]): Promise<any> {
// 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);
const portMain = await findFreePort(randomPort(), 10, 3000);
const portRenderer = await findFreePort(portMain + 1, 10, 3000);
const portExthost = await findFreePort(portRenderer + 1, 10, 3000);
if (!portMain || !portRenderer || !portExthost) {
console.error('Failed to find free ports for profiler to connect to do.');
return;
// fail the operation when one of the ports couldn't be accquired.
if (portMain * portRenderer * portExthost === 0) {
throw new Error('Failed to find free ports for profiler. Make sure to shutdown all instances of the editor first.');
}
const filenamePrefix = paths.join(os.homedir(), Math.random().toString(16).slice(-4));
const filenamePrefix = paths.join(os.homedir(), 'prof-' + Math.random().toString(16).slice(-4));
argv.push(`--inspect-brk=${portMain}`);
argv.push(`--remote-debugging-port=${portRenderer}`);
@@ -271,38 +258,81 @@ export async function main(argv: string[]): Promise<any> {
fs.writeFileSync(filenamePrefix, argv.slice(-6).join('|'));
processCallbacks.push(async child => {
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 });
class Profiler {
static async start(name: string, filenamePrefix: string, opts: { port: number, tries?: number, target?: (targets: Target[]) => Target }) {
const profiler = await import('v8-inspect-profiler');
// wait for the renderer to delete the
// marker file
whenDeleted(filenamePrefix);
let session: ProfilingSession;
try {
session = await profiler.startProfiling(opts);
} catch (err) {
console.error(`FAILED to start profiling for '${name}' on port '${opts.port}'`);
}
let profileMain = await main.stop();
let profileRenderer = await renderer.stop();
let profileExtHost = await extHost.stop();
let suffix = '';
return {
async stop() {
if (!session) {
return;
}
let suffix = '';
let profile = await session.stop();
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
profile = profiler.rewriteAbsolutePaths(profile, 'piiRemoved');
suffix = '.txt';
}
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';
await profiler.writeProfile(profile, `${filenamePrefix}.${name}.cpuprofile${suffix}`);
}
};
}
}
// 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}`);
try {
// load and start profiler
const mainProfileRequest = Profiler.start('main', filenamePrefix, { port: portMain });
const extHostProfileRequest = Profiler.start('extHost', filenamePrefix, { port: portExthost, tries: 300 });
const rendererProfileRequest = Profiler.start('renderer', filenamePrefix, {
port: portRenderer,
tries: 200,
target: function (targets) {
return targets.filter(target => {
if (!target.webSocketDebuggerUrl) {
return false;
}
if (target.type === 'page') {
return target.url.indexOf('workbench/workbench.html') > 0;
} else {
return true;
}
})[0];
}
});
const main = await mainProfileRequest;
const extHost = await extHostProfileRequest;
const renderer = await rendererProfileRequest;
// wait for the renderer to delete the
// marker file
await whenDeleted(filenamePrefix);
// stop profiling
await main.stop();
await renderer.stop();
await extHost.stop();
// re-create the marker file to signal that profiling is done
fs.writeFileSync(filenamePrefix, '');
} catch (e) {
console.error('Failed to profile startup. Make sure to quit Code first.');
}
});
}
@@ -327,13 +357,13 @@ export async function main(argv: string[]): Promise<any> {
const child = spawn(process.execPath, argv.slice(2), options);
if (args.wait && waitMarkerFilePath) {
return new TPromise<void>(c => {
return new Promise<void>(c => {
// Complete when process exits
child.once('exit', () => c(null));
child.once('exit', () => c(void 0));
// Complete when wait marker file is deleted
whenDeleted(waitMarkerFilePath).done(c, c);
whenDeleted(waitMarkerFilePath!).then(c, c);
}).then(() => {
// Make sure to delete the tmp stdin file if we have any
@@ -343,10 +373,8 @@ export async function main(argv: string[]): Promise<any> {
});
}
return TPromise.join(processCallbacks.map(callback => callback(child)));
return Promise.all(processCallbacks.map(callback => callback(child)));
}
return TPromise.as(null);
}
function eventuallyExit(code: number): void {

View File

@@ -11,7 +11,6 @@ import * as semver from 'semver';
import { TPromise } from 'vs/base/common/winjs.base';
import { sequence } from 'vs/base/common/async';
import { IPager } from 'vs/base/common/paging';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -19,7 +18,7 @@ import { InstantiationService } from 'vs/platform/instantiation/common/instantia
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { IExtensionManagementService, IExtensionGalleryService, IExtensionManifest, IGalleryExtension, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionManagementService, validateLocalExtension } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { ExtensionManagementService } 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';
@@ -37,10 +36,9 @@ import { StateService } from 'vs/platform/state/node/stateService';
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
import { ILogService, getLogLevel } from 'vs/platform/log/common/log';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { CommandLineDialogService } from 'vs/platform/dialogs/node/dialogService';
import { areSameExtensions, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import Severity from 'vs/base/common/severity';
import { areSameExtensions, getGalleryExtensionIdFromLocal, adoptToGalleryExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { URI } from 'vs/base/common/uri';
import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id);
const notInstalled = (id: string) => localize('notInstalled', "Extension '{0}' is not installed.", id);
@@ -54,6 +52,17 @@ function getId(manifest: IExtensionManifest, withVersion?: boolean): string {
}
}
const EXTENSION_ID_REGEX = /^([^.]+\..+)@(\d+\.\d+\.\d+(-.*)?)$/;
export function getIdAndVersion(id: string): [string, string] {
const matches = EXTENSION_ID_REGEX.exec(id);
if (matches && matches[1]) {
return [adoptToGalleryExtensionId(matches[1]), matches[2]];
}
return [adoptToGalleryExtensionId(id), void 0];
}
type Task = { (): TPromise<void> };
class Main {
@@ -61,8 +70,7 @@ class Main {
constructor(
@IEnvironmentService private environmentService: IEnvironmentService,
@IExtensionManagementService private extensionManagementService: IExtensionManagementService,
@IExtensionGalleryService private extensionGalleryService: IExtensionGalleryService,
@IDialogService private dialogService: IDialogService
@IExtensionGalleryService private extensionGalleryService: IExtensionGalleryService
) { }
run(argv: ParsedArgs): TPromise<any> {
@@ -76,7 +84,7 @@ class Main {
} else if (argv['install-extension']) {
const arg = argv['install-extension'];
const args: string[] = typeof arg === 'string' ? [arg] : arg;
returnPromise = this.installExtension(args);
returnPromise = this.installExtension(args, argv['force']);
} else if (argv['uninstall-extension']) {
const arg = argv['uninstall-extension'];
const ids: string[] = typeof arg === 'string' ? [arg] : arg;
@@ -95,30 +103,36 @@ class Main {
});
}
private installExtension(extensions: string[]): TPromise<any> {
private installExtension(extensions: string[], force: boolean): TPromise<any> {
const vsixTasks: Task[] = extensions
.filter(e => /\.vsix$/i.test(e))
.map(id => () => {
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!", getBaseLabel(extension)));
}, error => {
if (isPromiseCanceledError(error)) {
console.log(localize('cancelVsixInstall', "Cancelled installing Extension '{0}'.", getBaseLabel(extension)));
return this.validate(extension, force)
.then(valid => {
if (valid) {
return this.extensionManagementService.install(URI.file(extension)).then(() => {
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);
}
});
}
return null;
} else {
return TPromise.wrapError(error);
}
});
});
});
const galleryTasks: Task[] = extensions
.filter(e => !/\.vsix$/i.test(e))
.map(id => () => {
.map(e => () => {
const [id, version] = getIdAndVersion(e);
return this.extensionManagementService.getInstalled(LocalExtensionType.User)
.then(installed => this.extensionGalleryService.query({ names: [id], source: 'cli' })
.then<IPager<IGalleryExtension>>(null, err => {
.then(installed => this.extensionGalleryService.getExtension({ id }, version)
.then<IGalleryExtension>(null, err => {
if (err.responseText) {
try {
const response = JSON.parse(err.responseText);
@@ -129,29 +143,23 @@ class Main {
}
return TPromise.wrapError(err);
})
.then(result => {
const [extension] = result.firstPage;
.then(extension => {
if (!extension) {
return TPromise.wrapError(new Error(`${notFound(id)}\n${useId}`));
return TPromise.wrapError(new Error(`${notFound(version ? `${id}@${version}` : id)}\n${useId}`));
}
const [installedExtension] = installed.filter(e => areSameExtensions({ id: getGalleryExtensionIdFromLocal(e) }, { id }));
if (installedExtension) {
const outdated = semver.gt(extension.version, installedExtension.manifest.version);
if (outdated) {
const updateMessage = localize('updateMessage', "Extension '{0}' v{1} is already installed, but a newer version {2} is available in the marketplace. Would you like to update?", id, installedExtension.manifest.version, extension.version);
return this.dialogService.show(Severity.Info, updateMessage, [localize('yes', "Yes"), localize('no', "No")])
.then(option => {
if (option === 0) {
return this.installFromGallery(id, extension);
}
console.log(localize('cancelInstall', "Cancelled installing Extension '{0}'.", id));
return TPromise.as(null);
});
if (extension.version !== installedExtension.manifest.version) {
if (version || force) {
console.log(localize('updateMessage', "Updating the Extension '{0}' to the version {1}", id, extension.version));
return this.installFromGallery(id, extension);
} else {
console.log(localize('forceUpdate', "Extension '{0}' v{1} is already installed, but a newer version {2} is available in the marketplace. Use '--force' option to update to newer version.", id, installedExtension.manifest.version, extension.version));
return Promise.resolve(null);
}
} else {
console.log(localize('alreadyInstalled', "Extension '{0}' is already installed.", id));
console.log(localize('alreadyInstalled', "Extension '{0}' is already installed.", version ? `${id}@${version}` : id));
return TPromise.as(null);
}
} else {
@@ -165,6 +173,26 @@ class Main {
return sequence([...vsixTasks, ...galleryTasks]);
}
private validate(vsix: string, force: boolean): Thenable<boolean> {
return getManifest(vsix)
.then(manifest => {
if (manifest) {
const extensionIdentifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) };
return this.extensionManagementService.getInstalled(LocalExtensionType.User)
.then(installedExtensions => {
const newer = installedExtensions.filter(local => areSameExtensions(extensionIdentifier, { id: getGalleryExtensionIdFromLocal(local) }) && semver.gt(local.manifest.version, manifest.version))[0];
if (newer && !force) {
console.log(localize('forceDowngrade', "A newer version of this extension '{0}' v{1} is already installed. Use '--force' option to downgrade to older version.", newer.galleryIdentifier.id, newer.manifest.version, manifest.version));
return false;
}
return true;
});
} else {
return Promise.reject(new Error('Invalid vsix'));
}
});
}
private installFromGallery(id: string, extension: IGalleryExtension): TPromise<void> {
console.log(localize('installing', "Installing..."));
return this.extensionManagementService.installFromGallery(extension)
@@ -187,7 +215,7 @@ class Main {
}
const zipPath = path.isAbsolute(extensionDescription) ? extensionDescription : path.join(process.cwd(), extensionDescription);
const manifest = await validateLocalExtension(zipPath);
const manifest = await getManifest(zipPath);
return getId(manifest);
}
@@ -232,17 +260,16 @@ export function main(argv: ParsedArgs): TPromise<void> {
const stateService = accessor.get(IStateService);
return TPromise.join([envService.appSettingsHome, envService.extensionsPath].map(p => mkdirp(p))).then(() => {
const { appRoot, extensionsPath, extensionDevelopmentPath, isBuilt, installSourcePath } = envService;
const { appRoot, extensionsPath, extensionDevelopmentLocationURI, isBuilt, installSourcePath } = envService;
const services = new ServiceCollection();
services.set(IConfigurationService, new SyncDescriptor(ConfigurationService));
services.set(IRequestService, new SyncDescriptor(RequestService));
services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
services.set(IDialogService, new SyncDescriptor(CommandLineDialogService));
const appenders: AppInsightsAppender[] = [];
if (isBuilt && !extensionDevelopmentPath && !envService.args['disable-telemetry'] && product.enableTelemetry) {
if (isBuilt && !extensionDevelopmentLocationURI && !envService.args['disable-telemetry'] && product.enableTelemetry) {
if (product.aiConfig && product.aiConfig.asimovKey) {
appenders.push(new AppInsightsAppender(eventPrefix, null, product.aiConfig.asimovKey, logService));
@@ -254,7 +281,7 @@ export function main(argv: ParsedArgs): TPromise<void> {
piiPaths: [appRoot, extensionsPath]
};
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, config));
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config]));
} else {
services.set(ITelemetryService, NullTelemetryService);
}

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as path from 'path';
import * as arrays from 'vs/base/common/arrays';
import * as strings from 'vs/base/common/strings';
@@ -12,16 +10,17 @@ import * as paths from 'vs/base/common/paths';
import * as platform from 'vs/base/common/platform';
import * as types from 'vs/base/common/types';
import { ParsedArgs } from 'vs/platform/environment/common/environment';
import { realpathSync } from 'vs/base/node/extfs';
import { sanitizeFilePath } from 'vs/base/node/extfs';
export function validatePaths(args: ParsedArgs): ParsedArgs {
// Track URLs if they're going to be used
if (args['open-url']) {
args._urls = args._;
args._ = [];
}
// Realpath/normalize paths and watch out for goto line mode
// Normalize paths and watch out for goto line mode
const paths = doValidatePaths(args._, args.goto);
// Update environment
@@ -36,7 +35,7 @@ function doValidatePaths(args: string[], gotoLineMode?: boolean): string[] {
const result = args.map(arg => {
let pathCandidate = String(arg);
let parsedPath: IPathWithLineAndColumn;
let parsedPath: IPathWithLineAndColumn | undefined = undefined;
if (gotoLineMode) {
parsedPath = parseLineAndColumnAware(pathCandidate);
pathCandidate = parsedPath.path;
@@ -46,30 +45,24 @@ function doValidatePaths(args: string[], gotoLineMode?: boolean): string[] {
pathCandidate = preparePath(cwd, pathCandidate);
}
let realPath: string;
try {
realPath = realpathSync(pathCandidate);
} catch (error) {
// in case of an error, assume the user wants to create this file
// if the path is relative, we join it to the cwd
realPath = path.normalize(path.isAbsolute(pathCandidate) ? pathCandidate : path.join(cwd, pathCandidate));
}
const sanitizedFilePath = sanitizeFilePath(pathCandidate, cwd);
const basename = path.basename(realPath);
const basename = path.basename(sanitizedFilePath);
if (basename /* can be empty if code is opened on root */ && !paths.isValidBasename(basename)) {
return null; // do not allow invalid file names
}
if (gotoLineMode) {
parsedPath.path = realPath;
if (gotoLineMode && parsedPath) {
parsedPath.path = sanitizedFilePath;
return toPath(parsedPath);
}
return realPath;
return sanitizedFilePath;
});
const caseInsensitive = platform.isWindows || platform.isMacintosh;
const distinct = arrays.distinct(result, e => e && caseInsensitive ? e.toLowerCase() : e);
const distinct = arrays.distinct(result, e => e && caseInsensitive ? e.toLowerCase() : (e || ''));
return arrays.coalesce(distinct);
}
@@ -105,9 +98,9 @@ export interface IPathWithLineAndColumn {
export function parseLineAndColumnAware(rawPath: string): IPathWithLineAndColumn {
const segments = rawPath.split(':'); // C:\file.txt:<line>:<column>
let path: string;
let line: number = null;
let column: number = null;
let path: string | null = null;
let line: number | null = null;
let column: number | null = null;
segments.forEach(segment => {
const segmentAsNumber = Number(segment);

View File

@@ -3,16 +3,13 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as cp from 'child_process';
import { assign } from 'vs/base/common/objects';
import { generateUuid } from 'vs/base/common/uuid';
import { TPromise } from 'vs/base/common/winjs.base';
import { isWindows } from 'vs/base/common/platform';
function getUnixShellEnvironment(): TPromise<typeof process.env> {
const promise = new TPromise((c, e) => {
function getUnixShellEnvironment(): Promise<typeof process.env> {
const promise = new Promise((resolve, reject) => {
const runAsNode = process.env['ELECTRON_RUN_AS_NODE'];
const noAttach = process.env['ELECTRON_NO_ATTACH_CONSOLE'];
const mark = generateUuid().replace(/-/g, '').substr(0, 12);
@@ -24,19 +21,19 @@ function getUnixShellEnvironment(): TPromise<typeof process.env> {
});
const command = `'${process.execPath}' -p '"${mark}" + JSON.stringify(process.env) + "${mark}"'`;
const child = cp.spawn(process.env.SHELL, ['-ilc', command], {
const child = cp.spawn(process.env.SHELL!, ['-ilc', command], {
detached: true,
stdio: ['ignore', 'pipe', process.stderr],
env
});
const buffers: Buffer[] = [];
child.on('error', () => c({}));
child.on('error', () => resolve({}));
child.stdout.on('data', b => buffers.push(b as Buffer));
child.on('close', (code: number, signal: any) => {
if (code !== 0) {
return e(new Error('Failed to get environment'));
return reject(new Error('Failed to get environment'));
}
const raw = Buffer.concat(buffers).toString('utf8');
@@ -61,31 +58,31 @@ function getUnixShellEnvironment(): TPromise<typeof process.env> {
// https://github.com/Microsoft/vscode/issues/22593#issuecomment-336050758
delete env['XDG_RUNTIME_DIR'];
c(env);
resolve(env);
} catch (err) {
e(err);
reject(err);
}
});
});
// swallow errors
return promise.then(null, () => ({}));
return promise.then(undefined, () => ({}));
}
let _shellEnv: TPromise<typeof process.env>;
let _shellEnv: Promise<typeof process.env>;
/**
* We need to get the environment from a user's shell.
* This should only be done when Code itself is not launched
* from within a shell.
*/
export function getShellEnvironment(): TPromise<typeof process.env> {
export function getShellEnvironment(): Promise<typeof process.env> {
if (_shellEnv === undefined) {
if (isWindows) {
_shellEnv = TPromise.as({});
_shellEnv = Promise.resolve({});
} else if (process.env['VSCODE_CLI'] === '1') {
_shellEnv = TPromise.as({});
_shellEnv = Promise.resolve({});
} else {
_shellEnv = getUnixShellEnvironment();
}

26
src/vs/code/node/wait.ts Normal file
View File

@@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { join } from 'path';
import { tmpdir } from 'os';
import { writeFile } from 'vs/base/node/pfs';
export function createWaitMarkerFile(verbose?: boolean): Promise<string> {
const randomWaitMarkerPath = join(tmpdir(), Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10));
return writeFile(randomWaitMarkerPath, '').then(() => {
if (verbose) {
console.log(`Marker file for --wait created: ${randomWaitMarkerPath}`);
}
return randomWaitMarkerPath;
}, error => {
if (verbose) {
console.error(`Failed to create marker file for --wait: ${error}`);
}
return Promise.resolve(void 0);
});
}

View File

@@ -3,20 +3,17 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as platform from 'vs/base/common/platform';
import * as paths from 'vs/base/common/paths';
import { OpenContext } from 'vs/platform/windows/common/windows';
import { IWorkspaceIdentifier, IResolvedWorkspace, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { Schemas } from 'vs/base/common/network';
import URI from 'vs/base/common/uri';
import { hasToIgnoreCase, isEqual } from 'vs/base/common/resources';
import { IWorkspaceIdentifier, IResolvedWorkspace, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { URI } from 'vs/base/common/uri';
import { isEqual, isEqualOrParent } from 'vs/base/common/resources';
export interface ISimpleWindow {
openedWorkspace?: IWorkspaceIdentifier;
openedFolderUri?: URI;
openedFilePath?: string;
extensionDevelopmentPath?: string;
lastFocusTime: number;
}
@@ -26,39 +23,38 @@ export interface IBestWindowOrFolderOptions<W extends ISimpleWindow> {
newWindow: boolean;
reuseWindow: boolean;
context: OpenContext;
filePath?: string;
fileUri?: URI;
userHome?: string;
codeSettingsFolder?: string;
workspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace;
}
export function findBestWindowOrFolderForFile<W extends ISimpleWindow>({ windows, newWindow, reuseWindow, context, filePath, workspaceResolver }: IBestWindowOrFolderOptions<W>): W {
if (!newWindow && filePath && (context === OpenContext.DESKTOP || context === OpenContext.CLI || context === OpenContext.DOCK)) {
const windowOnFilePath = findWindowOnFilePath(windows, filePath, workspaceResolver);
export function findBestWindowOrFolderForFile<W extends ISimpleWindow>({ windows, newWindow, reuseWindow, context, fileUri, workspaceResolver }: IBestWindowOrFolderOptions<W>): W | null {
if (!newWindow && fileUri && (context === OpenContext.DESKTOP || context === OpenContext.CLI || context === OpenContext.DOCK)) {
const windowOnFilePath = findWindowOnFilePath(windows, fileUri, workspaceResolver);
if (windowOnFilePath) {
return windowOnFilePath;
}
}
return !newWindow ? getLastActiveWindow(windows) : null;
}
function findWindowOnFilePath<W extends ISimpleWindow>(windows: W[], filePath: string, workspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace): W {
function findWindowOnFilePath<W extends ISimpleWindow>(windows: W[], fileUri: URI, workspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace): W | null {
// First check for windows with workspaces that have a parent folder of the provided path opened
const workspaceWindows = windows.filter(window => !!window.openedWorkspace);
for (let i = 0; i < workspaceWindows.length; i++) {
const window = workspaceWindows[i];
const resolvedWorkspace = workspaceResolver(window.openedWorkspace);
if (resolvedWorkspace && resolvedWorkspace.folders.some(folder => folder.uri.scheme === Schemas.file && paths.isEqualOrParent(filePath, folder.uri.fsPath, !platform.isLinux /* ignorecase */))) {
const resolvedWorkspace = workspaceResolver(window.openedWorkspace!);
if (resolvedWorkspace && resolvedWorkspace.folders.some(folder => isEqualOrParent(fileUri, folder.uri))) {
return window;
}
}
// Then go with single folder windows that are parent of the provided file path
const singleFolderWindowsOnFilePath = windows.filter(window => window.openedFolderUri && window.openedFolderUri.scheme === Schemas.file && paths.isEqualOrParent(filePath, window.openedFolderUri.fsPath, !platform.isLinux /* ignorecase */));
const singleFolderWindowsOnFilePath = windows.filter(window => window.openedFolderUri && isEqualOrParent(fileUri, window.openedFolderUri));
if (singleFolderWindowsOnFilePath.length) {
return singleFolderWindowsOnFilePath.sort((a, b) => -(a.openedFolderUri.path.length - b.openedFolderUri.path.length))[0];
return singleFolderWindowsOnFilePath.sort((a, b) => -(a.openedFolderUri!.path.length - b.openedFolderUri!.path.length))[0];
}
return null;
@@ -70,52 +66,51 @@ export function getLastActiveWindow<W extends ISimpleWindow>(windows: W[]): W {
return windows.filter(window => window.lastFocusTime === lastFocusedDate)[0];
}
export function findWindowOnWorkspace<W extends ISimpleWindow>(windows: W[], workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier)): W {
return windows.filter(window => {
// match on folder
if (isSingleFolderWorkspaceIdentifier(workspace)) {
if (window.openedFolderUri && isEqual(window.openedFolderUri, workspace, hasToIgnoreCase(window.openedFolderUri))) {
return true;
export function findWindowOnWorkspace<W extends ISimpleWindow>(windows: W[], workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier)): W | null {
if (isSingleFolderWorkspaceIdentifier(workspace)) {
for (const window of windows) {
// match on folder
if (isSingleFolderWorkspaceIdentifier(workspace)) {
if (window.openedFolderUri && isEqual(window.openedFolderUri, workspace)) {
return window;
}
}
}
// match on workspace
else {
} else if (isWorkspaceIdentifier(workspace)) {
for (const window of windows) {
// match on workspace
if (window.openedWorkspace && window.openedWorkspace.id === workspace.id) {
return true;
return window;
}
}
return false;
})[0];
}
return null;
}
export function findWindowOnExtensionDevelopmentPath<W extends ISimpleWindow>(windows: W[], extensionDevelopmentPath: string): W {
return windows.filter(window => {
// match on extension development path
if (paths.isEqual(window.extensionDevelopmentPath, extensionDevelopmentPath, !platform.isLinux /* ignorecase */)) {
return true;
export function findWindowOnExtensionDevelopmentPath<W extends ISimpleWindow>(windows: W[], extensionDevelopmentPath: string): W | null {
for (const window of windows) {
// match on extension development path. The path can be a path or uri string, using paths.isEqual is not 100% correct but good enough
if (window.extensionDevelopmentPath && paths.isEqual(window.extensionDevelopmentPath, extensionDevelopmentPath, !platform.isLinux /* ignorecase */)) {
return window;
}
return false;
})[0];
}
return null;
}
export function findWindowOnWorkspaceOrFolderUri<W extends ISimpleWindow>(windows: W[], uri: URI): W {
return windows.filter(window => {
export function findWindowOnWorkspaceOrFolderUri<W extends ISimpleWindow>(windows: W[], uri: URI): W | null {
if (!uri) {
return null;
}
for (const window of windows) {
// check for workspace config path
if (window.openedWorkspace && isEqual(URI.file(window.openedWorkspace.configPath), uri, !platform.isLinux /* ignorecase */)) {
return true;
return window;
}
// check for folder path
if (window.openedFolderUri && isEqual(window.openedFolderUri, uri, hasToIgnoreCase(uri))) {
return true;
if (window.openedFolderUri && isEqual(window.openedFolderUri, uri)) {
return window;
}
return false;
})[0];
}
return null;
}

View File

@@ -2,8 +2,6 @@
* 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 * as assert from 'assert';
import { formatOptions } from 'vs/platform/environment/node/argv';

View File

@@ -2,17 +2,16 @@
* 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 * as assert from 'assert';
import * as path from 'path';
import { findBestWindowOrFolderForFile, ISimpleWindow, IBestWindowOrFolderOptions } from 'vs/code/node/windowsFinder';
import { OpenContext } from 'vs/platform/windows/common/windows';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { toWorkspaceFolders } from 'vs/platform/workspace/common/workspace';
import URI from 'vs/base/common/uri';
import { URI } from 'vs/base/common/uri';
import { getPathFromAmdModule } from 'vs/base/common/amd';
const fixturesFolder = require.toUrl('./fixtures');
const fixturesFolder = getPathFromAmdModule(require, './fixtures');
const testWorkspace: IWorkspaceIdentifier = {
id: Date.now().toString(),
@@ -45,32 +44,32 @@ suite('WindowsFinder', () => {
test('New window without folder when no windows exist', () => {
assert.equal(findBestWindowOrFolderForFile(options()), null);
assert.equal(findBestWindowOrFolderForFile(options({
filePath: path.join(fixturesFolder, 'no_vscode_folder', 'file.txt')
fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'file.txt'))
})), null);
assert.equal(findBestWindowOrFolderForFile(options({
filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt'),
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')),
newWindow: true
})), null);
assert.equal(findBestWindowOrFolderForFile(options({
filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt'),
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')),
reuseWindow: true
})), null);
assert.equal(findBestWindowOrFolderForFile(options({
filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt'),
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')),
context: OpenContext.API
})), null);
assert.equal(findBestWindowOrFolderForFile(options({
filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt')
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt'))
})), null);
assert.equal(findBestWindowOrFolderForFile(options({
filePath: path.join(fixturesFolder, 'vscode_folder', 'new_folder', 'new_file.txt')
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'new_folder', 'new_file.txt'))
})), null);
});
test('New window without folder when windows exist', () => {
assert.equal(findBestWindowOrFolderForFile(options({
windows,
filePath: path.join(fixturesFolder, 'no_vscode_folder', 'file.txt'),
fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'file.txt')),
newWindow: true
})), null);
});
@@ -81,16 +80,16 @@ suite('WindowsFinder', () => {
})), lastActiveWindow);
assert.equal(findBestWindowOrFolderForFile(options({
windows,
filePath: path.join(fixturesFolder, 'no_vscode_folder2', 'file.txt')
fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder2', 'file.txt'))
})), lastActiveWindow);
assert.equal(findBestWindowOrFolderForFile(options({
windows: [lastActiveWindow, noVscodeFolderWindow],
filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt'),
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')),
reuseWindow: true
})), lastActiveWindow);
assert.equal(findBestWindowOrFolderForFile(options({
windows,
filePath: path.join(fixturesFolder, 'no_vscode_folder', 'file.txt'),
fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'file.txt')),
context: OpenContext.API
})), lastActiveWindow);
});
@@ -98,16 +97,16 @@ suite('WindowsFinder', () => {
test('Existing window with folder', () => {
assert.equal(findBestWindowOrFolderForFile(options({
windows,
filePath: path.join(fixturesFolder, 'no_vscode_folder', 'file.txt')
fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'file.txt'))
})), noVscodeFolderWindow);
assert.equal(findBestWindowOrFolderForFile(options({
windows,
filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt')
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt'))
})), vscodeFolderWindow);
const window: ISimpleWindow = { lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'nested_folder')) };
assert.equal(findBestWindowOrFolderForFile(options({
windows: [window],
filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_folder', 'subfolder', 'file.txt')
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'nested_folder', 'subfolder', 'file.txt'))
})), window);
});
@@ -116,7 +115,7 @@ suite('WindowsFinder', () => {
const nestedFolderWindow: ISimpleWindow = { lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'nested_folder')) };
assert.equal(findBestWindowOrFolderForFile(options({
windows: [window, nestedFolderWindow],
filePath: path.join(fixturesFolder, 'no_vscode_folder', 'nested_folder', 'subfolder', 'file.txt')
fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'nested_folder', 'subfolder', 'file.txt'))
})), nestedFolderWindow);
});
@@ -124,7 +123,7 @@ suite('WindowsFinder', () => {
const window: ISimpleWindow = { lastFocusTime: 1, openedWorkspace: testWorkspace };
assert.equal(findBestWindowOrFolderForFile(options({
windows: [window],
filePath: path.join(fixturesFolder, 'vscode_workspace_2_folder', 'nested_vscode_folder', 'subfolder', 'file.txt')
fileUri: URI.file(path.join(fixturesFolder, 'vscode_workspace_2_folder', 'nested_vscode_folder', 'subfolder', 'file.txt'))
})), window);
});
});