mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-01 17:23:35 -05:00
Merge from master
This commit is contained in:
@@ -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';
|
||||
@@ -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 });
|
||||
@@ -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('');
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -24,6 +24,10 @@ td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
label {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.block-settingsSearchResults-details {
|
||||
padding-bottom: .5rem;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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 });
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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)
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
19
src/vs/code/electron-browser/workbench/workbench.html
Normal file
19
src/vs/code/electron-browser/workbench/workbench.html
Normal 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>
|
||||
161
src/vs/code/electron-browser/workbench/workbench.js
Normal file
161
src/vs/code/electron-browser/workbench/workbench.js
Normal 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');
|
||||
});
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
39
src/vs/code/electron-main/theme.ts
Normal file
39
src/vs/code/electron-main/theme.ts
Normal 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;
|
||||
}
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
26
src/vs/code/node/wait.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user