mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-09 17:52:34 -05:00
Merge from master
This commit is contained in:
@@ -3,23 +3,21 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { app, ipcMain as ipc, systemPreferences } from 'electron';
|
||||
import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, protocol } from 'electron';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { WindowsManager } from 'vs/code/electron-main/windows';
|
||||
import { IWindowsService, OpenContext, ActiveWindowManager } from 'vs/platform/windows/common/windows';
|
||||
import { WindowsChannel } from 'vs/platform/windows/common/windowsIpc';
|
||||
import { WindowsChannel } from 'vs/platform/windows/node/windowsIpc';
|
||||
import { WindowsService } from 'vs/platform/windows/electron-main/windowsService';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
|
||||
import { ILifecycleService, LifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
|
||||
import { getShellEnvironment } from 'vs/code/node/shellEnv';
|
||||
import { IUpdateService } from 'vs/platform/update/common/update';
|
||||
import { UpdateChannel } from 'vs/platform/update/common/updateIpc';
|
||||
import { UpdateChannel } from 'vs/platform/update/node/updateIpc';
|
||||
import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron-main';
|
||||
import { Server, connect, Client } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { SharedProcess } from 'vs/code/electron-main/sharedProcess';
|
||||
import { Mutex } from 'windows-mutex';
|
||||
import { LaunchService, LaunchChannel, ILaunchService } from './launch';
|
||||
import { LaunchService, LaunchChannel, ILaunchService } from 'vs/platform/launch/electron-main/launchService';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
@@ -28,49 +26,64 @@ import { IStateService } from 'vs/platform/state/common/state';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IURLService } from 'vs/platform/url/common/url';
|
||||
import { URLHandlerChannelClient, URLServiceChannel } from 'vs/platform/url/common/urlIpc';
|
||||
import { URLHandlerChannelClient, URLServiceChannel } from 'vs/platform/url/node/urlIpc';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { ITelemetryAppenderChannel, TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc';
|
||||
import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc';
|
||||
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
|
||||
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
|
||||
import { getDelayedChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { getDelayedChannel, StaticRouter } from 'vs/base/parts/ipc/node/ipc';
|
||||
import product from 'vs/platform/node/product';
|
||||
import pkg from 'vs/platform/node/package';
|
||||
import { ProxyAuthHandler } from './auth';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { ProxyAuthHandler } from 'vs/code/electron-main/auth';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
|
||||
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
|
||||
import { IHistoryMainService } from 'vs/platform/history/common/history';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { CodeWindow } from 'vs/code/electron-main/window';
|
||||
import { KeyboardLayoutMonitor } from 'vs/code/electron-main/keyboard';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { WorkspacesChannel } from 'vs/platform/workspaces/common/workspacesIpc';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { WorkspacesChannel } from 'vs/platform/workspaces/node/workspacesIpc';
|
||||
import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { getMachineId } from 'vs/base/node/id';
|
||||
import { Win32UpdateService } from 'vs/platform/update/electron-main/updateService.win32';
|
||||
import { LinuxUpdateService } from 'vs/platform/update/electron-main/updateService.linux';
|
||||
import { DarwinUpdateService } from 'vs/platform/update/electron-main/updateService.darwin';
|
||||
import { IIssueService } from 'vs/platform/issue/common/issue';
|
||||
import { IssueChannel } from 'vs/platform/issue/common/issueIpc';
|
||||
import { IssueChannel } from 'vs/platform/issue/node/issueIpc';
|
||||
import { IssueService } from 'vs/platform/issue/electron-main/issueService';
|
||||
import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc';
|
||||
import { LogLevelSetterChannel } from 'vs/platform/log/node/logIpc';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { ElectronURLListener } from 'vs/platform/url/electron-main/electronUrlListener';
|
||||
import { serve as serveDriver } from 'vs/platform/driver/electron-main/driver';
|
||||
import { connectRemoteAgentManagement, RemoteAgentConnectionContext } from 'vs/platform/remote/node/remoteAgentConnection';
|
||||
import { IMenubarService } from 'vs/platform/menubar/common/menubar';
|
||||
import { MenubarService } from 'vs/platform/menubar/electron-main/menubarService';
|
||||
import { MenubarChannel } from 'vs/platform/menubar/common/menubarIpc';
|
||||
import { IUriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay';
|
||||
import { CodeMenu } from 'vs/code/electron-main/menus';
|
||||
import { MenubarChannel } from 'vs/platform/menubar/node/menubarIpc';
|
||||
import { ILabelService, RegisterFormatterEvent } from 'vs/platform/label/common/label';
|
||||
import { hasArgs } from 'vs/platform/environment/node/argv';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { registerContextMenuListener } from 'vs/base/parts/contextmenu/electron-main/contextmenu';
|
||||
import { THEME_STORAGE_KEY, THEME_BG_STORAGE_KEY } from 'vs/code/electron-main/theme';
|
||||
import { nativeSep, join } from 'vs/base/common/paths';
|
||||
import { homedir } from 'os';
|
||||
import { localize } from 'vs/nls';
|
||||
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from 'vs/platform/remote/node/remoteAgentFileSystemChannel';
|
||||
import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { SnapUpdateService } from 'vs/platform/update/electron-main/updateService.snap';
|
||||
import { IStorageMainService, StorageMainService } from 'vs/platform/storage/node/storageMainService';
|
||||
import { GlobalStorageDatabaseChannel } from 'vs/platform/storage/node/storageIpc';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
// {{SQL CARBON EDIT}}
|
||||
import { CodeMenu } from 'sql/workbench/electron-browser/menus';
|
||||
// {{SQL CARBON EDIT}} - End
|
||||
|
||||
export class CodeApplication {
|
||||
export class CodeApplication extends Disposable {
|
||||
|
||||
private static readonly MACHINE_ID_KEY = 'telemetry.machineId';
|
||||
|
||||
private toDispose: IDisposable[];
|
||||
private windowsMainService: IWindowsMainService;
|
||||
|
||||
private electronIpcServer: ElectronIPCServer;
|
||||
@@ -88,9 +101,12 @@ export class CodeApplication {
|
||||
@IConfigurationService private configurationService: ConfigurationService,
|
||||
@IStateService private stateService: IStateService,
|
||||
@IHistoryMainService private historyMainService: IHistoryMainService,
|
||||
@IUriDisplayService private uriDisplayService: IUriDisplayService
|
||||
@ILabelService private labelService: ILabelService
|
||||
) {
|
||||
this.toDispose = [mainIpcServer, configurationService];
|
||||
super();
|
||||
|
||||
this._register(mainIpcServer);
|
||||
this._register(configurationService);
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
@@ -102,11 +118,11 @@ export class CodeApplication {
|
||||
process.on('uncaughtException', err => this.onUnexpectedError(err));
|
||||
process.on('unhandledRejection', (reason: any, promise: Promise<any>) => errors.onUnexpectedError(reason));
|
||||
|
||||
app.on('will-quit', () => {
|
||||
this.logService.trace('App#will-quit: disposing resources');
|
||||
// Contextmenu via IPC support
|
||||
registerContextMenuListener();
|
||||
|
||||
this.dispose();
|
||||
});
|
||||
// Dispose on shutdown
|
||||
this.lifecycleService.onWillShutdown(() => this.dispose());
|
||||
|
||||
app.on('accessibility-support-changed', (event: Event, accessibilitySupportEnabled: boolean) => {
|
||||
if (this.windowsMainService) {
|
||||
@@ -123,40 +139,122 @@ export class CodeApplication {
|
||||
}
|
||||
});
|
||||
|
||||
const isValidWebviewSource = (source: string): boolean => {
|
||||
if (!source) {
|
||||
return false;
|
||||
}
|
||||
if (source === 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%09%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E') {
|
||||
return true;
|
||||
}
|
||||
const srcUri: any = URI.parse(source.toLowerCase()).toString();
|
||||
return srcUri.startsWith(URI.file(this.environmentService.appRoot.toLowerCase()).toString());
|
||||
};
|
||||
|
||||
// Security related measures (https://electronjs.org/docs/tutorial/security)
|
||||
// DO NOT CHANGE without consulting the documentation
|
||||
app.on('web-contents-created', (event: any, contents) => {
|
||||
contents.on('will-attach-webview', (event: Electron.Event, webPreferences, params) => {
|
||||
|
||||
// Ensure defaults
|
||||
delete webPreferences.preload;
|
||||
webPreferences.nodeIntegration = false;
|
||||
|
||||
// Verify URLs being loaded
|
||||
if (isValidWebviewSource(params.src) && isValidWebviewSource(webPreferences.preloadURL)) {
|
||||
if (this.isValidWebviewSource(params.src) && this.isValidWebviewSource(webPreferences.preloadURL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete webPreferences.preloadUrl;
|
||||
|
||||
// Otherwise prevent loading
|
||||
this.logService.error('webContents#web-contents-created: Prevented webview attach');
|
||||
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
contents.on('will-navigate', event => {
|
||||
this.logService.error('webContents#will-navigate: Prevented webcontent navigation');
|
||||
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
contents.on('new-window', (event: Event, url: string) => {
|
||||
event.preventDefault(); // prevent code that wants to open links
|
||||
|
||||
shell.openExternal(url);
|
||||
});
|
||||
});
|
||||
|
||||
const connectionPool: Map<string, ActiveConnection> = new Map<string, ActiveConnection>();
|
||||
|
||||
class ActiveConnection {
|
||||
private _authority: string;
|
||||
private _client: TPromise<Client<RemoteAgentConnectionContext>>;
|
||||
private _disposeRunner: RunOnceScheduler;
|
||||
|
||||
constructor(authority: string, host: string, port: number) {
|
||||
this._authority = authority;
|
||||
this._client = connectRemoteAgentManagement(authority, host, port, `main`);
|
||||
this._disposeRunner = new RunOnceScheduler(() => this._dispose(), 5000);
|
||||
}
|
||||
|
||||
private _dispose(): void {
|
||||
this._disposeRunner.dispose();
|
||||
connectionPool.delete(this._authority);
|
||||
this._client.then((connection) => {
|
||||
connection.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
public getClient(): TPromise<Client<RemoteAgentConnectionContext>> {
|
||||
this._disposeRunner.schedule();
|
||||
return this._client;
|
||||
}
|
||||
}
|
||||
|
||||
const resolvedAuthorities = new Map<string, ResolvedAuthority>();
|
||||
ipc.on('vscode:remoteAuthorityResolved', (event: any, data: ResolvedAuthority) => {
|
||||
resolvedAuthorities.set(data.authority, data);
|
||||
});
|
||||
const resolveAuthority = (authority: string): ResolvedAuthority | null => {
|
||||
if (authority.indexOf('+') >= 0) {
|
||||
if (resolvedAuthorities.has(authority)) {
|
||||
return resolvedAuthorities.get(authority);
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
const [host, strPort] = authority.split(':');
|
||||
const port = parseInt(strPort, 10);
|
||||
return { authority, host, port, syncExtensions: false };
|
||||
}
|
||||
};
|
||||
|
||||
protocol.registerBufferProtocol(REMOTE_HOST_SCHEME, async (request, callback) => {
|
||||
if (request.method !== 'GET') {
|
||||
return callback(null);
|
||||
}
|
||||
const uri = URI.parse(request.url);
|
||||
|
||||
let activeConnection: ActiveConnection = null;
|
||||
if (connectionPool.has(uri.authority)) {
|
||||
activeConnection = connectionPool.get(uri.authority);
|
||||
} else {
|
||||
let resolvedAuthority = resolveAuthority(uri.authority);
|
||||
if (!resolvedAuthority) {
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
activeConnection = new ActiveConnection(uri.authority, resolvedAuthority.host, resolvedAuthority.port);
|
||||
connectionPool.set(uri.authority, activeConnection);
|
||||
}
|
||||
try {
|
||||
const rawClient = await activeConnection.getClient();
|
||||
if (connectionPool.has(uri.authority)) { // not disposed in the meantime
|
||||
const channel = rawClient.getChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME);
|
||||
|
||||
// TODO@alex don't use call directly, wrap it around a `RemoteExtensionsFileSystemProvider`
|
||||
const fileContents = await channel.call<Uint8Array>('readFile', [uri]);
|
||||
callback(Buffer.from(fileContents));
|
||||
} else {
|
||||
callback(null);
|
||||
}
|
||||
} catch (err) {
|
||||
errors.onUnexpectedError(err);
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
|
||||
let macOpenFileURIs: URI[] = [];
|
||||
let runningTimeout: number = null;
|
||||
let runningTimeout: any = null;
|
||||
app.on('open-file', (event: Event, path: string) => {
|
||||
this.logService.trace('App#open-file: ', path);
|
||||
event.preventDefault();
|
||||
@@ -189,15 +287,15 @@ export class CodeApplication {
|
||||
this.windowsMainService.openNewWindow(OpenContext.DESKTOP); //macOS native tab "+" button
|
||||
});
|
||||
|
||||
ipc.on('vscode:exit', (event: any, code: number) => {
|
||||
ipc.on('vscode:exit', (event: Event, code: number) => {
|
||||
this.logService.trace('IPC#vscode:exit', code);
|
||||
|
||||
this.dispose();
|
||||
this.lifecycleService.kill(code);
|
||||
});
|
||||
|
||||
ipc.on('vscode:fetchShellEnv', event => {
|
||||
const webContents = event.sender.webContents;
|
||||
ipc.on('vscode:fetchShellEnv', (event: Event) => {
|
||||
const webContents = event.sender;
|
||||
getShellEnvironment().then(shellEnv => {
|
||||
if (!webContents.isDestroyed()) {
|
||||
webContents.send('vscode:acceptShellEnv', shellEnv);
|
||||
@@ -211,7 +309,7 @@ export class CodeApplication {
|
||||
});
|
||||
});
|
||||
|
||||
ipc.on('vscode:broadcast', (event: any, windowId: number, broadcast: { channel: string; payload: any; }) => {
|
||||
ipc.on('vscode:broadcast', (event: Event, windowId: number, broadcast: { channel: string; payload: any; }) => {
|
||||
if (this.windowsMainService && broadcast.channel && !isUndefinedOrNull(broadcast.payload)) {
|
||||
this.logService.trace('IPC#vscode:broadcast', broadcast.channel, broadcast.payload);
|
||||
|
||||
@@ -223,8 +321,20 @@ export class CodeApplication {
|
||||
}
|
||||
});
|
||||
|
||||
ipc.on('vscode:uriDisplayRegisterFormater', (event: any, { scheme, formater }) => {
|
||||
this.uriDisplayService.registerFormater(scheme, formater);
|
||||
ipc.on('vscode:labelRegisterFormatter', (event: any, data: RegisterFormatterEvent) => {
|
||||
this.labelService.registerFormatter(data.selector, data.formatter);
|
||||
});
|
||||
|
||||
ipc.on('vscode:toggleDevTools', (event: Event) => {
|
||||
event.sender.toggleDevTools();
|
||||
});
|
||||
|
||||
ipc.on('vscode:openDevTools', (event: Event) => {
|
||||
event.sender.openDevTools();
|
||||
});
|
||||
|
||||
ipc.on('vscode:reloadWindow', (event: Event) => {
|
||||
event.sender.reload();
|
||||
});
|
||||
|
||||
// Keyboard layout changes
|
||||
@@ -235,6 +345,20 @@ export class CodeApplication {
|
||||
});
|
||||
}
|
||||
|
||||
private isValidWebviewSource(source: string): boolean {
|
||||
if (!source) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (source === 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%09%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E') {
|
||||
return true;
|
||||
}
|
||||
|
||||
const srcUri: any = URI.parse(source).fsPath.toLowerCase();
|
||||
const rootUri = URI.file(this.environmentService.appRoot).fsPath.toLowerCase();
|
||||
return srcUri.startsWith(rootUri + nativeSep);
|
||||
}
|
||||
|
||||
private onUnexpectedError(err: Error): void {
|
||||
if (err) {
|
||||
|
||||
@@ -262,8 +386,8 @@ export class CodeApplication {
|
||||
if (event === 'vscode:changeColorTheme' && typeof payload === 'string') {
|
||||
let data = JSON.parse(payload);
|
||||
|
||||
this.stateService.setItem(CodeWindow.themeStorageKey, data.id);
|
||||
this.stateService.setItem(CodeWindow.themeBackgroundStorageKey, data.background);
|
||||
this.stateService.setItem(THEME_STORAGE_KEY, data.baseTheme);
|
||||
this.stateService.setItem(THEME_BG_STORAGE_KEY, data.background);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +412,7 @@ export class CodeApplication {
|
||||
// See: https://github.com/Microsoft/vscode/issues/35361#issuecomment-399794085
|
||||
try {
|
||||
if (platform.isMacintosh && this.configurationService.getValue<boolean>('window.nativeTabs') === true && !systemPreferences.getUserDefault('NSUseImprovedLayoutPass', 'boolean')) {
|
||||
systemPreferences.registerDefaults({ NSUseImprovedLayoutPass: true });
|
||||
systemPreferences.setUserDefault('NSUseImprovedLayoutPass', 'boolean', true as any);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
@@ -303,37 +427,73 @@ export class CodeApplication {
|
||||
this.logService.trace(`Resolved machine identifier: ${machineId}`);
|
||||
|
||||
// Spawn shared process
|
||||
this.sharedProcess = new SharedProcess(this.environmentService, this.lifecycleService, this.logService, machineId, this.userEnv);
|
||||
this.sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv);
|
||||
this.sharedProcessClient = this.sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main'));
|
||||
|
||||
// Services
|
||||
const appInstantiationService = this.initServices(machineId);
|
||||
return this.initServices(machineId).then(appInstantiationService => {
|
||||
|
||||
let promise: TPromise<any> = TPromise.as(null);
|
||||
|
||||
// Create driver
|
||||
if (this.environmentService.driverHandle) {
|
||||
serveDriver(this.electronIpcServer, this.environmentService.driverHandle, this.environmentService, appInstantiationService).then(server => {
|
||||
this.logService.info('Driver started at:', this.environmentService.driverHandle);
|
||||
this.toDispose.push(server);
|
||||
});
|
||||
}
|
||||
|
||||
return promise.then(() => {
|
||||
// Create driver
|
||||
if (this.environmentService.driverHandle) {
|
||||
serveDriver(this.electronIpcServer, this.environmentService.driverHandle, this.environmentService, appInstantiationService).then(server => {
|
||||
this.logService.info('Driver started at:', this.environmentService.driverHandle);
|
||||
this._register(server);
|
||||
});
|
||||
}
|
||||
|
||||
// Setup Auth Handler
|
||||
const authHandler = appInstantiationService.createInstance(ProxyAuthHandler);
|
||||
this.toDispose.push(authHandler);
|
||||
this._register(authHandler);
|
||||
|
||||
// Open Windows
|
||||
appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor));
|
||||
const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor));
|
||||
|
||||
// Post Open Windows Tasks
|
||||
appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor));
|
||||
|
||||
// Tracing: Stop tracing after windows are ready if enabled
|
||||
if (this.environmentService.args.trace) {
|
||||
this.stopTracingEventually(windows);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private stopTracingEventually(windows: ICodeWindow[]): void {
|
||||
this.logService.info(`Tracing: waiting for windows to get ready...`);
|
||||
|
||||
let recordingStopped = false;
|
||||
const stopRecording = (timeout) => {
|
||||
if (recordingStopped) {
|
||||
return;
|
||||
}
|
||||
|
||||
recordingStopped = true; // only once
|
||||
|
||||
contentTracing.stopRecording(join(homedir(), `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`), path => {
|
||||
if (!timeout) {
|
||||
this.windowsMainService.showMessageBox({
|
||||
type: 'info',
|
||||
message: localize('trace.message', "Successfully created trace."),
|
||||
detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path),
|
||||
buttons: [localize('trace.ok', "Ok")]
|
||||
}, this.windowsMainService.getLastActiveWindow());
|
||||
} else {
|
||||
this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Wait up to 30s before creating the trace anyways
|
||||
const timeoutHandle = setTimeout(() => stopRecording(true), 30000);
|
||||
|
||||
// Wait for all windows to get ready and stop tracing then
|
||||
TPromise.join(windows.map(window => window.ready())).then(() => {
|
||||
clearTimeout(timeoutHandle);
|
||||
stopRecording(false);
|
||||
});
|
||||
}
|
||||
|
||||
private resolveMachineId(): TPromise<string> {
|
||||
const machineId = this.stateService.getItem<string>(CodeApplication.MACHINE_ID_KEY);
|
||||
if (machineId) {
|
||||
@@ -349,40 +509,84 @@ export class CodeApplication {
|
||||
});
|
||||
}
|
||||
|
||||
private initServices(machineId: string): IInstantiationService {
|
||||
private initServices(machineId: string): Thenable<IInstantiationService> {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
services.set(IUpdateService, new SyncDescriptor(Win32UpdateService));
|
||||
} else if (process.platform === 'linux') {
|
||||
services.set(IUpdateService, new SyncDescriptor(LinuxUpdateService));
|
||||
if (process.env.SNAP && process.env.SNAP_REVISION) {
|
||||
services.set(IUpdateService, new SyncDescriptor(SnapUpdateService));
|
||||
} else {
|
||||
services.set(IUpdateService, new SyncDescriptor(LinuxUpdateService));
|
||||
}
|
||||
} else if (process.platform === 'darwin') {
|
||||
services.set(IUpdateService, new SyncDescriptor(DarwinUpdateService));
|
||||
}
|
||||
|
||||
services.set(IWindowsMainService, new SyncDescriptor(WindowsManager, machineId));
|
||||
services.set(IWindowsService, new SyncDescriptor(WindowsService, this.sharedProcess));
|
||||
services.set(IWindowsMainService, new SyncDescriptor(WindowsManager, [machineId]));
|
||||
services.set(IWindowsService, new SyncDescriptor(WindowsService, [this.sharedProcess]));
|
||||
services.set(ILaunchService, new SyncDescriptor(LaunchService));
|
||||
services.set(IIssueService, new SyncDescriptor(IssueService, machineId, this.userEnv));
|
||||
services.set(IIssueService, new SyncDescriptor(IssueService, [machineId, this.userEnv]));
|
||||
services.set(IMenubarService, new SyncDescriptor(MenubarService));
|
||||
services.set(IStorageMainService, new SyncDescriptor(StorageMainService));
|
||||
|
||||
// Telemtry
|
||||
// Telemetry
|
||||
if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
|
||||
const channel = getDelayedChannel<ITelemetryAppenderChannel>(this.sharedProcessClient.then(c => c.getChannel('telemetryAppender')));
|
||||
const channel = getDelayedChannel(this.sharedProcessClient.then(c => c.getChannel('telemetryAppender')));
|
||||
const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(this.logService));
|
||||
const commonProperties = resolveCommonProperties(product.commit, pkg.version, machineId, this.environmentService.installSourcePath);
|
||||
const piiPaths = [this.environmentService.appRoot, this.environmentService.extensionsPath];
|
||||
const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths };
|
||||
|
||||
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, config));
|
||||
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config]));
|
||||
} else {
|
||||
services.set(ITelemetryService, NullTelemetryService);
|
||||
}
|
||||
|
||||
return this.instantiationService.createChild(services);
|
||||
const appInstantiationService = this.instantiationService.createChild(services);
|
||||
|
||||
return appInstantiationService.invokeFunction(accessor => this.initStorageService(accessor)).then(() => appInstantiationService);
|
||||
}
|
||||
|
||||
private openFirstWindow(accessor: ServicesAccessor): void {
|
||||
private initStorageService(accessor: ServicesAccessor): Thenable<void> {
|
||||
const storageMainService = accessor.get(IStorageMainService) as StorageMainService;
|
||||
|
||||
// Ensure to close storage on shutdown
|
||||
this.lifecycleService.onWillShutdown(e => e.join(storageMainService.close()));
|
||||
|
||||
// Initialize storage service
|
||||
return storageMainService.initialize().then(void 0, error => {
|
||||
errors.onUnexpectedError(error);
|
||||
this.logService.error(error);
|
||||
}).then(() => {
|
||||
|
||||
// Apply global telemetry values as part of the initialization
|
||||
// These are global across all windows and thereby should be
|
||||
// written from the main process once.
|
||||
|
||||
const telemetryInstanceId = 'telemetry.instanceId';
|
||||
const instanceId = storageMainService.get(telemetryInstanceId, null);
|
||||
if (instanceId === null) {
|
||||
storageMainService.store(telemetryInstanceId, generateUuid());
|
||||
}
|
||||
|
||||
const telemetryFirstSessionDate = 'telemetry.firstSessionDate';
|
||||
const firstSessionDate = storageMainService.get(telemetryFirstSessionDate, null);
|
||||
if (firstSessionDate === null) {
|
||||
storageMainService.store(telemetryFirstSessionDate, new Date().toUTCString());
|
||||
}
|
||||
|
||||
const telemetryCurrentSessionDate = 'telemetry.currentSessionDate';
|
||||
const telemetryLastSessionDate = 'telemetry.lastSessionDate';
|
||||
const lastSessionDate = storageMainService.get(telemetryCurrentSessionDate, null); // previous session date was the "current" one at that time
|
||||
const currentSessionDate = new Date().toUTCString(); // current session date is "now"
|
||||
storageMainService.store(telemetryLastSessionDate, lastSessionDate);
|
||||
storageMainService.store(telemetryCurrentSessionDate, currentSessionDate);
|
||||
});
|
||||
}
|
||||
|
||||
private openFirstWindow(accessor: ServicesAccessor): ICodeWindow[] {
|
||||
const appInstantiationService = accessor.get(IInstantiationService);
|
||||
|
||||
// Register more Main IPC services
|
||||
@@ -406,7 +610,7 @@ export class CodeApplication {
|
||||
const windowsService = accessor.get(IWindowsService);
|
||||
const windowsChannel = new WindowsChannel(windowsService);
|
||||
this.electronIpcServer.registerChannel('windows', windowsChannel);
|
||||
this.sharedProcessClient.done(client => client.registerChannel('windows', windowsChannel));
|
||||
this.sharedProcessClient.then(client => client.registerChannel('windows', windowsChannel));
|
||||
|
||||
const menubarService = accessor.get(IMenubarService);
|
||||
const menubarChannel = new MenubarChannel(menubarService);
|
||||
@@ -416,13 +620,17 @@ export class CodeApplication {
|
||||
const urlChannel = new URLServiceChannel(urlService);
|
||||
this.electronIpcServer.registerChannel('url', urlChannel);
|
||||
|
||||
const storageMainService = accessor.get(IStorageMainService);
|
||||
const storageChannel = this._register(new GlobalStorageDatabaseChannel(storageMainService as StorageMainService));
|
||||
this.electronIpcServer.registerChannel('storage', storageChannel);
|
||||
|
||||
// Log level management
|
||||
const logLevelChannel = new LogLevelSetterChannel(accessor.get(ILogService));
|
||||
this.electronIpcServer.registerChannel('loglevel', logLevelChannel);
|
||||
this.sharedProcessClient.done(client => client.registerChannel('loglevel', logLevelChannel));
|
||||
this.sharedProcessClient.then(client => client.registerChannel('loglevel', logLevelChannel));
|
||||
|
||||
// Lifecycle
|
||||
this.lifecycleService.ready();
|
||||
(this.lifecycleService as LifecycleService).ready();
|
||||
|
||||
// Propagate to clients
|
||||
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); // TODO@Joao: unfold this
|
||||
@@ -431,8 +639,8 @@ export class CodeApplication {
|
||||
|
||||
// Create a URL handler which forwards to the last active window
|
||||
const activeWindowManager = new ActiveWindowManager(windowsService);
|
||||
const route = () => activeWindowManager.getActiveClientId();
|
||||
const urlHandlerChannel = this.electronIpcServer.getChannel('urlHandler', { routeCall: route, routeEvent: route });
|
||||
const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id));
|
||||
const urlHandlerChannel = this.electronIpcServer.getChannel('urlHandler', activeWindowRouter);
|
||||
const multiplexURLHandler = new URLHandlerChannelClient(urlHandlerChannel);
|
||||
|
||||
// On Mac, Code can be running without any open windows, so we must create a window to handle urls,
|
||||
@@ -460,33 +668,39 @@ export class CodeApplication {
|
||||
// Watch Electron URLs and forward them to the UrlService
|
||||
const urls = args['open-url'] ? args._urls : [];
|
||||
const urlListener = new ElectronURLListener(urls, urlService, this.windowsMainService);
|
||||
this.toDispose.push(urlListener);
|
||||
this._register(urlListener);
|
||||
|
||||
this.windowsMainService.ready(this.userEnv);
|
||||
|
||||
// Open our first window
|
||||
const macOpenFiles = (<any>global).macOpenFiles as string[];
|
||||
const context = !!process.env['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP;
|
||||
if (args['new-window'] && args._.length === 0 && (args['folder-uri'] || []).length === 0) {
|
||||
this.windowsMainService.open({ context, cli: args, forceNewWindow: true, forceEmpty: true, initialStartup: true }); // new window if "-n" was used without paths
|
||||
} else if (macOpenFiles && macOpenFiles.length && (!args._ || !args._.length || !args['folder-uri'] || !args['folder-uri'].length)) {
|
||||
this.windowsMainService.open({ context: OpenContext.DOCK, cli: args, urisToOpen: macOpenFiles.map(file => URI.file(file)), initialStartup: true }); // mac: open-file event received on startup
|
||||
} else {
|
||||
this.windowsMainService.open({ context, cli: args, forceNewWindow: args['new-window'] || (!args._.length && args['unity-launch']), diffMode: args.diff, initialStartup: true }); // default: read paths from cli
|
||||
const hasCliArgs = hasArgs(args._);
|
||||
const hasFolderURIs = hasArgs(args['folder-uri']);
|
||||
const hasFileURIs = hasArgs(args['file-uri']);
|
||||
|
||||
if (args['new-window'] && !hasCliArgs && !hasFolderURIs && !hasFileURIs) {
|
||||
return this.windowsMainService.open({ context, cli: args, forceNewWindow: true, forceEmpty: true, initialStartup: true }); // new window if "-n" was used without paths
|
||||
}
|
||||
|
||||
if (macOpenFiles && macOpenFiles.length && !hasCliArgs && !hasFolderURIs && !hasFileURIs) {
|
||||
return this.windowsMainService.open({ context: OpenContext.DOCK, cli: args, urisToOpen: macOpenFiles.map(file => URI.file(file)), initialStartup: true }); // mac: open-file event received on startup
|
||||
}
|
||||
|
||||
return this.windowsMainService.open({ context, cli: args, forceNewWindow: args['new-window'] || (!hasCliArgs && args['unity-launch']), diffMode: args.diff, initialStartup: true }); // default: read paths from cli
|
||||
}
|
||||
|
||||
private afterWindowOpen(accessor: ServicesAccessor): void {
|
||||
const windowsMainService = accessor.get(IWindowsMainService);
|
||||
|
||||
let windowsMutex: Mutex = null;
|
||||
let windowsMutex: Mutex | null = null;
|
||||
if (platform.isWindows) {
|
||||
|
||||
// Setup Windows mutex
|
||||
try {
|
||||
const Mutex = (require.__$__nodeRequire('windows-mutex') as any).Mutex;
|
||||
windowsMutex = new Mutex(product.win32MutexName);
|
||||
this.toDispose.push({ dispose: () => windowsMutex.release() });
|
||||
this._register(toDisposable(() => windowsMutex.release()));
|
||||
} catch (e) {
|
||||
if (!this.environmentService.isBuilt) {
|
||||
windowsMainService.showMessageBox({
|
||||
@@ -516,23 +730,21 @@ export class CodeApplication {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO@sbatten: Remove when switching back to dynamic menu
|
||||
// {{SQL CARBON EDIT}} - Use static menu for now
|
||||
// Install Menu
|
||||
const instantiationService = accessor.get(IInstantiationService);
|
||||
const configurationService = accessor.get(IConfigurationService);
|
||||
if (platform.isMacintosh || configurationService.getValue<string>('window.titleBarStyle') !== 'custom') {
|
||||
instantiationService.createInstance(CodeMenu);
|
||||
}
|
||||
// {{SQL CARBON EDIT}} - End
|
||||
|
||||
// Jump List
|
||||
this.historyMainService.updateWindowsJumpList();
|
||||
this.historyMainService.onRecentlyOpenedChange(() => this.historyMainService.updateWindowsJumpList());
|
||||
|
||||
// Start shared process after a while
|
||||
TPromise.timeout(3000).then(() => this.sharedProcess.spawn());
|
||||
}
|
||||
|
||||
private dispose(): void {
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
const sharedProcessSpawn = this._register(new RunOnceScheduler(() => getShellEnvironment().then(userEnv => this.sharedProcess.spawn(userEnv)), 3000));
|
||||
sharedProcessSpawn.schedule();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
|
||||
@@ -14,8 +12,8 @@ import { BrowserWindow, app } from 'electron';
|
||||
type LoginEvent = {
|
||||
event: Electron.Event;
|
||||
webContents: Electron.WebContents;
|
||||
req: Electron.LoginRequest;
|
||||
authInfo: Electron.LoginAuthInfo;
|
||||
req: Electron.Request;
|
||||
authInfo: Electron.AuthInfo;
|
||||
cb: (username: string, password: string) => void;
|
||||
};
|
||||
|
||||
@@ -56,10 +54,7 @@ export class ProxyAuthHandler {
|
||||
width: 450,
|
||||
height: 220,
|
||||
show: true,
|
||||
title: 'VS Code',
|
||||
webPreferences: {
|
||||
disableBlinkFeatures: 'Auxclick'
|
||||
}
|
||||
title: 'VS Code'
|
||||
};
|
||||
|
||||
const focusedWindow = this.windowsMainService.getFocusedWindow();
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/platform/update/node/update.config.contribution';
|
||||
@@ -1,304 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { WorkspaceStats, collectWorkspaceStats, collectLaunchConfigs, WorkspaceStatItem } from 'vs/base/node/stats';
|
||||
import { IMainProcessInfo } from 'vs/code/electron-main/launch';
|
||||
import { ProcessItem, listProcesses } from 'vs/base/node/ps';
|
||||
import product from 'vs/platform/node/product';
|
||||
import pkg from 'vs/platform/node/package';
|
||||
import * as os from 'os';
|
||||
import { virtualMachineHint } from 'vs/base/node/id';
|
||||
import { repeat, pad } from 'vs/base/common/strings';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { app } from 'electron';
|
||||
import { basename } from 'path';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
export interface VersionInfo {
|
||||
vscodeVersion: string;
|
||||
os: string;
|
||||
}
|
||||
|
||||
export interface SystemInfo {
|
||||
CPUs?: string;
|
||||
'Memory (System)': string;
|
||||
'Load (avg)'?: string;
|
||||
VM: string;
|
||||
'Screen Reader': string;
|
||||
'Process Argv': string;
|
||||
'GPU Status': Electron.GPUFeatureStatus;
|
||||
}
|
||||
|
||||
export interface ProcessInfo {
|
||||
cpu: number;
|
||||
memory: number;
|
||||
pid: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface PerformanceInfo {
|
||||
processInfo?: string;
|
||||
workspaceInfo?: string;
|
||||
}
|
||||
|
||||
export function getPerformanceInfo(info: IMainProcessInfo): Promise<PerformanceInfo> {
|
||||
return listProcesses(info.mainPID).then(rootProcess => {
|
||||
const workspaceInfoMessages = [];
|
||||
|
||||
// Workspace Stats
|
||||
const workspaceStatPromises = [];
|
||||
if (info.windows.some(window => window.folderURIs && window.folderURIs.length > 0)) {
|
||||
info.windows.forEach(window => {
|
||||
if (window.folderURIs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
workspaceInfoMessages.push(`| Window (${window.title})`);
|
||||
|
||||
window.folderURIs.forEach(uriComponents => {
|
||||
const folderUri = URI.revive(uriComponents);
|
||||
if (folderUri.scheme === 'file') {
|
||||
const folder = folderUri.fsPath;
|
||||
workspaceStatPromises.push(collectWorkspaceStats(folder, ['node_modules', '.git']).then(async stats => {
|
||||
|
||||
let countMessage = `${stats.fileCount} files`;
|
||||
if (stats.maxFilesReached) {
|
||||
countMessage = `more than ${countMessage}`;
|
||||
}
|
||||
workspaceInfoMessages.push(`| Folder (${basename(folder)}): ${countMessage}`);
|
||||
workspaceInfoMessages.push(formatWorkspaceStats(stats));
|
||||
|
||||
const launchConfigs = await collectLaunchConfigs(folder);
|
||||
if (launchConfigs.length > 0) {
|
||||
workspaceInfoMessages.push(formatLaunchConfigs(launchConfigs));
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
workspaceInfoMessages.push(`| Folder (${folderUri.toString()}): RPerformance stats not available.`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.all(workspaceStatPromises).then(() => {
|
||||
return {
|
||||
processInfo: formatProcessList(info, rootProcess),
|
||||
workspaceInfo: workspaceInfoMessages.join('\n')
|
||||
};
|
||||
}).catch(error => {
|
||||
return {
|
||||
processInfo: formatProcessList(info, rootProcess),
|
||||
workspaceInfo: `Unable to calculate workspace stats: ${error}`
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function getSystemInfo(info: IMainProcessInfo): SystemInfo {
|
||||
const MB = 1024 * 1024;
|
||||
const GB = 1024 * MB;
|
||||
|
||||
const systemInfo: SystemInfo = {
|
||||
'Memory (System)': `${(os.totalmem() / GB).toFixed(2)}GB (${(os.freemem() / GB).toFixed(2)}GB free)`,
|
||||
VM: `${Math.round((virtualMachineHint.value() * 100))}%`,
|
||||
'Screen Reader': `${app.isAccessibilitySupportEnabled() ? 'yes' : 'no'}`,
|
||||
'Process Argv': `${info.mainArguments.join(' ')}`,
|
||||
'GPU Status': app.getGPUFeatureStatus()
|
||||
};
|
||||
|
||||
const cpus = os.cpus();
|
||||
if (cpus && cpus.length > 0) {
|
||||
systemInfo.CPUs = `${cpus[0].model} (${cpus.length} x ${cpus[0].speed})`;
|
||||
}
|
||||
|
||||
if (!isWindows) {
|
||||
systemInfo['Load (avg)'] = `${os.loadavg().map(l => Math.round(l)).join(', ')}`;
|
||||
}
|
||||
|
||||
|
||||
return systemInfo;
|
||||
}
|
||||
|
||||
export function printDiagnostics(info: IMainProcessInfo): Promise<any> {
|
||||
return listProcesses(info.mainPID).then(rootProcess => {
|
||||
|
||||
// Environment Info
|
||||
console.log('');
|
||||
console.log(formatEnvironment(info));
|
||||
|
||||
// Process List
|
||||
console.log('');
|
||||
console.log(formatProcessList(info, rootProcess));
|
||||
|
||||
// Workspace Stats
|
||||
const workspaceStatPromises = [];
|
||||
if (info.windows.some(window => window.folderURIs && window.folderURIs.length > 0)) {
|
||||
console.log('');
|
||||
console.log('Workspace Stats: ');
|
||||
info.windows.forEach(window => {
|
||||
if (window.folderURIs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`| Window (${window.title})`);
|
||||
|
||||
window.folderURIs.forEach(uriComponents => {
|
||||
const folderUri = URI.revive(uriComponents);
|
||||
if (folderUri.scheme === 'file') {
|
||||
const folder = folderUri.fsPath;
|
||||
workspaceStatPromises.push(collectWorkspaceStats(folder, ['node_modules', '.git']).then(async stats => {
|
||||
let countMessage = `${stats.fileCount} files`;
|
||||
if (stats.maxFilesReached) {
|
||||
countMessage = `more than ${countMessage}`;
|
||||
}
|
||||
console.log(`| Folder (${basename(folder)}): ${countMessage}`);
|
||||
console.log(formatWorkspaceStats(stats));
|
||||
|
||||
await collectLaunchConfigs(folder).then(launchConfigs => {
|
||||
if (launchConfigs.length > 0) {
|
||||
console.log(formatLaunchConfigs(launchConfigs));
|
||||
}
|
||||
});
|
||||
}).catch(error => {
|
||||
console.log(`| Error: Unable to collect workspace stats for folder ${folder} (${error.toString()})`);
|
||||
}));
|
||||
} else {
|
||||
console.log(`| Folder (${folderUri.toString()}): Workspace stats not available.`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.all(workspaceStatPromises).then(() => {
|
||||
console.log('');
|
||||
console.log('');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function formatWorkspaceStats(workspaceStats: WorkspaceStats): string {
|
||||
const output: string[] = [];
|
||||
const lineLength = 60;
|
||||
let col = 0;
|
||||
|
||||
const appendAndWrap = (name: string, count: number) => {
|
||||
const item = ` ${name}(${count})`;
|
||||
|
||||
if (col + item.length > lineLength) {
|
||||
output.push(line);
|
||||
line = '| ';
|
||||
col = line.length;
|
||||
}
|
||||
else {
|
||||
col += item.length;
|
||||
}
|
||||
line += item;
|
||||
};
|
||||
|
||||
// File Types
|
||||
let line = '| File types:';
|
||||
const maxShown = 10;
|
||||
let max = workspaceStats.fileTypes.length > maxShown ? maxShown : workspaceStats.fileTypes.length;
|
||||
for (let i = 0; i < max; i++) {
|
||||
const item = workspaceStats.fileTypes[i];
|
||||
appendAndWrap(item.name, item.count);
|
||||
}
|
||||
output.push(line);
|
||||
|
||||
// Conf Files
|
||||
if (workspaceStats.configFiles.length >= 0) {
|
||||
line = '| Conf files:';
|
||||
col = 0;
|
||||
workspaceStats.configFiles.forEach((item) => {
|
||||
appendAndWrap(item.name, item.count);
|
||||
});
|
||||
output.push(line);
|
||||
}
|
||||
|
||||
return output.join('\n');
|
||||
}
|
||||
|
||||
function formatLaunchConfigs(configs: WorkspaceStatItem[]): string {
|
||||
const output: string[] = [];
|
||||
let line = '| Launch Configs:';
|
||||
configs.forEach(each => {
|
||||
const item = each.count > 1 ? ` ${each.name}(${each.count})` : ` ${each.name}`;
|
||||
line += item;
|
||||
});
|
||||
output.push(line);
|
||||
return output.join('\n');
|
||||
}
|
||||
|
||||
function expandGPUFeatures(): string {
|
||||
const gpuFeatures = app.getGPUFeatureStatus();
|
||||
const longestFeatureName = Math.max(...Object.keys(gpuFeatures).map(feature => feature.length));
|
||||
// Make columns aligned by adding spaces after feature name
|
||||
return Object.keys(gpuFeatures).map(feature => `${feature}: ${repeat(' ', longestFeatureName - feature.length)} ${gpuFeatures[feature]}`).join('\n ');
|
||||
}
|
||||
|
||||
export function formatEnvironment(info: IMainProcessInfo): string {
|
||||
const MB = 1024 * 1024;
|
||||
const GB = 1024 * MB;
|
||||
|
||||
const output: string[] = [];
|
||||
output.push(`Version: ${pkg.name} ${pkg.version} (${product.commit || 'Commit unknown'}, ${product.date || 'Date unknown'})`);
|
||||
output.push(`OS Version: ${os.type()} ${os.arch()} ${os.release()}`);
|
||||
const cpus = os.cpus();
|
||||
if (cpus && cpus.length > 0) {
|
||||
output.push(`CPUs: ${cpus[0].model} (${cpus.length} x ${cpus[0].speed})`);
|
||||
}
|
||||
output.push(`Memory (System): ${(os.totalmem() / GB).toFixed(2)}GB (${(os.freemem() / GB).toFixed(2)}GB free)`);
|
||||
if (!isWindows) {
|
||||
output.push(`Load (avg): ${os.loadavg().map(l => Math.round(l)).join(', ')}`); // only provided on Linux/macOS
|
||||
}
|
||||
output.push(`VM: ${Math.round((virtualMachineHint.value() * 100))}%`);
|
||||
output.push(`Screen Reader: ${app.isAccessibilitySupportEnabled() ? 'yes' : 'no'}`);
|
||||
output.push(`Process Argv: ${info.mainArguments.join(' ')}`);
|
||||
output.push(`GPU Status: ${expandGPUFeatures()}`);
|
||||
|
||||
return output.join('\n');
|
||||
}
|
||||
|
||||
function formatProcessList(info: IMainProcessInfo, rootProcess: ProcessItem): string {
|
||||
const mapPidToWindowTitle = new Map<number, string>();
|
||||
info.windows.forEach(window => mapPidToWindowTitle.set(window.pid, window.title));
|
||||
|
||||
const output: string[] = [];
|
||||
|
||||
output.push('CPU %\tMem MB\t PID\tProcess');
|
||||
|
||||
if (rootProcess) {
|
||||
formatProcessItem(mapPidToWindowTitle, output, rootProcess, 0);
|
||||
}
|
||||
|
||||
return output.join('\n');
|
||||
}
|
||||
|
||||
function formatProcessItem(mapPidToWindowTitle: Map<number, string>, output: string[], item: ProcessItem, indent: number): void {
|
||||
const isRoot = (indent === 0);
|
||||
|
||||
const MB = 1024 * 1024;
|
||||
|
||||
// Format name with indent
|
||||
let name: string;
|
||||
if (isRoot) {
|
||||
name = `${product.applicationName} main`;
|
||||
} else {
|
||||
name = `${repeat(' ', indent)} ${item.name}`;
|
||||
|
||||
if (item.name === 'window') {
|
||||
name = `${name} (${mapPidToWindowTitle.get(item.pid)})`;
|
||||
}
|
||||
}
|
||||
const memory = process.platform === 'win32' ? item.mem : (os.totalmem() * (item.mem / 100));
|
||||
output.push(`${pad(Number(item.load.toFixed(0)), 5, ' ')}\t${pad(Number((memory / MB).toFixed(0)), 6, ' ')}\t${pad(Number((item.pid).toFixed(0)), 6, ' ')}\t${name}`);
|
||||
|
||||
// Recurse into children if any
|
||||
if (Array.isArray(item.children)) {
|
||||
item.children.forEach(child => formatProcessItem(mapPidToWindowTitle, output, child, indent + 1));
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,9 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as nativeKeymap from 'native-keymap';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IStateService } from 'vs/platform/state/common/state';
|
||||
import { Event, Emitter, once } from 'vs/base/common/event';
|
||||
import { ConfigWatcher } from 'vs/base/node/config';
|
||||
import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { ipcMain as ipc } from 'electron';
|
||||
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export class KeyboardLayoutMonitor {
|
||||
|
||||
@@ -38,109 +29,4 @@ export class KeyboardLayoutMonitor {
|
||||
}
|
||||
return this._emitter.event(callback);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IKeybinding {
|
||||
id: string;
|
||||
label: string;
|
||||
isNative: boolean;
|
||||
}
|
||||
|
||||
export class KeybindingsResolver {
|
||||
|
||||
private static readonly lastKnownKeybindingsMapStorageKey = 'lastKnownKeybindings';
|
||||
|
||||
private commandIds: Set<string>;
|
||||
private keybindings: { [commandId: string]: IKeybinding };
|
||||
private keybindingsWatcher: ConfigWatcher<IUserFriendlyKeybinding[]>;
|
||||
|
||||
private _onKeybindingsChanged = new Emitter<void>();
|
||||
onKeybindingsChanged: Event<void> = this._onKeybindingsChanged.event;
|
||||
|
||||
constructor(
|
||||
@IStateService private stateService: IStateService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IWindowsMainService private windowsMainService: IWindowsMainService,
|
||||
@ILogService private logService: ILogService
|
||||
) {
|
||||
this.commandIds = new Set<string>();
|
||||
this.keybindings = this.stateService.getItem<{ [id: string]: string; }>(KeybindingsResolver.lastKnownKeybindingsMapStorageKey) || Object.create(null);
|
||||
this.keybindingsWatcher = new ConfigWatcher<IUserFriendlyKeybinding[]>(environmentService.appKeybindingsPath, { changeBufferDelay: 100, onError: error => this.logService.error(error) });
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// Listen to resolved keybindings from window
|
||||
ipc.on('vscode:keybindingsResolved', (event, rawKeybindings: string) => {
|
||||
let keybindings: IKeybinding[] = [];
|
||||
try {
|
||||
keybindings = JSON.parse(rawKeybindings);
|
||||
} catch (error) {
|
||||
// Should not happen
|
||||
}
|
||||
|
||||
// Fill hash map of resolved keybindings and check for changes
|
||||
let keybindingsChanged = false;
|
||||
let keybindingsCount = 0;
|
||||
const resolvedKeybindings: { [commandId: string]: IKeybinding } = Object.create(null);
|
||||
keybindings.forEach(keybinding => {
|
||||
keybindingsCount++;
|
||||
|
||||
resolvedKeybindings[keybinding.id] = keybinding;
|
||||
|
||||
if (!this.keybindings[keybinding.id] || keybinding.label !== this.keybindings[keybinding.id].label) {
|
||||
keybindingsChanged = true;
|
||||
}
|
||||
});
|
||||
|
||||
// A keybinding might have been unassigned, so we have to account for that too
|
||||
if (Object.keys(this.keybindings).length !== keybindingsCount) {
|
||||
keybindingsChanged = true;
|
||||
}
|
||||
|
||||
if (keybindingsChanged) {
|
||||
this.keybindings = resolvedKeybindings;
|
||||
this.stateService.setItem(KeybindingsResolver.lastKnownKeybindingsMapStorageKey, this.keybindings); // keep to restore instantly after restart
|
||||
|
||||
this._onKeybindingsChanged.fire();
|
||||
}
|
||||
});
|
||||
|
||||
// Resolve keybindings when any first window is loaded
|
||||
const onceOnWindowReady = once(this.windowsMainService.onWindowReady);
|
||||
onceOnWindowReady(win => this.resolveKeybindings(win));
|
||||
|
||||
// Resolve keybindings again when keybindings.json changes
|
||||
this.keybindingsWatcher.onDidUpdateConfiguration(() => this.resolveKeybindings());
|
||||
|
||||
// Resolve keybindings when window reloads because an installed extension could have an impact
|
||||
this.windowsMainService.onWindowReload(() => this.resolveKeybindings());
|
||||
}
|
||||
|
||||
private resolveKeybindings(win = this.windowsMainService.getLastActiveWindow()): void {
|
||||
if (this.commandIds.size && win) {
|
||||
const commandIds: string[] = [];
|
||||
this.commandIds.forEach(id => commandIds.push(id));
|
||||
win.sendWhenReady('vscode:resolveKeybindings', JSON.stringify(commandIds));
|
||||
}
|
||||
}
|
||||
|
||||
public getKeybinding(commandId: string): IKeybinding {
|
||||
if (!commandId) {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
if (!this.commandIds.has(commandId)) {
|
||||
this.commandIds.add(commandId);
|
||||
}
|
||||
|
||||
return this.keybindings[commandId];
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._onKeybindingsChanged.dispose();
|
||||
this.keybindingsWatcher.dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,307 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IURLService } from 'vs/platform/url/common/url';
|
||||
import { IProcessEnvironment, isMacintosh } from 'vs/base/common/platform';
|
||||
import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { OpenContext, IWindowSettings } from 'vs/platform/windows/common/windows';
|
||||
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
|
||||
import { whenDeleted } from 'vs/base/node/pfs';
|
||||
import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { BrowserWindow } from 'electron';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export const ID = 'launchService';
|
||||
export const ILaunchService = createDecorator<ILaunchService>(ID);
|
||||
|
||||
export interface IStartArguments {
|
||||
args: ParsedArgs;
|
||||
userEnv: IProcessEnvironment;
|
||||
}
|
||||
|
||||
export interface IWindowInfo {
|
||||
pid: number;
|
||||
title: string;
|
||||
folderURIs: UriComponents[];
|
||||
}
|
||||
|
||||
export interface IMainProcessInfo {
|
||||
mainPID: number;
|
||||
mainArguments: string[];
|
||||
windows: IWindowInfo[];
|
||||
}
|
||||
|
||||
function parseOpenUrl(args: ParsedArgs): URI[] {
|
||||
if (args['open-url'] && args._urls && args._urls.length > 0) {
|
||||
// --open-url must contain -- followed by the url(s)
|
||||
// process.argv is used over args._ as args._ are resolved to file paths at this point
|
||||
return args._urls
|
||||
.map(url => {
|
||||
try {
|
||||
return URI.parse(url);
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(uri => !!uri);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
export interface ILaunchService {
|
||||
_serviceBrand: any;
|
||||
start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void>;
|
||||
getMainProcessId(): TPromise<number>;
|
||||
getMainProcessInfo(): TPromise<IMainProcessInfo>;
|
||||
getLogsPath(): TPromise<string>;
|
||||
}
|
||||
|
||||
export interface ILaunchChannel extends IChannel {
|
||||
call(command: 'start', arg: IStartArguments): TPromise<void>;
|
||||
call(command: 'get-main-process-id', arg: null): TPromise<any>;
|
||||
call(command: 'get-main-process-info', arg: null): TPromise<any>;
|
||||
call(command: 'get-logs-path', arg: null): TPromise<string>;
|
||||
call(command: string, arg: any): TPromise<any>;
|
||||
}
|
||||
|
||||
export class LaunchChannel implements ILaunchChannel {
|
||||
|
||||
constructor(private service: ILaunchService) { }
|
||||
|
||||
listen<T>(event: string): Event<T> {
|
||||
throw new Error('No event found');
|
||||
}
|
||||
|
||||
call(command: string, arg: any): TPromise<any> {
|
||||
switch (command) {
|
||||
case 'start':
|
||||
const { args, userEnv } = arg as IStartArguments;
|
||||
return this.service.start(args, userEnv);
|
||||
|
||||
case 'get-main-process-id':
|
||||
return this.service.getMainProcessId();
|
||||
|
||||
case 'get-main-process-info':
|
||||
return this.service.getMainProcessInfo();
|
||||
|
||||
case 'get-logs-path':
|
||||
return this.service.getLogsPath();
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export class LaunchChannelClient implements ILaunchService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
constructor(private channel: ILaunchChannel) { }
|
||||
|
||||
start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void> {
|
||||
return this.channel.call('start', { args, userEnv });
|
||||
}
|
||||
|
||||
getMainProcessId(): TPromise<number> {
|
||||
return this.channel.call('get-main-process-id', null);
|
||||
}
|
||||
|
||||
getMainProcessInfo(): TPromise<IMainProcessInfo> {
|
||||
return this.channel.call('get-main-process-info', null);
|
||||
}
|
||||
|
||||
getLogsPath(): TPromise<string> {
|
||||
return this.channel.call('get-logs-path', null);
|
||||
}
|
||||
}
|
||||
|
||||
export class LaunchService implements ILaunchService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
constructor(
|
||||
@ILogService private logService: ILogService,
|
||||
@IWindowsMainService private windowsMainService: IWindowsMainService,
|
||||
@IURLService private urlService: IURLService,
|
||||
@IWorkspacesMainService private workspacesMainService: IWorkspacesMainService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
) { }
|
||||
|
||||
start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void> {
|
||||
this.logService.trace('Received data from other instance: ', args, userEnv);
|
||||
|
||||
const urlsToOpen = parseOpenUrl(args);
|
||||
|
||||
// Check early for open-url which is handled in URL service
|
||||
if (urlsToOpen.length) {
|
||||
let whenWindowReady = TPromise.as<any>(null);
|
||||
|
||||
// Create a window if there is none
|
||||
if (this.windowsMainService.getWindowCount() === 0) {
|
||||
const window = this.windowsMainService.openNewWindow(OpenContext.DESKTOP)[0];
|
||||
whenWindowReady = window.ready();
|
||||
}
|
||||
|
||||
// Make sure a window is open, ready to receive the url event
|
||||
whenWindowReady.then(() => {
|
||||
for (const url of urlsToOpen) {
|
||||
this.urlService.open(url);
|
||||
}
|
||||
});
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
// Otherwise handle in windows service
|
||||
return this.startOpenWindow(args, userEnv);
|
||||
}
|
||||
|
||||
private startOpenWindow(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void> {
|
||||
const context = !!userEnv['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP;
|
||||
let usedWindows: ICodeWindow[];
|
||||
|
||||
// Special case extension development
|
||||
if (!!args.extensionDevelopmentPath) {
|
||||
this.windowsMainService.openExtensionDevelopmentHostWindow({ context, cli: args, userEnv });
|
||||
}
|
||||
|
||||
// Start without file/folder arguments
|
||||
else if (args._.length === 0 && (args['folder-uri'] || []).length === 0) {
|
||||
let openNewWindow = false;
|
||||
|
||||
// Force new window
|
||||
if (args['new-window'] || args['unity-launch']) {
|
||||
openNewWindow = true;
|
||||
}
|
||||
|
||||
// Force reuse window
|
||||
else if (args['reuse-window']) {
|
||||
openNewWindow = false;
|
||||
}
|
||||
|
||||
// Otherwise check for settings
|
||||
else {
|
||||
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
|
||||
const openWithoutArgumentsInNewWindowConfig = (windowConfig && windowConfig.openWithoutArgumentsInNewWindow) || 'default' /* default */;
|
||||
switch (openWithoutArgumentsInNewWindowConfig) {
|
||||
case 'on':
|
||||
openNewWindow = true;
|
||||
break;
|
||||
case 'off':
|
||||
openNewWindow = false;
|
||||
break;
|
||||
default:
|
||||
openNewWindow = !isMacintosh; // prefer to restore running instance on macOS
|
||||
}
|
||||
}
|
||||
|
||||
if (openNewWindow) {
|
||||
usedWindows = this.windowsMainService.open({ context, cli: args, userEnv, forceNewWindow: true, forceEmpty: true });
|
||||
} else {
|
||||
usedWindows = [this.windowsMainService.focusLastActive(args, context)];
|
||||
}
|
||||
}
|
||||
|
||||
// Start with file/folder arguments
|
||||
else {
|
||||
usedWindows = this.windowsMainService.open({
|
||||
context,
|
||||
cli: args,
|
||||
userEnv,
|
||||
forceNewWindow: args['new-window'],
|
||||
preferNewWindow: !args['reuse-window'] && !args.wait,
|
||||
forceReuseWindow: args['reuse-window'],
|
||||
diffMode: args.diff,
|
||||
addMode: args.add
|
||||
});
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
// give the first used window a chance to process the other command line arguments
|
||||
if (args['reuse-window'] && usedWindows.length > 0 && usedWindows[0])
|
||||
{
|
||||
let window = usedWindows[0];
|
||||
usedWindows[0].ready().then(() => window.send('ads:processCommandLine', args));
|
||||
}
|
||||
// {{SQL CARBON EDIT}}
|
||||
|
||||
// If the other instance is waiting to be killed, we hook up a window listener if one window
|
||||
// is being used and only then resolve the startup promise which will kill this second instance.
|
||||
// In addition, we poll for the wait marker file to be deleted to return.
|
||||
if (args.wait && usedWindows.length === 1 && usedWindows[0]) {
|
||||
return TPromise.any([
|
||||
this.windowsMainService.waitForWindowCloseOrLoad(usedWindows[0].id),
|
||||
whenDeleted(args.waitMarkerFilePath)
|
||||
]).then(() => void 0, () => void 0);
|
||||
}
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
getMainProcessId(): TPromise<number> {
|
||||
this.logService.trace('Received request for process ID from other instance.');
|
||||
|
||||
return TPromise.as(process.pid);
|
||||
}
|
||||
|
||||
getMainProcessInfo(): TPromise<IMainProcessInfo> {
|
||||
this.logService.trace('Received request for main process info from other instance.');
|
||||
|
||||
const windows: IWindowInfo[] = [];
|
||||
BrowserWindow.getAllWindows().forEach(window => {
|
||||
const codeWindow = this.windowsMainService.getWindowById(window.id);
|
||||
if (codeWindow) {
|
||||
windows.push(this.codeWindowToInfo(codeWindow));
|
||||
} else {
|
||||
windows.push(this.browserWindowToInfo(window));
|
||||
}
|
||||
});
|
||||
|
||||
return TPromise.wrap({
|
||||
mainPID: process.pid,
|
||||
mainArguments: process.argv,
|
||||
windows
|
||||
} as IMainProcessInfo);
|
||||
}
|
||||
|
||||
getLogsPath(): TPromise<string> {
|
||||
this.logService.trace('Received request for logs path from other instance.');
|
||||
|
||||
return TPromise.as(this.environmentService.logsPath);
|
||||
}
|
||||
|
||||
private codeWindowToInfo(window: ICodeWindow): IWindowInfo {
|
||||
const folderURIs: URI[] = [];
|
||||
|
||||
if (window.openedFolderUri) {
|
||||
folderURIs.push(window.openedFolderUri);
|
||||
} else if (window.openedWorkspace) {
|
||||
const rootFolders = this.workspacesMainService.resolveWorkspaceSync(window.openedWorkspace.configPath).folders;
|
||||
rootFolders.forEach(root => {
|
||||
folderURIs.push(root.uri);
|
||||
});
|
||||
}
|
||||
|
||||
return this.browserWindowToInfo(window.win, folderURIs);
|
||||
}
|
||||
|
||||
private browserWindowToInfo(win: BrowserWindow, folderURIs: URI[] = []): IWindowInfo {
|
||||
return {
|
||||
pid: win.webContents.getOSProcessId(),
|
||||
title: win.getTitle(),
|
||||
folderURIs
|
||||
} as IWindowInfo;
|
||||
}
|
||||
}
|
||||
@@ -3,20 +3,18 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as os from 'os';
|
||||
import * as cp from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { ILaunchChannel } from 'vs/code/electron-main/launch';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import product from 'vs/platform/node/product';
|
||||
import { IRequestService } from 'vs/platform/request/node/request';
|
||||
import { IRequestContext } from 'vs/base/node/request';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ILaunchService } from 'vs/platform/launch/electron-main/launchService';
|
||||
|
||||
interface PostResult {
|
||||
readonly blob_id: string;
|
||||
@@ -34,7 +32,7 @@ class Endpoint {
|
||||
}
|
||||
|
||||
export async function uploadLogs(
|
||||
channel: ILaunchChannel,
|
||||
launchService: ILaunchService,
|
||||
requestService: IRequestService,
|
||||
environmentService: IEnvironmentService
|
||||
): Promise<any> {
|
||||
@@ -44,7 +42,7 @@ export async function uploadLogs(
|
||||
return;
|
||||
}
|
||||
|
||||
const logsPath = await channel.call('get-logs-path', null);
|
||||
const logsPath = await launchService.getLogsPath();
|
||||
|
||||
if (await promptUserToConfirmLogUpload(logsPath, environmentService)) {
|
||||
console.log(localize('beginUploading', 'Uploading...'));
|
||||
@@ -86,14 +84,14 @@ async function postLogs(
|
||||
headers: {
|
||||
'Content-Type': 'application/zip'
|
||||
}
|
||||
});
|
||||
}, CancellationToken.None);
|
||||
} catch (e) {
|
||||
clearInterval(dotter);
|
||||
console.log(localize('postError', 'Error posting logs: {0}', e));
|
||||
throw e;
|
||||
}
|
||||
|
||||
return new TPromise<PostResult>((res, reject) => {
|
||||
return new Promise<PostResult>((resolve, reject) => {
|
||||
const parts: Buffer[] = [];
|
||||
result.stream.on('data', data => {
|
||||
parts.push(data);
|
||||
@@ -104,7 +102,7 @@ async function postLogs(
|
||||
try {
|
||||
const response = Buffer.concat(parts).toString('utf-8');
|
||||
if (result.res.statusCode === 200) {
|
||||
res(JSON.parse(response));
|
||||
resolve(JSON.parse(response));
|
||||
} else {
|
||||
const errorMessage = localize('responseError', 'Error posting logs. Got {0} — {1}', result.res.statusCode, response);
|
||||
console.log(errorMessage);
|
||||
@@ -120,13 +118,13 @@ async function postLogs(
|
||||
|
||||
function zipLogs(
|
||||
logsPath: string
|
||||
): TPromise<string> {
|
||||
): Promise<string> {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'vscode-log-upload'));
|
||||
const outZip = path.join(tempDir, 'logs.zip');
|
||||
return new TPromise<string>((resolve, reject) => {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
doZip(logsPath, outZip, tempDir, (err, stdout, stderr) => {
|
||||
if (err) {
|
||||
console.error(localize('zipError', 'Error zipping logs: {0}', err));
|
||||
console.error(localize('zipError', 'Error zipping logs: {0}', err.message));
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(outZip);
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/code/electron-main/contributions';
|
||||
import 'vs/code/code.main';
|
||||
import { app, dialog } from 'electron';
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
@@ -16,10 +14,9 @@ import { mkdirp, readdir, rimraf } from 'vs/base/node/pfs';
|
||||
import { validatePaths } from 'vs/code/node/paths';
|
||||
import { LifecycleService, ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
|
||||
import { Server, serve, connect } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ILaunchChannel, LaunchChannelClient } from 'vs/code/electron-main/launch';
|
||||
import { LaunchChannelClient } from 'vs/platform/launch/electron-main/launchService';
|
||||
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
|
||||
import { InstantiationService } from 'vs/platform/instantiation/node/instantiationService';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { ILogService, ConsoleLogMainService, MultiplexLogService, getLogLevel } from 'vs/platform/log/common/log';
|
||||
@@ -44,29 +41,26 @@ import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces
|
||||
import { localize } from 'vs/nls';
|
||||
import { mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { printDiagnostics } from 'vs/code/electron-main/diagnostics';
|
||||
import { IDiagnosticsService, DiagnosticsService } from 'vs/platform/diagnostics/electron-main/diagnosticsService';
|
||||
import { BufferLogService } from 'vs/platform/log/common/bufferLog';
|
||||
import { uploadLogs } from 'vs/code/electron-main/logUploader';
|
||||
import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { CommandLineDialogService } from 'vs/platform/dialogs/node/dialogService';
|
||||
import { IUriDisplayService, UriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay';
|
||||
import { ILabelService, LabelService } from 'vs/platform/label/common/label';
|
||||
import { createWaitMarkerFile } from 'vs/code/node/wait';
|
||||
|
||||
function createServices(args: ParsedArgs, bufferLogService: BufferLogService): IInstantiationService {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
const environmentService = new EnvironmentService(args, process.execPath);
|
||||
const consoleLogService = new ConsoleLogMainService(getLogLevel(environmentService));
|
||||
const logService = new MultiplexLogService([consoleLogService, bufferLogService]);
|
||||
const uriDisplayService = new UriDisplayService(environmentService, undefined);
|
||||
|
||||
const logService = new MultiplexLogService([new ConsoleLogMainService(getLogLevel(environmentService)), bufferLogService]);
|
||||
process.once('exit', () => logService.dispose());
|
||||
|
||||
// Eventually cleanup
|
||||
setTimeout(() => cleanupOlderLogs(environmentService).then(null, err => console.error(err)), 10000);
|
||||
|
||||
services.set(IEnvironmentService, environmentService);
|
||||
services.set(IUriDisplayService, uriDisplayService);
|
||||
services.set(ILabelService, new LabelService(environmentService, void 0, void 0));
|
||||
services.set(ILogService, logService);
|
||||
services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService));
|
||||
services.set(IHistoryMainService, new SyncDescriptor(HistoryMainService));
|
||||
@@ -77,6 +71,7 @@ function createServices(args: ParsedArgs, bufferLogService: BufferLogService): I
|
||||
services.set(IURLService, new SyncDescriptor(URLService));
|
||||
services.set(IBackupMainService, new SyncDescriptor(BackupMainService));
|
||||
services.set(IDialogService, new SyncDescriptor(CommandLineDialogService));
|
||||
services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService));
|
||||
|
||||
return new InstantiationService(services, true);
|
||||
}
|
||||
@@ -92,31 +87,33 @@ async function cleanupOlderLogs(environmentService: EnvironmentService): Promise
|
||||
const oldSessions = allSessions.sort().filter((d, i) => d !== currentLog);
|
||||
const toDelete = oldSessions.slice(0, Math.max(0, oldSessions.length - 9));
|
||||
|
||||
await TPromise.join(toDelete.map(name => rimraf(path.join(logsRoot, name))));
|
||||
await Promise.all(toDelete.map(name => rimraf(path.join(logsRoot, name))));
|
||||
}
|
||||
|
||||
function createPaths(environmentService: IEnvironmentService): TPromise<any> {
|
||||
function createPaths(environmentService: IEnvironmentService): Thenable<any> {
|
||||
const paths = [
|
||||
environmentService.appSettingsHome,
|
||||
environmentService.extensionsPath,
|
||||
environmentService.nodeCachedDataDir,
|
||||
environmentService.logsPath
|
||||
environmentService.logsPath,
|
||||
environmentService.globalStorageHome,
|
||||
environmentService.workspaceStorageHome
|
||||
];
|
||||
|
||||
return TPromise.join(paths.map(p => p && mkdirp(p))) as TPromise<any>;
|
||||
return Promise.all(paths.map(path => path && mkdirp(path)));
|
||||
}
|
||||
|
||||
class ExpectedError extends Error {
|
||||
public readonly isExpected = true;
|
||||
}
|
||||
|
||||
function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
|
||||
function setupIPC(accessor: ServicesAccessor): Thenable<Server> {
|
||||
const logService = accessor.get(ILogService);
|
||||
const environmentService = accessor.get(IEnvironmentService);
|
||||
const requestService = accessor.get(IRequestService);
|
||||
const diagnosticsService = accessor.get(IDiagnosticsService);
|
||||
|
||||
function allowSetForegroundWindow(service: LaunchChannelClient): TPromise<void> {
|
||||
let promise = TPromise.wrap<void>(void 0);
|
||||
function allowSetForegroundWindow(service: LaunchChannelClient): Thenable<void> {
|
||||
let promise: Thenable<void> = Promise.resolve();
|
||||
if (platform.isWindows) {
|
||||
promise = service.getMainProcessId()
|
||||
.then(processId => {
|
||||
@@ -134,7 +131,7 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
|
||||
return promise;
|
||||
}
|
||||
|
||||
function setup(retry: boolean): TPromise<Server> {
|
||||
function setup(retry: boolean): Thenable<Server> {
|
||||
return serve(environmentService.mainIPCHandle).then(server => {
|
||||
|
||||
// Print --status usage info
|
||||
@@ -161,7 +158,7 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
|
||||
return server;
|
||||
}, err => {
|
||||
if (err.code !== 'EADDRINUSE') {
|
||||
return TPromise.wrapError<Server>(err);
|
||||
return Promise.reject<Server>(err);
|
||||
}
|
||||
|
||||
// Since we are the second instance, we do not want to show the dock
|
||||
@@ -179,13 +176,13 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
|
||||
logService.error(msg);
|
||||
client.dispose();
|
||||
|
||||
return TPromise.wrapError<Server>(new Error(msg));
|
||||
return Promise.reject(new Error(msg));
|
||||
}
|
||||
|
||||
// Show a warning dialog after some timeout if it takes long to talk to the other instance
|
||||
// Skip this if we are running with --wait where it is expected that we wait for a while.
|
||||
// Also skip when gathering diagnostics (--status) which can take a longer time.
|
||||
let startupWarningDialogHandle: number;
|
||||
let startupWarningDialogHandle: any;
|
||||
if (!environmentService.wait && !environmentService.status && !environmentService.args['upload-logs']) {
|
||||
startupWarningDialogHandle = setTimeout(() => {
|
||||
showStartupWarningDialog(
|
||||
@@ -195,20 +192,20 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
const channel = client.getChannel<ILaunchChannel>('launch');
|
||||
const channel = client.getChannel('launch');
|
||||
const service = new LaunchChannelClient(channel);
|
||||
|
||||
// Process Info
|
||||
if (environmentService.args.status) {
|
||||
return service.getMainProcessInfo().then(info => {
|
||||
return printDiagnostics(info).then(() => TPromise.wrapError(new ExpectedError()));
|
||||
return diagnosticsService.printDiagnostics(info).then(() => Promise.reject(new ExpectedError()));
|
||||
});
|
||||
}
|
||||
|
||||
// Log uploader
|
||||
if (typeof environmentService.args['upload-logs'] !== 'undefined') {
|
||||
return uploadLogs(channel, requestService, environmentService)
|
||||
.then(() => TPromise.wrapError(new ExpectedError()));
|
||||
return uploadLogs(service, requestService, environmentService)
|
||||
.then(() => Promise.reject(new ExpectedError()));
|
||||
}
|
||||
|
||||
logService.trace('Sending env to running instance...');
|
||||
@@ -223,7 +220,7 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
|
||||
clearTimeout(startupWarningDialogHandle);
|
||||
}
|
||||
|
||||
return TPromise.wrapError(new ExpectedError('Sent env to running instance. Terminating...'));
|
||||
return Promise.reject(new ExpectedError('Sent env to running instance. Terminating...'));
|
||||
});
|
||||
},
|
||||
err => {
|
||||
@@ -235,7 +232,7 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
|
||||
);
|
||||
}
|
||||
|
||||
return TPromise.wrapError<Server>(err);
|
||||
return Promise.reject<Server>(err);
|
||||
}
|
||||
|
||||
// it happens on Linux and OS X that the pipe is left behind
|
||||
@@ -245,7 +242,7 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
|
||||
fs.unlinkSync(environmentService.mainIPCHandle);
|
||||
} catch (e) {
|
||||
logService.warn('Could not delete obsolete instance handle', e);
|
||||
return TPromise.wrapError<Server>(e);
|
||||
return Promise.reject<Server>(e);
|
||||
}
|
||||
|
||||
return setup(false);
|
||||
@@ -293,12 +290,55 @@ function quit(accessor: ServicesAccessor, reason?: ExpectedError | Error): void
|
||||
lifecycleService.kill(exitCode);
|
||||
}
|
||||
|
||||
function main() {
|
||||
function patchEnvironment(environmentService: IEnvironmentService): typeof process.env {
|
||||
const instanceEnvironment: typeof process.env = {
|
||||
VSCODE_IPC_HOOK: environmentService.mainIPCHandle,
|
||||
VSCODE_NLS_CONFIG: process.env['VSCODE_NLS_CONFIG'],
|
||||
VSCODE_LOGS: process.env['VSCODE_LOGS']
|
||||
};
|
||||
|
||||
if (process.env['VSCODE_PORTABLE']) {
|
||||
instanceEnvironment['VSCODE_PORTABLE'] = process.env['VSCODE_PORTABLE'];
|
||||
}
|
||||
|
||||
assign(process.env, instanceEnvironment);
|
||||
|
||||
return instanceEnvironment;
|
||||
}
|
||||
|
||||
function startup(args: ParsedArgs): void {
|
||||
|
||||
// We need to buffer the spdlog logs until we are sure
|
||||
// we are the only instance running, otherwise we'll have concurrent
|
||||
// log file access on Windows (https://github.com/Microsoft/vscode/issues/41218)
|
||||
const bufferLogService = new BufferLogService();
|
||||
|
||||
const instantiationService = createServices(args, bufferLogService);
|
||||
instantiationService.invokeFunction(accessor => {
|
||||
const environmentService = accessor.get(IEnvironmentService);
|
||||
|
||||
// Patch `process.env` with the instance's environment
|
||||
const instanceEnvironment = patchEnvironment(environmentService);
|
||||
|
||||
// Startup
|
||||
return instantiationService
|
||||
.invokeFunction(a => createPaths(a.get(IEnvironmentService)))
|
||||
.then(() => instantiationService.invokeFunction(setupIPC))
|
||||
.then(mainIpcServer => {
|
||||
bufferLogService.logger = createSpdLogService('main', bufferLogService.getLevel(), environmentService.logsPath);
|
||||
|
||||
return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup();
|
||||
});
|
||||
}).then(null, err => instantiationService.invokeFunction(quit, err));
|
||||
}
|
||||
|
||||
function main(): void {
|
||||
|
||||
// Set the error handler early enough so that we are not getting the
|
||||
// default electron error dialog popping up
|
||||
setUnexpectedErrorHandler(err => console.error(err));
|
||||
|
||||
// Parse arguments
|
||||
let args: ParsedArgs;
|
||||
try {
|
||||
args = parseMainProcessArgv(process.argv);
|
||||
@@ -307,40 +347,31 @@ function main() {
|
||||
console.error(err.message);
|
||||
app.exit(1);
|
||||
|
||||
return;
|
||||
return void 0;
|
||||
}
|
||||
|
||||
// We need to buffer the spdlog logs until we are sure
|
||||
// we are the only instance running, otherwise we'll have concurrent
|
||||
// log file access on Windows
|
||||
// https://github.com/Microsoft/vscode/issues/41218
|
||||
const bufferLogService = new BufferLogService();
|
||||
const instantiationService = createServices(args, bufferLogService);
|
||||
// If we are started with --wait create a random temporary file
|
||||
// and pass it over to the starting instance. We can use this file
|
||||
// to wait for it to be deleted to monitor that the edited file
|
||||
// is closed and then exit the waiting process.
|
||||
//
|
||||
// Note: we are not doing this if the wait marker has been already
|
||||
// added as argument. This can happen if Code was started from CLI.
|
||||
if (args.wait && !args.waitMarkerFilePath) {
|
||||
createWaitMarkerFile(args.verbose).then(waitMarkerFilePath => {
|
||||
if (waitMarkerFilePath) {
|
||||
process.argv.push('--waitMarkerFilePath', waitMarkerFilePath);
|
||||
args.waitMarkerFilePath = waitMarkerFilePath;
|
||||
}
|
||||
|
||||
return instantiationService.invokeFunction(accessor => {
|
||||
startup(args);
|
||||
});
|
||||
}
|
||||
|
||||
// Patch `process.env` with the instance's environment
|
||||
const environmentService = accessor.get(IEnvironmentService);
|
||||
const instanceEnv: typeof process.env = {
|
||||
VSCODE_IPC_HOOK: environmentService.mainIPCHandle,
|
||||
VSCODE_NLS_CONFIG: process.env['VSCODE_NLS_CONFIG'],
|
||||
VSCODE_LOGS: process.env['VSCODE_LOGS']
|
||||
};
|
||||
|
||||
if (process.env['VSCODE_PORTABLE']) {
|
||||
instanceEnv['VSCODE_PORTABLE'] = process.env['VSCODE_PORTABLE'];
|
||||
}
|
||||
|
||||
assign(process.env, instanceEnv);
|
||||
|
||||
// Startup
|
||||
return instantiationService.invokeFunction(a => createPaths(a.get(IEnvironmentService)))
|
||||
.then(() => instantiationService.invokeFunction(setupIPC))
|
||||
.then(mainIpcServer => {
|
||||
bufferLogService.logger = createSpdLogService('main', bufferLogService.getLevel(), environmentService.logsPath);
|
||||
return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnv).startup();
|
||||
});
|
||||
}).done(null, err => instantiationService.invokeFunction(quit, err));
|
||||
// Otherwise just startup normally
|
||||
else {
|
||||
startup(args);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
@@ -1,799 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { isMacintosh, language } from 'vs/base/common/platform';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { app, shell, Menu, MenuItem, BrowserWindow } from 'electron';
|
||||
import { OpenContext, IRunActionInWindowRequest } from 'vs/platform/windows/common/windows';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IUpdateService, StateType } from 'vs/platform/update/common/update';
|
||||
import product from 'vs/platform/node/product';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { mnemonicMenuLabel as baseMnemonicLabel, unmnemonicLabel } from 'vs/base/common/labels';
|
||||
import { IWindowsMainService, IWindowsCountChangedEvent } from 'vs/platform/windows/electron-main/windows';
|
||||
import { IHistoryMainService } from 'vs/platform/history/common/history';
|
||||
import { IWorkspaceIdentifier, getWorkspaceLabel, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IMenubarData, IMenubarKeybinding, MenubarMenuItem, isMenubarMenuItemSeparator, isMenubarMenuItemSubmenu, isMenubarMenuItemAction } from 'vs/platform/menubar/common/menubar';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IUriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay';
|
||||
|
||||
const telemetryFrom = 'menu';
|
||||
|
||||
export class Menubar {
|
||||
|
||||
private static readonly MAX_MENU_RECENT_ENTRIES = 10;
|
||||
private isQuitting: boolean;
|
||||
private appMenuInstalled: boolean;
|
||||
private closedLastWindow: boolean;
|
||||
|
||||
private menuUpdater: RunOnceScheduler;
|
||||
|
||||
private nativeTabMenuItems: Electron.MenuItem[];
|
||||
|
||||
private menubarMenus: IMenubarData;
|
||||
|
||||
private keybindings: { [commandId: string]: IMenubarKeybinding };
|
||||
|
||||
constructor(
|
||||
@IUpdateService private updateService: IUpdateService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IWindowsMainService private windowsMainService: IWindowsMainService,
|
||||
@IEnvironmentService private environmentService: IEnvironmentService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService,
|
||||
@IHistoryMainService private historyMainService: IHistoryMainService,
|
||||
@IUriDisplayService private uriDisplayService: IUriDisplayService
|
||||
) {
|
||||
this.menuUpdater = new RunOnceScheduler(() => this.doUpdateMenu(), 0);
|
||||
|
||||
this.keybindings = Object.create(null);
|
||||
|
||||
this.closedLastWindow = false;
|
||||
|
||||
this.install();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// Keep flag when app quits
|
||||
app.on('will-quit', () => {
|
||||
this.isQuitting = true;
|
||||
});
|
||||
|
||||
// // Listen to some events from window service to update menu
|
||||
this.historyMainService.onRecentlyOpenedChange(() => this.scheduleUpdateMenu());
|
||||
this.windowsMainService.onWindowsCountChanged(e => this.onWindowsCountChanged(e));
|
||||
// this.windowsMainService.onActiveWindowChanged(() => this.updateWorkspaceMenuItems());
|
||||
// this.windowsMainService.onWindowReady(() => this.updateWorkspaceMenuItems());
|
||||
// this.windowsMainService.onWindowClose(() => this.updateWorkspaceMenuItems());
|
||||
|
||||
// Listen to extension viewlets
|
||||
// ipc.on('vscode:extensionViewlets', (event: any, rawExtensionViewlets: string) => {
|
||||
// let extensionViewlets: IExtensionViewlet[] = [];
|
||||
// try {
|
||||
// extensionViewlets = JSON.parse(rawExtensionViewlets);
|
||||
// } catch (error) {
|
||||
// // Should not happen
|
||||
// }
|
||||
|
||||
// if (extensionViewlets.length) {
|
||||
// this.extensionViewlets = extensionViewlets;
|
||||
// this.updateMenu();
|
||||
// }
|
||||
// });
|
||||
|
||||
// Update when auto save config changes
|
||||
// this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e));
|
||||
|
||||
// Listen to update service
|
||||
// this.updateService.onStateChange(() => this.updateMenu());
|
||||
}
|
||||
|
||||
private get currentEnableMenuBarMnemonics(): boolean {
|
||||
let enableMenuBarMnemonics = this.configurationService.getValue<boolean>('window.enableMenuBarMnemonics');
|
||||
if (typeof enableMenuBarMnemonics !== 'boolean') {
|
||||
enableMenuBarMnemonics = true;
|
||||
}
|
||||
|
||||
return enableMenuBarMnemonics;
|
||||
}
|
||||
|
||||
private get currentEnableNativeTabs(): boolean {
|
||||
let enableNativeTabs = this.configurationService.getValue<boolean>('window.nativeTabs');
|
||||
if (typeof enableNativeTabs !== 'boolean') {
|
||||
enableNativeTabs = false;
|
||||
}
|
||||
return enableNativeTabs;
|
||||
}
|
||||
|
||||
updateMenu(menus: IMenubarData, windowId: number, additionalKeybindings?: Array<IMenubarKeybinding>) {
|
||||
this.menubarMenus = menus;
|
||||
if (additionalKeybindings) {
|
||||
additionalKeybindings.forEach(keybinding => {
|
||||
this.keybindings[keybinding.id] = keybinding;
|
||||
});
|
||||
}
|
||||
|
||||
this.scheduleUpdateMenu();
|
||||
}
|
||||
|
||||
|
||||
private scheduleUpdateMenu(): void {
|
||||
this.menuUpdater.schedule(); // buffer multiple attempts to update the menu
|
||||
}
|
||||
|
||||
private doUpdateMenu(): void {
|
||||
|
||||
// Due to limitations in Electron, it is not possible to update menu items dynamically. The suggested
|
||||
// workaround from Electron is to set the application menu again.
|
||||
// See also https://github.com/electron/electron/issues/846
|
||||
//
|
||||
// Run delayed to prevent updating menu while it is open
|
||||
if (!this.isQuitting) {
|
||||
setTimeout(() => {
|
||||
if (!this.isQuitting) {
|
||||
this.install();
|
||||
}
|
||||
}, 10 /* delay this because there is an issue with updating a menu when it is open */);
|
||||
}
|
||||
}
|
||||
|
||||
private onWindowsCountChanged(e: IWindowsCountChangedEvent): void {
|
||||
if (!isMacintosh) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update menu if window count goes from N > 0 or 0 > N to update menu item enablement
|
||||
if ((e.oldCount === 0 && e.newCount > 0) || (e.oldCount > 0 && e.newCount === 0)) {
|
||||
this.closedLastWindow = e.newCount === 0;
|
||||
this.scheduleUpdateMenu();
|
||||
}
|
||||
|
||||
// Update specific items that are dependent on window count
|
||||
else if (this.currentEnableNativeTabs) {
|
||||
this.nativeTabMenuItems.forEach(item => {
|
||||
if (item) {
|
||||
item.enabled = e.newCount > 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private install(): void {
|
||||
|
||||
// Menus
|
||||
const menubar = new Menu();
|
||||
|
||||
// Mac: Application
|
||||
let macApplicationMenuItem: Electron.MenuItem;
|
||||
if (isMacintosh) {
|
||||
const applicationMenu = new Menu();
|
||||
macApplicationMenuItem = new MenuItem({ label: product.nameShort, submenu: applicationMenu });
|
||||
this.setMacApplicationMenu(applicationMenu);
|
||||
menubar.append(macApplicationMenuItem);
|
||||
}
|
||||
|
||||
// Mac: Dock
|
||||
if (isMacintosh && !this.appMenuInstalled) {
|
||||
this.appMenuInstalled = true;
|
||||
|
||||
const dockMenu = new Menu();
|
||||
dockMenu.append(new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window")), click: () => this.windowsMainService.openNewWindow(OpenContext.DOCK) }));
|
||||
|
||||
app.dock.setMenu(dockMenu);
|
||||
}
|
||||
|
||||
// File
|
||||
const fileMenu = new Menu();
|
||||
const fileMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mFile', comment: ['&& denotes a mnemonic'] }, "&&File")), submenu: fileMenu });
|
||||
|
||||
if (this.shouldDrawMenu('File')) {
|
||||
if (this.shouldFallback('File')) {
|
||||
this.setFallbackMenuById(fileMenu, 'File');
|
||||
} else {
|
||||
this.setMenuById(fileMenu, 'File');
|
||||
}
|
||||
|
||||
menubar.append(fileMenuItem);
|
||||
}
|
||||
|
||||
|
||||
// Edit
|
||||
const editMenu = new Menu();
|
||||
const editMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mEdit', comment: ['&& denotes a mnemonic'] }, "&&Edit")), submenu: editMenu });
|
||||
|
||||
if (this.shouldDrawMenu('Edit')) {
|
||||
this.setMenuById(editMenu, 'Edit');
|
||||
menubar.append(editMenuItem);
|
||||
}
|
||||
|
||||
// Selection
|
||||
const selectionMenu = new Menu();
|
||||
const selectionMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mSelection', comment: ['&& denotes a mnemonic'] }, "&&Selection")), submenu: selectionMenu });
|
||||
|
||||
if (this.shouldDrawMenu('Selection')) {
|
||||
this.setMenuById(selectionMenu, 'Selection');
|
||||
menubar.append(selectionMenuItem);
|
||||
}
|
||||
|
||||
// View
|
||||
const viewMenu = new Menu();
|
||||
const viewMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mView', comment: ['&& denotes a mnemonic'] }, "&&View")), submenu: viewMenu });
|
||||
|
||||
if (this.shouldDrawMenu('View')) {
|
||||
this.setMenuById(viewMenu, 'View');
|
||||
menubar.append(viewMenuItem);
|
||||
}
|
||||
|
||||
// Layout
|
||||
const layoutMenu = new Menu();
|
||||
const layoutMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mLayout', comment: ['&& denotes a mnemonic'] }, "&&Layout")), submenu: layoutMenu });
|
||||
|
||||
if (this.shouldDrawMenu('Layout')) {
|
||||
this.setMenuById(layoutMenu, 'Layout');
|
||||
menubar.append(layoutMenuItem);
|
||||
}
|
||||
|
||||
// Go
|
||||
const gotoMenu = new Menu();
|
||||
const gotoMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go")), submenu: gotoMenu });
|
||||
|
||||
if (this.shouldDrawMenu('Go')) {
|
||||
this.setMenuById(gotoMenu, 'Go');
|
||||
menubar.append(gotoMenuItem);
|
||||
}
|
||||
|
||||
// Debug
|
||||
const debugMenu = new Menu();
|
||||
const debugMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug")), submenu: debugMenu });
|
||||
|
||||
if (this.shouldDrawMenu('Debug')) {
|
||||
this.setMenuById(debugMenu, 'Debug');
|
||||
menubar.append(debugMenuItem);
|
||||
}
|
||||
|
||||
// Tasks
|
||||
const taskMenu = new Menu();
|
||||
const taskMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mTask', comment: ['&& denotes a mnemonic'] }, "&&Tasks")), submenu: taskMenu });
|
||||
|
||||
if (this.shouldDrawMenu('Tasks')) {
|
||||
this.setMenuById(taskMenu, 'Tasks');
|
||||
menubar.append(taskMenuItem);
|
||||
}
|
||||
|
||||
// Mac: Window
|
||||
let macWindowMenuItem: Electron.MenuItem;
|
||||
if (this.shouldDrawMenu('Window')) {
|
||||
const windowMenu = new Menu();
|
||||
macWindowMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize('mWindow', "Window")), submenu: windowMenu, role: 'window' });
|
||||
this.setMacWindowMenu(windowMenu);
|
||||
}
|
||||
|
||||
if (macWindowMenuItem) {
|
||||
menubar.append(macWindowMenuItem);
|
||||
}
|
||||
|
||||
// Preferences
|
||||
if (!isMacintosh) {
|
||||
const preferencesMenu = new Menu();
|
||||
const preferencesMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mPreferences', comment: ['&& denotes a mnemonic'] }, "&&Preferences")), submenu: preferencesMenu });
|
||||
|
||||
if (this.shouldDrawMenu('Preferences')) {
|
||||
if (this.shouldFallback('Preferences')) {
|
||||
this.setFallbackMenuById(preferencesMenu, 'Preferences');
|
||||
} else {
|
||||
this.setMenuById(preferencesMenu, 'Preferences');
|
||||
}
|
||||
menubar.append(preferencesMenuItem);
|
||||
}
|
||||
}
|
||||
|
||||
// Help
|
||||
const helpMenu = new Menu();
|
||||
const helpMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mHelp', comment: ['&& denotes a mnemonic'] }, "&&Help")), submenu: helpMenu, role: 'help' });
|
||||
|
||||
if (this.shouldDrawMenu('Help')) {
|
||||
if (this.shouldFallback('Help')) {
|
||||
this.setFallbackMenuById(helpMenu, 'Help');
|
||||
} else {
|
||||
this.setMenuById(helpMenu, 'Help');
|
||||
}
|
||||
menubar.append(helpMenuItem);
|
||||
}
|
||||
|
||||
if (menubar.items && menubar.items.length > 0) {
|
||||
Menu.setApplicationMenu(menubar);
|
||||
} else {
|
||||
Menu.setApplicationMenu(null);
|
||||
}
|
||||
}
|
||||
|
||||
private setMacApplicationMenu(macApplicationMenu: Electron.Menu): void {
|
||||
const about = new MenuItem({ label: nls.localize('mAbout', "About {0}", product.nameLong), role: 'about' });
|
||||
const checkForUpdates = this.getUpdateMenuItems();
|
||||
|
||||
let preferences;
|
||||
if (this.shouldDrawMenu('Preferences')) {
|
||||
const preferencesMenu = new Menu();
|
||||
this.setMenuById(preferencesMenu, 'Preferences');
|
||||
preferences = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miPreferences', comment: ['&& denotes a mnemonic'] }, "&&Preferences")), submenu: preferencesMenu });
|
||||
}
|
||||
|
||||
const servicesMenu = new Menu();
|
||||
const services = new MenuItem({ label: nls.localize('mServices', "Services"), role: 'services', submenu: servicesMenu });
|
||||
const hide = new MenuItem({ label: nls.localize('mHide', "Hide {0}", product.nameLong), role: 'hide', accelerator: 'Command+H' });
|
||||
const hideOthers = new MenuItem({ label: nls.localize('mHideOthers', "Hide Others"), role: 'hideothers', accelerator: 'Command+Alt+H' });
|
||||
const showAll = new MenuItem({ label: nls.localize('mShowAll', "Show All"), role: 'unhide' });
|
||||
const quit = new MenuItem(this.likeAction('workbench.action.quit', {
|
||||
label: nls.localize('miQuit', "Quit {0}", product.nameLong), click: () => {
|
||||
if (this.windowsMainService.getWindowCount() === 0 || !!BrowserWindow.getFocusedWindow()) {
|
||||
this.windowsMainService.quit(); // fix for https://github.com/Microsoft/vscode/issues/39191
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
const actions = [about];
|
||||
actions.push(...checkForUpdates);
|
||||
|
||||
if (preferences) {
|
||||
actions.push(...[
|
||||
__separator__(),
|
||||
preferences
|
||||
]);
|
||||
}
|
||||
|
||||
actions.push(...[
|
||||
__separator__(),
|
||||
services,
|
||||
__separator__(),
|
||||
hide,
|
||||
hideOthers,
|
||||
showAll,
|
||||
__separator__(),
|
||||
quit
|
||||
]);
|
||||
|
||||
actions.forEach(i => macApplicationMenu.append(i));
|
||||
}
|
||||
|
||||
private shouldDrawMenu(menuId: string): boolean {
|
||||
// We need to draw an empty menu to override the electron default
|
||||
if (!isMacintosh && this.configurationService.getValue('window.titleBarStyle') === 'custom') {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (menuId) {
|
||||
case 'File':
|
||||
case 'Help':
|
||||
if (isMacintosh) {
|
||||
return (this.windowsMainService.getWindowCount() === 0 && this.closedLastWindow) || (!!this.menubarMenus && !!this.menubarMenus[menuId]);
|
||||
}
|
||||
|
||||
case 'Window':
|
||||
if (isMacintosh) {
|
||||
return (this.windowsMainService.getWindowCount() === 0 && this.closedLastWindow) || !!this.menubarMenus;
|
||||
}
|
||||
|
||||
default:
|
||||
return this.windowsMainService.getWindowCount() > 0 && (!!this.menubarMenus && !!this.menubarMenus[menuId]);
|
||||
}
|
||||
}
|
||||
|
||||
private shouldFallback(menuId: string): boolean {
|
||||
return this.shouldDrawMenu(menuId) && (this.windowsMainService.getWindowCount() === 0 && this.closedLastWindow && isMacintosh);
|
||||
}
|
||||
|
||||
private setFallbackMenuById(menu: Electron.Menu, menuId: string): void {
|
||||
switch (menuId) {
|
||||
case 'File':
|
||||
const newFile = new MenuItem(this.likeAction('workbench.action.files.newUntitledFile', { label: this.mnemonicLabel(nls.localize({ key: 'miNewFile', comment: ['&& denotes a mnemonic'] }, "&&New File")), click: () => this.windowsMainService.openNewWindow(OpenContext.MENU) }));
|
||||
|
||||
const newWindow = new MenuItem(this.likeAction('workbench.action.newWindow', { label: this.mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window")), click: () => this.windowsMainService.openNewWindow(OpenContext.MENU) }));
|
||||
|
||||
const open = new MenuItem(this.likeAction('workbench.action.files.openFileFolder', { label: this.mnemonicLabel(nls.localize({ key: 'miOpen', comment: ['&& denotes a mnemonic'] }, "&&Open...")), click: (menuItem, win, event) => this.windowsMainService.pickFileFolderAndOpen({ forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }) }));
|
||||
|
||||
const openWorkspace = new MenuItem(this.likeAction('workbench.action.openWorkspace', { label: this.mnemonicLabel(nls.localize({ key: 'miOpenWorkspace', comment: ['&& denotes a mnemonic'] }, "Open Wor&&kspace...")), click: (menuItem, win, event) => this.windowsMainService.pickWorkspaceAndOpen({ forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }) }));
|
||||
|
||||
const openRecentMenu = new Menu();
|
||||
this.setFallbackMenuById(openRecentMenu, 'Recent');
|
||||
const openRecent = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miOpenRecent', comment: ['&& denotes a mnemonic'] }, "Open &&Recent")), submenu: openRecentMenu });
|
||||
|
||||
menu.append(newFile);
|
||||
menu.append(newWindow);
|
||||
menu.append(__separator__());
|
||||
menu.append(open);
|
||||
menu.append(openWorkspace);
|
||||
menu.append(openRecent);
|
||||
|
||||
break;
|
||||
|
||||
case 'Recent':
|
||||
menu.append(this.createMenuItem(nls.localize({ key: 'miReopenClosedEditor', comment: ['&& denotes a mnemonic'] }, "&&Reopen Closed Editor"), 'workbench.action.reopenClosedEditor'));
|
||||
|
||||
this.insertRecentMenuItems(menu);
|
||||
|
||||
menu.append(__separator__());
|
||||
menu.append(this.createMenuItem(nls.localize({ key: 'miMore', comment: ['&& denotes a mnemonic'] }, "&&More..."), 'workbench.action.openRecent'));
|
||||
menu.append(__separator__());
|
||||
menu.append(new MenuItem(this.likeAction('workbench.action.clearRecentFiles', { label: this.mnemonicLabel(nls.localize({ key: 'miClearRecentOpen', comment: ['&& denotes a mnemonic'] }, "&&Clear Recently Opened")), click: () => this.historyMainService.clearRecentlyOpened() })));
|
||||
|
||||
break;
|
||||
|
||||
case 'Help':
|
||||
let twitterItem: MenuItem;
|
||||
if (product.twitterUrl) {
|
||||
twitterItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miTwitter', comment: ['&& denotes a mnemonic'] }, "&&Join us on Twitter")), click: () => this.openUrl(product.twitterUrl, 'openTwitterUrl') });
|
||||
}
|
||||
|
||||
let featureRequestsItem: MenuItem;
|
||||
if (product.requestFeatureUrl) {
|
||||
featureRequestsItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miUserVoice', comment: ['&& denotes a mnemonic'] }, "&&Search Feature Requests")), click: () => this.openUrl(product.requestFeatureUrl, 'openUserVoiceUrl') });
|
||||
}
|
||||
|
||||
let reportIssuesItem: MenuItem;
|
||||
if (product.reportIssueUrl) {
|
||||
const label = nls.localize({ key: 'miReportIssue', comment: ['&& denotes a mnemonic', 'Translate this to "Report Issue in English" in all languages please!'] }, "Report &&Issue");
|
||||
|
||||
reportIssuesItem = new MenuItem({ label: this.mnemonicLabel(label), click: () => this.openUrl(product.reportIssueUrl, 'openReportIssues') });
|
||||
}
|
||||
|
||||
let licenseItem: MenuItem;
|
||||
if (product.privacyStatementUrl) {
|
||||
licenseItem = new MenuItem({
|
||||
label: this.mnemonicLabel(nls.localize({ key: 'miLicense', comment: ['&& denotes a mnemonic'] }, "View &&License")), click: () => {
|
||||
if (language) {
|
||||
const queryArgChar = product.licenseUrl.indexOf('?') > 0 ? '&' : '?';
|
||||
this.openUrl(`${product.licenseUrl}${queryArgChar}lang=${language}`, 'openLicenseUrl');
|
||||
} else {
|
||||
this.openUrl(product.licenseUrl, 'openLicenseUrl');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let privacyStatementItem: MenuItem;
|
||||
if (product.privacyStatementUrl) {
|
||||
privacyStatementItem = new MenuItem({
|
||||
label: this.mnemonicLabel(nls.localize({ key: 'miPrivacyStatement', comment: ['&& denotes a mnemonic'] }, "&&Privacy Statement")), click: () => {
|
||||
if (language) {
|
||||
const queryArgChar = product.licenseUrl.indexOf('?') > 0 ? '&' : '?';
|
||||
this.openUrl(`${product.privacyStatementUrl}${queryArgChar}lang=${language}`, 'openPrivacyStatement');
|
||||
} else {
|
||||
this.openUrl(product.privacyStatementUrl, 'openPrivacyStatement');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (twitterItem) { menu.append(twitterItem); }
|
||||
if (featureRequestsItem) { menu.append(featureRequestsItem); }
|
||||
if (reportIssuesItem) { menu.append(reportIssuesItem); }
|
||||
if ((twitterItem || featureRequestsItem || reportIssuesItem) && (licenseItem || privacyStatementItem)) { menu.append(__separator__()); }
|
||||
if (licenseItem) { menu.append(licenseItem); }
|
||||
if (privacyStatementItem) { menu.append(privacyStatementItem); }
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private setMenu(menu: Electron.Menu, items: Array<MenubarMenuItem>) {
|
||||
items.forEach((item: MenubarMenuItem) => {
|
||||
if (isMenubarMenuItemSeparator(item)) {
|
||||
menu.append(__separator__());
|
||||
} else if (isMenubarMenuItemSubmenu(item)) {
|
||||
const submenu = new Menu();
|
||||
const submenuItem = new MenuItem({ label: this.mnemonicLabel(item.label), submenu: submenu });
|
||||
this.setMenu(submenu, item.submenu.items);
|
||||
menu.append(submenuItem);
|
||||
} else if (isMenubarMenuItemAction(item)) {
|
||||
if (item.id === 'workbench.action.openRecent') {
|
||||
this.insertRecentMenuItems(menu);
|
||||
} else if (item.id === 'workbench.action.showAboutDialog') {
|
||||
this.insertCheckForUpdatesItems(menu);
|
||||
}
|
||||
|
||||
// Store the keybinding
|
||||
if (item.keybinding) {
|
||||
this.keybindings[item.id] = item.keybinding;
|
||||
} else if (this.keybindings[item.id]) {
|
||||
this.keybindings[item.id] = undefined;
|
||||
}
|
||||
|
||||
const menuItem = this.createMenuItem(item.label, item.id, item.enabled, item.checked);
|
||||
menu.append(menuItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private setMenuById(menu: Electron.Menu, menuId: string): void {
|
||||
if (this.menubarMenus && this.menubarMenus[menuId]) {
|
||||
this.setMenu(menu, this.menubarMenus[menuId].items);
|
||||
}
|
||||
}
|
||||
|
||||
private insertCheckForUpdatesItems(menu: Electron.Menu) {
|
||||
const updateItems = this.getUpdateMenuItems();
|
||||
if (updateItems.length) {
|
||||
updateItems.forEach(i => menu.append(i));
|
||||
menu.append(__separator__());
|
||||
}
|
||||
}
|
||||
|
||||
private insertRecentMenuItems(menu: Electron.Menu) {
|
||||
const { workspaces, files } = this.historyMainService.getRecentlyOpened();
|
||||
|
||||
// Workspaces
|
||||
if (workspaces.length > 0) {
|
||||
for (let i = 0; i < Menubar.MAX_MENU_RECENT_ENTRIES && i < workspaces.length; i++) {
|
||||
menu.append(this.createOpenRecentMenuItem(workspaces[i], 'openRecentWorkspace', false));
|
||||
}
|
||||
|
||||
menu.append(__separator__());
|
||||
}
|
||||
|
||||
// Files
|
||||
if (files.length > 0) {
|
||||
for (let i = 0; i < Menubar.MAX_MENU_RECENT_ENTRIES && i < files.length; i++) {
|
||||
menu.append(this.createOpenRecentMenuItem(files[i], 'openRecentFile', true));
|
||||
}
|
||||
|
||||
menu.append(__separator__());
|
||||
}
|
||||
}
|
||||
|
||||
private createOpenRecentMenuItem(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | string, commandId: string, isFile: boolean): Electron.MenuItem {
|
||||
let label: string;
|
||||
let uri: URI;
|
||||
if (isSingleFolderWorkspaceIdentifier(workspace)) {
|
||||
label = unmnemonicLabel(getWorkspaceLabel(workspace, this.environmentService, this.uriDisplayService, { verbose: true }));
|
||||
uri = workspace;
|
||||
} else if (isWorkspaceIdentifier(workspace)) {
|
||||
label = getWorkspaceLabel(workspace, this.environmentService, this.uriDisplayService, { verbose: true });
|
||||
uri = URI.file(workspace.configPath);
|
||||
} else {
|
||||
uri = URI.file(workspace);
|
||||
label = unmnemonicLabel(this.uriDisplayService.getLabel(uri));
|
||||
}
|
||||
|
||||
return new MenuItem(this.likeAction(commandId, {
|
||||
label,
|
||||
click: (menuItem, win, event) => {
|
||||
const openInNewWindow = this.isOptionClick(event);
|
||||
const success = this.windowsMainService.open({
|
||||
context: OpenContext.MENU,
|
||||
cli: this.environmentService.args,
|
||||
urisToOpen: [uri],
|
||||
forceNewWindow: openInNewWindow,
|
||||
forceOpenWorkspaceAsFile: isFile
|
||||
}).length > 0;
|
||||
|
||||
if (!success) {
|
||||
this.historyMainService.removeFromRecentlyOpened([workspace]);
|
||||
}
|
||||
}
|
||||
}, false));
|
||||
}
|
||||
|
||||
private isOptionClick(event: Electron.Event): boolean {
|
||||
return event && ((!isMacintosh && (event.ctrlKey || event.shiftKey)) || (isMacintosh && (event.metaKey || event.altKey)));
|
||||
}
|
||||
|
||||
private setMacWindowMenu(macWindowMenu: Electron.Menu): void {
|
||||
const minimize = new MenuItem({ label: nls.localize('mMinimize', "Minimize"), role: 'minimize', accelerator: 'Command+M', enabled: this.windowsMainService.getWindowCount() > 0 });
|
||||
const zoom = new MenuItem({ label: nls.localize('mZoom', "Zoom"), role: 'zoom', enabled: this.windowsMainService.getWindowCount() > 0 });
|
||||
const bringAllToFront = new MenuItem({ label: nls.localize('mBringToFront', "Bring All to Front"), role: 'front', enabled: this.windowsMainService.getWindowCount() > 0 });
|
||||
const switchWindow = this.createMenuItem(nls.localize({ key: 'miSwitchWindow', comment: ['&& denotes a mnemonic'] }, "Switch &&Window..."), 'workbench.action.switchWindow');
|
||||
|
||||
this.nativeTabMenuItems = [];
|
||||
const nativeTabMenuItems: Electron.MenuItem[] = [];
|
||||
if (this.currentEnableNativeTabs) {
|
||||
const hasMultipleWindows = this.windowsMainService.getWindowCount() > 1;
|
||||
|
||||
this.nativeTabMenuItems.push(this.createMenuItem(nls.localize('mShowPreviousTab', "Show Previous Tab"), 'workbench.action.showPreviousWindowTab', hasMultipleWindows));
|
||||
this.nativeTabMenuItems.push(this.createMenuItem(nls.localize('mShowNextTab', "Show Next Tab"), 'workbench.action.showNextWindowTab', hasMultipleWindows));
|
||||
this.nativeTabMenuItems.push(this.createMenuItem(nls.localize('mMoveTabToNewWindow', "Move Tab to New Window"), 'workbench.action.moveWindowTabToNewWindow', hasMultipleWindows));
|
||||
this.nativeTabMenuItems.push(this.createMenuItem(nls.localize('mMergeAllWindows', "Merge All Windows"), 'workbench.action.mergeAllWindowTabs', hasMultipleWindows));
|
||||
|
||||
nativeTabMenuItems.push(__separator__(), ...this.nativeTabMenuItems);
|
||||
} else {
|
||||
this.nativeTabMenuItems = [];
|
||||
}
|
||||
|
||||
[
|
||||
minimize,
|
||||
zoom,
|
||||
switchWindow,
|
||||
...nativeTabMenuItems,
|
||||
__separator__(),
|
||||
bringAllToFront
|
||||
].forEach(item => macWindowMenu.append(item));
|
||||
}
|
||||
|
||||
private getUpdateMenuItems(): Electron.MenuItem[] {
|
||||
const state = this.updateService.state;
|
||||
|
||||
switch (state.type) {
|
||||
case StateType.Uninitialized:
|
||||
return [];
|
||||
|
||||
case StateType.Idle:
|
||||
return [new MenuItem({
|
||||
label: nls.localize('miCheckForUpdates', "Check for Updates..."), click: () => setTimeout(() => {
|
||||
this.reportMenuActionTelemetry('CheckForUpdate');
|
||||
|
||||
const focusedWindow = this.windowsMainService.getFocusedWindow();
|
||||
const context = focusedWindow ? { windowId: focusedWindow.id } : null;
|
||||
this.updateService.checkForUpdates(context);
|
||||
}, 0)
|
||||
})];
|
||||
|
||||
case StateType.CheckingForUpdates:
|
||||
return [new MenuItem({ label: nls.localize('miCheckingForUpdates', "Checking For Updates..."), enabled: false })];
|
||||
|
||||
case StateType.AvailableForDownload:
|
||||
return [new MenuItem({
|
||||
label: nls.localize('miDownloadUpdate', "Download Available Update"), click: () => {
|
||||
this.updateService.downloadUpdate();
|
||||
}
|
||||
})];
|
||||
|
||||
case StateType.Downloading:
|
||||
return [new MenuItem({ label: nls.localize('miDownloadingUpdate', "Downloading Update..."), enabled: false })];
|
||||
|
||||
case StateType.Downloaded:
|
||||
return [new MenuItem({
|
||||
label: nls.localize('miInstallUpdate', "Install Update..."), click: () => {
|
||||
this.reportMenuActionTelemetry('InstallUpdate');
|
||||
this.updateService.applyUpdate();
|
||||
}
|
||||
})];
|
||||
|
||||
case StateType.Updating:
|
||||
return [new MenuItem({ label: nls.localize('miInstallingUpdate', "Installing Update..."), enabled: false })];
|
||||
|
||||
case StateType.Ready:
|
||||
return [new MenuItem({
|
||||
label: nls.localize('miRestartToUpdate', "Restart to Update..."), click: () => {
|
||||
this.reportMenuActionTelemetry('RestartToUpdate');
|
||||
this.updateService.quitAndInstall();
|
||||
}
|
||||
})];
|
||||
}
|
||||
}
|
||||
|
||||
private createMenuItem(label: string, commandId: string | string[], enabled?: boolean, checked?: boolean): Electron.MenuItem;
|
||||
private createMenuItem(label: string, click: () => void, enabled?: boolean, checked?: boolean): Electron.MenuItem;
|
||||
private createMenuItem(arg1: string, arg2: any, arg3?: boolean, arg4?: boolean): Electron.MenuItem {
|
||||
const label = this.mnemonicLabel(arg1);
|
||||
const click: () => void = (typeof arg2 === 'function') ? arg2 : (menuItem: Electron.MenuItem, win: Electron.BrowserWindow, event: Electron.Event) => {
|
||||
let commandId = arg2;
|
||||
if (Array.isArray(arg2)) {
|
||||
commandId = this.isOptionClick(event) ? arg2[1] : arg2[0]; // support alternative action if we got multiple action Ids and the option key was pressed while invoking
|
||||
}
|
||||
|
||||
this.runActionInRenderer(commandId);
|
||||
};
|
||||
const enabled = typeof arg3 === 'boolean' ? arg3 : this.windowsMainService.getWindowCount() > 0;
|
||||
const checked = typeof arg4 === 'boolean' ? arg4 : false;
|
||||
|
||||
const options: Electron.MenuItemConstructorOptions = {
|
||||
label,
|
||||
click,
|
||||
enabled
|
||||
};
|
||||
|
||||
if (checked) {
|
||||
options['type'] = 'checkbox';
|
||||
options['checked'] = checked;
|
||||
}
|
||||
|
||||
let commandId: string;
|
||||
if (typeof arg2 === 'string') {
|
||||
commandId = arg2;
|
||||
} else if (Array.isArray(arg2)) {
|
||||
commandId = arg2[0];
|
||||
}
|
||||
|
||||
// Add role for special case menu items
|
||||
if (isMacintosh) {
|
||||
if (commandId === 'editor.action.clipboardCutAction') {
|
||||
options['role'] = 'cut';
|
||||
} else if (commandId === 'editor.action.clipboardCopyAction') {
|
||||
options['role'] = 'copy';
|
||||
} else if (commandId === 'editor.action.clipboardPasteAction') {
|
||||
options['role'] = 'paste';
|
||||
}
|
||||
}
|
||||
|
||||
return new MenuItem(this.withKeybinding(commandId, options));
|
||||
}
|
||||
|
||||
private runActionInRenderer(id: string): void {
|
||||
// We make sure to not run actions when the window has no focus, this helps
|
||||
// for https://github.com/Microsoft/vscode/issues/25907 and specifically for
|
||||
// https://github.com/Microsoft/vscode/issues/11928
|
||||
const activeWindow = this.windowsMainService.getFocusedWindow();
|
||||
if (activeWindow) {
|
||||
this.windowsMainService.sendToFocused('vscode:runAction', { id, from: 'menu' } as IRunActionInWindowRequest);
|
||||
}
|
||||
}
|
||||
|
||||
private withKeybinding(commandId: string, options: Electron.MenuItemConstructorOptions): Electron.MenuItemConstructorOptions {
|
||||
const binding = this.keybindings[commandId];
|
||||
|
||||
// Apply binding if there is one
|
||||
if (binding && binding.label) {
|
||||
|
||||
// if the binding is native, we can just apply it
|
||||
if (binding.isNative) {
|
||||
options.accelerator = binding.label;
|
||||
}
|
||||
|
||||
// the keybinding is not native so we cannot show it as part of the accelerator of
|
||||
// the menu item. we fallback to a different strategy so that we always display it
|
||||
else {
|
||||
const bindingIndex = options.label.indexOf('[');
|
||||
if (bindingIndex >= 0) {
|
||||
options.label = `${options.label.substr(0, bindingIndex)} [${binding.label}]`;
|
||||
} else {
|
||||
options.label = `${options.label} [${binding.label}]`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unset bindings if there is none
|
||||
else {
|
||||
options.accelerator = void 0;
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
private likeAction(commandId: string, options: Electron.MenuItemConstructorOptions, setAccelerator = !options.accelerator): Electron.MenuItemConstructorOptions {
|
||||
if (setAccelerator) {
|
||||
options = this.withKeybinding(commandId, options);
|
||||
}
|
||||
|
||||
const originalClick = options.click;
|
||||
options.click = (item, window, event) => {
|
||||
this.reportMenuActionTelemetry(commandId);
|
||||
if (originalClick) {
|
||||
originalClick(item, window, event);
|
||||
}
|
||||
};
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
private openUrl(url: string, id: string): void {
|
||||
shell.openExternal(url);
|
||||
this.reportMenuActionTelemetry(id);
|
||||
}
|
||||
|
||||
private reportMenuActionTelemetry(id: string): void {
|
||||
/* __GDPR__
|
||||
"workbenchActionExecuted" : {
|
||||
"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"from": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('workbenchActionExecuted', { id, from: telemetryFrom });
|
||||
}
|
||||
|
||||
private mnemonicLabel(label: string): string {
|
||||
return baseMnemonicLabel(label, !this.currentEnableMenuBarMnemonics);
|
||||
}
|
||||
}
|
||||
|
||||
function __separator__(): Electron.MenuItem {
|
||||
return new MenuItem({ type: 'separator' });
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,37 +6,40 @@
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IProcessEnvironment } from 'vs/base/common/platform';
|
||||
import { BrowserWindow, ipcMain } from 'electron';
|
||||
import { ISharedProcess } from 'vs/platform/windows/electron-main/windows';
|
||||
import { Barrier } from 'vs/base/common/async';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
|
||||
import { IStateService } from 'vs/platform/state/common/state';
|
||||
import { getBackgroundColor } from 'vs/code/electron-main/theme';
|
||||
import { dispose, toDisposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export class SharedProcess implements ISharedProcess {
|
||||
|
||||
private barrier = new Barrier();
|
||||
|
||||
private window: Electron.BrowserWindow;
|
||||
private window: Electron.BrowserWindow | null;
|
||||
|
||||
constructor(
|
||||
private readonly environmentService: IEnvironmentService,
|
||||
private readonly lifecycleService: ILifecycleService,
|
||||
private readonly logService: ILogService,
|
||||
private readonly machineId: string,
|
||||
private readonly userEnv: IProcessEnvironment,
|
||||
) {
|
||||
}
|
||||
private userEnv: NodeJS.ProcessEnv,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@ILifecycleService private readonly lifecycleService: ILifecycleService,
|
||||
@IStateService private readonly stateService: IStateService,
|
||||
@ILogService private readonly logService: ILogService
|
||||
) { }
|
||||
|
||||
@memoize
|
||||
private get _whenReady(): TPromise<void> {
|
||||
private get _whenReady(): Promise<void> {
|
||||
this.window = new BrowserWindow({
|
||||
show: false,
|
||||
backgroundColor: getBackgroundColor(this.stateService),
|
||||
webPreferences: {
|
||||
images: false,
|
||||
webaudio: false,
|
||||
webgl: false
|
||||
webgl: false,
|
||||
disableBlinkFeatures: 'Auxclick' // do NOT change, allows us to identify this window as shared-process in the process explorer
|
||||
}
|
||||
});
|
||||
const config = assign({
|
||||
@@ -57,26 +60,34 @@ export class SharedProcess implements ISharedProcess {
|
||||
e.preventDefault();
|
||||
|
||||
// Still hide the window though if visible
|
||||
if (this.window.isVisible()) {
|
||||
if (this.window && this.window.isVisible()) {
|
||||
this.window.hide();
|
||||
}
|
||||
};
|
||||
|
||||
this.window.on('close', onClose);
|
||||
|
||||
this.lifecycleService.onShutdown(() => {
|
||||
const disposables: IDisposable[] = [];
|
||||
|
||||
this.lifecycleService.onWillShutdown(() => {
|
||||
dispose(disposables);
|
||||
|
||||
// Shut the shared process down when we are quitting
|
||||
//
|
||||
// Note: because we veto the window close, we must first remove our veto.
|
||||
// Otherwise the application would never quit because the shared process
|
||||
// window is refusing to close!
|
||||
//
|
||||
this.window.removeListener('close', onClose);
|
||||
if (this.window) {
|
||||
this.window.removeListener('close', onClose);
|
||||
}
|
||||
|
||||
// Electron seems to crash on Windows without this setTimeout :|
|
||||
setTimeout(() => {
|
||||
try {
|
||||
this.window.close();
|
||||
if (this.window) {
|
||||
this.window.close();
|
||||
}
|
||||
} catch (err) {
|
||||
// ignore, as electron is already shutting down
|
||||
}
|
||||
@@ -85,7 +96,7 @@ export class SharedProcess implements ISharedProcess {
|
||||
}, 0);
|
||||
});
|
||||
|
||||
return new TPromise<void>((c, e) => {
|
||||
return new Promise<void>(c => {
|
||||
ipcMain.once('handshake:hello', ({ sender }: { sender: any }) => {
|
||||
sender.send('handshake:hey there', {
|
||||
sharedIPCHandle: this.environmentService.sharedIPCHandle,
|
||||
@@ -93,21 +104,24 @@ export class SharedProcess implements ISharedProcess {
|
||||
logLevel: this.logService.getLevel()
|
||||
});
|
||||
|
||||
ipcMain.once('handshake:im ready', () => c(null));
|
||||
disposables.push(toDisposable(() => sender.send('handshake:goodbye')));
|
||||
ipcMain.once('handshake:im ready', () => c(void 0));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
spawn(): void {
|
||||
spawn(userEnv: NodeJS.ProcessEnv): void {
|
||||
this.userEnv = { ...this.userEnv, ...userEnv };
|
||||
this.barrier.open();
|
||||
}
|
||||
|
||||
whenReady(): TPromise<void> {
|
||||
return this.barrier.wait().then(() => this._whenReady);
|
||||
async whenReady(): Promise<void> {
|
||||
await this.barrier.wait();
|
||||
await this._whenReady;
|
||||
}
|
||||
|
||||
toggle(): void {
|
||||
if (this.window.isVisible()) {
|
||||
if (!this.window || this.window.isVisible()) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show();
|
||||
@@ -115,12 +129,16 @@ export class SharedProcess implements ISharedProcess {
|
||||
}
|
||||
|
||||
show(): void {
|
||||
this.window.show();
|
||||
this.window.webContents.openDevTools();
|
||||
if (this.window) {
|
||||
this.window.show();
|
||||
this.window.webContents.openDevTools();
|
||||
}
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this.window.webContents.closeDevTools();
|
||||
this.window.hide();
|
||||
if (this.window) {
|
||||
this.window.webContents.closeDevTools();
|
||||
this.window.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
39
src/vs/code/electron-main/theme.ts
Normal file
39
src/vs/code/electron-main/theme.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { isWindows, isMacintosh } from 'vs/base/common/platform';
|
||||
import { systemPreferences } from 'electron';
|
||||
import { IStateService } from 'vs/platform/state/common/state';
|
||||
|
||||
export const DEFAULT_BG_LIGHT = '#FFFFFF';
|
||||
export const DEFAULT_BG_DARK = '#1E1E1E';
|
||||
export const DEFAULT_BG_HC_BLACK = '#000000';
|
||||
|
||||
export const THEME_STORAGE_KEY = 'theme';
|
||||
export const THEME_BG_STORAGE_KEY = 'themeBackground';
|
||||
|
||||
export function getBackgroundColor(stateService: IStateService): string {
|
||||
if (isWindows && systemPreferences.isInvertedColorScheme()) {
|
||||
return DEFAULT_BG_HC_BLACK;
|
||||
}
|
||||
|
||||
let background = stateService.getItem<string | null>(THEME_BG_STORAGE_KEY, null);
|
||||
if (!background) {
|
||||
let baseTheme: string;
|
||||
if (isWindows && systemPreferences.isInvertedColorScheme()) {
|
||||
baseTheme = 'hc-black';
|
||||
} else {
|
||||
baseTheme = stateService.getItem<string>(THEME_STORAGE_KEY, 'vs-dark').split(' ')[0];
|
||||
}
|
||||
|
||||
background = (baseTheme === 'hc-black') ? DEFAULT_BG_HC_BLACK : (baseTheme === 'vs' ? DEFAULT_BG_LIGHT : DEFAULT_BG_DARK);
|
||||
}
|
||||
|
||||
if (isMacintosh && background.toUpperCase() === DEFAULT_BG_DARK) {
|
||||
background = '#171717'; // https://github.com/electron/electron/issues/5150
|
||||
}
|
||||
|
||||
return background;
|
||||
}
|
||||
@@ -3,29 +3,28 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as path from 'path';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import * as nls from 'vs/nls';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IStateService } from 'vs/platform/state/common/state';
|
||||
import { shell, screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage } from 'electron';
|
||||
import { TPromise, TValueCallback } from 'vs/base/common/winjs.base';
|
||||
import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage } from 'electron';
|
||||
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { parseArgs } from 'vs/platform/environment/node/argv';
|
||||
import product from 'vs/platform/node/product';
|
||||
import { IWindowSettings, MenuBarVisibility, IWindowConfiguration, ReadyState, IRunActionInWindowRequest } from 'vs/platform/windows/common/windows';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IWindowSettings, MenuBarVisibility, IWindowConfiguration, ReadyState, IRunActionInWindowRequest, getTitleBarStyle } from 'vs/platform/windows/common/windows';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
|
||||
import { ICodeWindow, IWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows';
|
||||
import { IWorkspaceIdentifier, IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IBackupMainService } from 'vs/platform/backup/common/backup';
|
||||
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
|
||||
import { mark, exportEntries } from 'vs/base/common/performance';
|
||||
import * as perf from 'vs/base/common/performance';
|
||||
import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/node/extensionGalleryService';
|
||||
import { getBackgroundColor } from 'vs/code/electron-main/theme';
|
||||
import { IStorageMainService } from 'vs/platform/storage/node/storageMainService';
|
||||
|
||||
export interface IWindowCreationOptions {
|
||||
state: IWindowState;
|
||||
@@ -53,18 +52,13 @@ interface ITouchBarSegment extends Electron.SegmentedControlSegment {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export class CodeWindow implements ICodeWindow {
|
||||
|
||||
static readonly themeStorageKey = 'theme';
|
||||
static readonly themeBackgroundStorageKey = 'themeBackground';
|
||||
|
||||
private static readonly DEFAULT_BG_LIGHT = '#FFFFFF';
|
||||
private static readonly DEFAULT_BG_DARK = '#1E1E1E';
|
||||
private static readonly DEFAULT_BG_HC_BLACK = '#000000';
|
||||
export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
|
||||
private static readonly MIN_WIDTH = 200;
|
||||
private static readonly MIN_HEIGHT = 120;
|
||||
|
||||
private static readonly MAX_URL_LENGTH = 2 * 1024 * 1024; // https://cs.chromium.org/chromium/src/url/url_constants.cc?l=32
|
||||
|
||||
private hiddenTitleBarStyle: boolean;
|
||||
private showTimeoutHandle: any;
|
||||
private _id: number;
|
||||
@@ -73,15 +67,14 @@ export class CodeWindow implements ICodeWindow {
|
||||
private _readyState: ReadyState;
|
||||
private windowState: IWindowState;
|
||||
private currentMenuBarVisibility: MenuBarVisibility;
|
||||
private toDispose: IDisposable[];
|
||||
private representedFilename: string;
|
||||
|
||||
private whenReadyCallbacks: TValueCallback<ICodeWindow>[];
|
||||
private whenReadyCallbacks: { (window: ICodeWindow): void }[];
|
||||
|
||||
private currentConfig: IWindowConfiguration;
|
||||
private pendingLoadConfig: IWindowConfiguration;
|
||||
|
||||
private marketplaceHeadersPromise: TPromise<object>;
|
||||
private marketplaceHeadersPromise: Thenable<object>;
|
||||
|
||||
private touchBarGroups: Electron.TouchBarSegmentedControl[];
|
||||
|
||||
@@ -92,13 +85,15 @@ export class CodeWindow implements ICodeWindow {
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IStateService private stateService: IStateService,
|
||||
@IWorkspacesMainService private workspacesMainService: IWorkspacesMainService,
|
||||
@IBackupMainService private backupMainService: IBackupMainService
|
||||
@IBackupMainService private backupMainService: IBackupMainService,
|
||||
@IStorageMainService private storageMainService: IStorageMainService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.touchBarGroups = [];
|
||||
this._lastFocusTime = -1;
|
||||
this._readyState = ReadyState.NONE;
|
||||
this.whenReadyCallbacks = [];
|
||||
this.toDispose = [];
|
||||
|
||||
// create browser window
|
||||
this.createBrowserWindow(config);
|
||||
@@ -124,24 +119,22 @@ export class CodeWindow implements ICodeWindow {
|
||||
// in case we are maximized or fullscreen, only show later after the call to maximize/fullscreen (see below)
|
||||
const isFullscreenOrMaximized = (this.windowState.mode === WindowMode.Maximized || this.windowState.mode === WindowMode.Fullscreen);
|
||||
|
||||
let backgroundColor = this.getBackgroundColor();
|
||||
if (isMacintosh && backgroundColor.toUpperCase() === CodeWindow.DEFAULT_BG_DARK) {
|
||||
backgroundColor = '#171717'; // https://github.com/electron/electron/issues/5150
|
||||
}
|
||||
|
||||
const options: Electron.BrowserWindowConstructorOptions = {
|
||||
width: this.windowState.width,
|
||||
height: this.windowState.height,
|
||||
x: this.windowState.x,
|
||||
y: this.windowState.y,
|
||||
backgroundColor,
|
||||
backgroundColor: getBackgroundColor(this.stateService),
|
||||
minWidth: CodeWindow.MIN_WIDTH,
|
||||
minHeight: CodeWindow.MIN_HEIGHT,
|
||||
show: !isFullscreenOrMaximized,
|
||||
title: product.nameLong,
|
||||
webPreferences: {
|
||||
'backgroundThrottling': false, // by default if Code is in the background, intervals and timeouts get throttled,
|
||||
disableBlinkFeatures: 'Auxclick' // disable auxclick events (see https://developers.google.com/web/updates/2016/10/auxclick)
|
||||
// By default if Code is in the background, intervals and timeouts get throttled, so we
|
||||
// want to enforce that Code stays in the foreground. This triggers a disable_hidden_
|
||||
// flag that Electron provides via patch:
|
||||
// https://github.com/electron/libchromiumcontent/blob/master/patches/common/chromium/disable_hidden.patch
|
||||
'backgroundThrottling': false
|
||||
}
|
||||
};
|
||||
|
||||
@@ -151,6 +144,10 @@ export class CodeWindow implements ICodeWindow {
|
||||
|
||||
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
|
||||
|
||||
if (isMacintosh && !this.useNativeFullScreen()) {
|
||||
options.fullscreenable = false; // enables simple fullscreen mode
|
||||
}
|
||||
|
||||
if (isMacintosh) {
|
||||
options.acceptFirstMouse = true; // enabled by default
|
||||
|
||||
@@ -159,28 +156,14 @@ export class CodeWindow implements ICodeWindow {
|
||||
}
|
||||
}
|
||||
|
||||
let useNativeTabs = false;
|
||||
if (isMacintosh && windowConfig && windowConfig.nativeTabs === true) {
|
||||
options.tabbingIdentifier = product.nameShort; // this opts in to sierra tabs
|
||||
useNativeTabs = true;
|
||||
}
|
||||
|
||||
let useCustomTitleStyle = false;
|
||||
// {{SQL CARBON EDIT}}
|
||||
// if (isMacintosh) {
|
||||
// turn-off custom menus to avoid bug calculating size of SQL editor
|
||||
//
|
||||
// if (isMacintosh && (!windowConfig || !windowConfig.titleBarStyle || windowConfig.titleBarStyle === 'custom')) {
|
||||
// const isDev = !this.environmentService.isBuilt || !!config.extensionDevelopmentPath;
|
||||
// if (!isDev) {
|
||||
// useCustomTitleStyle = true; // not enabled when developing due to https://github.com/electron/electron/issues/3647
|
||||
// }
|
||||
// }
|
||||
|
||||
if (useNativeTabs) {
|
||||
useCustomTitleStyle = false; // native tabs on sierra do not work with custom title style
|
||||
}
|
||||
|
||||
// const useCustomTitleStyle = getTitleBarStyle(this.configurationService, this.environmentService, !!config.extensionDevelopmentPath) === 'custom';
|
||||
const useCustomTitleStyle = false;
|
||||
if (useCustomTitleStyle) {
|
||||
options.titleBarStyle = 'hidden';
|
||||
this.hiddenTitleBarStyle = true;
|
||||
@@ -193,7 +176,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
this._win = new BrowserWindow(options);
|
||||
this._id = this._win.id;
|
||||
|
||||
if (useCustomTitleStyle) {
|
||||
if (isMacintosh && useCustomTitleStyle) {
|
||||
this._win.setSheetOffset(22); // offset dialogs by the height of the custom title bar if we have any
|
||||
}
|
||||
|
||||
@@ -201,7 +184,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
this._win.maximize();
|
||||
|
||||
if (this.windowState.mode === WindowMode.Fullscreen) {
|
||||
this._win.setFullScreen(true);
|
||||
this.setFullScreen(true);
|
||||
}
|
||||
|
||||
if (!this._win.isVisible()) {
|
||||
@@ -284,6 +267,10 @@ export class CodeWindow implements ICodeWindow {
|
||||
return this.currentConfig ? this.currentConfig.folderUri : void 0;
|
||||
}
|
||||
|
||||
get remoteAuthority(): string {
|
||||
return this.currentConfig ? this.currentConfig.remoteAuthority : void 0;
|
||||
}
|
||||
|
||||
setReady(): void {
|
||||
this._readyState = ReadyState.READY;
|
||||
|
||||
@@ -293,19 +280,19 @@ export class CodeWindow implements ICodeWindow {
|
||||
}
|
||||
}
|
||||
|
||||
ready(): TPromise<ICodeWindow> {
|
||||
return new TPromise<ICodeWindow>((c) => {
|
||||
if (this._readyState === ReadyState.READY) {
|
||||
return c(this);
|
||||
ready(): Thenable<ICodeWindow> {
|
||||
return new Promise<ICodeWindow>(resolve => {
|
||||
if (this.isReady) {
|
||||
return resolve(this);
|
||||
}
|
||||
|
||||
// otherwise keep and call later when we are ready
|
||||
this.whenReadyCallbacks.push(c);
|
||||
this.whenReadyCallbacks.push(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
get readyState(): ReadyState {
|
||||
return this._readyState;
|
||||
get isReady(): boolean {
|
||||
return this._readyState === ReadyState.READY;
|
||||
}
|
||||
|
||||
private handleMarketplaceRequests(): void {
|
||||
@@ -316,7 +303,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
// Inject headers when requests are incoming
|
||||
const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*'];
|
||||
this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details: any, cb: any) => {
|
||||
this.marketplaceHeadersPromise.done(headers => {
|
||||
this.marketplaceHeadersPromise.then(headers => {
|
||||
cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) });
|
||||
});
|
||||
});
|
||||
@@ -371,20 +358,26 @@ export class CodeWindow implements ICodeWindow {
|
||||
// App commands support
|
||||
this.registerNavigationListenerOn('app-command', 'browser-backward', 'browser-forward', false);
|
||||
|
||||
// Handle code that wants to open links
|
||||
this._win.webContents.on('new-window', (event: Event, url: string) => {
|
||||
event.preventDefault();
|
||||
|
||||
shell.openExternal(url);
|
||||
});
|
||||
|
||||
// Window Focus
|
||||
this._win.on('focus', () => {
|
||||
this._lastFocusTime = Date.now();
|
||||
});
|
||||
|
||||
// Simple fullscreen doesn't resize automatically when the resolution changes
|
||||
if (isMacintosh) {
|
||||
const displayMetricsChangedListener = () => {
|
||||
if (this.isFullScreen() && !this.useNativeFullScreen()) {
|
||||
this.setFullScreen(false);
|
||||
this.setFullScreen(true);
|
||||
}
|
||||
};
|
||||
|
||||
screen.addListener('display-metrics-changed', displayMetricsChangedListener);
|
||||
this._register(toDisposable(() => screen.removeListener('display-metrics-changed', displayMetricsChangedListener)));
|
||||
}
|
||||
|
||||
// Window (Un)Maximize
|
||||
this._win.on('maximize', (e) => {
|
||||
this._win.on('maximize', e => {
|
||||
if (this.currentConfig) {
|
||||
this.currentConfig.maximized = true;
|
||||
}
|
||||
@@ -392,7 +385,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
app.emit('browser-window-maximize', e, this._win);
|
||||
});
|
||||
|
||||
this._win.on('unmaximize', (e) => {
|
||||
this._win.on('unmaximize', e => {
|
||||
if (this.currentConfig) {
|
||||
this.currentConfig.maximized = false;
|
||||
}
|
||||
@@ -414,21 +407,11 @@ export class CodeWindow implements ICodeWindow {
|
||||
this.logService.warn('[electron event]: fail to load, ', errorDescription);
|
||||
});
|
||||
|
||||
// Prevent any kind of navigation triggered by the user!
|
||||
// But do not touch this in dev version because it will prevent "Reload" from dev tools
|
||||
if (this.environmentService.isBuilt) {
|
||||
this._win.webContents.on('will-navigate', (event: Event) => {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle configuration changes
|
||||
this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated()));
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated()));
|
||||
|
||||
// Handle Workspace events
|
||||
this.toDispose.push(this.workspacesMainService.onUntitledWorkspaceDeleted(e => this.onUntitledWorkspaceDeleted(e)));
|
||||
this._register(this.workspacesMainService.onUntitledWorkspaceDeleted(e => this.onUntitledWorkspaceDeleted(e)));
|
||||
|
||||
// TODO@Ben workaround for https://github.com/Microsoft/vscode/issues/13612
|
||||
// It looks like smooth scrolling disappears as soon as the window is minimized
|
||||
@@ -488,7 +471,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
|
||||
private registerNavigationListenerOn(command: 'swipe' | 'app-command', back: 'left' | 'browser-backward', forward: 'right' | 'browser-forward', acrossEditors: boolean) {
|
||||
this._win.on(command as 'swipe' /* | 'app-command' */, (e: Electron.Event, cmd: string) => {
|
||||
if (this.readyState !== ReadyState.READY) {
|
||||
if (!this.isReady) {
|
||||
return; // window must be ready
|
||||
}
|
||||
|
||||
@@ -500,11 +483,17 @@ export class CodeWindow implements ICodeWindow {
|
||||
});
|
||||
}
|
||||
|
||||
addTabbedWindow(window: ICodeWindow): void {
|
||||
if (isMacintosh) {
|
||||
this._win.addTabbedWindow(window.win);
|
||||
}
|
||||
}
|
||||
|
||||
load(config: IWindowConfiguration, isReload?: boolean, disableExtensions?: boolean): void {
|
||||
|
||||
// If this is the first time the window is loaded, we associate the paths
|
||||
// directly with the window because we assume the loading will just work
|
||||
if (this.readyState === ReadyState.NONE) {
|
||||
if (this._readyState === ReadyState.NONE) {
|
||||
this.currentConfig = config;
|
||||
}
|
||||
|
||||
@@ -541,7 +530,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
}
|
||||
|
||||
// Load URL
|
||||
mark('main:loadWindow');
|
||||
perf.mark('main:loadWindow');
|
||||
this._win.loadURL(this.getUrl(configuration));
|
||||
|
||||
// Make window visible if it did not open in N seconds because this indicates an error
|
||||
@@ -601,7 +590,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
}
|
||||
|
||||
// Set fullscreen state
|
||||
windowConfiguration.fullscreen = this._win.isFullScreen();
|
||||
windowConfiguration.fullscreen = this.isFullScreen();
|
||||
|
||||
// Set Accessibility Config
|
||||
let autoDetectHighContrast = true;
|
||||
@@ -611,54 +600,45 @@ export class CodeWindow implements ICodeWindow {
|
||||
windowConfiguration.highContrast = isWindows && autoDetectHighContrast && systemPreferences.isInvertedColorScheme();
|
||||
windowConfiguration.accessibilitySupport = app.isAccessibilitySupportEnabled();
|
||||
|
||||
// Theme
|
||||
windowConfiguration.baseTheme = this.getBaseTheme();
|
||||
windowConfiguration.backgroundColor = this.getBackgroundColor();
|
||||
|
||||
// Title style related
|
||||
windowConfiguration.maximized = this._win.isMaximized();
|
||||
windowConfiguration.frameless = this.hasHiddenTitleBarStyle() && !isMacintosh;
|
||||
|
||||
// Perf Counters
|
||||
windowConfiguration.perfEntries = exportEntries();
|
||||
windowConfiguration.perfStartTime = (<any>global).perfStartTime;
|
||||
windowConfiguration.perfWindowLoadTime = Date.now();
|
||||
// Dump Perf Counters
|
||||
windowConfiguration.perfEntries = perf.exportEntries();
|
||||
|
||||
// Parts splash
|
||||
windowConfiguration.partsSplashData = this.storageMainService.get('parts-splash-data', void 0);
|
||||
|
||||
// Config (combination of process.argv and window configuration)
|
||||
const environment = parseArgs(process.argv);
|
||||
const config = objects.assign(environment, windowConfiguration);
|
||||
for (let key in config) {
|
||||
if (config[key] === void 0 || config[key] === null || config[key] === '') {
|
||||
if (config[key] === void 0 || config[key] === null || config[key] === '' || config[key] === false) {
|
||||
delete config[key]; // only send over properties that have a true value
|
||||
}
|
||||
}
|
||||
|
||||
return `${require.toUrl('vs/workbench/electron-browser/bootstrap/index.html')}?config=${encodeURIComponent(JSON.stringify(config))}`;
|
||||
// In the unlikely event of the URL becoming larger than 2MB, remove parts of
|
||||
// it that are not under our control. Mainly, the user environment can be very
|
||||
// large depending on user configuration, so we can only remove it in that case.
|
||||
let configUrl = this.doGetUrl(config);
|
||||
if (configUrl.length > CodeWindow.MAX_URL_LENGTH) {
|
||||
delete config.userEnv;
|
||||
this.logService.warn('Application URL exceeds maximum of 2MB and was shortened.');
|
||||
|
||||
configUrl = this.doGetUrl(config);
|
||||
|
||||
if (configUrl.length > CodeWindow.MAX_URL_LENGTH) {
|
||||
this.logService.error('Application URL exceeds maximum of 2MB and cannot be loaded.');
|
||||
}
|
||||
}
|
||||
|
||||
return configUrl;
|
||||
}
|
||||
|
||||
private getBaseTheme(): string {
|
||||
if (isWindows && systemPreferences.isInvertedColorScheme()) {
|
||||
return 'hc-black';
|
||||
}
|
||||
|
||||
const theme = this.stateService.getItem<string>(CodeWindow.themeStorageKey, 'vs-dark');
|
||||
|
||||
return theme.split(' ')[0];
|
||||
}
|
||||
|
||||
private getBackgroundColor(): string {
|
||||
if (isWindows && systemPreferences.isInvertedColorScheme()) {
|
||||
return CodeWindow.DEFAULT_BG_HC_BLACK;
|
||||
}
|
||||
|
||||
const background = this.stateService.getItem<string>(CodeWindow.themeBackgroundStorageKey, null);
|
||||
if (!background) {
|
||||
const baseTheme = this.getBaseTheme();
|
||||
|
||||
return baseTheme === 'hc-black' ? CodeWindow.DEFAULT_BG_HC_BLACK : (baseTheme === 'vs' ? CodeWindow.DEFAULT_BG_LIGHT : CodeWindow.DEFAULT_BG_DARK);
|
||||
}
|
||||
|
||||
return background;
|
||||
private doGetUrl(config: object): string {
|
||||
return `${require.toUrl('vs/code/electron-browser/workbench/workbench.html')}?config=${encodeURIComponent(JSON.stringify(config))}`;
|
||||
}
|
||||
|
||||
serializeWindowState(): IWindowState {
|
||||
@@ -667,19 +647,27 @@ export class CodeWindow implements ICodeWindow {
|
||||
}
|
||||
|
||||
// fullscreen gets special treatment
|
||||
if (this._win.isFullScreen()) {
|
||||
if (this.isFullScreen()) {
|
||||
const display = screen.getDisplayMatching(this.getBounds());
|
||||
|
||||
return {
|
||||
const defaultState = defaultWindowState();
|
||||
|
||||
const res = {
|
||||
mode: WindowMode.Fullscreen,
|
||||
display: display ? display.id : void 0,
|
||||
|
||||
// still carry over window dimensions from previous sessions!
|
||||
width: this.windowState.width,
|
||||
height: this.windowState.height,
|
||||
x: this.windowState.x,
|
||||
y: this.windowState.y
|
||||
// Still carry over window dimensions from previous sessions
|
||||
// if we can compute it in fullscreen state.
|
||||
// does not seem possible in all cases on Linux for example
|
||||
// (https://github.com/Microsoft/vscode/issues/58218) so we
|
||||
// fallback to the defaults in that case.
|
||||
width: this.windowState.width || defaultState.width,
|
||||
height: this.windowState.height || defaultState.height,
|
||||
x: this.windowState.x || 0,
|
||||
y: this.windowState.y || 0
|
||||
};
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
const state: IWindowState = Object.create(null);
|
||||
@@ -825,15 +813,63 @@ export class CodeWindow implements ICodeWindow {
|
||||
}
|
||||
|
||||
toggleFullScreen(): void {
|
||||
const willBeFullScreen = !this._win.isFullScreen();
|
||||
this.setFullScreen(!this.isFullScreen());
|
||||
}
|
||||
|
||||
// set fullscreen flag on window
|
||||
this._win.setFullScreen(willBeFullScreen);
|
||||
private setFullScreen(fullscreen: boolean): void {
|
||||
|
||||
// respect configured menu bar visibility or default to toggle if not set
|
||||
// Set fullscreen state
|
||||
if (this.useNativeFullScreen()) {
|
||||
this.setNativeFullScreen(fullscreen);
|
||||
} else {
|
||||
this.setSimpleFullScreen(fullscreen);
|
||||
}
|
||||
|
||||
// Events
|
||||
this.sendWhenReady(fullscreen ? 'vscode:enterFullScreen' : 'vscode:leaveFullScreen');
|
||||
|
||||
// Respect configured menu bar visibility or default to toggle if not set
|
||||
this.setMenuBarVisibility(this.currentMenuBarVisibility, false);
|
||||
}
|
||||
|
||||
isFullScreen(): boolean {
|
||||
return this._win.isFullScreen() || this._win.isSimpleFullScreen();
|
||||
}
|
||||
|
||||
private setNativeFullScreen(fullscreen: boolean): void {
|
||||
if (this._win.isSimpleFullScreen()) {
|
||||
this._win.setSimpleFullScreen(false);
|
||||
}
|
||||
|
||||
this._win.setFullScreen(fullscreen);
|
||||
}
|
||||
|
||||
private setSimpleFullScreen(fullscreen: boolean): void {
|
||||
if (this._win.isFullScreen()) {
|
||||
this._win.setFullScreen(false);
|
||||
}
|
||||
|
||||
this._win.setSimpleFullScreen(fullscreen);
|
||||
this._win.webContents.focus(); // workaround issue where focus is not going into window
|
||||
}
|
||||
|
||||
private useNativeFullScreen(): boolean {
|
||||
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
|
||||
if (!windowConfig || typeof windowConfig.nativeFullScreen !== 'boolean') {
|
||||
return true; // default
|
||||
}
|
||||
|
||||
if (windowConfig.nativeTabs) {
|
||||
return true; // https://github.com/electron/electron/issues/16142
|
||||
}
|
||||
|
||||
return windowConfig.nativeFullScreen !== false;
|
||||
}
|
||||
|
||||
isMinimized(): boolean {
|
||||
return this._win.isMinimized();
|
||||
}
|
||||
|
||||
private getMenuBarVisibility(): MenuBarVisibility {
|
||||
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
|
||||
if (!windowConfig || !windowConfig.menuBarVisibility) {
|
||||
@@ -874,7 +910,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
}
|
||||
|
||||
private doSetMenuBarVisibility(visibility: MenuBarVisibility): void {
|
||||
const isFullscreen = this._win.isFullScreen();
|
||||
const isFullscreen = this.isFullScreen();
|
||||
|
||||
switch (visibility) {
|
||||
case ('default'):
|
||||
@@ -912,7 +948,11 @@ export class CodeWindow implements ICodeWindow {
|
||||
break;
|
||||
case 'Maximize':
|
||||
default:
|
||||
this.win.maximize();
|
||||
if (this.win.isMaximized()) {
|
||||
this.win.unmaximize();
|
||||
} else {
|
||||
this.win.maximize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -933,9 +973,11 @@ export class CodeWindow implements ICodeWindow {
|
||||
}
|
||||
|
||||
sendWhenReady(channel: string, ...args: any[]): void {
|
||||
this.ready().then(() => {
|
||||
if (this.isReady) {
|
||||
this.send(channel, ...args);
|
||||
});
|
||||
} else {
|
||||
this.ready().then(() => this.send(channel, ...args));
|
||||
}
|
||||
}
|
||||
|
||||
send(channel: string, ...args: any[]): void {
|
||||
@@ -1012,12 +1054,12 @@ export class CodeWindow implements ICodeWindow {
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
if (this.showTimeoutHandle) {
|
||||
clearTimeout(this.showTimeoutHandle);
|
||||
}
|
||||
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
|
||||
this._win = null; // Important to dereference the window object to allow for GC
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user