mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-20 01:25:37 -05:00
This reverts commit d15a3fcc98.
This commit is contained in:
@@ -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 -->
|
||||
|
||||
@@ -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
6
src/vs/code/code.main.ts
Normal 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';
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) };
|
||||
}
|
||||
|
||||
32
src/vs/code/electron-main/keyboard.ts
Normal file
32
src/vs/code/electron-main/keyboard.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
44
src/vs/code/electron-main/theme.ts
Normal file
44
src/vs/code/electron-main/theme.ts
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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.');
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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') {
|
||||
|
||||
Reference in New Issue
Block a user