mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-29 21:11:38 +01:00
workbench/electron-main -> code/electron-main
This commit is contained in:
92
src/vs/code/electron-main/argv.ts
Normal file
92
src/vs/code/electron-main/argv.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as os from 'os';
|
||||
import * as minimist from 'minimist';
|
||||
import pkg from 'vs/code/node/package';
|
||||
|
||||
export interface ParsedArgs extends minimist.ParsedArgs {
|
||||
help: boolean;
|
||||
version: boolean;
|
||||
wait: boolean;
|
||||
diff: boolean;
|
||||
goto: boolean;
|
||||
'new-window': boolean;
|
||||
'reuse-window': boolean;
|
||||
locale: string;
|
||||
'user-data-dir': string;
|
||||
performance: boolean;
|
||||
verbose: boolean;
|
||||
logExtensionHostCommunication: boolean;
|
||||
debugBrkFileWatcherPort: string;
|
||||
'disable-extensions': boolean;
|
||||
extensionHomePath: string;
|
||||
extensionDevelopmentPath: string;
|
||||
extensionTestsPath: string;
|
||||
timestamp: string;
|
||||
debugBrkPluginHost: string;
|
||||
debugPluginHost: string;
|
||||
}
|
||||
|
||||
const options: minimist.Opts = {
|
||||
string: [
|
||||
'locale',
|
||||
'user-data-dir',
|
||||
'extensionHomePath',
|
||||
'extensionDevelopmentPath',
|
||||
'extensionTestsPath',
|
||||
'timestamp'
|
||||
],
|
||||
boolean: [
|
||||
'help',
|
||||
'version',
|
||||
'wait',
|
||||
'diff',
|
||||
'goto',
|
||||
'new-window',
|
||||
'reuse-window',
|
||||
'performance',
|
||||
'verbose',
|
||||
'logExtensionHostCommunication',
|
||||
'disable-extensions'
|
||||
],
|
||||
alias: {
|
||||
help: 'h',
|
||||
version: 'v',
|
||||
wait: 'w',
|
||||
diff: 'd',
|
||||
goto: 'g',
|
||||
'new-window': 'n',
|
||||
'reuse-window': 'r',
|
||||
performance: 'p',
|
||||
'disable-extensions': 'disableExtensions'
|
||||
}
|
||||
};
|
||||
|
||||
export function parseArgs(args: string[]) {
|
||||
return minimist(args, options) as ParsedArgs;
|
||||
}
|
||||
|
||||
const executable = 'code' + (os.platform() === 'win32' ? '.exe' : '');
|
||||
const indent = ' ';
|
||||
export const helpMessage = `Visual Studio Code v${ pkg.version }
|
||||
|
||||
Usage: ${ executable } [arguments] [paths...]
|
||||
|
||||
Options:
|
||||
${ indent }-d, --diff Open a diff editor. Requires to pass two file paths
|
||||
${ indent } as arguments.
|
||||
${ indent }--disable-extensions Disable all installed extensions.
|
||||
${ indent }-g, --goto Open the file at path at the line and column (add
|
||||
${ indent } :line[:column] to path).
|
||||
${ indent }-h, --help Print usage.
|
||||
${ indent }--locale <locale> The locale to use (e.g. en-US or zh-TW).
|
||||
${ indent }-n, --new-window Force a new instance of Code.
|
||||
${ indent }-r, --reuse-window Force opening a file or folder in the last active
|
||||
${ indent } window.
|
||||
${ indent }--user-data-dir <dir> Specifies the directory that user data is kept in,
|
||||
${ indent } useful when running as root.
|
||||
${ indent }-v, --version Print version.
|
||||
${ indent }-w, --wait Wait for the window to be closed before returning.`;
|
||||
75
src/vs/code/electron-main/auto-updater.linux.ts
Normal file
75
src/vs/code/electron-main/auto-updater.linux.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import events = require('events');
|
||||
import { isString } from 'vs/base/common/types';
|
||||
import { Promise } from 'vs/base/common/winjs.base';
|
||||
import { json } from 'vs/base/node/request';
|
||||
import { getProxyAgent } from 'vs/base/node/proxy';
|
||||
import { ISettingsService } from 'vs/code/electron-main/settings';
|
||||
import { IEnvironmentService } from 'vs/code/electron-main/env';
|
||||
|
||||
export interface IUpdate {
|
||||
url: string;
|
||||
name: string;
|
||||
releaseNotes?: string;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
export class LinuxAutoUpdaterImpl extends events.EventEmitter {
|
||||
|
||||
private url: string;
|
||||
private currentRequest: Promise;
|
||||
|
||||
constructor(
|
||||
@IEnvironmentService private envService: IEnvironmentService,
|
||||
@ISettingsService private settingsManager: ISettingsService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.url = null;
|
||||
this.currentRequest = null;
|
||||
}
|
||||
|
||||
setFeedURL(url: string): void {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
checkForUpdates(): void {
|
||||
if (!this.url) {
|
||||
throw new Error('No feed url set.');
|
||||
}
|
||||
|
||||
if (this.currentRequest) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit('checking-for-update');
|
||||
|
||||
const proxyUrl = this.settingsManager.getValue('http.proxy');
|
||||
const strictSSL = this.settingsManager.getValue('http.proxyStrictSSL', true);
|
||||
const agent = getProxyAgent(this.url, { proxyUrl, strictSSL });
|
||||
|
||||
this.currentRequest = json<IUpdate>({ url: this.url, agent })
|
||||
.then(update => {
|
||||
if (!update || !update.url || !update.version) {
|
||||
this.emit('update-not-available');
|
||||
} else {
|
||||
this.emit('update-available', null, this.envService.product.downloadUrl);
|
||||
}
|
||||
})
|
||||
.then(null, e => {
|
||||
if (isString(e) && /^Server returned/.test(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit('update-not-available');
|
||||
this.emit('error', e);
|
||||
})
|
||||
.then(() => this.currentRequest = null);
|
||||
}
|
||||
}
|
||||
144
src/vs/code/electron-main/auto-updater.win32.ts
Normal file
144
src/vs/code/electron-main/auto-updater.win32.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import events = require('events');
|
||||
import path = require('path');
|
||||
import os = require('os');
|
||||
import cp = require('child_process');
|
||||
import pfs = require('vs/base/node/pfs');
|
||||
import { mkdirp } from 'vs/base/node/extfs';
|
||||
import { isString } from 'vs/base/common/types';
|
||||
import { Promise, TPromise } from 'vs/base/common/winjs.base';
|
||||
import { download, json } from 'vs/base/node/request';
|
||||
import { getProxyAgent } from 'vs/base/node/proxy';
|
||||
import { ISettingsService } from 'vs/code/electron-main/settings';
|
||||
import { ILifecycleService } from 'vs/code/electron-main/lifecycle';
|
||||
import { IEnvironmentService } from './env';
|
||||
|
||||
export interface IUpdate {
|
||||
url: string;
|
||||
name: string;
|
||||
releaseNotes?: string;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
export class Win32AutoUpdaterImpl extends events.EventEmitter {
|
||||
|
||||
private url: string;
|
||||
private currentRequest: Promise;
|
||||
|
||||
constructor(
|
||||
@ILifecycleService private lifecycleService: ILifecycleService,
|
||||
@IEnvironmentService private envService: IEnvironmentService,
|
||||
@ISettingsService private settingsManager: ISettingsService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.url = null;
|
||||
this.currentRequest = null;
|
||||
}
|
||||
|
||||
public get cachePath(): TPromise<string> {
|
||||
let result = path.join(os.tmpdir(), 'vscode-update');
|
||||
return new TPromise<string>((c, e) => mkdirp(result, null, err => err ? e(err) : c(result)));
|
||||
}
|
||||
|
||||
public setFeedURL(url: string): void {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public checkForUpdates(): void {
|
||||
if (!this.url) {
|
||||
throw new Error('No feed url set.');
|
||||
}
|
||||
|
||||
if (this.currentRequest) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit('checking-for-update');
|
||||
|
||||
const proxyUrl = this.settingsManager.getValue('http.proxy');
|
||||
const strictSSL = this.settingsManager.getValue('http.proxyStrictSSL', true);
|
||||
const agent = getProxyAgent(this.url, { proxyUrl, strictSSL });
|
||||
|
||||
this.currentRequest = json<IUpdate>({ url: this.url, agent })
|
||||
.then(update => {
|
||||
if (!update || !update.url || !update.version) {
|
||||
this.emit('update-not-available');
|
||||
return this.cleanup();
|
||||
}
|
||||
|
||||
this.emit('update-available');
|
||||
|
||||
return this.cleanup(update.version).then(() => {
|
||||
return this.getUpdatePackagePath(update.version).then(updatePackagePath => {
|
||||
return pfs.exists(updatePackagePath).then(exists => {
|
||||
if (exists) {
|
||||
return TPromise.as(updatePackagePath);
|
||||
}
|
||||
|
||||
const url = update.url;
|
||||
const downloadPath = `${updatePackagePath}.tmp`;
|
||||
const agent = getProxyAgent(url, { proxyUrl, strictSSL });
|
||||
|
||||
return download(downloadPath, { url, agent, strictSSL })
|
||||
.then(() => pfs.rename(downloadPath, updatePackagePath))
|
||||
.then(() => updatePackagePath);
|
||||
});
|
||||
}).then(updatePackagePath => {
|
||||
this.emit('update-downloaded',
|
||||
{},
|
||||
update.releaseNotes,
|
||||
update.version,
|
||||
new Date(),
|
||||
this.url,
|
||||
() => this.quitAndUpdate(updatePackagePath)
|
||||
);
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(null, e => {
|
||||
if (isString(e) && /^Server returned/.test(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit('update-not-available');
|
||||
this.emit('error', e);
|
||||
})
|
||||
.then(() => this.currentRequest = null);
|
||||
}
|
||||
|
||||
private getUpdatePackagePath(version: string): TPromise<string> {
|
||||
return this.cachePath.then(cachePath => path.join(cachePath, `CodeSetup-${ this.envService.quality }-${ version }.exe`));
|
||||
}
|
||||
|
||||
private quitAndUpdate(updatePackagePath: string): void {
|
||||
this.lifecycleService.quit().done(vetod => {
|
||||
if (vetod) {
|
||||
return;
|
||||
}
|
||||
|
||||
cp.spawn(updatePackagePath, ['/silent', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], {
|
||||
detached: true,
|
||||
stdio: ['ignore', 'ignore', 'ignore']
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private cleanup(exceptVersion: string = null): Promise {
|
||||
const filter = exceptVersion ? one => !(new RegExp(`${ this.envService.quality }-${ exceptVersion }\\.exe$`).test(one)) : () => true;
|
||||
|
||||
return this.cachePath
|
||||
.then(cachePath => pfs.readdir(cachePath)
|
||||
.then(all => Promise.join(all
|
||||
.filter(filter)
|
||||
.map(one => pfs.unlink(path.join(cachePath, one)).then(null, () => null))
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
41
src/vs/code/electron-main/cli.ts
Normal file
41
src/vs/code/electron-main/cli.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { spawn } from 'child_process';
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import { parseArgs, helpMessage } from './argv';
|
||||
import pkg from 'vs/code/node/package';
|
||||
|
||||
export function main(args: string[]) {
|
||||
const argv = parseArgs(args);
|
||||
|
||||
if (argv.help) {
|
||||
console.log(helpMessage);
|
||||
} else if (argv.version) {
|
||||
console.log(pkg.version);
|
||||
} else {
|
||||
const env = assign({}, process.env, {
|
||||
// this will signal Code that it was spawned from this module
|
||||
'VSCODE_CLI': '1',
|
||||
'ELECTRON_NO_ATTACH_CONSOLE': '1'
|
||||
});
|
||||
delete env['ATOM_SHELL_INTERNAL_RUN_AS_NODE'];
|
||||
|
||||
const child = spawn(process.execPath, args, {
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
env
|
||||
});
|
||||
|
||||
if (argv.wait) {
|
||||
child.on('exit', process.exit);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
main(process.argv.slice(2));
|
||||
380
src/vs/code/electron-main/env.ts
Normal file
380
src/vs/code/electron-main/env.ts
Normal file
@@ -0,0 +1,380 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import crypto = require('crypto');
|
||||
import fs = require('fs');
|
||||
import path = require('path');
|
||||
import os = require('os');
|
||||
import {app} from 'electron';
|
||||
import arrays = require('vs/base/common/arrays');
|
||||
import strings = require('vs/base/common/strings');
|
||||
import paths = require('vs/base/common/paths');
|
||||
import platform = require('vs/base/common/platform');
|
||||
import uri from 'vs/base/common/uri';
|
||||
import types = require('vs/base/common/types');
|
||||
import {ServiceIdentifier, createDecorator} from 'vs/platform/instantiation/common/instantiation';
|
||||
import product, {IProductConfiguration} from 'vs/code/node/product';
|
||||
import { parseArgs } from './argv';
|
||||
|
||||
export interface IProcessEnvironment {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface ICommandLineArguments {
|
||||
verboseLogging: boolean;
|
||||
debugExtensionHostPort: number;
|
||||
debugBrkExtensionHost: boolean;
|
||||
debugBrkFileWatcherPort: number;
|
||||
logExtensionHostCommunication: boolean;
|
||||
disableExtensions: boolean;
|
||||
extensionsHomePath: string;
|
||||
extensionDevelopmentPath: string;
|
||||
extensionTestsPath: string;
|
||||
programStart: number;
|
||||
pathArguments?: string[];
|
||||
enablePerformance?: boolean;
|
||||
openNewWindow?: boolean;
|
||||
openInSameWindow?: boolean;
|
||||
gotoLineMode?: boolean;
|
||||
diffMode?: boolean;
|
||||
locale?: string;
|
||||
waitForWindowClose?: boolean;
|
||||
}
|
||||
|
||||
export const IEnvironmentService = createDecorator<IEnvironmentService>('environmentService');
|
||||
|
||||
export interface IEnvironmentService {
|
||||
serviceId: ServiceIdentifier<any>;
|
||||
cliArgs: ICommandLineArguments;
|
||||
userExtensionsHome: string;
|
||||
isTestingFromCli: boolean;
|
||||
isBuilt: boolean;
|
||||
product: IProductConfiguration;
|
||||
updateUrl: string;
|
||||
quality: string;
|
||||
userHome: string;
|
||||
appRoot: string;
|
||||
currentWorkingDirectory: string;
|
||||
version: string;
|
||||
appHome: string;
|
||||
appSettingsHome: string;
|
||||
appSettingsPath: string;
|
||||
appKeybindingsPath: string;
|
||||
mainIPCHandle: string;
|
||||
sharedIPCHandle: string;
|
||||
}
|
||||
|
||||
function getNumericValue(value: string, defaultValue: number, fallback: number = void 0) {
|
||||
const numericValue = parseInt(value);
|
||||
|
||||
if (types.isNumber(numericValue)) {
|
||||
return numericValue;
|
||||
}
|
||||
|
||||
if (value) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
export class EnvService implements IEnvironmentService {
|
||||
|
||||
serviceId = IEnvironmentService;
|
||||
|
||||
private _cliArgs: ICommandLineArguments;
|
||||
get cliArgs(): ICommandLineArguments { return this._cliArgs; }
|
||||
|
||||
private _userExtensionsHome: string;
|
||||
get userExtensionsHome(): string { return this._userExtensionsHome; }
|
||||
|
||||
private _isTestingFromCli: boolean;
|
||||
get isTestingFromCli(): boolean { return this._isTestingFromCli; }
|
||||
|
||||
get isBuilt(): boolean { return !process.env['VSCODE_DEV']; }
|
||||
|
||||
get product(): IProductConfiguration { return product; }
|
||||
get updateUrl(): string { return product.updateUrl; }
|
||||
get quality(): string { return product.quality; }
|
||||
|
||||
private _userHome: string;
|
||||
get userHome(): string { return this._userHome; }
|
||||
|
||||
private _appRoot: string;
|
||||
get appRoot(): string { return this._appRoot; }
|
||||
|
||||
private _currentWorkingDirectory: string;
|
||||
get currentWorkingDirectory(): string { return this._currentWorkingDirectory; }
|
||||
|
||||
private _version: string;
|
||||
get version(): string { return this._version; }
|
||||
|
||||
private _appHome: string;
|
||||
get appHome(): string { return this._appHome; }
|
||||
|
||||
private _appSettingsHome: string;
|
||||
get appSettingsHome(): string { return this._appSettingsHome; }
|
||||
|
||||
private _appSettingsPath: string;
|
||||
get appSettingsPath(): string { return this._appSettingsPath; }
|
||||
|
||||
private _appKeybindingsPath: string;
|
||||
get appKeybindingsPath(): string { return this._appKeybindingsPath; }
|
||||
|
||||
private _mainIPCHandle: string;
|
||||
get mainIPCHandle(): string { return this._mainIPCHandle; }
|
||||
|
||||
private _sharedIPCHandle: string;
|
||||
get sharedIPCHandle(): string { return this._sharedIPCHandle; }
|
||||
|
||||
constructor() {
|
||||
this._appRoot = path.dirname(uri.parse(require.toUrl('')).fsPath);
|
||||
this._currentWorkingDirectory = process.env['VSCODE_CWD'] || process.cwd();
|
||||
this._version = app.getVersion();
|
||||
this._appHome = app.getPath('userData');
|
||||
this._appSettingsHome = path.join(this._appHome, 'User');
|
||||
|
||||
// TODO move out of here!
|
||||
if (!fs.existsSync(this._appSettingsHome)) {
|
||||
fs.mkdirSync(this._appSettingsHome);
|
||||
}
|
||||
|
||||
this._appSettingsPath = path.join(this._appSettingsHome, 'settings.json');
|
||||
this._appKeybindingsPath = path.join(this._appSettingsHome, 'keybindings.json');
|
||||
|
||||
// Remove the Electron executable
|
||||
let [, ...args] = process.argv;
|
||||
|
||||
// If dev, remove the first non-option argument: it's the app location
|
||||
if (!this.isBuilt) {
|
||||
const index = arrays.firstIndex(args, a => !/^-/.test(a));
|
||||
|
||||
if (index > -1) {
|
||||
args.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, prepend any extra arguments from the 'argv' file
|
||||
if (fs.existsSync(path.join(this._appRoot, 'argv'))) {
|
||||
const extraargs: string[] = JSON.parse(fs.readFileSync(path.join(this._appRoot, 'argv'), 'utf8'));
|
||||
args = [...extraargs, ...args];
|
||||
}
|
||||
|
||||
const argv = parseArgs(args);
|
||||
|
||||
const debugBrkExtensionHostPort = getNumericValue(argv.debugBrkPluginHost, 5870);
|
||||
const debugExtensionHostPort = getNumericValue(argv.debugPluginHost, 5870, this.isBuilt ? void 0 : 5870);
|
||||
const pathArguments = parsePathArguments(this._currentWorkingDirectory, argv._, argv.goto);
|
||||
const timestamp = parseInt(argv.timestamp);
|
||||
const debugBrkFileWatcherPort = getNumericValue(argv.debugBrkFileWatcherPort, void 0);
|
||||
|
||||
this._cliArgs = Object.freeze({
|
||||
pathArguments: pathArguments,
|
||||
programStart: types.isNumber(timestamp) ? timestamp : 0,
|
||||
enablePerformance: argv.performance,
|
||||
verboseLogging: argv.verbose,
|
||||
debugExtensionHostPort: debugBrkExtensionHostPort || debugExtensionHostPort,
|
||||
debugBrkExtensionHost: !!debugBrkExtensionHostPort,
|
||||
logExtensionHostCommunication: argv.logExtensionHostCommunication,
|
||||
debugBrkFileWatcherPort: debugBrkFileWatcherPort,
|
||||
openNewWindow: argv['new-window'],
|
||||
openInSameWindow: argv['reuse-window'],
|
||||
gotoLineMode: argv.goto,
|
||||
diffMode: argv.diff && pathArguments.length === 2,
|
||||
extensionsHomePath: normalizePath(argv.extensionHomePath),
|
||||
extensionDevelopmentPath: normalizePath(argv.extensionDevelopmentPath),
|
||||
extensionTestsPath: normalizePath(argv.extensionTestsPath),
|
||||
disableExtensions: argv['disable-extensions'],
|
||||
locale: argv.locale,
|
||||
waitForWindowClose: argv.wait
|
||||
});
|
||||
|
||||
this._isTestingFromCli = this.cliArgs.extensionTestsPath && !this.cliArgs.debugBrkExtensionHost;
|
||||
|
||||
this._userHome = path.join(app.getPath('home'), product.dataFolderName);
|
||||
|
||||
// TODO move out of here!
|
||||
if (!fs.existsSync(this._userHome)) {
|
||||
fs.mkdirSync(this._userHome);
|
||||
}
|
||||
|
||||
this._userExtensionsHome = this.cliArgs.extensionsHomePath || path.join(this._userHome, 'extensions');
|
||||
|
||||
// TODO move out of here!
|
||||
if (!fs.existsSync(this._userExtensionsHome)) {
|
||||
fs.mkdirSync(this._userExtensionsHome);
|
||||
}
|
||||
|
||||
this._mainIPCHandle = this.getMainIPCHandle();
|
||||
this._sharedIPCHandle = this.getSharedIPCHandle();
|
||||
}
|
||||
|
||||
private getMainIPCHandle(): string {
|
||||
return this.getIPCHandleName() + (process.platform === 'win32' ? '-sock' : '.sock');
|
||||
}
|
||||
|
||||
private getSharedIPCHandle(): string {
|
||||
return this.getIPCHandleName() + '-shared' + (process.platform === 'win32' ? '-sock' : '.sock');
|
||||
}
|
||||
|
||||
private getIPCHandleName(): string {
|
||||
let handleName = app.getName();
|
||||
|
||||
if (!this.isBuilt) {
|
||||
handleName += '-dev';
|
||||
}
|
||||
|
||||
// Support to run VS Code multiple times as different user
|
||||
// by making the socket unique over the logged in user
|
||||
let userId = EnvService.getUniqueUserId();
|
||||
if (userId) {
|
||||
handleName += ('-' + userId);
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
return '\\\\.\\pipe\\' + handleName;
|
||||
}
|
||||
|
||||
return path.join(os.tmpdir(), handleName);
|
||||
}
|
||||
|
||||
private static getUniqueUserId(): string {
|
||||
let username: string;
|
||||
if (platform.isWindows) {
|
||||
username = process.env.USERNAME;
|
||||
} else {
|
||||
username = process.env.USER;
|
||||
}
|
||||
|
||||
if (!username) {
|
||||
return ''; // fail gracefully if there is no user name
|
||||
}
|
||||
|
||||
// use sha256 to ensure the userid value can be used in filenames and are unique
|
||||
return crypto.createHash('sha256').update(username).digest('hex').substr(0, 6);
|
||||
}
|
||||
}
|
||||
|
||||
function parsePathArguments(cwd: string, args: string[], gotoLineMode?: boolean): string[] {
|
||||
const result = args.map(arg => {
|
||||
let pathCandidate = arg;
|
||||
|
||||
let parsedPath: IParsedPath;
|
||||
if (gotoLineMode) {
|
||||
parsedPath = parseLineAndColumnAware(arg);
|
||||
pathCandidate = parsedPath.path;
|
||||
}
|
||||
|
||||
if (pathCandidate) {
|
||||
pathCandidate = preparePath(cwd, pathCandidate);
|
||||
}
|
||||
|
||||
let realPath: string;
|
||||
try {
|
||||
realPath = fs.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));
|
||||
}
|
||||
|
||||
if (!paths.isValidBasename(path.basename(realPath))) {
|
||||
return null; // do not allow invalid file names
|
||||
}
|
||||
|
||||
if (gotoLineMode) {
|
||||
parsedPath.path = realPath;
|
||||
return toLineAndColumnPath(parsedPath);
|
||||
}
|
||||
|
||||
return realPath;
|
||||
});
|
||||
|
||||
const caseInsensitive = platform.isWindows || platform.isMacintosh;
|
||||
const distinct = arrays.distinct(result, e => e && caseInsensitive ? e.toLowerCase() : e);
|
||||
|
||||
return arrays.coalesce(distinct);
|
||||
}
|
||||
|
||||
function preparePath(cwd: string, p: string): string {
|
||||
|
||||
// Trim trailing quotes
|
||||
if (platform.isWindows) {
|
||||
p = strings.rtrim(p, '"'); // https://github.com/Microsoft/vscode/issues/1498
|
||||
}
|
||||
|
||||
// Trim whitespaces
|
||||
p = strings.trim(strings.trim(p, ' '), '\t');
|
||||
|
||||
if (platform.isWindows) {
|
||||
|
||||
// Resolve the path against cwd if it is relative
|
||||
p = path.resolve(cwd, p);
|
||||
|
||||
// Trim trailing '.' chars on Windows to prevent invalid file names
|
||||
p = strings.rtrim(p, '.');
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
function normalizePath(p?: string): string {
|
||||
return p ? path.normalize(p) : p;
|
||||
}
|
||||
|
||||
export function getPlatformIdentifier(): string {
|
||||
if (process.platform === 'linux') {
|
||||
return `linux-${process.arch}`;
|
||||
}
|
||||
|
||||
return process.platform;
|
||||
}
|
||||
|
||||
export interface IParsedPath {
|
||||
path: string;
|
||||
line?: number;
|
||||
column?: number;
|
||||
}
|
||||
|
||||
export function parseLineAndColumnAware(rawPath: string): IParsedPath {
|
||||
let segments = rawPath.split(':'); // C:\file.txt:<line>:<column>
|
||||
|
||||
let path: string;
|
||||
let line: number = null;
|
||||
let column: number = null;
|
||||
|
||||
segments.forEach(segment => {
|
||||
let segmentAsNumber = Number(segment);
|
||||
if (!types.isNumber(segmentAsNumber)) {
|
||||
path = !!path ? [path, segment].join(':') : segment; // a colon can well be part of a path (e.g. C:\...)
|
||||
} else if (line === null) {
|
||||
line = segmentAsNumber;
|
||||
} else if (column === null) {
|
||||
column = segmentAsNumber;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
path: path,
|
||||
line: line !== null ? line : void 0,
|
||||
column: column !== null ? column : line !== null ? 1 : void 0 // if we have a line, make sure column is also set
|
||||
};
|
||||
}
|
||||
|
||||
export function toLineAndColumnPath(parsedPath: IParsedPath): string {
|
||||
let segments = [parsedPath.path];
|
||||
|
||||
if (types.isNumber(parsedPath.line)) {
|
||||
segments.push(String(parsedPath.line));
|
||||
}
|
||||
|
||||
if (types.isNumber(parsedPath.column)) {
|
||||
segments.push(String(parsedPath.column));
|
||||
}
|
||||
|
||||
return segments.join(':');
|
||||
}
|
||||
90
src/vs/code/electron-main/launch.ts
Normal file
90
src/vs/code/electron-main/launch.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { ICommandLineArguments, IProcessEnvironment } from 'vs/code/electron-main/env';
|
||||
import { IWindowsService } from 'vs/code/electron-main/windows';
|
||||
import { VSCodeWindow } from 'vs/code/electron-main/window';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { ILogService } from './log';
|
||||
|
||||
export interface ILaunchService {
|
||||
start(args: ICommandLineArguments, userEnv: IProcessEnvironment): TPromise<void>;
|
||||
}
|
||||
|
||||
export interface ILaunchChannel extends IChannel {
|
||||
call(command: 'start', args: ICommandLineArguments, userEnv: IProcessEnvironment): TPromise<void>;
|
||||
call(command: string, ...args: any[]): TPromise<any>;
|
||||
}
|
||||
|
||||
export class LaunchChannel implements ILaunchChannel {
|
||||
|
||||
constructor(private service: ILaunchService) { }
|
||||
|
||||
call(command: string, ...args: any[]): TPromise<any> {
|
||||
switch (command) {
|
||||
case 'start': return this.service.start(args[0], args[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class LaunchChannelClient implements ILaunchService {
|
||||
|
||||
constructor(private channel: ILaunchChannel) { }
|
||||
|
||||
start(args: ICommandLineArguments, userEnv: IProcessEnvironment): TPromise<void> {
|
||||
return this.channel.call('start', args, userEnv);
|
||||
}
|
||||
}
|
||||
|
||||
export class LaunchService implements ILaunchService {
|
||||
|
||||
constructor(
|
||||
@ILogService private logService: ILogService,
|
||||
@IWindowsService private windowsManager: IWindowsService
|
||||
) {}
|
||||
|
||||
start(args: ICommandLineArguments, userEnv: IProcessEnvironment): TPromise<void> {
|
||||
this.logService.log('Received data from other instance', args);
|
||||
|
||||
// Otherwise handle in windows manager
|
||||
let usedWindows: VSCodeWindow[];
|
||||
if (!!args.extensionDevelopmentPath) {
|
||||
this.windowsManager.openPluginDevelopmentHostWindow({ cli: args, userEnv: userEnv });
|
||||
} else if (args.pathArguments.length === 0 && args.openNewWindow) {
|
||||
usedWindows = this.windowsManager.open({ cli: args, userEnv: userEnv, forceNewWindow: true, forceEmpty: true });
|
||||
} else if (args.pathArguments.length === 0) {
|
||||
usedWindows = [this.windowsManager.focusLastActive(args)];
|
||||
} else {
|
||||
usedWindows = this.windowsManager.open({
|
||||
cli: args,
|
||||
userEnv: userEnv,
|
||||
forceNewWindow: args.waitForWindowClose || args.openNewWindow,
|
||||
preferNewWindow: !args.openInSameWindow,
|
||||
diffMode: args.diffMode
|
||||
});
|
||||
}
|
||||
|
||||
// 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
|
||||
if (args.waitForWindowClose && usedWindows && usedWindows.length === 1 && usedWindows[0]) {
|
||||
const windowId = usedWindows[0].id;
|
||||
|
||||
return new TPromise<void>((c, e) => {
|
||||
|
||||
const unbind = this.windowsManager.onClose(id => {
|
||||
if (id === windowId) {
|
||||
unbind();
|
||||
c(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
184
src/vs/code/electron-main/lifecycle.ts
Normal file
184
src/vs/code/electron-main/lifecycle.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import events = require('events');
|
||||
import {ipcMain as ipc, app} from 'electron';
|
||||
|
||||
import {TPromise, TValueCallback} from 'vs/base/common/winjs.base';
|
||||
import {ReadyState, VSCodeWindow} from 'vs/code/electron-main/window';
|
||||
import env = require('vs/code/electron-main/env');
|
||||
import {ServiceIdentifier, createDecorator} from 'vs/platform/instantiation/common/instantiation';
|
||||
import {ILogService} from './log';
|
||||
|
||||
const EventTypes = {
|
||||
BEFORE_QUIT: 'before-quit'
|
||||
};
|
||||
|
||||
export const ILifecycleService = createDecorator<ILifecycleService>('lifecycleService');
|
||||
|
||||
export interface ILifecycleService {
|
||||
serviceId: ServiceIdentifier<any>;
|
||||
onBeforeQuit(clb: () => void): () => void;
|
||||
ready(): void;
|
||||
registerWindow(vscodeWindow: VSCodeWindow): void;
|
||||
unload(vscodeWindow: VSCodeWindow): TPromise<boolean /* veto */>;
|
||||
quit(): TPromise<boolean /* veto */>;
|
||||
}
|
||||
|
||||
export class LifecycleService implements ILifecycleService {
|
||||
|
||||
serviceId = ILifecycleService;
|
||||
|
||||
private eventEmitter = new events.EventEmitter();
|
||||
private windowToCloseRequest: { [windowId: string]: boolean };
|
||||
private quitRequested: boolean;
|
||||
private pendingQuitPromise: TPromise<boolean>;
|
||||
private pendingQuitPromiseComplete: TValueCallback<boolean>;
|
||||
private oneTimeListenerTokenGenerator: number;
|
||||
|
||||
constructor(
|
||||
@env.IEnvironmentService private envService: env.IEnvironmentService,
|
||||
@ILogService private logService: ILogService
|
||||
) {
|
||||
this.windowToCloseRequest = Object.create(null);
|
||||
this.quitRequested = false;
|
||||
this.oneTimeListenerTokenGenerator = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Due to the way we handle lifecycle with eventing, the general app.on('before-quit')
|
||||
* event cannot be used because it can be called twice on shutdown. Instead the onBeforeQuit
|
||||
* handler in this module can be used and it is only called once on a shutdown sequence.
|
||||
*/
|
||||
onBeforeQuit(clb: () => void): () => void {
|
||||
this.eventEmitter.addListener(EventTypes.BEFORE_QUIT, clb);
|
||||
|
||||
return () => this.eventEmitter.removeListener(EventTypes.BEFORE_QUIT, clb);
|
||||
}
|
||||
|
||||
public ready(): void {
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// before-quit
|
||||
app.on('before-quit', (e) => {
|
||||
this.logService.log('Lifecycle#before-quit');
|
||||
|
||||
if (!this.quitRequested) {
|
||||
this.eventEmitter.emit(EventTypes.BEFORE_QUIT); // only send this if this is the first quit request we have
|
||||
}
|
||||
|
||||
this.quitRequested = true;
|
||||
});
|
||||
|
||||
// window-all-closed
|
||||
app.on('window-all-closed', () => {
|
||||
this.logService.log('Lifecycle#window-all-closed');
|
||||
|
||||
// Windows/Linux: we quit when all windows have closed
|
||||
// Mac: we only quit when quit was requested
|
||||
// --wait: we quit when all windows are closed
|
||||
if (this.quitRequested || process.platform !== 'darwin' || this.envService.cliArgs.waitForWindowClose) {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public registerWindow(vscodeWindow: VSCodeWindow): void {
|
||||
|
||||
// Window Before Closing: Main -> Renderer
|
||||
vscodeWindow.win.on('close', (e) => {
|
||||
let windowId = vscodeWindow.id;
|
||||
this.logService.log('Lifecycle#window-before-close', windowId);
|
||||
|
||||
// The window already acknowledged to be closed
|
||||
if (this.windowToCloseRequest[windowId]) {
|
||||
this.logService.log('Lifecycle#window-close', windowId);
|
||||
|
||||
delete this.windowToCloseRequest[windowId];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise prevent unload and handle it from window
|
||||
e.preventDefault();
|
||||
this.unload(vscodeWindow).done(veto => {
|
||||
if (!veto) {
|
||||
this.windowToCloseRequest[windowId] = true;
|
||||
vscodeWindow.win.close();
|
||||
} else {
|
||||
this.quitRequested = false;
|
||||
delete this.windowToCloseRequest[windowId];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public unload(vscodeWindow: VSCodeWindow): TPromise<boolean /* veto */> {
|
||||
|
||||
// Always allow to unload a window that is not yet ready
|
||||
if (vscodeWindow.readyState !== ReadyState.READY) {
|
||||
return TPromise.as<boolean>(false);
|
||||
}
|
||||
|
||||
this.logService.log('Lifecycle#unload()', vscodeWindow.id);
|
||||
|
||||
return new TPromise<boolean>((c) => {
|
||||
let oneTimeEventToken = this.oneTimeListenerTokenGenerator++;
|
||||
let oneTimeOkEvent = 'vscode:ok' + oneTimeEventToken;
|
||||
let oneTimeCancelEvent = 'vscode:cancel' + oneTimeEventToken;
|
||||
|
||||
ipc.once(oneTimeOkEvent, () => {
|
||||
c(false); // no veto
|
||||
});
|
||||
|
||||
ipc.once(oneTimeCancelEvent, () => {
|
||||
|
||||
// Any cancellation also cancels a pending quit if present
|
||||
if (this.pendingQuitPromiseComplete) {
|
||||
this.pendingQuitPromiseComplete(true /* veto */);
|
||||
this.pendingQuitPromiseComplete = null;
|
||||
this.pendingQuitPromise = null;
|
||||
}
|
||||
|
||||
c(true); // veto
|
||||
});
|
||||
|
||||
vscodeWindow.send('vscode:beforeUnload', { okChannel: oneTimeOkEvent, cancelChannel: oneTimeCancelEvent });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A promise that completes to indicate if the quit request has been veto'd
|
||||
* by the user or not.
|
||||
*/
|
||||
public quit(): TPromise<boolean /* veto */> {
|
||||
this.logService.log('Lifecycle#quit()');
|
||||
|
||||
if (!this.pendingQuitPromise) {
|
||||
this.pendingQuitPromise = new TPromise<boolean>((c) => {
|
||||
|
||||
// Store as field to access it from a window cancellation
|
||||
this.pendingQuitPromiseComplete = c;
|
||||
|
||||
app.once('will-quit', () => {
|
||||
if (this.pendingQuitPromiseComplete) {
|
||||
this.pendingQuitPromiseComplete(false /* no veto */);
|
||||
this.pendingQuitPromiseComplete = null;
|
||||
this.pendingQuitPromise = null;
|
||||
}
|
||||
});
|
||||
|
||||
app.quit();
|
||||
});
|
||||
}
|
||||
|
||||
return this.pendingQuitPromise;
|
||||
}
|
||||
}
|
||||
33
src/vs/code/electron-main/log.ts
Normal file
33
src/vs/code/electron-main/log.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { ServiceIdentifier, createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IEnvironmentService } from './env';
|
||||
|
||||
export const ILogService = createDecorator<ILogService>('logService');
|
||||
|
||||
export interface ILogService {
|
||||
serviceId: ServiceIdentifier<any>;
|
||||
log(... args: any[]): void;
|
||||
}
|
||||
|
||||
export class MainLogService implements ILogService {
|
||||
|
||||
serviceId = ILogService;
|
||||
|
||||
constructor(@IEnvironmentService private envService: IEnvironmentService) {
|
||||
|
||||
}
|
||||
|
||||
log(...args: any[]): void {
|
||||
const { verboseLogging } = this.envService.cliArgs;
|
||||
|
||||
if (verboseLogging) {
|
||||
console.log(`(${new Date().toLocaleTimeString()})`, ...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
288
src/vs/code/electron-main/main.ts
Normal file
288
src/vs/code/electron-main/main.ts
Normal file
@@ -0,0 +1,288 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import {app, ipcMain as ipc} from 'electron';
|
||||
import fs = require('fs');
|
||||
import nls = require('vs/nls');
|
||||
import {assign} from 'vs/base/common/objects';
|
||||
import platform = require('vs/base/common/platform');
|
||||
import { IProcessEnvironment, IEnvironmentService, EnvService } from 'vs/code/electron-main/env';
|
||||
import windows = require('vs/code/electron-main/windows');
|
||||
import { ILifecycleService, LifecycleService } from 'vs/code/electron-main/lifecycle';
|
||||
import { VSCodeMenu } from 'vs/code/electron-main/menus';
|
||||
import {ISettingsService, SettingsManager} from 'vs/code/electron-main/settings';
|
||||
import {IUpdateService, UpdateManager} from 'vs/code/electron-main/update-manager';
|
||||
import {Server, serve, connect} from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import {getUnixUserEnvironment, IEnv} from 'vs/base/node/env';
|
||||
import {TPromise} from 'vs/base/common/winjs.base';
|
||||
import {AskpassChannel} from 'vs/workbench/parts/git/common/gitIpc';
|
||||
import {GitAskpassService} from 'vs/workbench/parts/git/electron-main/askpassService';
|
||||
import {spawnSharedProcess} from 'vs/code/electron-main/sharedProcess';
|
||||
import {Mutex} from 'windows-mutex';
|
||||
import {LaunchService, ILaunchChannel, LaunchChannel, LaunchChannelClient} from './launch';
|
||||
import {ServicesAccessor, IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
|
||||
import {InstantiationService} from 'vs/platform/instantiation/common/instantiationService';
|
||||
import {ServiceCollection} from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import {SyncDescriptor} from 'vs/platform/instantiation/common/descriptors';
|
||||
import {ILogService, MainLogService} from './log';
|
||||
import {IStorageService, StorageService} from './storage';
|
||||
|
||||
function quit(accessor: ServicesAccessor, error?: Error);
|
||||
function quit(accessor: ServicesAccessor, message?: string);
|
||||
function quit(accessor: ServicesAccessor, arg?: any) {
|
||||
const logService = accessor.get(ILogService);
|
||||
|
||||
let exitCode = 0;
|
||||
if (typeof arg === 'string') {
|
||||
logService.log(arg);
|
||||
} else {
|
||||
exitCode = 1; // signal error to the outside
|
||||
if (arg.stack) {
|
||||
console.error(arg.stack);
|
||||
} else {
|
||||
console.error('Startup error: ' + arg.toString());
|
||||
}
|
||||
}
|
||||
|
||||
process.exit(exitCode); // in main, process.exit === app.exit
|
||||
}
|
||||
|
||||
function main(accessor: ServicesAccessor, ipcServer: Server, userEnv: IProcessEnvironment): void {
|
||||
const instantiationService = accessor.get(IInstantiationService);
|
||||
const logService = accessor.get(ILogService);
|
||||
const envService = accessor.get(IEnvironmentService);
|
||||
const windowManager = accessor.get(windows.IWindowsService);
|
||||
const lifecycleService = accessor.get(ILifecycleService);
|
||||
const updateManager = accessor.get(IUpdateService);
|
||||
const settingsManager = accessor.get(ISettingsService);
|
||||
|
||||
// We handle uncaught exceptions here to prevent electron from opening a dialog to the user
|
||||
process.on('uncaughtException', (err: any) => {
|
||||
if (err) {
|
||||
|
||||
// take only the message and stack property
|
||||
let friendlyError = {
|
||||
message: err.message,
|
||||
stack: err.stack
|
||||
};
|
||||
|
||||
// handle on client side
|
||||
windowManager.sendToFocused('vscode:reportError', JSON.stringify(friendlyError));
|
||||
}
|
||||
|
||||
console.error('[uncaught exception in main]: ' + err);
|
||||
if (err.stack) {
|
||||
console.error(err.stack);
|
||||
}
|
||||
});
|
||||
|
||||
logService.log('### VSCode main.js ###');
|
||||
logService.log(envService.appRoot, envService.cliArgs);
|
||||
|
||||
// Setup Windows mutex
|
||||
let windowsMutex: Mutex = null;
|
||||
try {
|
||||
const Mutex = (<any>require.__$__nodeRequire('windows-mutex')).Mutex;
|
||||
windowsMutex = new Mutex(envService.product.win32MutexName);
|
||||
} catch (e) {
|
||||
// noop
|
||||
}
|
||||
|
||||
// Register IPC services
|
||||
const launchService = instantiationService.createInstance(LaunchService);
|
||||
const launchChannel = new LaunchChannel(launchService);
|
||||
ipcServer.registerChannel('launch', launchChannel);
|
||||
|
||||
const askpassService = new GitAskpassService();
|
||||
const askpassChannel = new AskpassChannel(askpassService);
|
||||
ipcServer.registerChannel('askpass', askpassChannel);
|
||||
|
||||
// Used by sub processes to communicate back to the main instance
|
||||
process.env['VSCODE_PID'] = '' + process.pid;
|
||||
process.env['VSCODE_IPC_HOOK'] = envService.mainIPCHandle;
|
||||
process.env['VSCODE_SHARED_IPC_HOOK'] = envService.sharedIPCHandle;
|
||||
|
||||
// Spawn shared process
|
||||
const sharedProcess = instantiationService.invokeFunction(spawnSharedProcess);
|
||||
|
||||
// Make sure we associate the program with the app user model id
|
||||
// This will help Windows to associate the running program with
|
||||
// any shortcut that is pinned to the taskbar and prevent showing
|
||||
// two icons in the taskbar for the same app.
|
||||
if (platform.isWindows && envService.product.win32AppUserModelId) {
|
||||
app.setAppUserModelId(envService.product.win32AppUserModelId);
|
||||
}
|
||||
|
||||
// Set programStart in the global scope
|
||||
global.programStart = envService.cliArgs.programStart;
|
||||
|
||||
function dispose() {
|
||||
if (ipcServer) {
|
||||
ipcServer.dispose();
|
||||
ipcServer = null;
|
||||
}
|
||||
|
||||
sharedProcess.dispose();
|
||||
|
||||
if (windowsMutex) {
|
||||
windowsMutex.release();
|
||||
}
|
||||
}
|
||||
|
||||
// Dispose on app quit
|
||||
app.on('will-quit', () => {
|
||||
logService.log('App#will-quit: disposing resources');
|
||||
|
||||
dispose();
|
||||
});
|
||||
|
||||
// Dispose on vscode:exit
|
||||
ipc.on('vscode:exit', (event, code: number) => {
|
||||
logService.log('IPC#vscode:exit', code);
|
||||
|
||||
dispose();
|
||||
process.exit(code); // in main, process.exit === app.exit
|
||||
});
|
||||
|
||||
// Lifecycle
|
||||
lifecycleService.ready();
|
||||
|
||||
// Load settings
|
||||
settingsManager.loadSync();
|
||||
|
||||
// Propagate to clients
|
||||
windowManager.ready(userEnv);
|
||||
|
||||
// Install Menu
|
||||
const menuManager = instantiationService.createInstance(VSCodeMenu);
|
||||
menuManager.ready();
|
||||
|
||||
// Install Tasks
|
||||
if (platform.isWindows && envService.isBuilt) {
|
||||
app.setUserTasks([
|
||||
{
|
||||
title: nls.localize('newWindow', "New Window"),
|
||||
program: process.execPath,
|
||||
arguments: '-n', // force new window
|
||||
iconPath: process.execPath,
|
||||
iconIndex: 0
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
// Setup auto update
|
||||
updateManager.initialize();
|
||||
|
||||
// Open our first window
|
||||
if (envService.cliArgs.openNewWindow && envService.cliArgs.pathArguments.length === 0) {
|
||||
windowManager.open({ cli: envService.cliArgs, forceNewWindow: true, forceEmpty: true }); // new window if "-n" was used without paths
|
||||
} else if (global.macOpenFiles && global.macOpenFiles.length && (!envService.cliArgs.pathArguments || !envService.cliArgs.pathArguments.length)) {
|
||||
windowManager.open({ cli: envService.cliArgs, pathsToOpen: global.macOpenFiles }); // mac: open-file event received on startup
|
||||
} else {
|
||||
windowManager.open({ cli: envService.cliArgs, forceNewWindow: envService.cliArgs.openNewWindow, diffMode: envService.cliArgs.diffMode }); // default: read paths from cli
|
||||
}
|
||||
}
|
||||
|
||||
function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
|
||||
const logService = accessor.get(ILogService);
|
||||
const envService = accessor.get(IEnvironmentService);
|
||||
|
||||
function setup(retry: boolean): TPromise<Server> {
|
||||
return serve(envService.mainIPCHandle).then(server => {
|
||||
if (platform.isMacintosh) {
|
||||
app.dock.show(); // dock might be hidden at this case due to a retry
|
||||
}
|
||||
|
||||
return server;
|
||||
}, err => {
|
||||
if (err.code !== 'EADDRINUSE') {
|
||||
return TPromise.wrapError(err);
|
||||
}
|
||||
|
||||
// Since we are the second instance, we do not want to show the dock
|
||||
if (platform.isMacintosh) {
|
||||
app.dock.hide();
|
||||
}
|
||||
|
||||
// there's a running instance, let's connect to it
|
||||
return connect(envService.mainIPCHandle).then(
|
||||
client => {
|
||||
|
||||
// Tests from CLI require to be the only instance currently (TODO@Ben support multiple instances and output)
|
||||
if (envService.isTestingFromCli) {
|
||||
const msg = 'Running extension tests from the command line is currently only supported if no other instance of Code is running.';
|
||||
console.error(msg);
|
||||
client.dispose();
|
||||
return TPromise.wrapError(msg);
|
||||
}
|
||||
|
||||
logService.log('Sending env to running instance...');
|
||||
|
||||
const channel = client.getChannel<ILaunchChannel>('launch');
|
||||
const service = new LaunchChannelClient(channel);
|
||||
|
||||
return service.start(envService.cliArgs, process.env)
|
||||
.then(() => client.dispose())
|
||||
.then(() => TPromise.wrapError('Sent env to running instance. Terminating...'));
|
||||
},
|
||||
err => {
|
||||
if (!retry || platform.isWindows || err.code !== 'ECONNREFUSED') {
|
||||
return TPromise.wrapError(err);
|
||||
}
|
||||
|
||||
// it happens on Linux and OS X that the pipe is left behind
|
||||
// let's delete it, since we can't connect to it
|
||||
// and the retry the whole thing
|
||||
try {
|
||||
fs.unlinkSync(envService.mainIPCHandle);
|
||||
} catch (e) {
|
||||
logService.log('Fatal error deleting obsolete instance handle', e);
|
||||
return TPromise.wrapError(e);
|
||||
}
|
||||
|
||||
return setup(false);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return setup(true);
|
||||
}
|
||||
|
||||
// TODO: isolate
|
||||
const services = new ServiceCollection();
|
||||
|
||||
services.set(IEnvironmentService, new SyncDescriptor(EnvService));
|
||||
services.set(ILogService, new SyncDescriptor(MainLogService));
|
||||
services.set(windows.IWindowsService, new SyncDescriptor(windows.WindowsManager));
|
||||
services.set(ILifecycleService, new SyncDescriptor(LifecycleService));
|
||||
services.set(IStorageService, new SyncDescriptor(StorageService));
|
||||
services.set(IUpdateService, new SyncDescriptor(UpdateManager));
|
||||
services.set(ISettingsService, new SyncDescriptor(SettingsManager));
|
||||
|
||||
const instantiationService = new InstantiationService(services);
|
||||
|
||||
function getUserEnvironment(): TPromise<IEnv> {
|
||||
return platform.isWindows ? TPromise.as({}) : getUnixUserEnvironment();
|
||||
}
|
||||
|
||||
// On some platforms we need to manually read from the global environment variables
|
||||
// and assign them to the process environment (e.g. when doubleclick app on Mac)
|
||||
getUserEnvironment()
|
||||
.then(userEnv => {
|
||||
if (process.env['VSCODE_CLI'] !== '1') {
|
||||
assign(process.env, userEnv);
|
||||
}
|
||||
|
||||
// Make sure the NLS Config travels to the rendered process
|
||||
// See also https://github.com/Microsoft/vscode/issues/4558
|
||||
userEnv['VSCODE_NLS_CONFIG'] = process.env['VSCODE_NLS_CONFIG'];
|
||||
|
||||
return instantiationService.invokeFunction(setupIPC)
|
||||
.then(ipcServer => instantiationService.invokeFunction(main, ipcServer, userEnv));
|
||||
})
|
||||
.done(null, err => instantiationService.invokeFunction(quit, err));
|
||||
801
src/vs/code/electron-main/menus.ts
Normal file
801
src/vs/code/electron-main/menus.ts
Normal file
@@ -0,0 +1,801 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import {ipcMain as ipc, app, shell, dialog, Menu, MenuItem} from 'electron';
|
||||
|
||||
import nls = require('vs/nls');
|
||||
import platform = require('vs/base/common/platform');
|
||||
import arrays = require('vs/base/common/arrays');
|
||||
import { IWindowsService, WindowsManager, IOpenedPathsList } from 'vs/code/electron-main/windows';
|
||||
import window = require('vs/code/electron-main/window');
|
||||
import env = require('vs/code/electron-main/env');
|
||||
import { IStorageService } from 'vs/code/electron-main/storage';
|
||||
import { IUpdateService, State as UpdateState } from 'vs/code/electron-main/update-manager';
|
||||
import {Keybinding} from 'vs/base/common/keyCodes';
|
||||
|
||||
interface IResolvedKeybinding {
|
||||
id: string;
|
||||
binding: number;
|
||||
}
|
||||
|
||||
export class VSCodeMenu {
|
||||
|
||||
private static lastKnownKeybindingsMapStorageKey = 'lastKnownKeybindings';
|
||||
|
||||
private static MAX_RECENT_ENTRIES = 10;
|
||||
|
||||
private isQuitting: boolean;
|
||||
private appMenuInstalled: boolean;
|
||||
|
||||
private actionIdKeybindingRequests: string[];
|
||||
private mapLastKnownKeybindingToActionId: { [id: string]: string; };
|
||||
private mapResolvedKeybindingToActionId: { [id: string]: string; };
|
||||
private keybindingsResolved: boolean;
|
||||
|
||||
constructor(
|
||||
@IStorageService private storageService: IStorageService,
|
||||
@IUpdateService private updateManager: IUpdateService,
|
||||
@IWindowsService private windowsManager: IWindowsService,
|
||||
@env.IEnvironmentService private envService: env.IEnvironmentService
|
||||
) {
|
||||
this.actionIdKeybindingRequests = [];
|
||||
|
||||
this.mapResolvedKeybindingToActionId = Object.create(null);
|
||||
this.mapLastKnownKeybindingToActionId = this.storageService.getItem<{ [id: string]: string; }>(VSCodeMenu.lastKnownKeybindingsMapStorageKey) || Object.create(null);
|
||||
}
|
||||
|
||||
public ready(): void {
|
||||
this.registerListeners();
|
||||
this.install();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// Keep flag when app quits
|
||||
app.on('will-quit', () => {
|
||||
this.isQuitting = true;
|
||||
});
|
||||
|
||||
// Listen to "open" & "close" event from window manager
|
||||
this.windowsManager.onOpen((paths) => this.onOpen(paths));
|
||||
this.windowsManager.onClose(_ => this.onClose(this.windowsManager.getWindowCount()));
|
||||
|
||||
// Resolve keybindings when any first workbench is loaded
|
||||
this.windowsManager.onReady((win) => this.resolveKeybindings(win));
|
||||
|
||||
// Listen to resolved keybindings
|
||||
ipc.on('vscode:keybindingsResolved', (event, rawKeybindings) => {
|
||||
let keybindings: IResolvedKeybinding[] = [];
|
||||
try {
|
||||
keybindings = JSON.parse(rawKeybindings);
|
||||
} catch (error) {
|
||||
// Should not happen
|
||||
}
|
||||
|
||||
// Fill hash map of resolved keybindings
|
||||
let needsMenuUpdate = false;
|
||||
keybindings.forEach((keybinding) => {
|
||||
let accelerator = new Keybinding(keybinding.binding)._toElectronAccelerator();
|
||||
if (accelerator) {
|
||||
this.mapResolvedKeybindingToActionId[keybinding.id] = accelerator;
|
||||
if (this.mapLastKnownKeybindingToActionId[keybinding.id] !== accelerator) {
|
||||
needsMenuUpdate = true; // we only need to update when something changed!
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// A keybinding might have been unassigned, so we have to account for that too
|
||||
if (Object.keys(this.mapLastKnownKeybindingToActionId).length !== Object.keys(this.mapResolvedKeybindingToActionId).length) {
|
||||
needsMenuUpdate = true;
|
||||
}
|
||||
|
||||
if (needsMenuUpdate) {
|
||||
this.storageService.setItem(VSCodeMenu.lastKnownKeybindingsMapStorageKey, this.mapResolvedKeybindingToActionId); // keep to restore instantly after restart
|
||||
this.mapLastKnownKeybindingToActionId = this.mapResolvedKeybindingToActionId; // update our last known map
|
||||
|
||||
this.updateMenu();
|
||||
}
|
||||
});
|
||||
|
||||
// Listen to update manager
|
||||
this.updateManager.on('change', () => this.updateMenu());
|
||||
}
|
||||
|
||||
private resolveKeybindings(win: window.VSCodeWindow): void {
|
||||
if (this.keybindingsResolved) {
|
||||
return; // only resolve once
|
||||
}
|
||||
|
||||
this.keybindingsResolved = true;
|
||||
|
||||
// Resolve keybindings when workbench window is up
|
||||
if (this.actionIdKeybindingRequests.length) {
|
||||
win.send('vscode:resolveKeybindings', JSON.stringify(this.actionIdKeybindingRequests));
|
||||
}
|
||||
}
|
||||
|
||||
private updateMenu(): 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 onOpen(path: window.IPath): void {
|
||||
this.addToOpenedPathsList(path.filePath || path.workspacePath, !!path.filePath);
|
||||
this.updateMenu();
|
||||
}
|
||||
|
||||
private onClose(remainingWindowCount: number): void {
|
||||
if (remainingWindowCount === 0 && platform.isMacintosh) {
|
||||
this.updateMenu();
|
||||
}
|
||||
}
|
||||
|
||||
private install(): void {
|
||||
|
||||
// Menus
|
||||
let menubar = new Menu();
|
||||
|
||||
// Mac: Application
|
||||
let macApplicationMenuItem: Electron.MenuItem;
|
||||
if (platform.isMacintosh) {
|
||||
let applicationMenu = new Menu();
|
||||
macApplicationMenuItem = new MenuItem({ label: this.envService.product.nameShort, submenu: applicationMenu });
|
||||
this.setMacApplicationMenu(applicationMenu);
|
||||
}
|
||||
|
||||
// File
|
||||
let fileMenu = new Menu();
|
||||
let fileMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'mFile', comment: ['&& denotes a mnemonic'] }, "&&File")), submenu: fileMenu });
|
||||
this.setFileMenu(fileMenu);
|
||||
|
||||
// Edit
|
||||
let editMenu = new Menu();
|
||||
let editMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'mEdit', comment: ['&& denotes a mnemonic'] }, "&&Edit")), submenu: editMenu });
|
||||
this.setEditMenu(editMenu);
|
||||
|
||||
// View
|
||||
let viewMenu = new Menu();
|
||||
let viewMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'mView', comment: ['&& denotes a mnemonic'] }, "&&View")), submenu: viewMenu });
|
||||
this.setViewMenu(viewMenu);
|
||||
|
||||
// Goto
|
||||
let gotoMenu = new Menu();
|
||||
let gotoMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Goto")), submenu: gotoMenu });
|
||||
this.setGotoMenu(gotoMenu);
|
||||
|
||||
// Mac: Window
|
||||
let macWindowMenuItem: Electron.MenuItem;
|
||||
if (platform.isMacintosh) {
|
||||
let windowMenu = new Menu();
|
||||
macWindowMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize('mWindow', "Window")), submenu: windowMenu, role: 'window' });
|
||||
this.setMacWindowMenu(windowMenu);
|
||||
}
|
||||
|
||||
// Help
|
||||
let helpMenu = new Menu();
|
||||
let helpMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'mHelp', comment: ['&& denotes a mnemonic'] }, "&&Help")), submenu: helpMenu, role: 'help' });
|
||||
this.setHelpMenu(helpMenu);
|
||||
|
||||
// Menu Structure
|
||||
if (macApplicationMenuItem) {
|
||||
menubar.append(macApplicationMenuItem);
|
||||
}
|
||||
|
||||
menubar.append(fileMenuItem);
|
||||
menubar.append(editMenuItem);
|
||||
menubar.append(viewMenuItem);
|
||||
menubar.append(gotoMenuItem);
|
||||
|
||||
if (macWindowMenuItem) {
|
||||
menubar.append(macWindowMenuItem);
|
||||
}
|
||||
|
||||
menubar.append(helpMenuItem);
|
||||
|
||||
Menu.setApplicationMenu(menubar);
|
||||
|
||||
// Dock Menu
|
||||
if (platform.isMacintosh && !this.appMenuInstalled) {
|
||||
this.appMenuInstalled = true;
|
||||
|
||||
let dockMenu = new Menu();
|
||||
dockMenu.append(new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "&&New Window")), click: () => this.windowsManager.openNewWindow() }));
|
||||
|
||||
app.dock.setMenu(dockMenu);
|
||||
}
|
||||
}
|
||||
|
||||
private addToOpenedPathsList(path?: string, isFile?: boolean): void {
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mru = this.getOpenedPathsList();
|
||||
if (isFile) {
|
||||
mru.files.unshift(path);
|
||||
mru.files = arrays.distinct(mru.files, (f) => platform.isLinux ? f : f.toLowerCase());
|
||||
} else {
|
||||
mru.folders.unshift(path);
|
||||
mru.folders = arrays.distinct(mru.folders, (f) => platform.isLinux ? f : f.toLowerCase());
|
||||
}
|
||||
|
||||
// Make sure its bounded
|
||||
mru.folders = mru.folders.slice(0, VSCodeMenu.MAX_RECENT_ENTRIES);
|
||||
mru.files = mru.files.slice(0, VSCodeMenu.MAX_RECENT_ENTRIES);
|
||||
|
||||
this.storageService.setItem(WindowsManager.openedPathsListStorageKey, mru);
|
||||
}
|
||||
|
||||
private removeFromOpenedPathsList(path: string): void {
|
||||
let mru = this.getOpenedPathsList();
|
||||
|
||||
let index = mru.files.indexOf(path);
|
||||
if (index >= 0) {
|
||||
mru.files.splice(index, 1);
|
||||
}
|
||||
|
||||
index = mru.folders.indexOf(path);
|
||||
if (index >= 0) {
|
||||
mru.folders.splice(index, 1);
|
||||
}
|
||||
|
||||
this.storageService.setItem(WindowsManager.openedPathsListStorageKey, mru);
|
||||
}
|
||||
|
||||
private clearOpenedPathsList(): void {
|
||||
this.storageService.setItem(WindowsManager.openedPathsListStorageKey, { folders: [], files: [] });
|
||||
app.clearRecentDocuments();
|
||||
|
||||
this.updateMenu();
|
||||
}
|
||||
|
||||
private getOpenedPathsList(): IOpenedPathsList {
|
||||
let mru = this.storageService.getItem<IOpenedPathsList>(WindowsManager.openedPathsListStorageKey);
|
||||
if (!mru) {
|
||||
mru = { folders: [], files: [] };
|
||||
}
|
||||
|
||||
return mru;
|
||||
}
|
||||
|
||||
private setMacApplicationMenu(macApplicationMenu: Electron.Menu): void {
|
||||
let about = new MenuItem({ label: nls.localize('mAbout', "About {0}", this.envService.product.nameLong), role: 'about' });
|
||||
let checkForUpdates = this.getUpdateMenuItems();
|
||||
let preferences = this.getPreferencesMenu();
|
||||
let hide = new MenuItem({ label: nls.localize('mHide', "Hide {0}", this.envService.product.nameLong), role: 'hide', accelerator: 'Command+H' });
|
||||
let hideOthers = new MenuItem({ label: nls.localize('mHideOthers', "Hide Others"), role: 'hideothers', accelerator: 'Command+Alt+H' });
|
||||
let showAll = new MenuItem({ label: nls.localize('mShowAll', "Show All"), role: 'unhide' });
|
||||
let quit = new MenuItem({ label: nls.localize('miQuit', "Quit {0}", this.envService.product.nameLong), click: () => this.quit(), accelerator: 'Command+Q' });
|
||||
|
||||
let actions = [about];
|
||||
actions.push(...checkForUpdates);
|
||||
actions.push(...[
|
||||
__separator__(),
|
||||
preferences,
|
||||
__separator__(),
|
||||
hide,
|
||||
hideOthers,
|
||||
showAll,
|
||||
__separator__(),
|
||||
quit
|
||||
]);
|
||||
|
||||
actions.forEach(i => macApplicationMenu.append(i));
|
||||
}
|
||||
|
||||
private setFileMenu(fileMenu: Electron.Menu): void {
|
||||
let hasNoWindows = (this.windowsManager.getWindowCount() === 0);
|
||||
|
||||
let newFile: Electron.MenuItem;
|
||||
if (hasNoWindows) {
|
||||
newFile = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miNewFile', comment: ['&& denotes a mnemonic'] }, "&&New File")), accelerator: this.getAccelerator('workbench.action.files.newUntitledFile'), click: () => this.windowsManager.openNewWindow() });
|
||||
} else {
|
||||
newFile = this.createMenuItem(nls.localize({ key: 'miNewFile', comment: ['&& denotes a mnemonic'] }, "&&New File"), 'workbench.action.files.newUntitledFile');
|
||||
}
|
||||
|
||||
let open = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miOpen', comment: ['&& denotes a mnemonic'] }, "&&Open...")), accelerator: this.getAccelerator('workbench.action.files.openFileFolder'), click: () => this.windowsManager.openFileFolderPicker() });
|
||||
let openFile = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File...")), accelerator: this.getAccelerator('workbench.action.files.openFile'), click: () => this.windowsManager.openFilePicker() });
|
||||
let openFolder = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder...")), accelerator: this.getAccelerator('workbench.action.files.openFolder'), click: () => this.windowsManager.openFolderPicker() });
|
||||
|
||||
let openRecentMenu = new Menu();
|
||||
this.setOpenRecentMenu(openRecentMenu);
|
||||
let openRecent = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miOpenRecent', comment: ['&& denotes a mnemonic'] }, "Open &&Recent")), submenu: openRecentMenu, enabled: openRecentMenu.items.length > 0 });
|
||||
|
||||
let saveFile = this.createMenuItem(nls.localize({ key: 'miSave', comment: ['&& denotes a mnemonic'] }, "&&Save"), 'workbench.action.files.save', this.windowsManager.getWindowCount() > 0);
|
||||
let saveFileAs = this.createMenuItem(nls.localize({ key: 'miSaveAs', comment: ['&& denotes a mnemonic'] }, "Save &&As..."), 'workbench.action.files.saveAs', this.windowsManager.getWindowCount() > 0);
|
||||
let saveAllFiles = this.createMenuItem(nls.localize({ key: 'miSaveAll', comment: ['&& denotes a mnemonic'] }, "Save A&&ll"), 'workbench.action.files.saveAll', this.windowsManager.getWindowCount() > 0);
|
||||
|
||||
let preferences = this.getPreferencesMenu();
|
||||
|
||||
let newWindow = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "&&New Window")), accelerator: this.getAccelerator('workbench.action.newWindow'), click: () => this.windowsManager.openNewWindow() });
|
||||
let revertFile = this.createMenuItem(nls.localize({ key: 'miRevert', comment: ['&& denotes a mnemonic'] }, "Revert F&&ile"), 'workbench.action.files.revert', this.windowsManager.getWindowCount() > 0);
|
||||
let closeWindow = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miCloseWindow', comment: ['&& denotes a mnemonic'] }, "Close &&Window")), accelerator: this.getAccelerator('workbench.action.closeWindow'), click: () => this.windowsManager.getLastActiveWindow().win.close(), enabled: this.windowsManager.getWindowCount() > 0 });
|
||||
|
||||
let closeFolder = this.createMenuItem(nls.localize({ key: 'miCloseFolder', comment: ['&& denotes a mnemonic'] }, "Close &&Folder"), 'workbench.action.closeFolder');
|
||||
let closeEditor = this.createMenuItem(nls.localize({ key: 'miCloseEditor', comment: ['&& denotes a mnemonic'] }, "Close &&Editor"), 'workbench.action.closeActiveEditor');
|
||||
|
||||
let exit = this.createMenuItem(nls.localize({ key: 'miExit', comment: ['&& denotes a mnemonic'] }, "E&&xit"), () => this.quit());
|
||||
|
||||
arrays.coalesce([
|
||||
newFile,
|
||||
newWindow,
|
||||
__separator__(),
|
||||
platform.isMacintosh ? open : null,
|
||||
!platform.isMacintosh ? openFile : null,
|
||||
!platform.isMacintosh ? openFolder : null,
|
||||
openRecent,
|
||||
__separator__(),
|
||||
saveFile,
|
||||
saveFileAs,
|
||||
saveAllFiles,
|
||||
__separator__(),
|
||||
!platform.isMacintosh ? preferences : null,
|
||||
!platform.isMacintosh ? __separator__() : null,
|
||||
revertFile,
|
||||
closeEditor,
|
||||
closeFolder,
|
||||
!platform.isMacintosh ? closeWindow : null,
|
||||
!platform.isMacintosh ? __separator__() : null,
|
||||
!platform.isMacintosh ? exit : null
|
||||
]).forEach((item) => fileMenu.append(item));
|
||||
}
|
||||
|
||||
private getPreferencesMenu(): Electron.MenuItem {
|
||||
let userSettings = this.createMenuItem(nls.localize({ key: 'miOpenSettings', comment: ['&& denotes a mnemonic'] }, "&&User Settings"), 'workbench.action.openGlobalSettings');
|
||||
let workspaceSettings = this.createMenuItem(nls.localize({ key: 'miOpenWorkspaceSettings', comment: ['&& denotes a mnemonic'] }, "&&Workspace Settings"), 'workbench.action.openWorkspaceSettings');
|
||||
let kebindingSettings = this.createMenuItem(nls.localize({ key: 'miOpenKeymap', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts"), 'workbench.action.openGlobalKeybindings');
|
||||
let snippetsSettings = this.createMenuItem(nls.localize({ key: 'miOpenSnippets', comment: ['&& denotes a mnemonic'] }, "User &&Snippets"), 'workbench.action.openSnippets');
|
||||
let themeSelection = this.createMenuItem(nls.localize({ key: 'miSelectTheme', comment: ['&& denotes a mnemonic'] }, "&&Color Theme"), 'workbench.action.selectTheme');
|
||||
let preferencesMenu = new Menu();
|
||||
preferencesMenu.append(userSettings);
|
||||
preferencesMenu.append(workspaceSettings);
|
||||
preferencesMenu.append(__separator__());
|
||||
preferencesMenu.append(kebindingSettings);
|
||||
preferencesMenu.append(__separator__());
|
||||
preferencesMenu.append(snippetsSettings);
|
||||
preferencesMenu.append(__separator__());
|
||||
preferencesMenu.append(themeSelection);
|
||||
|
||||
return new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miPreferences', comment: ['&& denotes a mnemonic'] }, "&&Preferences")), submenu: preferencesMenu });
|
||||
}
|
||||
|
||||
private quit(): void {
|
||||
|
||||
// If the user selected to exit from an extension development host window, do not quit, but just
|
||||
// close the window unless this is the last window that is opened.
|
||||
let vscodeWindow = this.windowsManager.getFocusedWindow();
|
||||
if (vscodeWindow && vscodeWindow.isPluginDevelopmentHost && this.windowsManager.getWindowCount() > 1) {
|
||||
vscodeWindow.win.close();
|
||||
}
|
||||
|
||||
// Otherwise: normal quit
|
||||
else {
|
||||
setTimeout(() => {
|
||||
this.isQuitting = true;
|
||||
|
||||
app.quit();
|
||||
}, 10 /* delay this because there is an issue with quitting while the menu is open */);
|
||||
}
|
||||
}
|
||||
|
||||
private setOpenRecentMenu(openRecentMenu: Electron.Menu): void {
|
||||
openRecentMenu.append(this.createMenuItem(nls.localize({ key: 'miReopenClosedFile', comment: ['&& denotes a mnemonic'] }, "&&Reopen Closed File"), 'workbench.files.action.reopenClosedFile'));
|
||||
|
||||
let recentList = this.getOpenedPathsList();
|
||||
|
||||
// Folders
|
||||
if (recentList.folders.length > 0) {
|
||||
openRecentMenu.append(__separator__());
|
||||
recentList.folders.forEach((folder, index) => {
|
||||
if (index < VSCodeMenu.MAX_RECENT_ENTRIES) {
|
||||
openRecentMenu.append(this.createOpenRecentMenuItem(folder));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Files
|
||||
let files = recentList.files;
|
||||
if (platform.isMacintosh && recentList.files.length > 0) {
|
||||
files = recentList.files.filter(f => recentList.folders.indexOf(f) < 0); // TODO@Ben migration (remove in the future)
|
||||
}
|
||||
|
||||
if (files.length > 0) {
|
||||
openRecentMenu.append(__separator__());
|
||||
|
||||
files.forEach((file, index) => {
|
||||
if (index < VSCodeMenu.MAX_RECENT_ENTRIES) {
|
||||
openRecentMenu.append(this.createOpenRecentMenuItem(file));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (recentList.folders.length || files.length) {
|
||||
openRecentMenu.append(__separator__());
|
||||
openRecentMenu.append(new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miClearItems', comment: ['&& denotes a mnemonic'] }, "&&Clear Items")), click: () => this.clearOpenedPathsList() }));
|
||||
}
|
||||
}
|
||||
|
||||
private createOpenRecentMenuItem(path: string): Electron.MenuItem {
|
||||
return new MenuItem({
|
||||
label: path, click: () => {
|
||||
let success = !!this.windowsManager.open({ cli: this.envService.cliArgs, pathsToOpen: [path] });
|
||||
if (!success) {
|
||||
this.removeFromOpenedPathsList(path);
|
||||
this.updateMenu();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private createRoleMenuItem(label: string, actionId: string, role: string): Electron.MenuItem {
|
||||
let options: Electron.MenuItemOptions = {
|
||||
label: mnemonicLabel(label),
|
||||
accelerator: this.getAccelerator(actionId),
|
||||
role: role,
|
||||
enabled: true
|
||||
};
|
||||
|
||||
return new MenuItem(options);
|
||||
}
|
||||
|
||||
private setEditMenu(winLinuxEditMenu: Electron.Menu): void {
|
||||
let undo: Electron.MenuItem;
|
||||
let redo: Electron.MenuItem;
|
||||
let cut: Electron.MenuItem;
|
||||
let copy: Electron.MenuItem;
|
||||
let paste: Electron.MenuItem;
|
||||
let selectAll: Electron.MenuItem;
|
||||
|
||||
if (platform.isMacintosh) {
|
||||
undo = this.createDevToolsAwareMenuItem(nls.localize({ key: 'miUndo', comment: ['&& denotes a mnemonic'] }, "&&Undo"), 'undo', (devTools) => devTools.undo());
|
||||
redo = this.createDevToolsAwareMenuItem(nls.localize({ key: 'miRedo', comment: ['&& denotes a mnemonic'] }, "&&Redo"), 'redo', (devTools) => devTools.redo());
|
||||
cut = this.createRoleMenuItem(nls.localize({ key: 'miCut', comment: ['&& denotes a mnemonic'] }, "&&Cut"), 'editor.action.clipboardCutAction', 'cut');
|
||||
copy = this.createRoleMenuItem(nls.localize({ key: 'miCopy', comment: ['&& denotes a mnemonic'] }, "C&&opy"), 'editor.action.clipboardCopyAction', 'copy');
|
||||
paste = this.createRoleMenuItem(nls.localize({ key: 'miPaste', comment: ['&& denotes a mnemonic'] }, "&&Paste"), 'editor.action.clipboardPasteAction', 'paste');
|
||||
selectAll = this.createDevToolsAwareMenuItem(nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"), 'editor.action.selectAll', (devTools) => devTools.selectAll());
|
||||
} else {
|
||||
undo = this.createMenuItem(nls.localize({ key: 'miUndo', comment: ['&& denotes a mnemonic'] }, "&&Undo"), 'undo');
|
||||
redo = this.createMenuItem(nls.localize({ key: 'miRedo', comment: ['&& denotes a mnemonic'] }, "&&Redo"), 'redo');
|
||||
cut = this.createMenuItem(nls.localize({ key: 'miCut', comment: ['&& denotes a mnemonic'] }, "&&Cut"), 'editor.action.clipboardCutAction');
|
||||
copy = this.createMenuItem(nls.localize({ key: 'miCopy', comment: ['&& denotes a mnemonic'] }, "C&&opy"), 'editor.action.clipboardCopyAction');
|
||||
paste = this.createMenuItem(nls.localize({ key: 'miPaste', comment: ['&& denotes a mnemonic'] }, "&&Paste"), 'editor.action.clipboardPasteAction');
|
||||
selectAll = this.createMenuItem(nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"), 'editor.action.selectAll');
|
||||
}
|
||||
|
||||
let find = this.createMenuItem(nls.localize({ key: 'miFind', comment: ['&& denotes a mnemonic'] }, "&&Find"), 'actions.find');
|
||||
let replace = this.createMenuItem(nls.localize({ key: 'miReplace', comment: ['&& denotes a mnemonic'] }, "&&Replace"), 'editor.action.startFindReplaceAction');
|
||||
let findInFiles = this.createMenuItem(nls.localize({ key: 'miFindInFiles', comment: ['&& denotes a mnemonic'] }, "Find &&in Files"), 'workbench.view.search');
|
||||
|
||||
[
|
||||
undo,
|
||||
redo,
|
||||
__separator__(),
|
||||
cut,
|
||||
copy,
|
||||
paste,
|
||||
selectAll,
|
||||
__separator__(),
|
||||
find,
|
||||
replace,
|
||||
__separator__(),
|
||||
findInFiles
|
||||
].forEach((item) => winLinuxEditMenu.append(item));
|
||||
}
|
||||
|
||||
private setViewMenu(viewMenu: Electron.Menu): void {
|
||||
let explorer = this.createMenuItem(nls.localize({ key: 'miViewExplorer', comment: ['&& denotes a mnemonic'] }, "&&Explorer"), 'workbench.view.explorer');
|
||||
let search = this.createMenuItem(nls.localize({ key: 'miViewSearch', comment: ['&& denotes a mnemonic'] }, "&&Search"), 'workbench.view.search');
|
||||
let git = this.createMenuItem(nls.localize({ key: 'miViewGit', comment: ['&& denotes a mnemonic'] }, "&&Git"), 'workbench.view.git');
|
||||
let debug = this.createMenuItem(nls.localize({ key: 'miViewDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug"), 'workbench.view.debug');
|
||||
|
||||
let commands = this.createMenuItem(nls.localize({ key: 'miCommandPalette', comment: ['&& denotes a mnemonic'] }, "&&Command Palette..."), 'workbench.action.showCommands');
|
||||
let markers = this.createMenuItem(nls.localize({ key: 'miMarker', comment: ['&& denotes a mnemonic'] }, "&&Errors and Warnings..."), 'workbench.action.showErrorsWarnings');
|
||||
|
||||
let output = this.createMenuItem(nls.localize({ key: 'miToggleOutput', comment: ['&& denotes a mnemonic'] }, "Toggle &&Output"), 'workbench.action.output.toggleOutput');
|
||||
let debugConsole = this.createMenuItem(nls.localize({ key: 'miToggleDebugConsole', comment: ['&& denotes a mnemonic'] }, "Toggle De&&bug Console"), 'workbench.debug.action.toggleRepl');
|
||||
|
||||
let fullscreen = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miToggleFullScreen', comment: ['&& denotes a mnemonic'] }, "Toggle &&Full Screen")), accelerator: this.getAccelerator('workbench.action.toggleFullScreen'), click: () => this.windowsManager.getLastActiveWindow().toggleFullScreen(), enabled: this.windowsManager.getWindowCount() > 0 });
|
||||
let toggleMenuBar = this.createMenuItem(nls.localize({ key: 'miToggleMenuBar', comment: ['&& denotes a mnemonic'] }, "Toggle Menu &&Bar"), 'workbench.action.toggleMenuBar');
|
||||
let splitEditor = this.createMenuItem(nls.localize({ key: 'miSplitEditor', comment: ['&& denotes a mnemonic'] }, "Split &&Editor"), 'workbench.action.splitEditor');
|
||||
let toggleSidebar = this.createMenuItem(nls.localize({ key: 'miToggleSidebar', comment: ['&& denotes a mnemonic'] }, "&&Toggle Side Bar"), 'workbench.action.toggleSidebarVisibility');
|
||||
let moveSidebar = this.createMenuItem(nls.localize({ key: 'miMoveSidebar', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar"), 'workbench.action.toggleSidebarPosition');
|
||||
let togglePanel = this.createMenuItem(nls.localize({ key: 'miTogglePanel', comment: ['&& denotes a mnemonic'] }, "Toggle &&Panel"), 'workbench.action.togglePanel');
|
||||
|
||||
const toggleWordWrap = this.createMenuItem(nls.localize({ key: 'miToggleWordWrap', comment: ['&& denotes a mnemonic'] }, "Toggle &&Word Wrap"), 'editor.action.toggleWordWrap');
|
||||
const toggleRenderWhitespace = this.createMenuItem(nls.localize({ key: 'miToggleRenderWhitespace', comment: ['&& denotes a mnemonic'] }, "Toggle &&Render Whitespace"), 'editor.action.toggleRenderWhitespace');
|
||||
|
||||
|
||||
let zoomIn = this.createMenuItem(nls.localize({ key: 'miZoomIn', comment: ['&& denotes a mnemonic'] }, "&&Zoom in"), 'workbench.action.zoomIn');
|
||||
let zoomOut = this.createMenuItem(nls.localize({ key: 'miZoomOut', comment: ['&& denotes a mnemonic'] }, "Zoom o&&ut"), 'workbench.action.zoomOut');
|
||||
|
||||
arrays.coalesce([
|
||||
explorer,
|
||||
search,
|
||||
git,
|
||||
debug,
|
||||
__separator__(),
|
||||
commands,
|
||||
markers,
|
||||
__separator__(),
|
||||
output,
|
||||
debugConsole,
|
||||
__separator__(),
|
||||
fullscreen,
|
||||
platform.isWindows || platform.isLinux ? toggleMenuBar : void 0,
|
||||
__separator__(),
|
||||
splitEditor,
|
||||
toggleSidebar,
|
||||
moveSidebar,
|
||||
togglePanel,
|
||||
__separator__(),
|
||||
toggleWordWrap,
|
||||
toggleRenderWhitespace,
|
||||
__separator__(),
|
||||
zoomIn,
|
||||
zoomOut
|
||||
]).forEach((item) => viewMenu.append(item));
|
||||
}
|
||||
|
||||
private setGotoMenu(gotoMenu: Electron.Menu): void {
|
||||
let back = this.createMenuItem(nls.localize({ key: 'miBack', comment: ['&& denotes a mnemonic'] }, "&&Back"), 'workbench.action.navigateBack');
|
||||
let forward = this.createMenuItem(nls.localize({ key: 'miForward', comment: ['&& denotes a mnemonic'] }, "&&Forward"), 'workbench.action.navigateForward');
|
||||
let navigateHistory = this.createMenuItem(nls.localize({ key: 'miNavigateHistory', comment: ['&& denotes a mnemonic'] }, "&&Navigate History"), 'workbench.action.openPreviousEditor');
|
||||
let gotoFile = this.createMenuItem(nls.localize({ key: 'miGotoFile', comment: ['&& denotes a mnemonic'] }, "Go to &&File..."), 'workbench.action.quickOpen');
|
||||
let gotoSymbol = this.createMenuItem(nls.localize({ key: 'miGotoSymbol', comment: ['&& denotes a mnemonic'] }, "Go to &&Symbol..."), 'workbench.action.gotoSymbol');
|
||||
let gotoDefinition = this.createMenuItem(nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition"), 'editor.action.goToDeclaration');
|
||||
let gotoLine = this.createMenuItem(nls.localize({ key: 'miGotoLine', comment: ['&& denotes a mnemonic'] }, "Go to &&Line..."), 'workbench.action.gotoLine');
|
||||
|
||||
[
|
||||
back,
|
||||
forward,
|
||||
__separator__(),
|
||||
navigateHistory,
|
||||
__separator__(),
|
||||
gotoFile,
|
||||
gotoSymbol,
|
||||
gotoDefinition,
|
||||
gotoLine
|
||||
].forEach((item) => gotoMenu.append(item));
|
||||
}
|
||||
|
||||
private setMacWindowMenu(macWindowMenu: Electron.Menu): void {
|
||||
let minimize = new MenuItem({ label: nls.localize('mMinimize', "Minimize"), role: 'minimize', accelerator: 'Command+M', enabled: this.windowsManager.getWindowCount() > 0 });
|
||||
let close = new MenuItem({ label: nls.localize('mClose', "Close"), role: 'close', accelerator: 'Command+W', enabled: this.windowsManager.getWindowCount() > 0 });
|
||||
let bringAllToFront = new MenuItem({ label: nls.localize('mBringToFront', "Bring All to Front"), role: 'front', enabled: this.windowsManager.getWindowCount() > 0 });
|
||||
|
||||
[
|
||||
minimize,
|
||||
close,
|
||||
__separator__(),
|
||||
bringAllToFront
|
||||
].forEach((item) => macWindowMenu.append(item));
|
||||
}
|
||||
|
||||
private toggleDevTools(): void {
|
||||
let w = this.windowsManager.getFocusedWindow();
|
||||
if (w && w.win) {
|
||||
w.win.webContents.toggleDevTools();
|
||||
}
|
||||
}
|
||||
|
||||
private setHelpMenu(helpMenu: Electron.Menu): void {
|
||||
let toggleDevToolsItem = new MenuItem({
|
||||
label: mnemonicLabel(nls.localize({ key: 'miToggleDevTools', comment: ['&& denotes a mnemonic'] }, "&&Toggle Developer Tools")),
|
||||
accelerator: this.getAccelerator('workbench.action.toggleDevTools'),
|
||||
click: () => this.toggleDevTools(),
|
||||
enabled: (this.windowsManager.getWindowCount() > 0)
|
||||
});
|
||||
|
||||
arrays.coalesce([
|
||||
this.envService.product.documentationUrl ? new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miDocumentation', comment: ['&& denotes a mnemonic'] }, "&&Documentation")), click: () => this.openUrl(this.envService.product.documentationUrl, 'openDocumentationUrl') }) : null,
|
||||
this.envService.product.releaseNotesUrl ? new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miReleaseNotes', comment: ['&& denotes a mnemonic'] }, "&&Release Notes")), click: () => this.openUrl(this.envService.product.releaseNotesUrl, 'openReleaseNotesUrl') }) : null,
|
||||
(this.envService.product.documentationUrl || this.envService.product.releaseNotesUrl) ? __separator__() : null,
|
||||
this.envService.product.twitterUrl ? new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miTwitter', comment: ['&& denotes a mnemonic'] }, "&&Join us on Twitter")), click: () => this.openUrl(this.envService.product.twitterUrl, 'openTwitterUrl') }) : null,
|
||||
this.envService.product.requestFeatureUrl ? new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miUserVoice', comment: ['&& denotes a mnemonic'] }, "&&Request Features")), click: () => this.openUrl(this.envService.product.requestFeatureUrl, 'openUserVoiceUrl') }) : null,
|
||||
this.envService.product.reportIssueUrl ? new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miReportIssues', comment: ['&& denotes a mnemonic'] }, "Report &&Issues")), click: () => this.openUrl(this.envService.product.reportIssueUrl, 'openReportIssues') }) : null,
|
||||
(this.envService.product.twitterUrl || this.envService.product.requestFeatureUrl || this.envService.product.reportIssueUrl) ? __separator__() : null,
|
||||
this.envService.product.licenseUrl ? new MenuItem({
|
||||
label: mnemonicLabel(nls.localize({ key: 'miLicense', comment: ['&& denotes a mnemonic'] }, "&&View License")), click: () => {
|
||||
if (platform.language) {
|
||||
let queryArgChar = this.envService.product.licenseUrl.indexOf('?') > 0 ? '&' : '?';
|
||||
this.openUrl(`${this.envService.product.licenseUrl}${queryArgChar}lang=${platform.language}`, 'openLicenseUrl');
|
||||
} else {
|
||||
this.openUrl(this.envService.product.licenseUrl, 'openLicenseUrl');
|
||||
}
|
||||
}
|
||||
}) : null,
|
||||
this.envService.product.privacyStatementUrl ? new MenuItem({
|
||||
label: mnemonicLabel(nls.localize({ key: 'miPrivacyStatement', comment: ['&& denotes a mnemonic'] }, "&&Privacy Statement")), click: () => {
|
||||
if (platform.language) {
|
||||
let queryArgChar = this.envService.product.licenseUrl.indexOf('?') > 0 ? '&' : '?';
|
||||
this.openUrl(`${this.envService.product.privacyStatementUrl}${queryArgChar}lang=${platform.language}`, 'openPrivacyStatement');
|
||||
} else {
|
||||
this.openUrl(this.envService.product.privacyStatementUrl, 'openPrivacyStatement');
|
||||
}
|
||||
}
|
||||
}) : null,
|
||||
(this.envService.product.licenseUrl || this.envService.product.privacyStatementUrl) ? __separator__() : null,
|
||||
toggleDevToolsItem,
|
||||
]).forEach((item) => helpMenu.append(item));
|
||||
|
||||
if (!platform.isMacintosh) {
|
||||
const updateMenuItems = this.getUpdateMenuItems();
|
||||
if (updateMenuItems.length) {
|
||||
helpMenu.append(__separator__());
|
||||
updateMenuItems.forEach(i => helpMenu.append(i));
|
||||
}
|
||||
|
||||
helpMenu.append(__separator__());
|
||||
helpMenu.append(new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miAbout', comment: ['&& denotes a mnemonic'] }, "&&About")), click: () => this.openAboutDialog() }));
|
||||
}
|
||||
}
|
||||
|
||||
private getUpdateMenuItems(): Electron.MenuItem[] {
|
||||
switch (this.updateManager.state) {
|
||||
case UpdateState.Uninitialized:
|
||||
return [];
|
||||
|
||||
case UpdateState.UpdateDownloaded:
|
||||
let update = this.updateManager.availableUpdate;
|
||||
return [new MenuItem({
|
||||
label: nls.localize('miRestartToUpdate', "Restart To Update..."), click: () => {
|
||||
this.reportMenuActionTelemetry('RestartToUpdate');
|
||||
update.quitAndUpdate();
|
||||
}
|
||||
})];
|
||||
|
||||
case UpdateState.CheckingForUpdate:
|
||||
return [new MenuItem({ label: nls.localize('miCheckingForUpdates', "Checking For Updates..."), enabled: false })];
|
||||
|
||||
case UpdateState.UpdateAvailable:
|
||||
if (platform.isLinux) {
|
||||
const update = this.updateManager.availableUpdate;
|
||||
return [new MenuItem({
|
||||
label: nls.localize('miDownloadUpdate', "Download Available Update"), click: () => {
|
||||
update.quitAndUpdate();
|
||||
}
|
||||
})];
|
||||
}
|
||||
|
||||
let updateAvailableLabel = platform.isWindows
|
||||
? nls.localize('miDownloadingUpdate', "Downloading Update...")
|
||||
: nls.localize('miInstallingUpdate', "Installing Update...");
|
||||
|
||||
return [new MenuItem({ label: updateAvailableLabel, enabled: false })];
|
||||
|
||||
default:
|
||||
let result = [new MenuItem({
|
||||
label: nls.localize('miCheckForUpdates', "Check For Updates..."), click: () => setTimeout(() => {
|
||||
this.reportMenuActionTelemetry('CheckForUpdate');
|
||||
this.updateManager.checkForUpdates(true);
|
||||
}, 0)
|
||||
})];
|
||||
|
||||
if (this.updateManager.lastCheckDate) {
|
||||
result.push(new MenuItem({ label: nls.localize('miLastCheckedAt', "Last checked at {0}", this.updateManager.lastCheckDate.toLocaleTimeString()), enabled: false }));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private createMenuItem(label: string, actionId: string, enabled?: boolean): Electron.MenuItem;
|
||||
private createMenuItem(label: string, click: () => void, enabled?: boolean): Electron.MenuItem;
|
||||
private createMenuItem(arg1: string, arg2: any, arg3?: boolean): Electron.MenuItem {
|
||||
let label = mnemonicLabel(arg1);
|
||||
let click: () => void = (typeof arg2 === 'function') ? arg2 : () => this.windowsManager.sendToFocused('vscode:runAction', arg2);
|
||||
let enabled = typeof arg3 === 'boolean' ? arg3 : this.windowsManager.getWindowCount() > 0;
|
||||
|
||||
let actionId: string;
|
||||
if (typeof arg2 === 'string') {
|
||||
actionId = arg2;
|
||||
}
|
||||
|
||||
let options: Electron.MenuItemOptions = {
|
||||
label: label,
|
||||
accelerator: this.getAccelerator(actionId),
|
||||
click: click,
|
||||
enabled: enabled
|
||||
};
|
||||
|
||||
return new MenuItem(options);
|
||||
}
|
||||
|
||||
private createDevToolsAwareMenuItem(label: string, actionId: string, devToolsFocusedFn: (contents: Electron.WebContents) => void): Electron.MenuItem {
|
||||
return new MenuItem({
|
||||
label: mnemonicLabel(label),
|
||||
accelerator: this.getAccelerator(actionId),
|
||||
enabled: this.windowsManager.getWindowCount() > 0,
|
||||
click: () => {
|
||||
let windowInFocus = this.windowsManager.getFocusedWindow();
|
||||
if (!windowInFocus) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (windowInFocus.win.isDevToolsFocused()) {
|
||||
devToolsFocusedFn(windowInFocus.win.devToolsWebContents);
|
||||
} else {
|
||||
this.windowsManager.sendToFocused('vscode:runAction', actionId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getAccelerator(actionId: string): string {
|
||||
if (actionId) {
|
||||
let resolvedKeybinding = this.mapResolvedKeybindingToActionId[actionId];
|
||||
if (resolvedKeybinding) {
|
||||
return resolvedKeybinding; // keybinding is fully resolved
|
||||
}
|
||||
|
||||
if (!this.keybindingsResolved) {
|
||||
this.actionIdKeybindingRequests.push(actionId); // keybinding needs to be resolved
|
||||
}
|
||||
|
||||
let lastKnownKeybinding = this.mapLastKnownKeybindingToActionId[actionId];
|
||||
|
||||
return lastKnownKeybinding; // return the last known keybining (chance of mismatch is very low unless it changed)
|
||||
}
|
||||
|
||||
return void (0);
|
||||
}
|
||||
|
||||
private openAboutDialog(): void {
|
||||
let lastActiveWindow = this.windowsManager.getFocusedWindow() || this.windowsManager.getLastActiveWindow();
|
||||
|
||||
dialog.showMessageBox(lastActiveWindow && lastActiveWindow.win, {
|
||||
title: this.envService.product.nameLong,
|
||||
type: 'info',
|
||||
message: this.envService.product.nameLong,
|
||||
detail: nls.localize('aboutDetail',
|
||||
"\nVersion {0}\nCommit {1}\nDate {2}\nShell {3}\nRenderer {4}\nNode {5}",
|
||||
app.getVersion(),
|
||||
this.envService.product.commit || 'Unknown',
|
||||
this.envService.product.date || 'Unknown',
|
||||
process.versions['electron'],
|
||||
process.versions['chrome'],
|
||||
process.versions['node']
|
||||
),
|
||||
buttons: [nls.localize('okButton', "OK")],
|
||||
noLink: true
|
||||
}, (result) => null);
|
||||
|
||||
this.reportMenuActionTelemetry('showAboutDialog');
|
||||
}
|
||||
|
||||
private openUrl(url: string, id: string): void {
|
||||
shell.openExternal(url);
|
||||
this.reportMenuActionTelemetry(id);
|
||||
}
|
||||
|
||||
private reportMenuActionTelemetry(id: string): void {
|
||||
this.windowsManager.sendToFocused('vscode:telemetry', { eventName: 'workbenchActionExecuted', data: { id, from: 'menu' } });
|
||||
}
|
||||
}
|
||||
|
||||
function __separator__(): Electron.MenuItem {
|
||||
return new MenuItem({ type: 'separator' });
|
||||
}
|
||||
|
||||
function mnemonicLabel(label: string): string {
|
||||
if (platform.isMacintosh) {
|
||||
return label.replace(/&&/g, ''); // no mnemonic support on mac
|
||||
}
|
||||
|
||||
return label.replace(/&&/g, '&');
|
||||
}
|
||||
46
src/vs/code/electron-main/settings.ts
Normal file
46
src/vs/code/electron-main/settings.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import {app} from 'electron';
|
||||
import { ServiceIdentifier, createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { UserSettings, ISettings } from 'vs/workbench/node/userSettings';
|
||||
import { IEnvironmentService } from 'vs/code/electron-main/env';
|
||||
import Event from 'vs/base/common/event';
|
||||
|
||||
export const ISettingsService = createDecorator<ISettingsService>('settingsService');
|
||||
|
||||
export interface ISettingsService {
|
||||
serviceId: ServiceIdentifier<any>;
|
||||
globalSettings: ISettings;
|
||||
loadSync(): boolean;
|
||||
getValue(key: string, fallback?: any): any;
|
||||
onChange: Event<ISettings>;
|
||||
}
|
||||
|
||||
export class SettingsManager extends UserSettings implements ISettingsService {
|
||||
|
||||
serviceId = ISettingsService;
|
||||
|
||||
constructor(@IEnvironmentService envService: IEnvironmentService) {
|
||||
super(envService.appSettingsPath, envService.appKeybindingsPath);
|
||||
|
||||
app.on('will-quit', () => {
|
||||
this.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
loadSync(): boolean {
|
||||
const settingsChanged = super.loadSync();
|
||||
|
||||
// Store into global so that any renderer can access the value with remote.getGlobal()
|
||||
if (settingsChanged) {
|
||||
global.globalSettingsValue = JSON.stringify(this.globalSettings);
|
||||
}
|
||||
|
||||
return settingsChanged;
|
||||
}
|
||||
}
|
||||
91
src/vs/code/electron-main/sharedProcess.ts
Normal file
91
src/vs/code/electron-main/sharedProcess.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as cp from 'child_process';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import { IEnvironment } from 'vs/platform/workspace/common/workspace';
|
||||
import { IEnvironmentService } from 'vs/code/electron-main/env';
|
||||
import { ISettingsService } from 'vs/code/electron-main/settings';
|
||||
import { IUpdateService } from 'vs/code/electron-main/update-manager';
|
||||
import {ServicesAccessor} from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
const boostrapPath = URI.parse(require.toUrl('bootstrap')).fsPath;
|
||||
|
||||
function getEnvironment(envService: IEnvironmentService, updateManager: IUpdateService): IEnvironment {
|
||||
let configuration: IEnvironment = assign({}, envService.cliArgs);
|
||||
configuration.execPath = process.execPath;
|
||||
configuration.appName = envService.product.nameLong;
|
||||
configuration.appRoot = envService.appRoot;
|
||||
configuration.version = envService.version;
|
||||
configuration.commitHash = envService.product.commit;
|
||||
configuration.appSettingsHome = envService.appSettingsHome;
|
||||
configuration.appSettingsPath = envService.appSettingsPath;
|
||||
configuration.appKeybindingsPath = envService.appKeybindingsPath;
|
||||
configuration.userExtensionsHome = envService.userExtensionsHome;
|
||||
configuration.isBuilt = envService.isBuilt;
|
||||
configuration.updateFeedUrl = updateManager.feedUrl;
|
||||
configuration.updateChannel = updateManager.channel;
|
||||
configuration.extensionsGallery = envService.product.extensionsGallery;
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
||||
function _spawnSharedProcess(envService: IEnvironmentService, updateManager: IUpdateService, settingsManager: ISettingsService): cp.ChildProcess {
|
||||
// Make sure the nls configuration travels to the shared process.
|
||||
const opts = {
|
||||
env: assign(assign({}, process.env), {
|
||||
AMD_ENTRYPOINT: 'vs/code/electron-main/sharedProcessMain'
|
||||
})
|
||||
};
|
||||
|
||||
const result = cp.fork(boostrapPath, ['--type=SharedProcess'], opts);
|
||||
|
||||
// handshake
|
||||
result.once('message', () => {
|
||||
result.send({
|
||||
configuration: {
|
||||
env: getEnvironment(envService, updateManager)
|
||||
},
|
||||
contextServiceOptions: {
|
||||
globalSettings: settingsManager.globalSettings
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
let spawnCount = 0;
|
||||
|
||||
export function spawnSharedProcess(accessor: ServicesAccessor): IDisposable {
|
||||
const envService = accessor.get(IEnvironmentService);
|
||||
const updateManager = accessor.get(IUpdateService);
|
||||
const settingsManager = accessor.get(ISettingsService);
|
||||
|
||||
let child: cp.ChildProcess;
|
||||
|
||||
const spawn = () => {
|
||||
if (++spawnCount > 10) {
|
||||
return;
|
||||
}
|
||||
|
||||
child = _spawnSharedProcess(envService, updateManager, settingsManager);
|
||||
child.on('exit', spawn);
|
||||
};
|
||||
|
||||
spawn();
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
if (child) {
|
||||
child.removeListener('exit', spawn);
|
||||
child.kill();
|
||||
child = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
99
src/vs/code/electron-main/sharedProcessMain.ts
Normal file
99
src/vs/code/electron-main/sharedProcessMain.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import platform = require('vs/base/common/platform');
|
||||
import { serve, Server, connect } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IConfiguration } from 'vs/platform/workspace/common/workspace';
|
||||
import { WorkspaceContextService } from 'vs/workbench/services/workspace/common/contextService';
|
||||
import { EventService } from 'vs/platform/event/common/eventService';
|
||||
import { ExtensionsChannel } from 'vs/workbench/parts/extensions/common/extensionsIpc';
|
||||
import { ExtensionsService } from 'vs/workbench/parts/extensions/node/extensionsService';
|
||||
|
||||
interface IInitData {
|
||||
configuration: IConfiguration;
|
||||
contextServiceOptions: { settings: any };
|
||||
}
|
||||
|
||||
function quit(err?: Error) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
process.exit(err ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Plan B is to kill oneself if one's parent dies. Much drama.
|
||||
*/
|
||||
function setupPlanB(parentPid: number): void {
|
||||
setInterval(function () {
|
||||
try {
|
||||
process.kill(parentPid, 0); // throws an exception if the main process doesn't exist anymore.
|
||||
} catch (e) {
|
||||
process.exit();
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
function main(server: Server, initData: IInitData): void {
|
||||
const eventService = new EventService();
|
||||
const contextService = new WorkspaceContextService(eventService, null, initData.configuration, initData.contextServiceOptions);
|
||||
const extensionService = new ExtensionsService(contextService);
|
||||
const channel = new ExtensionsChannel(extensionService);
|
||||
|
||||
server.registerChannel('extensions', channel);
|
||||
|
||||
// eventually clean up old extensions
|
||||
setTimeout(() => extensionService.removeDeprecatedExtensions(), 5000);
|
||||
}
|
||||
|
||||
function setupIPC(hook: string): TPromise<Server> {
|
||||
function setup(retry: boolean): TPromise<Server> {
|
||||
return serve(hook).then(null, err => {
|
||||
if (!retry || platform.isWindows || err.code !== 'EADDRINUSE') {
|
||||
return TPromise.wrapError(err);
|
||||
}
|
||||
|
||||
// should retry, not windows and eaddrinuse
|
||||
|
||||
return connect(hook).then(
|
||||
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.'));
|
||||
},
|
||||
err => {
|
||||
// it happens on Linux and OS X that the pipe is left behind
|
||||
// let's delete it, since we can't connect to it
|
||||
// and the retry the whole thing
|
||||
try {
|
||||
fs.unlinkSync(hook);
|
||||
} catch (e) {
|
||||
return TPromise.wrapError(new Error('Error deleting the shared ipc hook.'));
|
||||
}
|
||||
|
||||
return setup(false);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return setup(true);
|
||||
}
|
||||
|
||||
function handshake(): TPromise<IInitData> {
|
||||
return new TPromise<IInitData>((c, e) => {
|
||||
process.once('message', c);
|
||||
process.once('error', e);
|
||||
process.send('hello');
|
||||
});
|
||||
}
|
||||
|
||||
TPromise.join<any>([setupIPC(process.env['VSCODE_SHARED_IPC_HOOK']), handshake()])
|
||||
.then(r => main(r[0], r[1]))
|
||||
.then(() => setupPlanB(process.env['VSCODE_PID']))
|
||||
.done(null, quit);
|
||||
108
src/vs/code/electron-main/storage.ts
Normal file
108
src/vs/code/electron-main/storage.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
'use strict';
|
||||
|
||||
import path = require('path');
|
||||
import fs = require('fs');
|
||||
import events = require('events');
|
||||
import env = require('vs/code/electron-main/env');
|
||||
import {ServiceIdentifier, createDecorator} from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
const EventTypes = {
|
||||
STORE: 'store'
|
||||
};
|
||||
|
||||
export const IStorageService = createDecorator<IStorageService>('storageService');
|
||||
|
||||
export interface IStorageService {
|
||||
serviceId: ServiceIdentifier<any>;
|
||||
onStore<T>(clb: (key: string, oldValue: T, newValue: T) => void): () => void;
|
||||
getItem<T>(key: string, defaultValue?: T): T;
|
||||
setItem(key: string, data: any): void;
|
||||
removeItem(key: string): void;
|
||||
}
|
||||
|
||||
export class StorageService implements IStorageService {
|
||||
|
||||
serviceId = IStorageService;
|
||||
|
||||
private dbPath: string;
|
||||
private database: any = null;
|
||||
private eventEmitter = new events.EventEmitter();
|
||||
|
||||
constructor(@env.IEnvironmentService private envService: env.IEnvironmentService) {
|
||||
this.dbPath = path.join(envService.appHome, 'storage.json');
|
||||
}
|
||||
|
||||
onStore<T>(clb: (key: string, oldValue: T, newValue: T) => void): () => void {
|
||||
this.eventEmitter.addListener(EventTypes.STORE, clb);
|
||||
|
||||
return () => this.eventEmitter.removeListener(EventTypes.STORE, clb);
|
||||
}
|
||||
|
||||
getItem<T>(key: string, defaultValue?: T): T {
|
||||
if (!this.database) {
|
||||
this.database = this.load();
|
||||
}
|
||||
|
||||
const res = this.database[key];
|
||||
if (typeof res === 'undefined') {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return this.database[key];
|
||||
}
|
||||
|
||||
setItem(key: string, data: any): void {
|
||||
if (!this.database) {
|
||||
this.database = this.load();
|
||||
}
|
||||
|
||||
// Shortcut for primitives that did not change
|
||||
if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean') {
|
||||
if (this.database[key] === data) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let oldValue = this.database[key];
|
||||
this.database[key] = data;
|
||||
this.save();
|
||||
|
||||
this.eventEmitter.emit(EventTypes.STORE, key, oldValue, data);
|
||||
}
|
||||
|
||||
removeItem(key: string): void {
|
||||
if (!this.database) {
|
||||
this.database = this.load();
|
||||
}
|
||||
|
||||
if (this.database[key]) {
|
||||
let oldValue = this.database[key];
|
||||
delete this.database[key];
|
||||
this.save();
|
||||
|
||||
this.eventEmitter.emit(EventTypes.STORE, key, oldValue, null);
|
||||
}
|
||||
}
|
||||
|
||||
private load(): any {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(this.dbPath).toString());
|
||||
} catch (error) {
|
||||
if (this.envService.cliArgs.verboseLogging) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
private save(): void {
|
||||
fs.writeFileSync(this.dbPath, JSON.stringify(this.database, null, 4));
|
||||
}
|
||||
}
|
||||
252
src/vs/code/electron-main/update-manager.ts
Normal file
252
src/vs/code/electron-main/update-manager.ts
Normal file
@@ -0,0 +1,252 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import fs = require('fs');
|
||||
import path = require('path');
|
||||
import events = require('events');
|
||||
import electron = require('electron');
|
||||
import platform = require('vs/base/common/platform');
|
||||
import { IEnvironmentService, getPlatformIdentifier } from 'vs/code/electron-main/env';
|
||||
import { ISettingsService } from 'vs/code/electron-main/settings';
|
||||
import { Win32AutoUpdaterImpl } from 'vs/code/electron-main/auto-updater.win32';
|
||||
import { LinuxAutoUpdaterImpl } from 'vs/code/electron-main/auto-updater.linux';
|
||||
import { ILifecycleService } from 'vs/code/electron-main/lifecycle';
|
||||
import { ServiceIdentifier, createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export enum State {
|
||||
Uninitialized,
|
||||
Idle,
|
||||
CheckingForUpdate,
|
||||
UpdateAvailable,
|
||||
UpdateDownloaded
|
||||
}
|
||||
|
||||
export enum ExplicitState {
|
||||
Implicit,
|
||||
Explicit
|
||||
}
|
||||
|
||||
export interface IUpdate {
|
||||
releaseNotes: string;
|
||||
version: string;
|
||||
date: Date;
|
||||
quitAndUpdate: () => void;
|
||||
}
|
||||
|
||||
interface IAutoUpdater extends NodeJS.EventEmitter {
|
||||
setFeedURL(url: string): void;
|
||||
checkForUpdates(): void;
|
||||
}
|
||||
|
||||
export const IUpdateService = createDecorator<IUpdateService>('updateService');
|
||||
|
||||
export interface IUpdateService {
|
||||
serviceId: ServiceIdentifier<any>;
|
||||
feedUrl: string;
|
||||
channel: string;
|
||||
initialize(): void;
|
||||
state: State;
|
||||
availableUpdate: IUpdate;
|
||||
lastCheckDate: Date;
|
||||
checkForUpdates(explicit: boolean): void;
|
||||
on(event: string, listener: Function): this;
|
||||
}
|
||||
|
||||
export class UpdateManager extends events.EventEmitter implements IUpdateService {
|
||||
|
||||
serviceId = IUpdateService;
|
||||
|
||||
private _state: State;
|
||||
private explicitState: ExplicitState;
|
||||
private _availableUpdate: IUpdate;
|
||||
private _lastCheckDate: Date;
|
||||
private raw: IAutoUpdater;
|
||||
private _feedUrl: string;
|
||||
private _channel: string;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@ILifecycleService private lifecycleService: ILifecycleService,
|
||||
@IEnvironmentService private envService: IEnvironmentService,
|
||||
@ISettingsService private settingsManager: ISettingsService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._state = State.Uninitialized;
|
||||
this.explicitState = ExplicitState.Implicit;
|
||||
this._availableUpdate = null;
|
||||
this._lastCheckDate = null;
|
||||
this._feedUrl = null;
|
||||
this._channel = null;
|
||||
|
||||
if (platform.isWindows) {
|
||||
this.raw = instantiationService.createInstance(Win32AutoUpdaterImpl);
|
||||
} else if (platform.isLinux) {
|
||||
this.raw = instantiationService.createInstance(LinuxAutoUpdaterImpl);
|
||||
} else if (platform.isMacintosh) {
|
||||
this.raw = electron.autoUpdater;
|
||||
}
|
||||
|
||||
if (this.raw) {
|
||||
this.initRaw();
|
||||
}
|
||||
}
|
||||
|
||||
private initRaw(): void {
|
||||
this.raw.on('error', (event: any, message: string) => {
|
||||
this.emit('error', event, message);
|
||||
this.setState(State.Idle);
|
||||
});
|
||||
|
||||
this.raw.on('checking-for-update', () => {
|
||||
this.emit('checking-for-update');
|
||||
this.setState(State.CheckingForUpdate);
|
||||
});
|
||||
|
||||
this.raw.on('update-available', (event, url: string) => {
|
||||
this.emit('update-available', url);
|
||||
|
||||
let data: IUpdate = null;
|
||||
|
||||
if (url) {
|
||||
data = {
|
||||
releaseNotes: '',
|
||||
version: '',
|
||||
date: new Date(),
|
||||
quitAndUpdate: () => electron.shell.openExternal(url)
|
||||
};
|
||||
}
|
||||
|
||||
this.setState(State.UpdateAvailable, data);
|
||||
});
|
||||
|
||||
this.raw.on('update-not-available', () => {
|
||||
this.emit('update-not-available', this.explicitState === ExplicitState.Explicit);
|
||||
this.setState(State.Idle);
|
||||
});
|
||||
|
||||
this.raw.on('update-downloaded', (event: any, releaseNotes: string, version: string, date: Date, url: string, rawQuitAndUpdate: () => void) => {
|
||||
let data: IUpdate = {
|
||||
releaseNotes: releaseNotes,
|
||||
version: version,
|
||||
date: date,
|
||||
quitAndUpdate: () => this.quitAndUpdate(rawQuitAndUpdate)
|
||||
};
|
||||
|
||||
this.emit('update-downloaded', data);
|
||||
this.setState(State.UpdateDownloaded, data);
|
||||
});
|
||||
}
|
||||
|
||||
private quitAndUpdate(rawQuitAndUpdate: () => void): void {
|
||||
this.lifecycleService.quit().done(vetod => {
|
||||
if (vetod) {
|
||||
return;
|
||||
}
|
||||
|
||||
// for some reason updating on Mac causes the local storage not to be flushed.
|
||||
// we workaround this issue by forcing an explicit flush of the storage data.
|
||||
// see also https://github.com/Microsoft/vscode/issues/172
|
||||
if (platform.isMacintosh) {
|
||||
electron.session.defaultSession.flushStorageData();
|
||||
}
|
||||
|
||||
rawQuitAndUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
public get feedUrl(): string {
|
||||
return this._feedUrl;
|
||||
}
|
||||
|
||||
public get channel(): string {
|
||||
return this._channel;
|
||||
}
|
||||
|
||||
public initialize(): void {
|
||||
if (this.feedUrl) {
|
||||
return; // already initialized
|
||||
}
|
||||
|
||||
const channel = this.getUpdateChannel();
|
||||
const feedUrl = this.getUpdateFeedUrl(channel);
|
||||
|
||||
if (!feedUrl) {
|
||||
return; // updates not available
|
||||
}
|
||||
|
||||
try {
|
||||
this.raw.setFeedURL(feedUrl);
|
||||
} catch (e) {
|
||||
return; // application not signed
|
||||
}
|
||||
|
||||
this._channel = channel;
|
||||
this._feedUrl = feedUrl;
|
||||
|
||||
this.setState(State.Idle);
|
||||
|
||||
// Check for updates on startup after 30 seconds
|
||||
let timer = setTimeout(() => this.checkForUpdates(), 30 * 1000);
|
||||
|
||||
// Clear timer when checking for update
|
||||
this.on('error', (error: any, message: string) => console.error(error, message));
|
||||
|
||||
// Clear timer when checking for update
|
||||
this.on('checking-for-update', () => clearTimeout(timer));
|
||||
|
||||
// If update not found, try again in 10 minutes
|
||||
this.on('update-not-available', () => {
|
||||
timer = setTimeout(() => this.checkForUpdates(), 10 * 60 * 1000);
|
||||
});
|
||||
}
|
||||
|
||||
public get state(): State {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
public get availableUpdate(): IUpdate {
|
||||
return this._availableUpdate;
|
||||
}
|
||||
|
||||
public get lastCheckDate(): Date {
|
||||
return this._lastCheckDate;
|
||||
}
|
||||
|
||||
public checkForUpdates(explicit = false): void {
|
||||
this.explicitState = explicit ? ExplicitState.Explicit : ExplicitState.Implicit;
|
||||
this._lastCheckDate = new Date();
|
||||
this.raw.checkForUpdates();
|
||||
}
|
||||
|
||||
private setState(state: State, availableUpdate: IUpdate = null): void {
|
||||
this._state = state;
|
||||
this._availableUpdate = availableUpdate;
|
||||
this.emit('change');
|
||||
}
|
||||
|
||||
private getUpdateChannel(): string {
|
||||
const channel = this.settingsManager.getValue('update.channel') || 'default';
|
||||
return channel === 'none' ? null : this.envService.quality;
|
||||
}
|
||||
|
||||
private getUpdateFeedUrl(channel: string): string {
|
||||
if (!channel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (platform.isWindows && !fs.existsSync(path.join(path.dirname(process.execPath), 'unins000.exe'))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this.envService.updateUrl || !this.envService.product.commit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return `${ this.envService.updateUrl }/api/update/${ getPlatformIdentifier() }/${ channel }/${ this.envService.product.commit }`;
|
||||
}
|
||||
}
|
||||
594
src/vs/code/electron-main/window.ts
Normal file
594
src/vs/code/electron-main/window.ts
Normal file
@@ -0,0 +1,594 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import path = require('path');
|
||||
import {shell, screen, BrowserWindow} from 'electron';
|
||||
import {TPromise, TValueCallback} from 'vs/base/common/winjs.base';
|
||||
import platform = require('vs/base/common/platform');
|
||||
import objects = require('vs/base/common/objects');
|
||||
import { ICommandLineArguments, IEnvironmentService, IProcessEnvironment } from 'vs/code/electron-main/env';
|
||||
import storage = require('vs/code/electron-main/storage');
|
||||
import { ILogService } from './log';
|
||||
|
||||
export interface IWindowState {
|
||||
width?: number;
|
||||
height?: number;
|
||||
x?: number;
|
||||
y?: number;
|
||||
mode?: WindowMode;
|
||||
}
|
||||
|
||||
export interface IWindowCreationOptions {
|
||||
state: IWindowState;
|
||||
extensionDevelopmentPath?: string;
|
||||
}
|
||||
|
||||
export enum WindowMode {
|
||||
Maximized,
|
||||
Normal,
|
||||
Minimized
|
||||
}
|
||||
|
||||
export const defaultWindowState = function (mode = WindowMode.Normal): IWindowState {
|
||||
return {
|
||||
width: 1024,
|
||||
height: 768,
|
||||
mode: mode
|
||||
};
|
||||
};
|
||||
|
||||
export enum ReadyState {
|
||||
|
||||
/**
|
||||
* This window has not loaded any HTML yet
|
||||
*/
|
||||
NONE,
|
||||
|
||||
/**
|
||||
* This window is loading HTML
|
||||
*/
|
||||
LOADING,
|
||||
|
||||
/**
|
||||
* This window is navigating to another HTML
|
||||
*/
|
||||
NAVIGATING,
|
||||
|
||||
/**
|
||||
* This window is done loading HTML
|
||||
*/
|
||||
READY
|
||||
}
|
||||
|
||||
export interface IPath {
|
||||
|
||||
// the workspace spath for a VSCode instance which can be null
|
||||
workspacePath?: string;
|
||||
|
||||
// the file path to open within a VSCode instance
|
||||
filePath?: string;
|
||||
|
||||
// the line number in the file path to open
|
||||
lineNumber?: number;
|
||||
|
||||
// the column number in the file path to open
|
||||
columnNumber?: number;
|
||||
|
||||
// indicator to create the file path in the VSCode instance
|
||||
createFilePath?: boolean;
|
||||
|
||||
// indicator to install the extension (path to .vsix) in the VSCode instance
|
||||
installExtensionPath?: boolean;
|
||||
}
|
||||
|
||||
export interface IWindowConfiguration extends ICommandLineArguments {
|
||||
execPath: string;
|
||||
version: string;
|
||||
appName: string;
|
||||
applicationName: string;
|
||||
darwinBundleIdentifier: string;
|
||||
appSettingsHome: string;
|
||||
appSettingsPath: string;
|
||||
appKeybindingsPath: string;
|
||||
userExtensionsHome: string;
|
||||
mainIPCHandle: string;
|
||||
sharedIPCHandle: string;
|
||||
appRoot: string;
|
||||
isBuilt: boolean;
|
||||
commitHash: string;
|
||||
updateFeedUrl: string;
|
||||
updateChannel: string;
|
||||
recentFiles: string[];
|
||||
recentFolders: string[];
|
||||
workspacePath?: string;
|
||||
filesToOpen?: IPath[];
|
||||
filesToCreate?: IPath[];
|
||||
filesToDiff?: IPath[];
|
||||
extensionsToInstall: string[];
|
||||
crashReporter: Electron.CrashReporterStartOptions;
|
||||
extensionsGallery: {
|
||||
serviceUrl: string;
|
||||
cacheUrl: string;
|
||||
itemUrl: string;
|
||||
};
|
||||
extensionTips: { [id: string]: string; };
|
||||
welcomePage: string;
|
||||
releaseNotesUrl: string;
|
||||
licenseUrl: string;
|
||||
productDownloadUrl: string;
|
||||
enableTelemetry: boolean;
|
||||
userEnv: IProcessEnvironment;
|
||||
aiConfig: {
|
||||
key: string;
|
||||
asimovKey: string;
|
||||
};
|
||||
sendASmile: {
|
||||
reportIssueUrl: string,
|
||||
requestFeatureUrl: string
|
||||
};
|
||||
}
|
||||
|
||||
export class VSCodeWindow {
|
||||
|
||||
public static menuBarHiddenKey = 'menuBarHidden';
|
||||
public static themeStorageKey = 'theme';
|
||||
|
||||
private static MIN_WIDTH = 200;
|
||||
private static MIN_HEIGHT = 120;
|
||||
|
||||
private showTimeoutHandle: any;
|
||||
private _id: number;
|
||||
private _win: Electron.BrowserWindow;
|
||||
private _lastFocusTime: number;
|
||||
private _readyState: ReadyState;
|
||||
private _extensionDevelopmentPath: string;
|
||||
private windowState: IWindowState;
|
||||
private currentWindowMode: WindowMode;
|
||||
|
||||
private whenReadyCallbacks: TValueCallback<VSCodeWindow>[];
|
||||
|
||||
private currentConfig: IWindowConfiguration;
|
||||
private pendingLoadConfig: IWindowConfiguration;
|
||||
|
||||
constructor(
|
||||
config: IWindowCreationOptions,
|
||||
@ILogService private logService: ILogService,
|
||||
@IEnvironmentService private envService: IEnvironmentService,
|
||||
@storage.IStorageService private storageService: storage.IStorageService
|
||||
) {
|
||||
this._lastFocusTime = -1;
|
||||
this._readyState = ReadyState.NONE;
|
||||
this._extensionDevelopmentPath = config.extensionDevelopmentPath;
|
||||
this.whenReadyCallbacks = [];
|
||||
|
||||
// Load window state
|
||||
this.restoreWindowState(config.state);
|
||||
|
||||
// For VS theme we can show directly because background is white
|
||||
const usesLightTheme = /vs($| )/.test(this.storageService.getItem<string>(VSCodeWindow.themeStorageKey));
|
||||
let showDirectly = true; // set to false to prevent background color flash (flash should be fixed for Electron >= 0.37.x)
|
||||
if (showDirectly && !global.windowShow) {
|
||||
global.windowShow = new Date().getTime();
|
||||
}
|
||||
|
||||
let options: Electron.BrowserWindowOptions = {
|
||||
width: this.windowState.width,
|
||||
height: this.windowState.height,
|
||||
x: this.windowState.x,
|
||||
y: this.windowState.y,
|
||||
backgroundColor: usesLightTheme ? '#FFFFFF' : platform.isMacintosh ? '#131313' : '#1E1E1E', // https://github.com/electron/electron/issues/5150
|
||||
minWidth: VSCodeWindow.MIN_WIDTH,
|
||||
minHeight: VSCodeWindow.MIN_HEIGHT,
|
||||
show: showDirectly && this.currentWindowMode !== WindowMode.Maximized, // in case we are maximized, only show later after the call to maximize (see below)
|
||||
title: this.envService.product.nameLong,
|
||||
webPreferences: {
|
||||
'backgroundThrottling': false // by default if Code is in the background, intervals and timeouts get throttled
|
||||
}
|
||||
};
|
||||
|
||||
if (platform.isLinux) {
|
||||
options.icon = path.join(this.envService.appRoot, 'resources/linux/code.png'); // Windows and Mac are better off using the embedded icon(s)
|
||||
}
|
||||
|
||||
// Create the browser window.
|
||||
this._win = new BrowserWindow(options);
|
||||
this._id = this._win.id;
|
||||
|
||||
if (showDirectly && this.currentWindowMode === WindowMode.Maximized) {
|
||||
this.win.maximize();
|
||||
|
||||
if (!this.win.isVisible()) {
|
||||
this.win.show(); // to reduce flicker from the default window size to maximize, we only show after maximize
|
||||
}
|
||||
}
|
||||
|
||||
if (showDirectly) {
|
||||
this._lastFocusTime = new Date().getTime(); // since we show directly, we need to set the last focus time too
|
||||
}
|
||||
|
||||
if (this.storageService.getItem<boolean>(VSCodeWindow.menuBarHiddenKey, false)) {
|
||||
this.setMenuBarVisibility(false); // respect configured menu bar visibility
|
||||
}
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
public get isPluginDevelopmentHost(): boolean {
|
||||
return !!this._extensionDevelopmentPath;
|
||||
}
|
||||
|
||||
public get extensionDevelopmentPath(): string {
|
||||
return this._extensionDevelopmentPath;
|
||||
}
|
||||
|
||||
public get config(): IWindowConfiguration {
|
||||
return this.currentConfig;
|
||||
}
|
||||
|
||||
public get id(): number {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public get win(): Electron.BrowserWindow {
|
||||
return this._win;
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
if (!this._win) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._win.isMinimized()) {
|
||||
this._win.restore();
|
||||
}
|
||||
|
||||
this._win.focus();
|
||||
}
|
||||
|
||||
public get lastFocusTime(): number {
|
||||
return this._lastFocusTime;
|
||||
}
|
||||
|
||||
public get openedWorkspacePath(): string {
|
||||
return this.currentConfig.workspacePath;
|
||||
}
|
||||
|
||||
public get openedFilePath(): string {
|
||||
return this.currentConfig.filesToOpen && this.currentConfig.filesToOpen[0] && this.currentConfig.filesToOpen[0].filePath;
|
||||
}
|
||||
|
||||
public setReady(): void {
|
||||
this._readyState = ReadyState.READY;
|
||||
|
||||
// inform all waiting promises that we are ready now
|
||||
while (this.whenReadyCallbacks.length) {
|
||||
this.whenReadyCallbacks.pop()(this);
|
||||
}
|
||||
}
|
||||
|
||||
public ready(): TPromise<VSCodeWindow> {
|
||||
return new TPromise<VSCodeWindow>((c) => {
|
||||
if (this._readyState === ReadyState.READY) {
|
||||
return c(this);
|
||||
}
|
||||
|
||||
// otherwise keep and call later when we are ready
|
||||
this.whenReadyCallbacks.push(c);
|
||||
});
|
||||
}
|
||||
|
||||
public get readyState(): ReadyState {
|
||||
return this._readyState;
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// Remember that we loaded
|
||||
this._win.webContents.on('did-finish-load', () => {
|
||||
this._readyState = ReadyState.LOADING;
|
||||
|
||||
// Associate properties from the load request if provided
|
||||
if (this.pendingLoadConfig) {
|
||||
this.currentConfig = this.pendingLoadConfig;
|
||||
|
||||
this.pendingLoadConfig = null;
|
||||
}
|
||||
|
||||
// To prevent flashing, we set the window visible after the page has finished to load but before VSCode is loaded
|
||||
if (!this.win.isVisible()) {
|
||||
if (!global.windowShow) {
|
||||
global.windowShow = new Date().getTime();
|
||||
}
|
||||
|
||||
if (this.currentWindowMode === WindowMode.Maximized) {
|
||||
this.win.maximize();
|
||||
}
|
||||
|
||||
if (!this.win.isVisible()) { // maximize also makes visible
|
||||
this.win.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// App commands support
|
||||
this._win.on('app-command', (e, cmd) => {
|
||||
if (this.readyState !== ReadyState.READY) {
|
||||
return; // window must be ready
|
||||
}
|
||||
|
||||
// Support navigation via mouse buttons 4/5
|
||||
if (cmd === 'browser-backward') {
|
||||
this.send('vscode:runAction', 'workbench.action.navigateBack');
|
||||
} else if (cmd === 'browser-forward') {
|
||||
this.send('vscode:runAction', 'workbench.action.navigateForward');
|
||||
}
|
||||
});
|
||||
|
||||
// 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 = new Date().getTime();
|
||||
});
|
||||
|
||||
// Window Failed to load
|
||||
this._win.webContents.on('did-fail-load', (event: Event, errorCode: string, errorDescription: string) => {
|
||||
console.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.envService.isBuilt) {
|
||||
this._win.webContents.on('will-navigate', (event: Event) => {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public load(config: IWindowConfiguration): 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) {
|
||||
this.currentConfig = config;
|
||||
}
|
||||
|
||||
// Otherwise, the window is currently showing a folder and if there is an
|
||||
// unload handler preventing the load, we cannot just associate the paths
|
||||
// because the loading might be vetoed. Instead we associate it later when
|
||||
// the window load event has fired.
|
||||
else {
|
||||
this.pendingLoadConfig = config;
|
||||
this._readyState = ReadyState.NAVIGATING;
|
||||
}
|
||||
|
||||
// Load URL
|
||||
this._win.loadURL(this.getUrl(config));
|
||||
|
||||
// Make window visible if it did not open in N seconds because this indicates an error
|
||||
if (!config.isBuilt) {
|
||||
this.showTimeoutHandle = setTimeout(() => {
|
||||
if (this._win && !this._win.isVisible() && !this._win.isMinimized()) {
|
||||
this._win.show();
|
||||
this._win.focus();
|
||||
this._win.webContents.openDevTools();
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
public reload(cli?: ICommandLineArguments): void {
|
||||
|
||||
// Inherit current properties but overwrite some
|
||||
let configuration: IWindowConfiguration = objects.mixin({}, this.currentConfig);
|
||||
delete configuration.filesToOpen;
|
||||
delete configuration.filesToCreate;
|
||||
delete configuration.filesToDiff;
|
||||
delete configuration.extensionsToInstall;
|
||||
|
||||
// Some configuration things get inherited if the window is being reloaded and we are
|
||||
// in plugin development mode. These options are all development related.
|
||||
if (this.isPluginDevelopmentHost && cli) {
|
||||
configuration.verboseLogging = cli.verboseLogging;
|
||||
configuration.logExtensionHostCommunication = cli.logExtensionHostCommunication;
|
||||
configuration.debugBrkFileWatcherPort = cli.debugBrkFileWatcherPort;
|
||||
configuration.debugExtensionHostPort = cli.debugExtensionHostPort;
|
||||
configuration.debugBrkExtensionHost = cli.debugBrkExtensionHost;
|
||||
configuration.extensionsHomePath = cli.extensionsHomePath;
|
||||
}
|
||||
|
||||
// Load config
|
||||
this.load(configuration);
|
||||
}
|
||||
|
||||
private getUrl(config: IWindowConfiguration): string {
|
||||
let url = require.toUrl('vs/workbench/electron-browser/index.html');
|
||||
|
||||
// Config
|
||||
url += '?config=' + encodeURIComponent(JSON.stringify(config));
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
public serializeWindowState(): IWindowState {
|
||||
if (this.win.isFullScreen()) {
|
||||
return defaultWindowState(); // ignore state when in fullscreen mode and return defaults
|
||||
}
|
||||
|
||||
let state: IWindowState = Object.create(null);
|
||||
let mode: WindowMode;
|
||||
|
||||
// get window mode
|
||||
if (!platform.isMacintosh && this.win.isMaximized()) {
|
||||
mode = WindowMode.Maximized;
|
||||
} else if (this.win.isMinimized()) {
|
||||
mode = WindowMode.Minimized;
|
||||
} else {
|
||||
mode = WindowMode.Normal;
|
||||
}
|
||||
|
||||
// we don't want to save minimized state, only maximized or normal
|
||||
if (mode === WindowMode.Maximized) {
|
||||
state.mode = WindowMode.Maximized;
|
||||
} else if (mode !== WindowMode.Minimized) {
|
||||
state.mode = WindowMode.Normal;
|
||||
}
|
||||
|
||||
// only consider non-minimized window states
|
||||
if (mode === WindowMode.Normal || mode === WindowMode.Maximized) {
|
||||
let pos = this.win.getPosition();
|
||||
let size = this.win.getSize();
|
||||
|
||||
state.x = pos[0];
|
||||
state.y = pos[1];
|
||||
state.width = size[0];
|
||||
state.height = size[1];
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
private restoreWindowState(state?: IWindowState): void {
|
||||
if (state) {
|
||||
try {
|
||||
state = this.validateWindowState(state);
|
||||
} catch (err) {
|
||||
this.logService.log(`Unexpected error validating window state: ${err}\n${err.stack}`); // somehow display API can be picky about the state to validate
|
||||
}
|
||||
}
|
||||
|
||||
if (!state) {
|
||||
state = defaultWindowState();
|
||||
}
|
||||
|
||||
this.windowState = state;
|
||||
this.currentWindowMode = this.windowState.mode;
|
||||
}
|
||||
|
||||
private validateWindowState(state: IWindowState): IWindowState {
|
||||
if (!state) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ([state.x, state.y, state.width, state.height].some(n => typeof n !== 'number')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (state.width <= 0 || state.height <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let displays = screen.getAllDisplays();
|
||||
|
||||
// Single Monitor: be strict about x/y positioning
|
||||
if (displays.length === 1) {
|
||||
let displayBounds = displays[0].bounds;
|
||||
|
||||
// Careful with maximized: in that mode x/y can well be negative!
|
||||
if (state.mode !== WindowMode.Maximized && displayBounds.width > 0 && displayBounds.height > 0 /* Linux X11 sessions sometimes report wrong display bounds */) {
|
||||
if (state.x < displayBounds.x) {
|
||||
state.x = displayBounds.x; // prevent window from falling out of the screen to the left
|
||||
}
|
||||
|
||||
if (state.y < displayBounds.y) {
|
||||
state.y = displayBounds.y; // prevent window from falling out of the screen to the top
|
||||
}
|
||||
|
||||
if (state.x > (displayBounds.x + displayBounds.width)) {
|
||||
state.x = displayBounds.x; // prevent window from falling out of the screen to the right
|
||||
}
|
||||
|
||||
if (state.y > (displayBounds.y + displayBounds.height)) {
|
||||
state.y = displayBounds.y; // prevent window from falling out of the screen to the bottom
|
||||
}
|
||||
|
||||
if (state.width > displayBounds.width) {
|
||||
state.width = displayBounds.width; // prevent window from exceeding display bounds width
|
||||
}
|
||||
|
||||
if (state.height > displayBounds.height) {
|
||||
state.height = displayBounds.height; // prevent window from exceeding display bounds height
|
||||
}
|
||||
}
|
||||
|
||||
if (state.mode === WindowMode.Maximized) {
|
||||
return defaultWindowState(WindowMode.Maximized); // when maximized, make sure we have good values when the user restores the window
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
// Multi Monitor: be less strict because metrics can be crazy
|
||||
let bounds = { x: state.x, y: state.y, width: state.width, height: state.height };
|
||||
let display = screen.getDisplayMatching(bounds);
|
||||
if (display && display.bounds.x + display.bounds.width > bounds.x && display.bounds.y + display.bounds.height > bounds.y) {
|
||||
if (state.mode === WindowMode.Maximized) {
|
||||
let defaults = defaultWindowState(WindowMode.Maximized); // when maximized, make sure we have good values when the user restores the window
|
||||
defaults.x = state.x; // carefull to keep x/y position so that the window ends up on the correct monitor
|
||||
defaults.y = state.y;
|
||||
|
||||
return defaults;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public getBounds(): Electron.Bounds {
|
||||
let pos = this.win.getPosition();
|
||||
let dimension = this.win.getSize();
|
||||
|
||||
return { x: pos[0], y: pos[1], width: dimension[0], height: dimension[1] };
|
||||
}
|
||||
|
||||
public toggleFullScreen(): void {
|
||||
let willBeFullScreen = !this.win.isFullScreen();
|
||||
|
||||
this.win.setFullScreen(willBeFullScreen);
|
||||
|
||||
// Windows & Linux: Hide the menu bar but still allow to bring it up by pressing the Alt key
|
||||
if (platform.isWindows || platform.isLinux) {
|
||||
if (willBeFullScreen) {
|
||||
this.setMenuBarVisibility(false);
|
||||
} else {
|
||||
this.setMenuBarVisibility(!this.storageService.getItem<boolean>(VSCodeWindow.menuBarHiddenKey, false)); // restore as configured
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public setMenuBarVisibility(visible: boolean): void {
|
||||
this.win.setMenuBarVisibility(visible);
|
||||
this.win.setAutoHideMenuBar(!visible);
|
||||
}
|
||||
|
||||
public sendWhenReady(channel: string, ...args: any[]): void {
|
||||
this.ready().then(() => {
|
||||
this.send(channel, ...args);
|
||||
});
|
||||
}
|
||||
|
||||
public send(channel: string, ...args: any[]): void {
|
||||
this._win.webContents.send(channel, ...args);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this.showTimeoutHandle) {
|
||||
clearTimeout(this.showTimeoutHandle);
|
||||
}
|
||||
|
||||
this._win = null; // Important to dereference the window object to allow for GC
|
||||
}
|
||||
}
|
||||
1279
src/vs/code/electron-main/windows.ts
Normal file
1279
src/vs/code/electron-main/windows.ts
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user