Revert "Merge from vscode 81d7885dc2e9dc617e1522697a2966bc4025a45d (#5949)" (#5983)

This reverts commit d15a3fcc98.
This commit is contained in:
Karl Burtram
2019-06-11 12:35:58 -07:00
committed by GitHub
parent 95a50b7892
commit 5a7562a37b
926 changed files with 11394 additions and 19540 deletions

View File

@@ -13,12 +13,7 @@
<!-- Startup via workbench.js -->
<script>
self.CONNECTION_AUTH_TOKEN = '{{CONNECTION_AUTH_TOKEN}}';
self.SERVER_APP_ROOT = '{{SERVER_APP_ROOT}}';
self.WINDOW_CONFIGURATION = {
settingsPath: '{{SETTINGS}}',
folderPath: '{{FOLDER}}',
workspacePath: '{{WORKSPACE}}',
}
self.USER_HOME_DIR = '{{USER_HOME_DIR}}';
</script>
<!-- Startup via workbench.js -->

View File

@@ -21,11 +21,7 @@
// @ts-ignore
require.config({
baseUrl: `${window.location.origin}/out`,
paths: {
'vscode-textmate': `${window.location.origin}/node_modules/vscode-textmate/release/main`,
'onigasm-umd': `${window.location.origin}/node_modules/onigasm-umd/release/main`,
}
baseUrl: `${window.location.origin}/out`
});
// @ts-ignore
@@ -36,8 +32,9 @@
],
// @ts-ignore
function () {
// @ts-ignore
require('vs/workbench/browser/web.main').main(self['WINDOW_CONFIGURATION']).then(undefined, console.error);
require('vs/workbench/browser/web.main').main().then(undefined, console.error);
});
});
})();

6
src/vs/code/code.main.ts Normal file
View File

@@ -0,0 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/platform/update/node/update.config.contribution';

View File

@@ -33,6 +33,7 @@ import { EnvironmentService } from 'vs/platform/environment/node/environmentServ
import { IssueReporterModel, IssueReporterData as IssueReporterModelData } from 'vs/code/electron-browser/issue/issueReporterModel';
import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssueReporterData, IssueReporterFeatures, IssueReporterExtensionData } from 'vs/platform/issue/common/issue';
import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage';
import { createBufferSpdLogService } from 'vs/platform/log/node/spdlogService';
import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/node/logIpc';
import { ILogService, getLogLevel } from 'vs/platform/log/common/log';
import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
@@ -40,7 +41,6 @@ import { normalizeGitHubUrl } from 'vs/code/electron-browser/issue/issueReporter
import { Button } from 'vs/base/browser/ui/button/button';
import { withUndefinedAsNull } from 'vs/base/common/types';
import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService';
import { SpdLogService } from 'vs/platform/log/node/spdlogService';
const MAX_URL_LENGTH = 2045;
@@ -300,7 +300,7 @@ export class IssueReporter extends Disposable {
serviceCollection.set(IWindowsService, new WindowsService(mainProcessService));
this.environmentService = new EnvironmentService(configuration, configuration.execPath);
const logService = new SpdLogService(`issuereporter${configuration.windowId}`, this.environmentService.logsPath, getLogLevel(this.environmentService));
const logService = createBufferSpdLogService(`issuereporter${configuration.windowId}`, getLogLevel(this.environmentService), this.environmentService.logsPath);
const logLevelClient = new LogLevelSetterChannelClient(mainProcessService.getChannel('loglevel'));
this.logService = new FollowerLogService(logLevelClient, logService);

View File

@@ -8,7 +8,7 @@ import * as pfs from 'vs/base/node/pfs';
import { IStringDictionary } from 'vs/base/common/collections';
import product from 'vs/platform/product/node/product';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { onUnexpectedError } from 'vs/base/common/errors';
import { ILogService } from 'vs/platform/log/common/log';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -30,13 +30,14 @@ interface LanguagePackFile {
[locale: string]: LanguagePackEntry;
}
export class LanguagePackCachedDataCleaner extends Disposable {
export class LanguagePackCachedDataCleaner {
private _disposables: IDisposable[] = [];
constructor(
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@ILogService private readonly _logService: ILogService
) {
super();
// We have no Language pack support for dev version (run from source)
// So only cleanup when we have a build version.
if (this._environmentService.isBuilt) {
@@ -44,6 +45,10 @@ export class LanguagePackCachedDataCleaner extends Disposable {
}
}
dispose(): void {
this._disposables = dispose(this._disposables);
}
private _manageCachedDataSoon(): void {
let handle: any = setTimeout(async () => {
handle = undefined;
@@ -96,10 +101,12 @@ export class LanguagePackCachedDataCleaner extends Disposable {
}
}, 40 * 1000);
this._register(toDisposable(() => {
if (handle !== undefined) {
clearTimeout(handle);
this._disposables.push({
dispose() {
if (handle !== undefined) {
clearTimeout(handle);
}
}
}));
});
}
}

View File

@@ -30,6 +30,7 @@ import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppen
import { IWindowsService, ActiveWindowManager } from 'vs/platform/windows/common/windows';
import { WindowsService } from 'vs/platform/windows/electron-browser/windowsService';
import { ipcRenderer } from 'electron';
import { createBufferSpdLogService } from 'vs/platform/log/node/spdlogService';
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/node/logIpc';
import { LocalizationsService } from 'vs/platform/localizations/node/localizations';
@@ -47,7 +48,6 @@ import { StorageDataCleaner } from 'vs/code/electron-browser/sharedProcess/contr
import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner';
import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { SpdLogService } from 'vs/platform/log/node/spdlogService';
export interface ISharedProcessConfiguration {
readonly machineId: string;
@@ -79,7 +79,7 @@ class MainProcessService implements IMainProcessService {
}
}
async function main(server: Server, initData: ISharedProcessInitData, configuration: ISharedProcessConfiguration): Promise<void> {
function main(server: Server, initData: ISharedProcessInitData, configuration: ISharedProcessConfiguration): void {
const services = new ServiceCollection();
const disposables: IDisposable[] = [];
@@ -94,17 +94,14 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
const mainRouter = new StaticRouter(ctx => ctx === 'main');
const logLevelClient = new LogLevelSetterChannelClient(server.getChannel('loglevel', mainRouter));
const logService = new FollowerLogService(logLevelClient, new SpdLogService('sharedprocess', environmentService.logsPath, initData.logLevel));
const logService = new FollowerLogService(logLevelClient, createBufferSpdLogService('sharedprocess', initData.logLevel, environmentService.logsPath));
disposables.push(logService);
logService.info('main', JSON.stringify(configuration));
const configurationService = new ConfigurationService(environmentService.settingsResource.path);
disposables.push(configurationService);
await configurationService.initialize();
logService.info('main', JSON.stringify(configuration));
services.set(IEnvironmentService, environmentService);
services.set(ILogService, logService);
services.set(IConfigurationService, configurationService);
services.set(IConfigurationService, new SyncDescriptor(ConfigurationService, [environmentService.appSettingsPath]));
services.set(IRequestService, new SyncDescriptor(RequestService));
services.set(IDownloadService, new SyncDescriptor(DownloadService));
@@ -125,7 +122,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
const services = new ServiceCollection();
const environmentService = accessor.get(IEnvironmentService);
const { appRoot, extensionsPath, extensionDevelopmentLocationURI: extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService;
const telemetryLogService = new FollowerLogService(logLevelClient, new SpdLogService('telemetry', environmentService.logsPath, initData.logLevel));
const telemetryLogService = new FollowerLogService(logLevelClient, createBufferSpdLogService('telemetry', initData.logLevel, environmentService.logsPath));
telemetryLogService.info('The below are logs for every telemetry event sent from VS Code once the log level is set to trace.');
telemetryLogService.info('===========================================================');
@@ -168,12 +165,12 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
// update localizations cache
(localizationsService as LocalizationsService).update();
// cache clean ups
disposables.push(combinedDisposable(
disposables.push(combinedDisposable([
instantiationService2.createInstance(NodeCachedDataCleaner),
instantiationService2.createInstance(LanguagePackCachedDataCleaner),
instantiationService2.createInstance(StorageDataCleaner),
instantiationService2.createInstance(LogsDataCleaner)
));
]));
disposables.push(extensionManagementService as ExtensionManagementService);
});
});
@@ -221,6 +218,6 @@ async function handshake(configuration: ISharedProcessConfiguration): Promise<vo
const server = await setupIPC(data.sharedIPCHandle);
await main(server, data, configuration);
main(server, data, configuration);
ipcRenderer.send('handshake:im ready');
}

View File

@@ -4,7 +4,7 @@
<head>
<meta charset="utf-8" />
<!-- {{SQL CARBON EDIT}} @anthonydresser add 'unsafe-eval' under script src; since its required by angular -->
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data: blob: vscode-remote:; media-src 'none'; child-src 'self'; object-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; connect-src 'self' https:; font-src 'self' https: vscode-remote:;">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data: vscode-remote:; media-src 'none'; child-src 'self'; object-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; connect-src 'self' https:; font-src 'self' https: vscode-remote:;">
</head>
<body class="vs-dark" aria-label="">
</body>

View File

@@ -54,7 +54,13 @@ bootstrapWindow.load([
showPartsSplash(windowConfig);
},
beforeLoaderConfig: function (windowConfig, loaderConfig) {
loaderConfig.recordStats = true;
loaderConfig.recordStats = !!windowConfig['prof-modules'];
if (loaderConfig.nodeCachedData) {
const onNodeCachedData = window['MonacoEnvironment'].onNodeCachedData = [];
loaderConfig.nodeCachedData.onData = function () {
onNodeCachedData.push(arguments);
};
}
},
beforeRequire: function () {
perf.mark('willLoadWorkbenchMain');
@@ -83,7 +89,7 @@ function showPartsSplash(configuration) {
}
}
// high contrast mode has been turned on from the outside, e.g. OS -> ignore stored colors and layouts
// high contrast mode has been turned on from the outside, e.g OS -> ignore stored colors and layouts
if (data && configuration.highContrast && data.baseTheme !== 'hc-black') {
data = undefined;
}

View File

@@ -9,7 +9,7 @@ import { WindowsManager } from 'vs/code/electron-main/windows';
import { IWindowsService, OpenContext, ActiveWindowManager, IURIToOpen } from 'vs/platform/windows/common/windows';
import { WindowsChannel } from 'vs/platform/windows/node/windowsIpc';
import { WindowsService } from 'vs/platform/windows/electron-main/windowsService';
import { ILifecycleService, LifecycleMainPhase } 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/node/updateIpc';
@@ -36,10 +36,12 @@ import { getDelayedChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc';
import product from 'vs/platform/product/node/product';
import pkg from 'vs/platform/product/node/package';
import { ProxyAuthHandler } from 'vs/code/electron-main/auth';
import { Disposable } from 'vs/base/common/lifecycle';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
import { IHistoryMainService } from 'vs/platform/history/common/history';
import { withUndefinedAsNull } from 'vs/base/common/types';
import { isUndefinedOrNull, withUndefinedAsNull } from 'vs/base/common/types';
import { KeyboardLayoutMonitor } from 'vs/code/electron-main/keyboard';
import { URI } from 'vs/base/common/uri';
import { WorkspacesChannel } from 'vs/platform/workspaces/node/workspacesIpc';
import { IWorkspacesMainService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
@@ -61,6 +63,7 @@ import { MenubarChannel } from 'vs/platform/menubar/node/menubarIpc';
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 { storeBackgroundColor } from 'vs/code/electron-main/theme';
import { homedir } from 'os';
import { join, sep } from 'vs/base/common/path';
import { localize } from 'vs/nls';
@@ -85,7 +88,12 @@ export class CodeApplication extends Disposable {
private static readonly MACHINE_ID_KEY = 'telemetry.machineId';
private windowsMainService: IWindowsMainService | undefined;
private windowsMainService: IWindowsMainService;
private electronIpcServer: ElectronIPCServer;
private sharedProcess: SharedProcess;
private sharedProcessClient: Promise<Client>;
constructor(
private readonly mainIpcServer: Server,
@@ -94,11 +102,14 @@ export class CodeApplication extends Disposable {
@ILogService private readonly logService: ILogService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@ILifecycleService private readonly lifecycleService: ILifecycleService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IConfigurationService private readonly configurationService: ConfigurationService,
@IStateService private readonly stateService: IStateService
) {
super();
this._register(mainIpcServer);
this._register(configurationService);
this.registerListeners();
}
@@ -109,12 +120,12 @@ export class CodeApplication extends Disposable {
process.on('uncaughtException', err => this.onUnexpectedError(err));
process.on('unhandledRejection', (reason: unknown) => onUnexpectedError(reason));
// Dispose on shutdown
this.lifecycleService.onWillShutdown(() => this.dispose());
// Contextmenu via IPC support
registerContextMenuListener();
// Dispose on shutdown
this.lifecycleService.onWillShutdown(() => this.dispose());
app.on('accessibility-support-changed', (event: Event, accessibilitySupportEnabled: boolean) => {
if (this.windowsMainService) {
this.windowsMainService.sendToAll('vscode:accessibilitySupportChanged', accessibilitySupportEnabled);
@@ -187,7 +198,7 @@ export class CodeApplication extends Disposable {
event.preventDefault();
// Keep in array because more might come!
macOpenFileURIs.push(this.getURIToOpenFromPathSync(path));
macOpenFileURIs.push(getURIToOpenFromPathSync(path));
// Clear previous handler if any
if (runningTimeout !== null) {
@@ -204,7 +215,6 @@ export class CodeApplication extends Disposable {
urisToOpen: macOpenFileURIs,
preferNewWindow: true /* dropping on the dock or opening from finder prefers to open in a new window */
});
macOpenFileURIs = [];
runningTimeout = null;
}
@@ -212,9 +222,7 @@ export class CodeApplication extends Disposable {
});
app.on('new-window-for-tab', () => {
if (this.windowsMainService) {
this.windowsMainService.openNewWindow(OpenContext.DESKTOP); //macOS native tab "+" button
}
this.windowsMainService.openNewWindow(OpenContext.DESKTOP); //macOS native tab "+" button
});
ipc.on('vscode:exit', (event: Event, code: number) => {
@@ -224,26 +232,37 @@ export class CodeApplication extends Disposable {
this.lifecycleService.kill(code);
});
ipc.on('vscode:fetchShellEnv', async (event: Event) => {
ipc.on('vscode:fetchShellEnv', (event: Event) => {
const webContents = event.sender;
try {
const shellEnv = await getShellEnvironment(this.logService, this.environmentService);
getShellEnvironment(this.logService).then(shellEnv => {
if (!webContents.isDestroyed()) {
webContents.send('vscode:acceptShellEnv', shellEnv);
}
} catch (error) {
}, err => {
if (!webContents.isDestroyed()) {
webContents.send('vscode:acceptShellEnv', {});
}
this.logService.error('Error fetching shell env', error);
this.logService.error('Error fetching shell env', err);
});
});
ipc.on('vscode:broadcast', (event: Event, windowId: number, broadcast: { channel: string; payload: object; }) => {
if (this.windowsMainService && broadcast.channel && !isUndefinedOrNull(broadcast.payload)) {
this.logService.trace('IPC#vscode:broadcast', broadcast.channel, broadcast.payload);
// Handle specific events on main side
this.onBroadcast(broadcast.channel, broadcast.payload);
// Send to all windows (except sender window)
this.windowsMainService.sendToAll('vscode:broadcast', broadcast, [windowId]);
}
});
ipc.on('vscode:extensionHostDebug', (_: Event, windowId: number, broadcast: any) => {
if (this.windowsMainService) {
this.windowsMainService.sendToAll('vscode:extensionHostDebug', broadcast, [windowId]); // Send to all windows (except sender window)
// Send to all windows (except sender window)
this.windowsMainService.sendToAll('vscode:extensionHostDebug', broadcast, [windowId]);
}
});
@@ -252,28 +271,11 @@ export class CodeApplication extends Disposable {
ipc.on('vscode:reloadWindow', (event: Event) => event.sender.reload());
// After waking up from sleep (after window opened)
(async () => {
await this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen);
powerMonitor.on('resume', () => {
if (this.windowsMainService) {
this.windowsMainService.sendToAll('vscode:osResume', undefined);
}
});
})();
// Keyboard layout changes (after window opened)
(async () => {
await this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen);
const nativeKeymap = await import('native-keymap');
nativeKeymap.onDidChangeKeyboardLayout(() => {
if (this.windowsMainService) {
this.windowsMainService.sendToAll('vscode:keyboardLayoutChanged', false);
}
});
})();
powerMonitor.on('resume', () => { // After waking up from sleep
if (this.windowsMainService) {
this.windowsMainService.sendToAll('vscode:osResume', undefined);
}
});
}
private onUnexpectedError(err: Error): void {
@@ -297,7 +299,15 @@ export class CodeApplication extends Disposable {
}
}
async startup(): Promise<void> {
private onBroadcast(event: string, payload: object): void {
// Theme changes
if (event === 'vscode:changeColorTheme' && typeof payload === 'string') {
storeBackgroundColor(this.stateService, JSON.parse(payload));
}
}
startup(): Promise<void> {
this.logService.debug('Starting VS Code');
this.logService.debug(`from: ${this.environmentService.appRoot}`);
this.logService.debug('args:', this.environmentService.args);
@@ -325,122 +335,64 @@ export class CodeApplication extends Disposable {
}
// Create Electron IPC Server
const electronIpcServer = new ElectronIPCServer();
this.electronIpcServer = new ElectronIPCServer();
const startupWithMachineId = (machineId: string) => {
this.logService.trace(`Resolved machine identifier: ${machineId}`);
// Spawn shared process
this.sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv);
this.sharedProcessClient = this.sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main'));
// Services
return this.initServices(machineId).then(appInstantiationService => {
// 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._register(authHandler);
// Open Windows
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);
}
});
};
// Resolve unique machine ID
this.logService.trace('Resolving machine identifier...');
const machineId = await this.resolveMachineId();
this.logService.trace(`Resolved machine identifier: ${machineId}`);
// Spawn shared process after the first window has opened and 3s have passed
const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv);
const sharedProcessClient = sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main'));
this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen).then(() => {
this._register(new RunOnceScheduler(async () => {
const userEnv = await getShellEnvironment(this.logService, this.environmentService);
sharedProcess.spawn(userEnv);
}, 3000)).schedule();
});
// Services
const appInstantiationService = await this.createServices(machineId, sharedProcess, sharedProcessClient);
// Create driver
if (this.environmentService.driverHandle) {
(async () => {
const server = await serveDriver(electronIpcServer, this.environmentService.driverHandle!, this.environmentService, appInstantiationService);
this.logService.info('Driver started at:', this.environmentService.driverHandle);
this._register(server);
})();
}
// Setup Auth Handler
const authHandler = appInstantiationService.createInstance(ProxyAuthHandler);
this._register(authHandler);
// Open Windows
const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient));
// Post Open Windows Tasks
this.afterWindowOpen();
// Tracing: Stop tracing after windows are ready if enabled
if (this.environmentService.args.trace) {
this.stopTracingEventually(windows);
}
}
private async resolveMachineId(): Promise<string> {
// We cache the machineId for faster lookups on startup
// and resolve it only once initially if not cached
let machineId = this.stateService.getItem<string>(CodeApplication.MACHINE_ID_KEY);
if (!machineId) {
machineId = await getMachineId();
this.stateService.setItem(CodeApplication.MACHINE_ID_KEY, machineId);
}
return machineId;
}
private async createServices(machineId: string, sharedProcess: SharedProcess, sharedProcessClient: Promise<Client<string>>): Promise<IInstantiationService> {
const services = new ServiceCollection();
switch (process.platform) {
case 'win32':
services.set(IUpdateService, new SyncDescriptor(Win32UpdateService));
break;
case 'linux':
if (process.env.SNAP && process.env.SNAP_REVISION) {
services.set(IUpdateService, new SyncDescriptor(SnapUpdateService, [process.env.SNAP, process.env.SNAP_REVISION]));
} else {
services.set(IUpdateService, new SyncDescriptor(LinuxUpdateService));
}
break;
case 'darwin':
services.set(IUpdateService, new SyncDescriptor(DarwinUpdateService));
break;
}
services.set(IWindowsMainService, new SyncDescriptor(WindowsManager, [machineId, this.userEnv]));
services.set(IWindowsService, new SyncDescriptor(WindowsService, [sharedProcess]));
services.set(ILaunchService, new SyncDescriptor(LaunchService));
services.set(IIssueService, new SyncDescriptor(IssueService, [machineId, this.userEnv]));
services.set(IMenubarService, new SyncDescriptor(MenubarService));
const storageMainService = new StorageMainService(this.logService, this.environmentService);
services.set(IStorageMainService, storageMainService);
this.lifecycleService.onWillShutdown(e => e.join(storageMainService.close()));
const backupMainService = new BackupMainService(this.environmentService, this.configurationService, this.logService);
services.set(IBackupMainService, backupMainService);
services.set(IHistoryMainService, new SyncDescriptor(HistoryMainService));
services.set(IURLService, new SyncDescriptor(URLService));
services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService));
// Telemetry
if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
const channel = getDelayedChannel(sharedProcessClient.then(client => client.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]));
const resolvedMachineId = this.resolveMachineId();
if (typeof resolvedMachineId === 'string') {
return startupWithMachineId(resolvedMachineId);
} else {
services.set(ITelemetryService, NullTelemetryService);
return resolvedMachineId.then(machineId => startupWithMachineId(machineId));
}
}
private resolveMachineId(): string | Promise<string> {
const machineId = this.stateService.getItem<string>(CodeApplication.MACHINE_ID_KEY);
if (machineId) {
return machineId;
}
// Init services that require it
await backupMainService.initialize();
return getMachineId().then(machineId => {
this.stateService.setItem(CodeApplication.MACHINE_ID_KEY, machineId);
return this.instantiationService.createChild(services);
return machineId;
});
}
private stopTracingEventually(windows: ICodeWindow[]): void {
@@ -456,14 +408,12 @@ export class CodeApplication extends Disposable {
contentTracing.stopRecording(join(homedir(), `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`), path => {
if (!timeout) {
if (this.windowsMainService) {
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());
}
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}`);
}
@@ -480,7 +430,72 @@ export class CodeApplication extends Disposable {
});
}
private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise<Client<string>>): ICodeWindow[] {
private initServices(machineId: string): Promise<IInstantiationService> {
const services = new ServiceCollection();
if (process.platform === 'win32') {
services.set(IUpdateService, new SyncDescriptor(Win32UpdateService));
} else if (process.platform === 'linux') {
if (process.env.SNAP && process.env.SNAP_REVISION) {
services.set(IUpdateService, new SyncDescriptor(SnapUpdateService, [process.env.SNAP, process.env.SNAP_REVISION]));
} 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(ILaunchService, new SyncDescriptor(LaunchService));
services.set(IIssueService, new SyncDescriptor(IssueService, [machineId, this.userEnv]));
services.set(IMenubarService, new SyncDescriptor(MenubarService));
services.set(IStorageMainService, new SyncDescriptor(StorageMainService));
services.set(IBackupMainService, new SyncDescriptor(BackupMainService));
services.set(IHistoryMainService, new SyncDescriptor(HistoryMainService));
services.set(IURLService, new SyncDescriptor(URLService));
services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService));
// Telemetry
if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
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]));
} else {
services.set(ITelemetryService, NullTelemetryService);
}
const appInstantiationService = this.instantiationService.createChild(services);
// Init services that require it
return appInstantiationService.invokeFunction(accessor => Promise.all([
this.initStorageService(accessor),
this.initBackupService(accessor)
])).then(() => appInstantiationService);
}
private initStorageService(accessor: ServicesAccessor): Promise<void> {
const storageMainService = accessor.get(IStorageMainService) as StorageMainService;
// Ensure to close storage on shutdown
this.lifecycleService.onWillShutdown(e => e.join(storageMainService.close()));
return Promise.resolve();
}
private initBackupService(accessor: ServicesAccessor): Promise<void> {
const backupMainService = accessor.get(IBackupMainService) as BackupMainService;
return backupMainService.initialize();
}
private openFirstWindow(accessor: ServicesAccessor): ICodeWindow[] {
const appInstantiationService = accessor.get(IInstantiationService);
// Register more Main IPC services
const launchService = accessor.get(ILaunchService);
@@ -490,40 +505,40 @@ export class CodeApplication extends Disposable {
// Register more Electron IPC services
const updateService = accessor.get(IUpdateService);
const updateChannel = new UpdateChannel(updateService);
electronIpcServer.registerChannel('update', updateChannel);
this.electronIpcServer.registerChannel('update', updateChannel);
const issueService = accessor.get(IIssueService);
const issueChannel = new IssueChannel(issueService);
electronIpcServer.registerChannel('issue', issueChannel);
this.electronIpcServer.registerChannel('issue', issueChannel);
const workspacesService = accessor.get(IWorkspacesMainService);
const workspacesChannel = new WorkspacesChannel(workspacesService);
electronIpcServer.registerChannel('workspaces', workspacesChannel);
const workspacesChannel = appInstantiationService.createInstance(WorkspacesChannel, workspacesService);
this.electronIpcServer.registerChannel('workspaces', workspacesChannel);
const windowsService = accessor.get(IWindowsService);
const windowsChannel = new WindowsChannel(windowsService);
electronIpcServer.registerChannel('windows', windowsChannel);
sharedProcessClient.then(client => client.registerChannel('windows', windowsChannel));
this.electronIpcServer.registerChannel('windows', windowsChannel);
this.sharedProcessClient.then(client => client.registerChannel('windows', windowsChannel));
const menubarService = accessor.get(IMenubarService);
const menubarChannel = new MenubarChannel(menubarService);
electronIpcServer.registerChannel('menubar', menubarChannel);
this.electronIpcServer.registerChannel('menubar', menubarChannel);
const urlService = accessor.get(IURLService);
const urlChannel = new URLServiceChannel(urlService);
electronIpcServer.registerChannel('url', urlChannel);
this.electronIpcServer.registerChannel('url', urlChannel);
const storageMainService = accessor.get(IStorageMainService);
const storageChannel = this._register(new GlobalStorageDatabaseChannel(this.logService, storageMainService as StorageMainService));
electronIpcServer.registerChannel('storage', storageChannel);
this.electronIpcServer.registerChannel('storage', storageChannel);
// Log level management
const logLevelChannel = new LogLevelSetterChannel(accessor.get(ILogService));
electronIpcServer.registerChannel('loglevel', logLevelChannel);
sharedProcessClient.then(client => client.registerChannel('loglevel', logLevelChannel));
this.electronIpcServer.registerChannel('loglevel', logLevelChannel);
this.sharedProcessClient.then(client => client.registerChannel('loglevel', logLevelChannel));
// Signal phase: ready (services set)
this.lifecycleService.phase = LifecycleMainPhase.Ready;
// Lifecycle
(this.lifecycleService as LifecycleService).ready();
// Propagate to clients
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); // TODO@Joao: unfold this
@@ -531,7 +546,7 @@ export class CodeApplication extends Disposable {
// Create a URL handler which forwards to the last active window
const activeWindowManager = new ActiveWindowManager(windowsService);
const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id));
const urlHandlerChannel = electronIpcServer.getChannel('urlHandler', activeWindowRouter);
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,
@@ -540,17 +555,15 @@ export class CodeApplication extends Disposable {
const environmentService = accessor.get(IEnvironmentService);
urlService.registerHandler({
async handleURL(uri: URI): Promise<boolean> {
handleURL(uri: URI): Promise<boolean> {
if (windowsMainService.getWindowCount() === 0) {
const cli = { ...environmentService.args, goto: true };
const [window] = windowsMainService.open({ context: OpenContext.API, cli, forceEmpty: true });
await window.ready();
return urlService.open(uri);
return window.ready().then(() => urlService.open(uri));
}
return false;
return Promise.resolve(false);
}
});
}
@@ -561,9 +574,11 @@ export class CodeApplication extends Disposable {
// Watch Electron URLs and forward them to the UrlService
const args = this.environmentService.args;
const urls = args['open-url'] ? args._urls : [];
const urlListener = new ElectronURLListener(urls || [], urlService, windowsMainService);
const urlListener = new ElectronURLListener(urls || [], urlService, this.windowsMainService);
this._register(urlListener);
this.windowsMainService.ready(this.userEnv);
// Open our first window
const macOpenFiles: string[] = (<any>global).macOpenFiles;
const context = !!process.env['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP;
@@ -573,9 +588,9 @@ export class CodeApplication extends Disposable {
const noRecentEntry = args['skip-add-to-recently-opened'] === true;
const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined;
// new window if "-n" was used without paths
if (args['new-window'] && !hasCliArgs && !hasFolderURIs && !hasFileURIs) {
return windowsMainService.open({
// new window if "-n" was used without paths
return this.windowsMainService.open({
context,
cli: args,
forceNewWindow: true,
@@ -586,12 +601,12 @@ export class CodeApplication extends Disposable {
});
}
// mac: open-file event received on startup
if (macOpenFiles && macOpenFiles.length && !hasCliArgs && !hasFolderURIs && !hasFileURIs) {
return windowsMainService.open({
// mac: open-file event received on startup
return this.windowsMainService.open({
context: OpenContext.DOCK,
cli: args,
urisToOpen: macOpenFiles.map(file => this.getURIToOpenFromPathSync(file)),
urisToOpen: macOpenFiles.map(getURIToOpenFromPathSync),
noRecentEntry,
waitMarkerFileURI,
initialStartup: true
@@ -599,7 +614,7 @@ export class CodeApplication extends Disposable {
}
// default: read paths from cli
return windowsMainService.open({
return this.windowsMainService.open({
context,
cli: args,
forceNewWindow: args['new-window'] || (!hasCliArgs && args['unity-launch']),
@@ -610,30 +625,61 @@ export class CodeApplication extends Disposable {
});
}
private getURIToOpenFromPathSync(path: string): IURIToOpen {
try {
const fileStat = statSync(path);
if (fileStat.isDirectory()) {
return { folderUri: URI.file(path) };
private afterWindowOpen(accessor: ServicesAccessor): void {
const windowsMainService = accessor.get(IWindowsMainService);
const historyMainService = accessor.get(IHistoryMainService);
if (isWindows) {
// Setup Windows mutex
try {
const Mutex = (require.__$__nodeRequire('windows-mutex') as any).Mutex;
const windowsMutex = new Mutex(product.win32MutexName);
this._register(toDisposable(() => windowsMutex.release()));
} catch (e) {
if (!this.environmentService.isBuilt) {
windowsMainService.showMessageBox({
title: product.nameLong,
type: 'warning',
message: 'Failed to load windows-mutex!',
detail: e.toString(),
noLink: true
});
}
}
if (hasWorkspaceFileExtension(path)) {
return { workspaceUri: URI.file(path) };
// Ensure Windows foreground love module
try {
// tslint:disable-next-line:no-unused-expression
require.__$__nodeRequire('windows-foreground-love');
} catch (e) {
if (!this.environmentService.isBuilt) {
windowsMainService.showMessageBox({
title: product.nameLong,
type: 'warning',
message: 'Failed to load windows-foreground-love!',
detail: e.toString(),
noLink: true
});
}
}
} catch (error) {
// ignore errors
}
return { fileUri: URI.file(path) };
}
private afterWindowOpen(): void {
// Signal phase: after window open
this.lifecycleService.phase = LifecycleMainPhase.AfterWindowOpen;
// Remote Authorities
this.handleRemoteAuthorities();
// Keyboard layout changes
KeyboardLayoutMonitor.INSTANCE.onDidChangeKeyboardLayout(() => {
this.windowsMainService.sendToAll('vscode:keyboardLayoutChanged', false);
});
// Jump List
historyMainService.updateWindowsJumpList();
historyMainService.onRecentlyOpenedChange(() => historyMainService.updateWindowsJumpList());
// Start shared process after a while
const sharedProcessSpawn = this._register(new RunOnceScheduler(() => getShellEnvironment(this.logService).then(userEnv => this.sharedProcess.spawn(userEnv)), 3000));
sharedProcessSpawn.schedule();
}
private handleRemoteAuthorities(): void {
@@ -648,9 +694,8 @@ export class CodeApplication extends Disposable {
constructor(authority: string, host: string, port: number) {
this._authority = authority;
const options: IConnectionOptions = {
isBuilt,
isBuilt: isBuilt,
commit: product.commit,
webSocketFactory: nodeWebSocketFactory,
addressProvider: {
@@ -659,7 +704,6 @@ export class CodeApplication extends Disposable {
}
}
};
this._connection = connectRemoteAgentManagement(options, authority, `main`);
this._disposeRunner = new RunOnceScheduler(() => this.dispose(), 5000);
}
@@ -667,13 +711,14 @@ export class CodeApplication extends Disposable {
dispose(): void {
this._disposeRunner.dispose();
connectionPool.delete(this._authority);
this._connection.then(connection => connection.dispose());
this._connection.then((connection) => {
connection.dispose();
});
}
async getClient(): Promise<Client<RemoteAgentConnectionContext>> {
this._disposeRunner.schedule();
const connection = await this._connection;
return connection.client;
}
}
@@ -681,9 +726,7 @@ export class CodeApplication extends Disposable {
const resolvedAuthorities = new Map<string, ResolvedAuthority>();
ipc.on('vscode:remoteAuthorityResolved', (event: Electron.Event, data: ResolvedAuthority) => {
this.logService.info('Received resolved authority', data.authority);
resolvedAuthorities.set(data.authority, data);
// Make sure to close and remove any existing connections
if (connectionPool.has(data.authority)) {
connectionPool.get(data.authority)!.dispose();
@@ -692,19 +735,15 @@ export class CodeApplication extends Disposable {
const resolveAuthority = (authority: string): ResolvedAuthority | null => {
this.logService.info('Resolving authority', authority);
if (authority.indexOf('+') >= 0) {
if (resolvedAuthorities.has(authority)) {
return withUndefinedAsNull(resolvedAuthorities.get(authority));
}
this.logService.info('Didnot find resolved authority for', authority);
return null;
} else {
const [host, strPort] = authority.split(':');
const port = parseInt(strPort, 10);
return { authority, host, port };
}
};
@@ -713,7 +752,6 @@ export class CodeApplication extends Disposable {
if (request.method !== 'GET') {
return callback(undefined);
}
const uri = URI.parse(request.url);
let activeConnection: ActiveConnection | undefined;
@@ -725,11 +763,9 @@ export class CodeApplication extends Disposable {
callback(undefined);
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
@@ -748,3 +784,16 @@ export class CodeApplication extends Disposable {
});
}
}
function getURIToOpenFromPathSync(path: string): IURIToOpen {
try {
const fileStat = statSync(path);
if (fileStat.isDirectory()) {
return { folderUri: URI.file(path) };
} else if (hasWorkspaceFileExtension(path)) {
return { workspaceUri: URI.file(path) };
}
} catch (error) {
}
return { fileUri: URI.file(path) };
}

View File

@@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nativeKeymap from 'native-keymap';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Emitter } from 'vs/base/common/event';
export class KeyboardLayoutMonitor {
public static readonly INSTANCE = new KeyboardLayoutMonitor();
private readonly _emitter: Emitter<void>;
private _registered: boolean;
private constructor() {
this._emitter = new Emitter<void>();
this._registered = false;
}
public onDidChangeKeyboardLayout(callback: () => void): IDisposable {
if (!this._registered) {
this._registered = true;
nativeKeymap.onDidChangeKeyboardLayout(() => {
this._emitter.fire();
});
}
return this._emitter.event(callback);
}
}

View File

@@ -22,10 +22,10 @@ interface PostResult {
class Endpoint {
private constructor(
readonly url: string
public readonly url: string
) { }
static getFromProduct(): Endpoint | undefined {
public static getFromProduct(): Endpoint | undefined {
const logUploaderUrl = product.logUploaderUrl;
return logUploaderUrl ? new Endpoint(logUploaderUrl) : undefined;
}

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/platform/update/node/update.config.contribution';
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';
@@ -32,184 +32,76 @@ import * as fs from 'fs';
import { CodeApplication } from 'vs/code/electron-main/app';
import { localize } from 'vs/nls';
import { mnemonicButtonLabel } from 'vs/base/common/labels';
import { SpdLogService } from 'vs/platform/log/node/spdlogService';
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
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 { IThemeMainService, ThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
import { Client } from 'vs/base/parts/ipc/common/ipc.net';
import { once } from 'vs/base/common/functional';
class ExpectedError extends Error {
readonly isExpected = true;
}
class CodeMain {
function setupIPC(accessor: ServicesAccessor): Promise<Server> {
const logService = accessor.get(ILogService);
const environmentService = accessor.get(IEnvironmentService);
const instantiationService = accessor.get(IInstantiationService);
main(): void {
function allowSetForegroundWindow(service: LaunchChannelClient): Promise<void> {
let promise: Promise<void> = Promise.resolve();
if (platform.isWindows) {
promise = service.getMainProcessId()
.then(processId => {
logService.trace('Sending some foreground love to the running instance:', processId);
// 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);
args = validatePaths(args);
} catch (err) {
console.error(err.message);
app.exit(1);
return;
try {
const { allowSetForegroundWindow } = require.__$__nodeRequire('windows-foreground-love');
allowSetForegroundWindow(processId);
} catch (e) {
// noop
}
});
}
// 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) {
const waitMarkerFilePath = createWaitMarkerFile(args.verbose);
if (waitMarkerFilePath) {
addArg(process.argv, '--waitMarkerFilePath', waitMarkerFilePath);
args.waitMarkerFilePath = waitMarkerFilePath;
return promise;
}
function setup(retry: boolean): Promise<Server> {
return serve(environmentService.mainIPCHandle).then(server => {
// Print --status usage info
if (environmentService.args.status) {
logService.warn('Warning: The --status argument can only be used if Code is already running. Please run it again after Code has started.');
throw new ExpectedError('Terminating...');
}
}
// Launch
this.startup(args);
}
// Log uploader usage info
if (typeof environmentService.args['upload-logs'] !== 'undefined') {
logService.warn('Warning: The --upload-logs argument can only be used if Code is already running. Please run it again after Code has started.');
throw new ExpectedError('Terminating...');
}
private async startup(args: ParsedArgs): Promise<void> {
// dock might be hidden at this case due to a retry
if (platform.isMacintosh) {
app.dock.show();
}
// 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();
// Set the VSCODE_PID variable here when we are sure we are the first
// instance to startup. Otherwise we would wrongly overwrite the PID
process.env['VSCODE_PID'] = String(process.pid);
const [instantiationService, instanceEnvironment] = this.createServices(args, bufferLogService);
try {
// Init services
await instantiationService.invokeFunction(async accessor => {
const environmentService = accessor.get(IEnvironmentService);
const configurationService = accessor.get(IConfigurationService);
const stateService = accessor.get(IStateService);
try {
await this.initServices(environmentService, configurationService as ConfigurationService, stateService as StateService);
} catch (error) {
// Show a dialog for errors that can be resolved by the user
this.handleStartupDataDirError(environmentService, error);
throw error;
}
});
// Startup
await instantiationService.invokeFunction(async accessor => {
const environmentService = accessor.get(IEnvironmentService);
const logService = accessor.get(ILogService);
const lifecycleService = accessor.get(ILifecycleService);
const configurationService = accessor.get(IConfigurationService);
const mainIpcServer = await this.doStartup(logService, environmentService, lifecycleService, instantiationService, true);
bufferLogService.logger = new SpdLogService('main', environmentService.logsPath, bufferLogService.getLevel());
once(lifecycleService.onWillShutdown)(() => (configurationService as ConfigurationService).dispose());
return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup();
});
} catch (error) {
instantiationService.invokeFunction(this.quit, error);
}
}
private createServices(args: ParsedArgs, bufferLogService: BufferLogService): [IInstantiationService, typeof process.env] {
const services = new ServiceCollection();
const environmentService = new EnvironmentService(args, process.execPath);
const instanceEnvironment = this.patchEnvironment(environmentService); // Patch `process.env` with the instance's environment
services.set(IEnvironmentService, environmentService);
const logService = new MultiplexLogService([new ConsoleLogMainService(getLogLevel(environmentService)), bufferLogService]);
process.once('exit', () => logService.dispose());
services.set(ILogService, logService);
services.set(IConfigurationService, new ConfigurationService(environmentService.settingsResource.path));
services.set(ILifecycleService, new SyncDescriptor(LifecycleService));
services.set(IStateService, new SyncDescriptor(StateService));
services.set(IRequestService, new SyncDescriptor(RequestService));
services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService));
services.set(IThemeMainService, new SyncDescriptor(ThemeMainService));
return [new InstantiationService(services, true), instanceEnvironment];
}
private initServices(environmentService: IEnvironmentService, configurationService: ConfigurationService, stateService: StateService): Promise<unknown> {
// Environment service (paths)
const environmentServiceInitialization = Promise.all<void | undefined>([
environmentService.extensionsPath,
environmentService.nodeCachedDataDir,
environmentService.logsPath,
environmentService.globalStorageHome,
environmentService.workspaceStorageHome,
environmentService.backupHome
].map((path): undefined | Promise<void> => path ? mkdirp(path) : undefined));
// Configuration service
const configurationServiceInitialization = configurationService.initialize();
// State service
const stateServiceInitialization = stateService.init();
return Promise.all([environmentServiceInitialization, configurationServiceInitialization, stateServiceInitialization]);
}
private 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'],
// {{SQL CARBON EDIT}} We keep VSCODE_LOGS to not break functionality for merged code
ADS_LOGS: process.env['ADS_LOGS']
};
if (process.env['VSCODE_PORTABLE']) {
instanceEnvironment['VSCODE_PORTABLE'] = process.env['VSCODE_PORTABLE'];
}
assign(process.env, instanceEnvironment);
return instanceEnvironment;
}
private async doStartup(logService: ILogService, environmentService: IEnvironmentService, lifecycleService: ILifecycleService, instantiationService: IInstantiationService, retry: boolean): Promise<Server> {
// Try to setup a server for running. If that succeeds it means
// we are the first instance to startup. Otherwise it is likely
// that another instance is already running.
let server: Server;
try {
server = await serve(environmentService.mainIPCHandle);
once(lifecycleService.onWillShutdown)(() => server.dispose());
} catch (error) {
return server;
}, err => {
// Handle unexpected errors (the only expected error is EADDRINUSE that
// indicates a second instance of Code is running)
if (error.code !== 'EADDRINUSE') {
if (err.code !== 'EADDRINUSE') {
// Show a dialog for errors that can be resolved by the user
this.handleStartupDataDirError(environmentService, error);
handleStartupDataDirError(environmentService, err);
// Any other runtime error is just printed to the console
throw error;
return Promise.reject<Server>(err);
}
// Since we are the second instance, we do not want to show the dock
@@ -218,188 +110,263 @@ class CodeMain {
}
// there's a running instance, let's connect to it
let client: Client<string>;
try {
client = await connect(environmentService.mainIPCHandle, 'main');
} catch (error) {
return connect(environmentService.mainIPCHandle, 'main').then(
client => {
// Handle unexpected connection errors by showing a dialog to the user
if (!retry || platform.isWindows || error.code !== 'ECONNREFUSED') {
if (error.code === 'EPERM') {
this.showStartupWarningDialog(
localize('secondInstanceAdmin', "A second instance of {0} is already running as administrator.", product.nameShort),
localize('secondInstanceAdminDetail', "Please close the other instance and try again.")
);
// Tests from CLI require to be the only instance currently
if (environmentService.extensionTestsLocationURI && !environmentService.debugExtensionHost.break) {
const msg = 'Running extension tests from the command line is currently only supported if no other instance of Code is running.';
logService.error(msg);
client.dispose();
return Promise.reject(new Error(msg));
}
throw error;
// 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: NodeJS.Timeout;
if (!environmentService.wait && !environmentService.status && !environmentService.args['upload-logs']) {
startupWarningDialogHandle = setTimeout(() => {
showStartupWarningDialog(
localize('secondInstanceNoResponse', "Another instance of {0} is running but not responding", product.nameShort),
localize('secondInstanceNoResponseDetail', "Please close all other instances and try again.")
);
}, 10000);
}
const channel = client.getChannel('launch');
const service = new LaunchChannelClient(channel);
// Process Info
if (environmentService.args.status) {
return instantiationService.invokeFunction(accessor => {
return accessor.get(IDiagnosticsService).getDiagnostics(service).then(diagnostics => {
console.log(diagnostics);
return Promise.reject(new ExpectedError());
});
});
}
// Log uploader
if (typeof environmentService.args['upload-logs'] !== 'undefined') {
return instantiationService.invokeFunction(accessor => {
return uploadLogs(service, accessor.get(IRequestService), environmentService)
.then(() => Promise.reject(new ExpectedError()));
});
}
logService.trace('Sending env to running instance...');
return allowSetForegroundWindow(service)
.then(() => service.start(environmentService.args, process.env as platform.IProcessEnvironment))
.then(() => client.dispose())
.then(() => {
// Now that we started, make sure the warning dialog is prevented
if (startupWarningDialogHandle) {
clearTimeout(startupWarningDialogHandle);
}
return Promise.reject(new ExpectedError('Sent env to running instance. Terminating...'));
});
},
err => {
if (!retry || platform.isWindows || err.code !== 'ECONNREFUSED') {
if (err.code === 'EPERM') {
showStartupWarningDialog(
localize('secondInstanceAdmin', "A second instance of {0} is already running as administrator.", product.nameShort),
localize('secondInstanceAdminDetail', "Please close the other instance and try again.")
);
}
return Promise.reject<Server>(err);
}
// it happens on Linux and OS X that the pipe is left behind
// let's delete it, since we can't connect to it
// and then retry the whole thing
try {
fs.unlinkSync(environmentService.mainIPCHandle);
} catch (e) {
logService.warn('Could not delete obsolete instance handle', e);
return Promise.reject<Server>(e);
}
return setup(false);
}
// it happens on Linux and OS X that the pipe is left behind
// let's delete it, since we can't connect to it and then
// retry the whole thing
try {
fs.unlinkSync(environmentService.mainIPCHandle);
} catch (error) {
logService.warn('Could not delete obsolete instance handle', error);
throw error;
}
return this.doStartup(logService, environmentService, lifecycleService, instantiationService, false);
}
// Tests from CLI require to be the only instance currently
if (environmentService.extensionTestsLocationURI && !environmentService.debugExtensionHost.break) {
const msg = 'Running extension tests from the command line is currently only supported if no other instance of Code is running.';
logService.error(msg);
client.dispose();
throw 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: NodeJS.Timeout | undefined = undefined;
if (!environmentService.wait && !environmentService.status && !environmentService.args['upload-logs']) {
startupWarningDialogHandle = setTimeout(() => {
this.showStartupWarningDialog(
localize('secondInstanceNoResponse', "Another instance of {0} is running but not responding", product.nameShort),
localize('secondInstanceNoResponseDetail', "Please close all other instances and try again.")
);
}, 10000);
}
const channel = client.getChannel('launch');
const launchClient = new LaunchChannelClient(channel);
// Process Info
if (environmentService.args.status) {
return instantiationService.invokeFunction(async accessor => {
const diagnostics = await accessor.get(IDiagnosticsService).getDiagnostics(launchClient);
console.log(diagnostics);
throw new ExpectedError();
});
}
// Log uploader
if (typeof environmentService.args['upload-logs'] !== 'undefined') {
return instantiationService.invokeFunction(async accessor => {
await uploadLogs(launchClient, accessor.get(IRequestService), environmentService);
throw new ExpectedError();
});
}
// Windows: allow to set foreground
if (platform.isWindows) {
await this.windowsAllowSetForegroundWindow(launchClient, logService);
}
// Send environment over...
logService.trace('Sending env to running instance...');
await launchClient.start(environmentService.args, process.env as platform.IProcessEnvironment);
// Cleanup
await client.dispose();
// Now that we started, make sure the warning dialog is prevented
if (startupWarningDialogHandle) {
clearTimeout(startupWarningDialogHandle);
}
throw new ExpectedError('Sent env to running instance. Terminating...');
}
// Print --status usage info
if (environmentService.args.status) {
logService.warn('Warning: The --status argument can only be used if Code is already running. Please run it again after Code has started.');
throw new ExpectedError('Terminating...');
}
// Log uploader usage info
if (typeof environmentService.args['upload-logs'] !== 'undefined') {
logService.warn('Warning: The --upload-logs argument can only be used if Code is already running. Please run it again after Code has started.');
throw new ExpectedError('Terminating...');
}
// dock might be hidden at this case due to a retry
if (platform.isMacintosh) {
app.dock.show();
}
// Set the VSCODE_PID variable here when we are sure we are the first
// instance to startup. Otherwise we would wrongly overwrite the PID
process.env['VSCODE_PID'] = String(process.pid);
return server;
}
private handleStartupDataDirError(environmentService: IEnvironmentService, error: NodeJS.ErrnoException): void {
if (error.code === 'EACCES' || error.code === 'EPERM') {
this.showStartupWarningDialog(
localize('startupDataDirError', "Unable to write program user data."),
localize('startupDataDirErrorDetail', "Please make sure the directories {0} and {1} are writeable.", environmentService.userDataPath, environmentService.extensionsPath)
);
}
}
private showStartupWarningDialog(message: string, detail: string): void {
dialog.showMessageBox({
title: product.nameLong,
type: 'warning',
buttons: [mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
message,
detail,
noLink: true
});
}
private async windowsAllowSetForegroundWindow(client: LaunchChannelClient, logService: ILogService): Promise<void> {
if (platform.isWindows) {
const processId = await client.getMainProcessId();
return setup(true);
}
logService.trace('Sending some foreground love to the running instance:', processId);
function showStartupWarningDialog(message: string, detail: string): void {
dialog.showMessageBox({
title: product.nameLong,
type: 'warning',
buttons: [mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
message,
detail,
noLink: true
});
}
try {
(await import('windows-foreground-love')).allowSetForegroundWindow(processId);
} catch (error) {
logService.error(error);
}
}
}
private quit(accessor: ServicesAccessor, reason?: ExpectedError | Error): void {
const logService = accessor.get(ILogService);
const lifecycleService = accessor.get(ILifecycleService);
let exitCode = 0;
if (reason) {
if ((reason as ExpectedError).isExpected) {
if (reason.message) {
logService.trace(reason.message);
}
} else {
exitCode = 1; // signal error to the outside
if (reason.stack) {
logService.error(reason.stack);
} else {
logService.error(`Startup error: ${reason.toString()}`);
}
}
}
lifecycleService.kill(exitCode);
function handleStartupDataDirError(environmentService: IEnvironmentService, error: NodeJS.ErrnoException): void {
if (error.code === 'EACCES' || error.code === 'EPERM') {
showStartupWarningDialog(
localize('startupDataDirError', "Unable to write program user data."),
localize('startupDataDirErrorDetail', "Please make sure the directories {0} and {1} are writeable.", environmentService.userDataPath, environmentService.extensionsPath)
);
}
}
// Main Startup
const code = new CodeMain();
code.main();
function quit(accessor: ServicesAccessor, reason?: ExpectedError | Error): void {
const logService = accessor.get(ILogService);
const lifecycleService = accessor.get(ILifecycleService);
let exitCode = 0;
if (reason) {
if ((reason as ExpectedError).isExpected) {
if (reason.message) {
logService.trace(reason.message);
}
} else {
exitCode = 1; // signal error to the outside
if (reason.stack) {
logService.error(reason.stack);
} else {
logService.error(`Startup error: ${reason.toString()}`);
}
}
}
lifecycleService.kill(exitCode);
}
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'],
// {{SQL CARBON EDIT}} We keep VSCODE_LOGS to not break functionality for merged code
ADS_LOGS: process.env['ADS_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);
const stateService = accessor.get(IStateService);
// Patch `process.env` with the instance's environment
const instanceEnvironment = patchEnvironment(environmentService);
// Startup
return initServices(environmentService, stateService as StateService)
.then(() => instantiationService.invokeFunction(setupIPC), error => {
// Show a dialog for errors that can be resolved by the user
handleStartupDataDirError(environmentService, error);
return Promise.reject(error);
})
.then(mainIpcServer => {
createSpdLogService('main', bufferLogService.getLevel(), environmentService.logsPath).then(logger => bufferLogService.logger = logger);
return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup();
});
}).then(null, err => instantiationService.invokeFunction(quit, err));
}
function createServices(args: ParsedArgs, bufferLogService: BufferLogService): IInstantiationService {
const services = new ServiceCollection();
const environmentService = new EnvironmentService(args, process.execPath);
const logService = new MultiplexLogService([new ConsoleLogMainService(getLogLevel(environmentService)), bufferLogService]);
process.once('exit', () => logService.dispose());
services.set(IEnvironmentService, environmentService);
services.set(ILogService, logService);
services.set(ILifecycleService, new SyncDescriptor(LifecycleService));
services.set(IStateService, new SyncDescriptor(StateService));
services.set(IConfigurationService, new SyncDescriptor(ConfigurationService, [environmentService.appSettingsPath]));
services.set(IRequestService, new SyncDescriptor(RequestService));
services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService));
return new InstantiationService(services, true);
}
function initServices(environmentService: IEnvironmentService, stateService: StateService): Promise<unknown> {
// Ensure paths for environment service exist
const environmentServiceInitialization = Promise.all<void | undefined>([
environmentService.extensionsPath,
environmentService.nodeCachedDataDir,
environmentService.logsPath,
environmentService.globalStorageHome,
environmentService.workspaceStorageHome,
environmentService.backupHome
].map((path): undefined | Promise<void> => path ? mkdirp(path) : undefined));
// State service
const stateServiceInitialization = stateService.init();
return Promise.all([environmentServiceInitialization, stateServiceInitialization]);
}
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);
args = validatePaths(args);
} catch (err) {
console.error(err.message);
app.exit(1);
return undefined;
}
// 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) {
const waitMarkerFilePath = createWaitMarkerFile(args.verbose);
if (waitMarkerFilePath) {
addArg(process.argv, '--waitMarkerFilePath', waitMarkerFilePath);
args.waitMarkerFilePath = waitMarkerFilePath;
}
}
startup(args);
}
main();

View File

@@ -11,7 +11,8 @@ 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 { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
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 {
@@ -25,15 +26,15 @@ export class SharedProcess implements ISharedProcess {
private userEnv: NodeJS.ProcessEnv,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@ILifecycleService private readonly lifecycleService: ILifecycleService,
@ILogService private readonly logService: ILogService,
@IThemeMainService private readonly themeMainService: IThemeMainService
@IStateService private readonly stateService: IStateService,
@ILogService private readonly logService: ILogService
) { }
@memoize
private get _whenReady(): Promise<void> {
this.window = new BrowserWindow({
show: false,
backgroundColor: this.themeMainService.getBackgroundColor(),
backgroundColor: getBackgroundColor(this.stateService),
webPreferences: {
images: false,
webaudio: false,

View File

@@ -0,0 +1,44 @@
/*---------------------------------------------------------------------------------------------
* 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';
const DEFAULT_BG_LIGHT = '#FFFFFF';
const DEFAULT_BG_DARK = '#1E1E1E';
const DEFAULT_BG_HC_BLACK = '#000000';
const THEME_STORAGE_KEY = 'theme';
const THEME_BG_STORAGE_KEY = 'themeBackground';
export function storeBackgroundColor(stateService: IStateService, data: { baseTheme: string, background: string }): void {
stateService.setItem(THEME_STORAGE_KEY, data.baseTheme);
stateService.setItem(THEME_BG_STORAGE_KEY, data.background);
}
export function getBackgroundColor(stateService: IStateService): string {
if (isWindows && systemPreferences.isInvertedColorScheme()) {
return DEFAULT_BG_HC_BLACK;
}
let background = stateService.getItem<string | null>(THEME_BG_STORAGE_KEY, null);
if (!background) {
let baseTheme: string;
if (isWindows && systemPreferences.isInvertedColorScheme()) {
baseTheme = 'hc-black';
} else {
baseTheme = stateService.getItem<string>(THEME_STORAGE_KEY, 'vs-dark').split(' ')[0];
}
background = (baseTheme === 'hc-black') ? DEFAULT_BG_HC_BLACK : (baseTheme === 'vs' ? DEFAULT_BG_LIGHT : DEFAULT_BG_DARK);
}
if (isMacintosh && background.toUpperCase() === DEFAULT_BG_DARK) {
background = '#171717'; // https://github.com/electron/electron/issues/5150
}
return background;
}

View File

@@ -7,6 +7,7 @@ import * as path from 'vs/base/common/path';
import * as objects from 'vs/base/common/objects';
import * as nls from 'vs/nls';
import { URI } from 'vs/base/common/uri';
import { IStateService } from 'vs/platform/state/common/state';
import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display } from 'electron';
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
import { ILogService } from 'vs/platform/log/common/log';
@@ -22,9 +23,9 @@ import { IBackupMainService } from 'vs/platform/backup/common/backup';
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
import * as perf from 'vs/base/common/performance';
import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/node/extensionGalleryService';
import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
import { endsWith } from 'vs/base/common/strings';
import { getBackgroundColor } from 'vs/code/electron-main/theme';
import { RunOnceScheduler } from 'vs/base/common/async';
import { endsWith } from 'vs/base/common/strings';
export interface IWindowCreationOptions {
state: IWindowState;
@@ -83,7 +84,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
@ILogService private readonly logService: ILogService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IThemeMainService private readonly themeMainService: IThemeMainService,
@IStateService private readonly stateService: IStateService,
@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
@IBackupMainService private readonly backupMainService: IBackupMainService,
) {
@@ -123,7 +124,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
height: this.windowState.height,
x: this.windowState.x,
y: this.windowState.y,
backgroundColor: this.themeMainService.getBackgroundColor(),
backgroundColor: getBackgroundColor(this.stateService),
minWidth: CodeWindow.MIN_WIDTH,
minHeight: CodeWindow.MIN_HEIGHT,
show: !isFullscreenOrMaximized,
@@ -203,6 +204,12 @@ export class CodeWindow extends Disposable implements ICodeWindow {
return !!this.config.extensionTestsPath;
}
/*
get extensionDevelopmentPaths(): string | string[] | undefined {
return this.config.extensionDevelopmentPath;
}
*/
get config(): IWindowConfiguration {
return this.currentConfig;
}

View File

@@ -15,7 +15,7 @@ import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window';
import { hasArgs, asArray } from 'vs/platform/environment/node/argv';
import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences, FileFilter } from 'electron';
import { parseLineAndColumnAware } from 'vs/code/node/paths';
import { ILifecycleService, UnloadReason, LifecycleService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
import { ILifecycleService, UnloadReason, LifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, INativeOpenDialogOptions, IPathsToWaitFor, IEnterWorkspaceResult, IMessageBoxResult, INewWindowOptions, IURIToOpen, isFileToOpen, isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/windows';
@@ -38,8 +38,6 @@ import { getComparisonKey, isEqual, normalizePath, basename as resourcesBasename
import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
import { restoreWindowsState, WindowsStateStorageData, getWindowsStateStoreData } from 'vs/code/electron-main/windowsStateStorage';
import { getWorkspaceIdentifier } from 'vs/platform/workspaces/electron-main/workspacesMainService';
import { once } from 'vs/base/common/functional';
import { Disposable } from 'vs/base/common/lifecycle';
const enum WindowError {
UNRESPONSIVE = 1,
@@ -155,13 +153,15 @@ interface IWorkspacePathToOpen {
label?: string;
}
export class WindowsManager extends Disposable implements IWindowsMainService {
export class WindowsManager implements IWindowsMainService {
_serviceBrand: any;
private static readonly windowsStateStorageKey = 'windowsState';
private static readonly WINDOWS: ICodeWindow[] = [];
private static WINDOWS: ICodeWindow[] = [];
private initialUserEnv: IProcessEnvironment;
private readonly windowsState: IWindowsState;
private lastClosedWindowState?: IWindowState;
@@ -169,21 +169,20 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
private readonly dialogs: Dialogs;
private readonly workspacesManager: WorkspacesManager;
private readonly _onWindowReady = this._register(new Emitter<ICodeWindow>());
readonly onWindowReady: CommonEvent<ICodeWindow> = this._onWindowReady.event;
private _onWindowReady = new Emitter<ICodeWindow>();
onWindowReady: CommonEvent<ICodeWindow> = this._onWindowReady.event;
private readonly _onWindowClose = this._register(new Emitter<number>());
readonly onWindowClose: CommonEvent<number> = this._onWindowClose.event;
private _onWindowClose = new Emitter<number>();
onWindowClose: CommonEvent<number> = this._onWindowClose.event;
private readonly _onWindowLoad = this._register(new Emitter<number>());
readonly onWindowLoad: CommonEvent<number> = this._onWindowLoad.event;
private _onWindowLoad = new Emitter<number>();
onWindowLoad: CommonEvent<number> = this._onWindowLoad.event;
private readonly _onWindowsCountChanged = this._register(new Emitter<IWindowsCountChangedEvent>());
readonly onWindowsCountChanged: CommonEvent<IWindowsCountChangedEvent> = this._onWindowsCountChanged.event;
private _onWindowsCountChanged = new Emitter<IWindowsCountChangedEvent>();
onWindowsCountChanged: CommonEvent<IWindowsCountChangedEvent> = this._onWindowsCountChanged.event;
constructor(
private readonly machineId: string,
private readonly initialUserEnv: IProcessEnvironment,
@ILogService private readonly logService: ILogService,
@IStateService private readonly stateService: IStateService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@@ -195,7 +194,6 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
@IInstantiationService private readonly instantiationService: IInstantiationService
) {
super();
const windowsStateStoreData = this.stateService.getItem<WindowsStateStorageData>(WindowsManager.windowsStateStorageKey);
this.windowsState = restoreWindowsState(windowsStateStoreData);
@@ -205,50 +203,12 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
this.dialogs = new Dialogs(stateService, this);
this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, this);
this.lifecycleService.when(LifecycleMainPhase.Ready).then(() => this.registerListeners());
this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.setupNativeHelpers());
}
private setupNativeHelpers(): void {
if (isWindows) {
ready(initialUserEnv: IProcessEnvironment): void {
this.initialUserEnv = initialUserEnv;
// Setup Windows mutex
try {
const WindowsMutex = (require.__$__nodeRequire('windows-mutex') as typeof import('windows-mutex')).Mutex;
const mutex = new WindowsMutex(product.win32MutexName);
once(this.lifecycleService.onWillShutdown)(() => mutex.release());
} catch (e) {
this.logService.error(e);
if (!this.environmentService.isBuilt) {
this.showMessageBox({
title: product.nameLong,
type: 'warning',
message: 'Failed to load windows-mutex!',
detail: e.toString(),
noLink: true
});
}
}
// Dev only: Ensure Windows foreground love module is present
if (!this.environmentService.isBuilt) {
try {
require.__$__nodeRequire('windows-foreground-love');
} catch (e) {
this.logService.error(e);
this.showMessageBox({
title: product.nameLong,
type: 'warning',
message: 'Failed to load windows-foreground-love!',
detail: e.toString(),
noLink: true
});
}
}
}
this.registerListeners();
}
private registerListeners(): void {
@@ -583,7 +543,6 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
// Find suitable window or folder path to open files in
const fileToCheck = fileInputs.filesToOpenOrCreate[0] || fileInputs.filesToDiff[0];
// only look at the windows with correct authority
const windows = WindowsManager.WINDOWS.filter(w => w.remoteAuthority === fileInputs!.remoteAuthority);
@@ -680,6 +639,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
// Handle folders to open (instructed and to restore)
const allFoldersToOpen = arrays.distinct(foldersToOpen, folder => getComparisonKey(folder.folderUri)); // prevent duplicates
if (allFoldersToOpen.length > 0) {
// Check for existing instances
@@ -753,9 +713,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
if (fileInputs && !emptyToOpen) {
emptyToOpen++;
}
const remoteAuthority = fileInputs ? fileInputs.remoteAuthority : (openConfig.cli && openConfig.cli.remote || undefined);
for (let i = 0; i < emptyToOpen; i++) {
usedWindows.push(this.openInBrowserWindow({
userEnv: openConfig.userEnv,
@@ -1578,13 +1536,14 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
return state;
}
async reload(win: ICodeWindow, cli?: ParsedArgs): Promise<void> {
reload(win: ICodeWindow, cli?: ParsedArgs): void {
// Only reload when the window has not vetoed this
const veto = await this.lifecycleService.unload(win, UnloadReason.RELOAD);
if (!veto) {
win.reload(undefined, cli);
}
this.lifecycleService.unload(win, UnloadReason.RELOAD).then(veto => {
if (!veto) {
win.reload(undefined, cli);
}
});
}
closeWorkspace(win: ICodeWindow): void {
@@ -1595,10 +1554,8 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
});
}
async enterWorkspace(win: ICodeWindow, path: URI): Promise<IEnterWorkspaceResult | undefined> {
const result = await this.workspacesManager.enterWorkspace(win, path);
return result ? this.doEnterWorkspace(win, result) : undefined;
enterWorkspace(win: ICodeWindow, path: URI): Promise<IEnterWorkspaceResult | undefined> {
return this.workspacesManager.enterWorkspace(win, path).then(result => result ? this.doEnterWorkspace(win, result) : undefined);
}
private doEnterWorkspace(win: ICodeWindow, result: IEnterWorkspaceResult): IEnterWorkspaceResult {
@@ -1794,10 +1751,8 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
const paths = await this.dialogs.pick({ ...options, pickFolders: true, pickFiles: true, title });
if (paths) {
this.sendPickerTelemetry(paths, options.telemetryEventName || 'openFileFolder', options.telemetryExtraData);
const urisToOpen = await Promise.all(paths.map(async path => {
const isDir = await dirExists(path);
return isDir ? { folderUri: URI.file(path) } : { fileUri: URI.file(path) };
const urisToOpen = await Promise.all(paths.map(path => {
return dirExists(path).then(isDir => isDir ? { folderUri: URI.file(path) } : { fileUri: URI.file(path) });
}));
this.open({
context: OpenContext.DIALOG,
@@ -1925,7 +1880,7 @@ class Dialogs {
this.noWindowDialogQueue = new Queue<void>();
}
async pick(options: IInternalNativeOpenDialogOptions): Promise<string[] | undefined> {
pick(options: IInternalNativeOpenDialogOptions): Promise<string[] | undefined> {
// Ensure dialog options
const dialogOptions: Electron.OpenDialogOptions = {
@@ -1958,16 +1913,16 @@ class Dialogs {
// Show Dialog
const focusedWindow = (typeof options.windowId === 'number' ? this.windowsMainService.getWindowById(options.windowId) : undefined) || this.windowsMainService.getFocusedWindow();
const paths = await this.showOpenDialog(dialogOptions, focusedWindow);
if (paths && paths.length > 0) {
return this.showOpenDialog(dialogOptions, focusedWindow).then(paths => {
if (paths && paths.length > 0) {
// Remember path in storage for next time
this.stateService.setItem(Dialogs.workingDirPickerStorageKey, dirname(paths[0]));
// Remember path in storage for next time
this.stateService.setItem(Dialogs.workingDirPickerStorageKey, dirname(paths[0]));
return paths;
}
return paths;
}
return undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-check
return undefined;
});
}
private getDialogQueue(window?: ICodeWindow): Queue<any> {
@@ -2073,26 +2028,28 @@ class WorkspacesManager {
private readonly windowsMainService: IWindowsMainService,
) { }
async enterWorkspace(window: ICodeWindow, path: URI): Promise<IEnterWorkspaceResult | null> {
enterWorkspace(window: ICodeWindow, path: URI): Promise<IEnterWorkspaceResult | null> {
if (!window || !window.win || !window.isReady) {
return null; // return early if the window is not ready or disposed
return Promise.resolve(null); // return early if the window is not ready or disposed
}
const isValid = await this.isValidTargetWorkspacePath(window, path);
if (!isValid) {
return null; // return early if the workspace is not valid
}
return this.isValidTargetWorkspacePath(window, path).then(isValid => {
if (!isValid) {
return null; // return early if the workspace is not valid
}
const workspaceIdentifier = getWorkspaceIdentifier(path);
return this.doOpenWorkspace(window, workspaceIdentifier);
});
return this.doOpenWorkspace(window, getWorkspaceIdentifier(path));
}
private async isValidTargetWorkspacePath(window: ICodeWindow, path?: URI): Promise<boolean> {
private isValidTargetWorkspacePath(window: ICodeWindow, path?: URI): Promise<boolean> {
if (!path) {
return true;
return Promise.resolve(true);
}
if (window.openedWorkspace && isEqual(window.openedWorkspace.configPath, path)) {
return false; // window is already opened on a workspace with that path
return Promise.resolve(false); // window is already opened on a workspace with that path
}
// Prevent overwriting a workspace that is currently opened in another window
@@ -2106,12 +2063,10 @@ class WorkspacesManager {
noLink: true
};
await this.windowsMainService.showMessageBox(options, this.windowsMainService.getFocusedWindow());
return false;
return this.windowsMainService.showMessageBox(options, this.windowsMainService.getFocusedWindow()).then(() => false);
}
return true; // OK
return Promise.resolve(true); // OK
}
private doOpenWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): IEnterWorkspaceResult {
@@ -2135,16 +2090,14 @@ class WorkspacesManager {
return { workspace, backupPath };
}
}
function resourceFromURIToOpen(u: IURIToOpen): URI {
function resourceFromURIToOpen(u: IURIToOpen) {
if (isWorkspaceToOpen(u)) {
return u.workspaceUri;
}
if (isFolderToOpen(u)) {
} else if (isFolderToOpen(u)) {
return u.folderUri;
}
return u.fileUri;
}

View File

@@ -57,7 +57,6 @@ export async function main(argv: string[]): Promise<any> {
else if (shouldSpawnCliProcess(args)) {
const cli = await new Promise<IMainCli>((c, e) => require(['vs/code/node/cliProcessMain'], c, e));
await cli.main(args);
return;
}
@@ -258,7 +257,7 @@ export async function main(argv: string[]): Promise<any> {
addArg(argv, `--prof-startup-prefix`, filenamePrefix);
addArg(argv, `--no-cached-data`);
writeFileSync(filenamePrefix, argv.slice(-6).join('|'));
fs.writeFileSync(filenamePrefix, argv.slice(-6).join('|'));
processCallbacks.push(async _child => {
@@ -330,7 +329,7 @@ export async function main(argv: string[]): Promise<any> {
await extHost.stop();
// re-create the marker file to signal that profiling is done
writeFileSync(filenamePrefix, '');
fs.writeFileSync(filenamePrefix, '');
} catch (e) {
console.error('Failed to profile startup. Make sure to quit Code first.');

View File

@@ -31,6 +31,7 @@ import { mkdirp, writeFile } from 'vs/base/node/pfs';
import { getBaseLabel } from 'vs/base/common/labels';
import { IStateService } from 'vs/platform/state/common/state';
import { StateService } from 'vs/platform/state/node/stateService';
import { createBufferSpdLogService } from 'vs/platform/log/node/spdlogService';
import { ILogService, getLogLevel } from 'vs/platform/log/common/log';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import { areSameExtensions, adoptToGalleryExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
@@ -40,11 +41,10 @@ import { IExtensionManifest, ExtensionType, isLanguagePackExtension } from 'vs/p
import { CancellationToken } from 'vs/base/common/cancellation';
import { LocalizationsService } from 'vs/platform/localizations/node/localizations';
import { Schemas } from 'vs/base/common/network';
import { SpdLogService } from 'vs/platform/log/node/spdlogService';
const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id);
const notInstalled = (id: string) => localize('notInstalled', "Extension '{0}' is not installed.", id);
const useId = localize('useId', "Make sure you use the full extension ID, including the publisher, e.g.: {0}", 'ms-vscode.csharp');
const useId = localize('useId', "Make sure you use the full extension ID, including the publisher, eg: {0}", 'ms-vscode.csharp');
function getId(manifest: IExtensionManifest, withVersion?: boolean): string {
if (withVersion) {
@@ -275,22 +275,17 @@ export class Main {
const eventPrefix = 'monacoworkbench';
export async function main(argv: ParsedArgs): Promise<void> {
export function main(argv: ParsedArgs): Promise<void> {
const services = new ServiceCollection();
const environmentService = new EnvironmentService(argv, process.execPath);
const logService: ILogService = new SpdLogService('cli', environmentService.logsPath, getLogLevel(environmentService));
const logService = createBufferSpdLogService('cli', getLogLevel(environmentService), environmentService.logsPath);
process.once('exit', () => logService.dispose());
logService.info('main', argv);
await Promise.all([environmentService.appSettingsHome, environmentService.extensionsPath].map(p => mkdirp(p)));
const configurationService = new ConfigurationService(environmentService.settingsResource.path);
await configurationService.initialize();
services.set(IEnvironmentService, environmentService);
services.set(ILogService, logService);
services.set(IConfigurationService, configurationService);
services.set(IStateService, new SyncDescriptor(StateService));
const instantiationService: IInstantiationService = new InstantiationService(services);
@@ -299,37 +294,40 @@ export async function main(argv: ParsedArgs): Promise<void> {
const envService = accessor.get(IEnvironmentService);
const stateService = accessor.get(IStateService);
const { appRoot, extensionsPath, extensionDevelopmentLocationURI: extensionDevelopmentLocationURI, isBuilt, installSourcePath } = envService;
return Promise.all([envService.appSettingsHome, envService.extensionsPath].map(p => mkdirp(p))).then(() => {
const { appRoot, extensionsPath, extensionDevelopmentLocationURI: extensionDevelopmentLocationURI, isBuilt, installSourcePath } = envService;
const services = new ServiceCollection();
services.set(IRequestService, new SyncDescriptor(RequestService));
services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
const services = new ServiceCollection();
services.set(IConfigurationService, new SyncDescriptor(ConfigurationService, [environmentService.appSettingsPath]));
services.set(IRequestService, new SyncDescriptor(RequestService));
services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
const appenders: AppInsightsAppender[] = [];
if (isBuilt && !extensionDevelopmentLocationURI && !envService.args['disable-telemetry'] && product.enableTelemetry) {
const appenders: AppInsightsAppender[] = [];
if (isBuilt && !extensionDevelopmentLocationURI && !envService.args['disable-telemetry'] && product.enableTelemetry) {
if (product.aiConfig && product.aiConfig.asimovKey) {
appenders.push(new AppInsightsAppender(eventPrefix, null, product.aiConfig.asimovKey, logService));
if (product.aiConfig && product.aiConfig.asimovKey) {
appenders.push(new AppInsightsAppender(eventPrefix, null, product.aiConfig.asimovKey, logService));
}
const config: ITelemetryServiceConfig = {
appender: combinedAppender(...appenders),
commonProperties: resolveCommonProperties(product.commit, pkg.version, stateService.getItem('telemetry.machineId'), installSourcePath),
piiPaths: [appRoot, extensionsPath]
};
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config]));
} else {
services.set(ITelemetryService, NullTelemetryService);
}
const config: ITelemetryServiceConfig = {
appender: combinedAppender(...appenders),
commonProperties: resolveCommonProperties(product.commit, pkg.version, stateService.getItem('telemetry.machineId'), installSourcePath),
piiPaths: [appRoot, extensionsPath]
};
const instantiationService2 = instantiationService.createChild(services);
const main = instantiationService2.createInstance(Main);
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config]));
} else {
services.set(ITelemetryService, NullTelemetryService);
}
const instantiationService2 = instantiationService.createChild(services);
const main = instantiationService2.createInstance(Main);
return main.run(argv).then(() => {
// Dispose the AI adapter so that remaining data gets flushed.
return combinedAppender(...appenders).dispose();
return main.run(argv).then(() => {
// Dispose the AI adapter so that remaining data gets flushed.
return combinedAppender(...appenders).dispose();
});
});
});
}

View File

@@ -8,7 +8,6 @@ import { assign } from 'vs/base/common/objects';
import { generateUuid } from 'vs/base/common/uuid';
import { isWindows } from 'vs/base/common/platform';
import { ILogService } from 'vs/platform/log/common/log';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
function getUnixShellEnvironment(logService: ILogService): Promise<typeof process.env> {
const promise = new Promise<typeof process.env>((resolve, reject) => {
@@ -90,12 +89,9 @@ let _shellEnv: Promise<typeof process.env>;
* This should only be done when Code itself is not launched
* from within a shell.
*/
export function getShellEnvironment(logService: ILogService, environmentService: IEnvironmentService): Promise<typeof process.env> {
export function getShellEnvironment(logService: ILogService): Promise<typeof process.env> {
if (_shellEnv === undefined) {
if (environmentService.args['disable-user-env-probe']) {
logService.trace('getShellEnvironment: disable-user-env-probe set, skipping');
_shellEnv = Promise.resolve({});
} else if (isWindows) {
if (isWindows) {
logService.trace('getShellEnvironment: runing on windows, skipping');
_shellEnv = Promise.resolve({});
} else if (process.env['VSCODE_CLI'] === '1') {