Merge from vscode 8e0f348413f4f616c23a88ae30030efa85811973 (#6381)

* Merge from vscode 8e0f348413f4f616c23a88ae30030efa85811973

* disable strict null check
This commit is contained in:
Anthony Dresser
2019-07-15 22:35:46 -07:00
committed by GitHub
parent f720ec642f
commit 0b7e7ddbf9
2406 changed files with 59140 additions and 35464 deletions

View File

@@ -4,17 +4,31 @@
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
<!-- Disable pinch zooming -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; img-src 'self' https: data: blob: vscode-remote:; media-src 'none'; child-src 'self' {{WEBVIEW_ENDPOINT}}; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; connect-src 'self' ws: wss: https:; font-src 'self' blob: vscode-remote:;">
<meta id="vscode-workbench-web-configuration" data-settings="{{WORKBENCH_WEB_CONGIGURATION}}">
<!-- Workaround to pass remote user data uri-->
<meta id="vscode-remote-user-data-uri" data-settings="{{REMOTE_USER_DATA_URI}}">
<!-- Workaround to pass product configuration-->
<meta id="vscode-remote-product-configuration" data-settings="{{PRODUCT_CONFIGURATION}}">
<!-- Workaround to pass remote connection token-->
<meta id="vscode-remote-connection-token" data-settings="{{CONNECTION_AUTH_TOKEN}}">
</head>
<body class="vs-dark" aria-label="">
<body aria-label="">
</body>
<!-- Startup via workbench.js -->
<script>
self.CONNECTION_AUTH_TOKEN = '{{CONNECTION_AUTH_TOKEN}}';
self.USER_HOME_DIR = '{{USER_HOME_DIR}}';
</script>
<!-- Require our AMD loader -->
<script src="./out/vs/loader.js"></script>
<!-- Startup via workbench.js -->
<script src="./out/vs/code/browser/workbench/workbench.js"></script>

View File

@@ -3,38 +3,24 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
//@ts-check
'use strict';
(function () {
function loadScript(path, callback) {
let script = document.createElement('script');
script.onload = callback;
script.async = true;
script.type = 'text/javascript';
script.src = path;
document.head.appendChild(script);
}
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`,
'xterm': `${window.location.origin}/node_modules/xterm/lib/xterm.js`,
'xterm-addon-search': `${window.location.origin}/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
'xterm-addon-web-links': `${window.location.origin}/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
}
});
loadScript('./out/vs/loader.js', function () {
require(['vs/workbench/workbench.web.api'], function (api) {
const options = JSON.parse(document.getElementById('vscode-workbench-web-configuration').getAttribute('data-settings'));
// @ts-ignore
require.config({
baseUrl: `${window.location.origin}/out`
});
// @ts-ignore
require([
'vs/workbench/workbench.web.main',
'vs/nls!vs/workbench/workbench.web.main',
'vs/css!vs/workbench/workbench.web.main'
],
// @ts-ignore
function () {
// @ts-ignore
require('vs/workbench/browser/web.main').main().then(undefined, console.error);
});
api.create(document.body, options);
});
})();

View File

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

View File

@@ -33,14 +33,14 @@ 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 { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc';
import { ILogService, getLogLevel } from 'vs/platform/log/common/log';
import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
import { normalizeGitHubUrl } from 'vs/code/electron-browser/issue/issueReporterUtil';
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 = createBufferSpdLogService(`issuereporter${configuration.windowId}`, getLogLevel(this.environmentService), this.environmentService.logsPath);
const logService = new SpdLogService(`issuereporter${configuration.windowId}`, this.environmentService.logsPath, getLogLevel(this.environmentService));
const logLevelClient = new LogLevelSetterChannelClient(mainProcessService.getChannel('loglevel'));
this.logService = new FollowerLogService(logLevelClient, logService);
@@ -336,7 +336,7 @@ export class IssueReporter extends Disposable {
this.render();
});
['includeSystemInfo', 'includeProcessInfo', 'includeWorkspaceInfo', 'includeExtensions', 'includeSearchedExtensions', 'includeSettingsSearchDetails'].forEach(elementId => {
(['includeSystemInfo', 'includeProcessInfo', 'includeWorkspaceInfo', 'includeExtensions', 'includeSearchedExtensions', 'includeSettingsSearchDetails'] as const).forEach(elementId => {
this.addEventListener(elementId, 'click', (event: Event) => {
event.stopPropagation();
this.issueReporterModel.update({ [elementId]: !this.issueReporterModel.getData()[elementId] });
@@ -346,7 +346,7 @@ export class IssueReporter extends Disposable {
const showInfoElements = document.getElementsByClassName('showInfo');
for (let i = 0; i < showInfoElements.length; i++) {
const showInfo = showInfoElements.item(i);
showInfo!.addEventListener('click', (e) => {
showInfo!.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
const label = (<HTMLDivElement>e.target);
if (label) {
@@ -677,12 +677,14 @@ export class IssueReporter extends Disposable {
private logSearchError(error: Error) {
this.logService.warn('issueReporter#search ', error.message);
/* __GDPR__
"issueReporterSearchError" : {
"message" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }
}
*/
this.telemetryService.publicLog('issueReporterSearchError', { message: error.message });
type IssueReporterSearchErrorClassification = {
message: { classification: 'CallstackOrException', purpose: 'PerformanceAndHealth' }
};
type IssueReporterSearchError = {
message: string;
};
this.telemetryService.publicLog2<IssueReporterSearchError, IssueReporterSearchErrorClassification>('issueReporterSearchError', { message: error.message });
}
private setUpTypes(): void {
@@ -874,13 +876,15 @@ export class IssueReporter extends Disposable {
return false;
}
/* __GDPR__
"issueReporterSubmit" : {
"issueType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"numSimilarIssuesDisplayed" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
}
*/
this.telemetryService.publicLog('issueReporterSubmit', { issueType: this.issueReporterModel.getData().issueType, numSimilarIssuesDisplayed: this.numberOfSearchResultsDisplayed });
type IssueReporterSubmitClassification = {
issueType: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
numSimilarIssuesDisplayed: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
};
type IssueReporterSubmitEvent = {
issueType: any;
numSimilarIssuesDisplayed: number;
};
this.telemetryService.publicLog2<IssueReporterSubmitEvent, IssueReporterSubmitClassification>('issueReporterSubmit', { issueType: this.issueReporterModel.getData().issueType, numSimilarIssuesDisplayed: this.numberOfSearchResultsDisplayed });
this.hasBeenSubmitted = true;
const baseUrl = this.getIssueUrlWithTitle((<HTMLInputElement>this.getElementById('issue-title')).value);
@@ -1108,11 +1112,7 @@ export class IssueReporter extends Disposable {
// Exclude right click
if (event.which < 3) {
shell.openExternal((<HTMLAnchorElement>event.target).href);
/* __GDPR__
"issueReporterViewSimilarIssue" : { }
*/
this.telemetryService.publicLog('issueReporterViewSimilarIssue');
this.telemetryService.publicLog2('issueReporterViewSimilarIssue');
}
}
@@ -1123,12 +1123,13 @@ export class IssueReporter extends Disposable {
} else {
const error = new Error(`${elementId} not found.`);
this.logService.error(error);
/* __GDPR__
"issueReporterGetElementError" : {
"message" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }
}
*/
this.telemetryService.publicLog('issueReporterGetElementError', { message: error.message });
type IssueReporterGetElementErrorClassification = {
message: { classification: 'CallstackOrException', purpose: 'PerformanceAndHealth' };
};
type IssueReporterGetElementErrorEvent = {
message: string;
};
this.telemetryService.publicLog2<IssueReporterGetElementErrorEvent, IssueReporterGetElementErrorClassification>('issueReporterGetElementError', { message: error.message });
return undefined;
}

View File

@@ -16,7 +16,7 @@ import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu';
import { popup } from 'vs/base/parts/contextmenu/electron-browser/contextmenu';
import { ProcessItem } from 'vs/base/common/processes';
import { addDisposableListener } from 'vs/base/browser/dom';
import { IDisposable } from 'vs/base/common/lifecycle';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { isRemoteDiagnosticError, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService';
@@ -24,7 +24,7 @@ let mapPidToWindowTitle = new Map<number, string>();
const DEBUG_FLAGS_PATTERN = /\s--(inspect|debug)(-brk|port)?=(\d+)?/;
const DEBUG_PORT_PATTERN = /\s--(inspect|debug)-port=(\d+)/;
const listeners: IDisposable[] = [];
const listeners = new DisposableStore();
const collapsedStateCache: Map<string, boolean> = new Map<string, boolean>();
let lastRequestTime: number;
@@ -171,7 +171,7 @@ function renderProcessGroupHeader(sectionName: string, body: HTMLElement, contai
updateSectionCollapsedState(!collapsedStateCache.get(sectionName), body, twistie, sectionName);
data.prepend(twistie);
listeners.push(addDisposableListener(data, 'click', (e) => {
listeners.add(addDisposableListener(data, 'click', (e) => {
const isHidden = body.classList.contains('hidden');
updateSectionCollapsedState(isHidden, body, twistie, sectionName);
}));
@@ -222,7 +222,7 @@ function renderTableSection(sectionName: string, processList: FormattedProcessIt
row.append(cpu, memory, pid, name);
listeners.push(addDisposableListener(row, 'contextmenu', (e) => {
listeners.add(addDisposableListener(row, 'contextmenu', (e) => {
showContextMenu(e, p, sectionIsLocal);
}));
@@ -239,7 +239,7 @@ function updateProcessInfo(processLists: [{ name: string, rootProcess: ProcessIt
}
container.innerHTML = '';
listeners.forEach(l => l.dispose());
listeners.clear();
const tableHead = document.createElement('thead');
tableHead.innerHTML = `<tr>

View File

@@ -8,7 +8,7 @@ import * as pfs from 'vs/base/node/pfs';
import { IStringDictionary } from 'vs/base/common/collections';
import product from 'vs/platform/product/node/product';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Disposable, toDisposable } 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,14 +30,13 @@ interface LanguagePackFile {
[locale: string]: LanguagePackEntry;
}
export class LanguagePackCachedDataCleaner {
private _disposables: IDisposable[] = [];
export class LanguagePackCachedDataCleaner extends Disposable {
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) {
@@ -45,10 +44,6 @@ export class LanguagePackCachedDataCleaner {
}
}
dispose(): void {
this._disposables = dispose(this._disposables);
}
private _manageCachedDataSoon(): void {
let handle: any = setTimeout(async () => {
handle = undefined;
@@ -101,12 +96,10 @@ export class LanguagePackCachedDataCleaner {
}
}, 40 * 1000);
this._disposables.push({
dispose() {
if (handle !== undefined) {
clearTimeout(handle);
}
this._register(toDisposable(() => {
if (handle !== undefined) {
clearTimeout(handle);
}
});
}));
}
}

View File

@@ -5,7 +5,7 @@
import { basename, dirname, join } from 'vs/base/common/path';
import { onUnexpectedError } from 'vs/base/common/errors';
import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { readdir, rimraf, stat } from 'vs/base/node/pfs';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import product from 'vs/platform/product/node/product';
@@ -16,7 +16,7 @@ export class NodeCachedDataCleaner {
? 1000 * 60 * 60 * 24 * 7 // roughly 1 week
: 1000 * 60 * 60 * 24 * 30 * 3; // roughly 3 months
private _disposables: IDisposable[] = [];
private readonly _disposables = new DisposableStore();
constructor(
@IEnvironmentService private readonly _environmentService: IEnvironmentService
@@ -25,7 +25,7 @@ export class NodeCachedDataCleaner {
}
dispose(): void {
this._disposables = dispose(this._disposables);
this._disposables.dispose();
}
private _manageCachedDataSoon(): void {
@@ -76,7 +76,7 @@ export class NodeCachedDataCleaner {
}, 30 * 1000);
this._disposables.push(toDisposable(() => {
this._disposables.add(toDisposable(() => {
if (handle) {
clearTimeout(handle);
handle = undefined;

View File

@@ -16,11 +16,11 @@ import { EnvironmentService } from 'vs/platform/environment/node/environmentServ
import { ExtensionManagementChannel } from 'vs/platform/extensionManagement/node/extensionManagementIpc';
import { IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
import { IRequestService } from 'vs/platform/request/node/request';
import { RequestService } from 'vs/platform/request/electron-browser/requestService';
import { IRequestService } from 'vs/platform/request/common/request';
import { RequestService } from 'vs/platform/request/browser/requestService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { combinedAppender, NullTelemetryService, ITelemetryAppender, NullAppender, LogAppender } from 'vs/platform/telemetry/common/telemetryUtils';
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
@@ -30,15 +30,14 @@ 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 { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc';
import { LocalizationsService } from 'vs/platform/localizations/node/localizations';
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
import { LocalizationsChannel } from 'vs/platform/localizations/node/localizationsIpc';
import { DialogChannelClient } from 'vs/platform/dialogs/node/dialogIpc';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle';
import { combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { DownloadService } from 'vs/platform/download/node/downloadService';
import { IDownloadService } from 'vs/platform/download/common/download';
import { IChannel, IServerChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc';
@@ -48,6 +47,16 @@ 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';
import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService';
import { IDiagnosticsService } from 'vs/platform/diagnostics/common/diagnosticsService';
import { DiagnosticsChannel } from 'vs/platform/diagnostics/node/diagnosticsIpc';
import { FileService } from 'vs/platform/files/common/fileService';
import { IFileService } from 'vs/platform/files/common/files';
import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider';
import { Schemas } from 'vs/base/common/network';
import { IProductService } from 'vs/platform/product/common/product';
import { ProductService } from 'vs/platform/product/node/productService';
export interface ISharedProcessConfiguration {
readonly machineId: string;
@@ -79,31 +88,35 @@ class MainProcessService implements IMainProcessService {
}
}
function main(server: Server, initData: ISharedProcessInitData, configuration: ISharedProcessConfiguration): void {
async function main(server: Server, initData: ISharedProcessInitData, configuration: ISharedProcessConfiguration): Promise<void> {
const services = new ServiceCollection();
const disposables: IDisposable[] = [];
const disposables = new DisposableStore();
const onExit = () => dispose(disposables);
const onExit = () => disposables.dispose();
process.once('exit', onExit);
ipcRenderer.once('handshake:goodbye', onExit);
disposables.push(server);
disposables.add(server);
const environmentService = new EnvironmentService(initData.args, process.execPath);
const mainRouter = new StaticRouter(ctx => ctx === 'main');
const logLevelClient = new LogLevelSetterChannelClient(server.getChannel('loglevel', mainRouter));
const logService = new FollowerLogService(logLevelClient, createBufferSpdLogService('sharedprocess', initData.logLevel, environmentService.logsPath));
disposables.push(logService);
const logService = new FollowerLogService(logLevelClient, new SpdLogService('sharedprocess', environmentService.logsPath, initData.logLevel));
disposables.add(logService);
logService.info('main', JSON.stringify(configuration));
const configurationService = new ConfigurationService(environmentService.settingsResource);
disposables.add(configurationService);
await configurationService.initialize();
services.set(IEnvironmentService, environmentService);
services.set(ILogService, logService);
services.set(IConfigurationService, new SyncDescriptor(ConfigurationService, [environmentService.appSettingsPath]));
services.set(IConfigurationService, configurationService);
services.set(IRequestService, new SyncDescriptor(RequestService));
services.set(IDownloadService, new SyncDescriptor(DownloadService));
services.set(IProductService, new SyncDescriptor(ProductService));
const mainProcessService = new MainProcessService(server, mainRouter);
services.set(IMainProcessService, mainProcessService);
@@ -116,13 +129,23 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
const dialogChannel = server.getChannel('dialog', activeWindowRouter);
services.set(IDialogService, new DialogChannelClient(dialogChannel));
// Files
const fileService = new FileService(logService);
services.set(IFileService, fileService);
disposables.add(fileService);
const diskFileSystemProvider = new DiskFileSystemProvider(logService);
disposables.add(diskFileSystemProvider);
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
const instantiationService = new InstantiationService(services);
let telemetryService: ITelemetryService;
instantiationService.invokeFunction(accessor => {
const services = new ServiceCollection();
const environmentService = accessor.get(IEnvironmentService);
const { appRoot, extensionsPath, extensionDevelopmentLocationURI: extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService;
const telemetryLogService = new FollowerLogService(logLevelClient, createBufferSpdLogService('telemetry', initData.logLevel, environmentService.logsPath));
const telemetryLogService = new FollowerLogService(logLevelClient, new SpdLogService('telemetry', environmentService.logsPath, initData.logLevel));
telemetryLogService.info('The below are logs for every telemetry event sent from VS Code once the log level is set to trace.');
telemetryLogService.info('===========================================================');
@@ -130,7 +153,7 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
if (!extensionDevelopmentLocationURI && !environmentService.args['disable-telemetry'] && product.enableTelemetry) {
if (product.aiConfig && product.aiConfig.asimovKey && isBuilt) {
appInsightsAppender = new AppInsightsAppender(eventPrefix, null, product.aiConfig.asimovKey, telemetryLogService);
disposables.push(appInsightsAppender); // Ensure the AI appender is disposed so that it flushes remaining data
disposables.add(appInsightsAppender); // Ensure the AI appender is disposed so that it flushes remaining data
}
const config: ITelemetryServiceConfig = {
appender: combinedAppender(appInsightsAppender, new LogAppender(logService)),
@@ -138,8 +161,10 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
piiPaths: [appRoot, extensionsPath]
};
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config]));
telemetryService = new TelemetryService(config, configurationService);
services.set(ITelemetryService, telemetryService);
} else {
telemetryService = NullTelemetryService;
services.set(ITelemetryService, NullTelemetryService);
}
server.registerChannel('telemetryAppender', new TelemetryAppenderChannel(appInsightsAppender));
@@ -147,6 +172,7 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService));
services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService));
const instantiationService2 = instantiationService.createChild(services);
@@ -160,18 +186,22 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
const localizationsChannel = new LocalizationsChannel(localizationsService);
server.registerChannel('localizations', localizationsChannel);
const diagnosticsService = accessor.get(IDiagnosticsService);
const diagnosticsChannel = new DiagnosticsChannel(diagnosticsService);
server.registerChannel('diagnostics', diagnosticsChannel);
// clean up deprecated extensions
(extensionManagementService as ExtensionManagementService).removeDeprecatedExtensions();
// update localizations cache
(localizationsService as LocalizationsService).update();
// cache clean ups
disposables.push(combinedDisposable([
disposables.add(combinedDisposable(
instantiationService2.createInstance(NodeCachedDataCleaner),
instantiationService2.createInstance(LanguagePackCachedDataCleaner),
instantiationService2.createInstance(StorageDataCleaner),
instantiationService2.createInstance(LogsDataCleaner)
]));
disposables.push(extensionManagementService as ExtensionManagementService);
));
disposables.add(extensionManagementService as ExtensionManagementService);
});
});
}
@@ -218,6 +248,6 @@ async function handshake(configuration: ISharedProcessConfiguration): Promise<vo
const server = await setupIPC(data.sharedIPCHandle);
main(server, data, configuration);
await main(server, data, configuration);
ipcRenderer.send('handshake:im ready');
}

View File

@@ -4,7 +4,7 @@
<head>
<meta charset="utf-8" />
<!-- {{SQL CARBON EDIT}} @anthonydresser add 'unsafe-eval' under script src; since its required by angular -->
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data: 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: 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:;">
</head>
<body class="vs-dark" aria-label="">
</body>

View File

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

View File

@@ -9,7 +9,7 @@ import { WindowsManager } from 'vs/code/electron-main/windows';
import { IWindowsService, OpenContext, ActiveWindowManager, IURIToOpen } from 'vs/platform/windows/common/windows';
import { WindowsChannel } from 'vs/platform/windows/node/windowsIpc';
import { WindowsService } from 'vs/platform/windows/electron-main/windowsService';
import { ILifecycleService, LifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
import { ILifecycleService, LifecycleMainPhase } 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,12 +36,10 @@ 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, toDisposable } from 'vs/base/common/lifecycle';
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
import { Disposable } from 'vs/base/common/lifecycle';
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
import { IHistoryMainService } from 'vs/platform/history/common/history';
import { isUndefinedOrNull, withUndefinedAsNull } from 'vs/base/common/types';
import { KeyboardLayoutMonitor } from 'vs/code/electron-main/keyboard';
import { withUndefinedAsNull } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { WorkspacesChannel } from 'vs/platform/workspaces/node/workspacesIpc';
import { IWorkspacesMainService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
@@ -52,7 +50,7 @@ import { DarwinUpdateService } from 'vs/platform/update/electron-main/updateServ
import { IIssueService } from 'vs/platform/issue/common/issue';
import { IssueChannel } from 'vs/platform/issue/node/issueIpc';
import { IssueService } from 'vs/platform/issue/electron-main/issueService';
import { LogLevelSetterChannel } from 'vs/platform/log/node/logIpc';
import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc';
import { setUnexpectedErrorHandler, onUnexpectedError } from 'vs/base/common/errors';
import { ElectronURLListener } from 'vs/platform/url/electron-main/electronUrlListener';
import { serve as serveDriver } from 'vs/platform/driver/electron-main/driver';
@@ -63,7 +61,6 @@ 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';
@@ -83,17 +80,19 @@ import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAg
import { nodeWebSocketFactory } from 'vs/platform/remote/node/nodeWebSocketFactory';
import { VSBuffer } from 'vs/base/common/buffer';
import { statSync } from 'fs';
import { ISignService } from 'vs/platform/sign/common/sign';
import { IDiagnosticsService } from 'vs/platform/diagnostics/common/diagnosticsService';
import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsIpc';
import { FileService } from 'vs/platform/files/common/fileService';
import { IFileService } from 'vs/platform/files/common/files';
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
export class CodeApplication extends Disposable {
private static readonly MACHINE_ID_KEY = 'telemetry.machineId';
private static readonly TRUE_MACHINE_ID_KEY = 'telemetry.trueMachineId';
private windowsMainService: IWindowsMainService;
private electronIpcServer: ElectronIPCServer;
private sharedProcess: SharedProcess;
private sharedProcessClient: Promise<Client>;
private windowsMainService: IWindowsMainService | undefined;
constructor(
private readonly mainIpcServer: Server,
@@ -102,14 +101,12 @@ export class CodeApplication extends Disposable {
@ILogService private readonly logService: ILogService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@ILifecycleService private readonly lifecycleService: ILifecycleService,
@IConfigurationService private readonly configurationService: ConfigurationService,
@IStateService private readonly stateService: IStateService
@IConfigurationService private readonly configurationService: IConfigurationService,
@IStateService private readonly stateService: IStateService,
@ISignService private readonly signService: ISignService
) {
super();
this._register(mainIpcServer);
this._register(configurationService);
this.registerListeners();
}
@@ -120,12 +117,12 @@ export class CodeApplication extends Disposable {
process.on('uncaughtException', err => this.onUnexpectedError(err));
process.on('unhandledRejection', (reason: unknown) => onUnexpectedError(reason));
// Contextmenu via IPC support
registerContextMenuListener();
// Dispose on shutdown
this.lifecycleService.onWillShutdown(() => this.dispose());
// Contextmenu via IPC support
registerContextMenuListener();
app.on('accessibility-support-changed', (event: Event, accessibilitySupportEnabled: boolean) => {
if (this.windowsMainService) {
this.windowsMainService.sendToAll('vscode:accessibilitySupportChanged', accessibilitySupportEnabled);
@@ -142,8 +139,38 @@ export class CodeApplication extends Disposable {
});
// Security related measures (https://electronjs.org/docs/tutorial/security)
// DO NOT CHANGE without consulting the documentation
app.on('web-contents-created', (event: Electron.Event, contents) => {
//
// !!! DO NOT CHANGE without consulting the documentation !!!
//
// app.on('remote-get-guest-web-contents', event => event.preventDefault()); // TODO@Ben TODO@Matt revisit this need for <webview>
app.on('remote-require', (event, sender, module) => {
this.logService.trace('App#on(remote-require): prevented');
event.preventDefault();
});
app.on('remote-get-global', (event, sender, module) => {
this.logService.trace(`App#on(remote-get-global): prevented on ${module}`);
event.preventDefault();
});
app.on('remote-get-builtin', (event, sender, module) => {
this.logService.trace(`App#on(remote-get-builtin): prevented on ${module}`);
if (module !== 'clipboard') {
event.preventDefault();
}
});
app.on('remote-get-current-window', event => {
this.logService.trace(`App#on(remote-get-current-window): prevented`);
event.preventDefault();
});
app.on('remote-get-current-web-contents', event => {
this.logService.trace(`App#on(remote-get-current-web-contents): prevented`);
event.preventDefault();
});
app.on('web-contents-created', (_event: Electron.Event, contents) => {
contents.on('will-attach-webview', (event: Electron.Event, webPreferences, params) => {
const isValidWebviewSource = (source: string): boolean => {
@@ -198,7 +225,7 @@ export class CodeApplication extends Disposable {
event.preventDefault();
// Keep in array because more might come!
macOpenFileURIs.push(getURIToOpenFromPathSync(path));
macOpenFileURIs.push(this.getURIToOpenFromPathSync(path));
// Clear previous handler if any
if (runningTimeout !== null) {
@@ -215,6 +242,7 @@ 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;
}
@@ -222,7 +250,9 @@ export class CodeApplication extends Disposable {
});
app.on('new-window-for-tab', () => {
this.windowsMainService.openNewWindow(OpenContext.DESKTOP); //macOS native tab "+" button
if (this.windowsMainService) {
this.windowsMainService.openNewWindow(OpenContext.DESKTOP); //macOS native tab "+" button
}
});
ipc.on('vscode:exit', (event: Event, code: number) => {
@@ -232,37 +262,26 @@ export class CodeApplication extends Disposable {
this.lifecycleService.kill(code);
});
ipc.on('vscode:fetchShellEnv', (event: Event) => {
ipc.on('vscode:fetchShellEnv', async (event: Event) => {
const webContents = event.sender;
getShellEnvironment(this.logService).then(shellEnv => {
try {
const shellEnv = await getShellEnvironment(this.logService, this.environmentService);
if (!webContents.isDestroyed()) {
webContents.send('vscode:acceptShellEnv', shellEnv);
}
}, err => {
} catch (error) {
if (!webContents.isDestroyed()) {
webContents.send('vscode:acceptShellEnv', {});
}
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]);
this.logService.error('Error fetching shell env', error);
}
});
ipc.on('vscode:extensionHostDebug', (_: Event, windowId: number, broadcast: any) => {
if (this.windowsMainService) {
// Send to all windows (except sender window)
this.windowsMainService.sendToAll('vscode:extensionHostDebug', broadcast, [windowId]);
this.windowsMainService.sendToAll('vscode:extensionHostDebug', broadcast, [windowId]); // Send to all windows (except sender window)
}
});
@@ -271,11 +290,25 @@ export class CodeApplication extends Disposable {
ipc.on('vscode:reloadWindow', (event: Event) => event.sender.reload());
powerMonitor.on('resume', () => { // After waking up from sleep
if (this.windowsMainService) {
this.windowsMainService.sendToAll('vscode:osResume', undefined);
}
});
// Some listeners after window opened
(async () => {
await this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen);
// After waking up from sleep (after window opened)
powerMonitor.on('resume', () => {
if (this.windowsMainService) {
this.windowsMainService.sendToAll('vscode:osResume', undefined);
}
});
// Keyboard layout changes (after window opened)
const nativeKeymap = await import('native-keymap');
nativeKeymap.onDidChangeKeyboardLayout(() => {
if (this.windowsMainService) {
this.windowsMainService.sendToAll('vscode:keyboardLayoutChanged', false);
}
});
})();
}
private onUnexpectedError(err: Error): void {
@@ -299,15 +332,7 @@ export class CodeApplication extends Disposable {
}
}
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> {
async startup(): Promise<void> {
this.logService.debug('Starting VS Code');
this.logService.debug(`from: ${this.environmentService.appRoot}`);
this.logService.debug('args:', this.environmentService.args);
@@ -335,64 +360,142 @@ export class CodeApplication extends Disposable {
}
// Create Electron IPC Server
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);
}
});
};
const electronIpcServer = new ElectronIPCServer();
// Resolve unique machine ID
this.logService.trace('Resolving machine identifier...');
const resolvedMachineId = this.resolveMachineId();
if (typeof resolvedMachineId === 'string') {
return startupWithMachineId(resolvedMachineId);
} else {
return resolvedMachineId.then(machineId => startupWithMachineId(machineId));
const { machineId, trueMachineId } = await this.resolveMachineId();
this.logService.trace(`Resolved machine identifier: ${machineId} (trueMachineId: ${trueMachineId})`);
// 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, trueMachineId, sharedProcess, sharedProcessClient);
// Create driver
if (this.environmentService.driverHandle) {
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 resolveMachineId(): string | Promise<string> {
const machineId = this.stateService.getItem<string>(CodeApplication.MACHINE_ID_KEY);
if (machineId) {
return machineId;
private async resolveMachineId(): Promise<{ machineId: string, trueMachineId?: 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 getMachineId().then(machineId => {
this.stateService.setItem(CodeApplication.MACHINE_ID_KEY, machineId);
// Check if machineId is hashed iBridge Device
let trueMachineId: string | undefined;
if (isMacintosh && machineId === '6c9d2bc8f91b89624add29c0abeae7fb42bf539fa1cdb2e3e57cd668fa9bcead') {
trueMachineId = this.stateService.getItem<string>(CodeApplication.TRUE_MACHINE_ID_KEY);
if (!trueMachineId) {
trueMachineId = await getMachineId();
return machineId;
});
this.stateService.setItem(CodeApplication.TRUE_MACHINE_ID_KEY, trueMachineId);
}
}
return { machineId, trueMachineId };
}
private async createServices(machineId: string, trueMachineId: string | undefined, sharedProcess: SharedProcess, sharedProcessClient: Promise<Client<string>>): Promise<IInstantiationService> {
const services = new ServiceCollection();
// Files
const fileService = this._register(new FileService(this.logService));
services.set(IFileService, fileService);
const diskFileSystemProvider = this._register(new DiskFileSystemProvider(this.logService));
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
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));
const diagnosticsChannel = getDelayedChannel(sharedProcessClient.then(client => client.getChannel('diagnostics')));
services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService, [diagnosticsChannel]));
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, trueMachineId };
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config]));
} else {
services.set(ITelemetryService, NullTelemetryService);
}
// Init services that require it
await backupMainService.initialize();
return this.instantiationService.createChild(services);
}
private stopTracingEventually(windows: ICodeWindow[]): void {
@@ -408,12 +511,14 @@ export class CodeApplication extends Disposable {
contentTracing.stopRecording(join(homedir(), `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`), path => {
if (!timeout) {
this.windowsMainService.showMessageBox({
type: 'info',
message: localize('trace.message', "Successfully created trace."),
detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path),
buttons: [localize('trace.ok', "Ok")]
}, this.windowsMainService.getLastActiveWindow());
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());
}
} else {
this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`);
}
@@ -430,72 +535,7 @@ export class CodeApplication extends Disposable {
});
}
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);
private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise<Client<string>>): ICodeWindow[] {
// Register more Main IPC services
const launchService = accessor.get(ILaunchService);
@@ -505,48 +545,48 @@ export class CodeApplication extends Disposable {
// Register more Electron IPC services
const updateService = accessor.get(IUpdateService);
const updateChannel = new UpdateChannel(updateService);
this.electronIpcServer.registerChannel('update', updateChannel);
electronIpcServer.registerChannel('update', updateChannel);
const issueService = accessor.get(IIssueService);
const issueChannel = new IssueChannel(issueService);
this.electronIpcServer.registerChannel('issue', issueChannel);
electronIpcServer.registerChannel('issue', issueChannel);
const workspacesService = accessor.get(IWorkspacesMainService);
const workspacesChannel = appInstantiationService.createInstance(WorkspacesChannel, workspacesService);
this.electronIpcServer.registerChannel('workspaces', workspacesChannel);
const workspacesChannel = new WorkspacesChannel(workspacesService);
electronIpcServer.registerChannel('workspaces', workspacesChannel);
const windowsService = accessor.get(IWindowsService);
const windowsChannel = new WindowsChannel(windowsService);
this.electronIpcServer.registerChannel('windows', windowsChannel);
this.sharedProcessClient.then(client => client.registerChannel('windows', windowsChannel));
electronIpcServer.registerChannel('windows', windowsChannel);
sharedProcessClient.then(client => client.registerChannel('windows', windowsChannel));
const menubarService = accessor.get(IMenubarService);
const menubarChannel = new MenubarChannel(menubarService);
this.electronIpcServer.registerChannel('menubar', menubarChannel);
electronIpcServer.registerChannel('menubar', menubarChannel);
const urlService = accessor.get(IURLService);
const urlChannel = new URLServiceChannel(urlService);
this.electronIpcServer.registerChannel('url', urlChannel);
electronIpcServer.registerChannel('url', urlChannel);
const storageMainService = accessor.get(IStorageMainService);
const storageChannel = this._register(new GlobalStorageDatabaseChannel(this.logService, storageMainService as StorageMainService));
this.electronIpcServer.registerChannel('storage', storageChannel);
electronIpcServer.registerChannel('storage', storageChannel);
// Log level management
const logLevelChannel = new LogLevelSetterChannel(accessor.get(ILogService));
this.electronIpcServer.registerChannel('loglevel', logLevelChannel);
this.sharedProcessClient.then(client => client.registerChannel('loglevel', logLevelChannel));
electronIpcServer.registerChannel('loglevel', logLevelChannel);
sharedProcessClient.then(client => client.registerChannel('loglevel', logLevelChannel));
// Lifecycle
(this.lifecycleService as LifecycleService).ready();
// Signal phase: ready (services set)
this.lifecycleService.phase = LifecycleMainPhase.Ready;
// Propagate to clients
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); // TODO@Joao: unfold this
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);
// 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 = this.electronIpcServer.getChannel('urlHandler', activeWindowRouter);
const urlHandlerChannel = 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,
@@ -555,15 +595,17 @@ export class CodeApplication extends Disposable {
const environmentService = accessor.get(IEnvironmentService);
urlService.registerHandler({
handleURL(uri: URI): Promise<boolean> {
async 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 });
return window.ready().then(() => urlService.open(uri));
await window.ready();
return urlService.open(uri);
}
return Promise.resolve(false);
return false;
}
});
}
@@ -574,11 +616,9 @@ 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, this.windowsMainService);
const urlListener = new ElectronURLListener(urls || [], urlService, 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;
@@ -588,9 +628,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) {
// new window if "-n" was used without paths
return this.windowsMainService.open({
return windowsMainService.open({
context,
cli: args,
forceNewWindow: true,
@@ -601,12 +641,12 @@ export class CodeApplication extends Disposable {
});
}
// mac: open-file event received on startup
if (macOpenFiles && macOpenFiles.length && !hasCliArgs && !hasFolderURIs && !hasFileURIs) {
// mac: open-file event received on startup
return this.windowsMainService.open({
return windowsMainService.open({
context: OpenContext.DOCK,
cli: args,
urisToOpen: macOpenFiles.map(getURIToOpenFromPathSync),
urisToOpen: macOpenFiles.map(file => this.getURIToOpenFromPathSync(file)),
noRecentEntry,
waitMarkerFileURI,
initialStartup: true
@@ -614,7 +654,7 @@ export class CodeApplication extends Disposable {
}
// default: read paths from cli
return this.windowsMainService.open({
return windowsMainService.open({
context,
cli: args,
forceNewWindow: args['new-window'] || (!hasCliArgs && args['unity-launch']),
@@ -625,61 +665,30 @@ export class CodeApplication extends Disposable {
});
}
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
});
}
private getURIToOpenFromPathSync(path: string): IURIToOpen {
try {
const fileStat = statSync(path);
if (fileStat.isDirectory()) {
return { folderUri: 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
});
}
if (hasWorkspaceFileExtension(path)) {
return { workspaceUri: URI.file(path) };
}
} 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 {
@@ -692,18 +701,21 @@ export class CodeApplication extends Disposable {
private readonly _connection: Promise<ManagementPersistentConnection>;
private readonly _disposeRunner: RunOnceScheduler;
constructor(authority: string, host: string, port: number) {
constructor(authority: string, host: string, port: number, signService: ISignService) {
this._authority = authority;
const options: IConnectionOptions = {
isBuilt: isBuilt,
isBuilt,
commit: product.commit,
webSocketFactory: nodeWebSocketFactory,
addressProvider: {
getAddress: () => {
return Promise.resolve({ host, port });
}
}
},
signService
};
this._connection = connectRemoteAgentManagement(options, authority, `main`);
this._disposeRunner = new RunOnceScheduler(() => this.dispose(), 5000);
}
@@ -711,14 +723,13 @@ 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;
}
}
@@ -726,7 +737,9 @@ 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();
@@ -735,15 +748,19 @@ 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 };
}
};
@@ -752,6 +769,7 @@ export class CodeApplication extends Disposable {
if (request.method !== 'GET') {
return callback(undefined);
}
const uri = URI.parse(request.url);
let activeConnection: ActiveConnection | undefined;
@@ -763,9 +781,11 @@ export class CodeApplication extends Disposable {
callback(undefined);
return;
}
activeConnection = new ActiveConnection(uri.authority, resolvedAuthority.host, resolvedAuthority.port);
activeConnection = new ActiveConnection(uri.authority, resolvedAuthority.host, resolvedAuthority.port, this.signService);
connectionPool.set(uri.authority, activeConnection);
}
try {
const rawClient = await activeConnection!.getClient();
if (connectionPool.has(uri.authority)) { // not disposed in the meantime
@@ -784,16 +804,3 @@ export class CodeApplication extends Disposable {
});
}
}
function getURIToOpenFromPathSync(path: string): IURIToOpen {
try {
const fileStat = statSync(path);
if (fileStat.isDirectory()) {
return { folderUri: URI.file(path) };
} else if (hasWorkspaceFileExtension(path)) {
return { workspaceUri: URI.file(path) };
}
} catch (error) {
}
return { fileUri: URI.file(path) };
}

View File

@@ -54,7 +54,11 @@ export class ProxyAuthHandler {
width: 450,
height: 220,
show: true,
title: 'VS Code'
title: 'VS Code',
webPreferences: {
nodeIntegration: true,
webviewTag: true
}
};
const focusedWindow = this.windowsMainService.getFocusedWindow();

View File

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

View File

@@ -1,153 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as os from 'os';
import * as cp from 'child_process';
import * as fs from 'fs';
import * as path from 'vs/base/common/path';
import { localize } from 'vs/nls';
import product from 'vs/platform/product/node/product';
import { IRequestService } from 'vs/platform/request/node/request';
import { IRequestContext } from 'vs/base/node/request';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ILaunchService } from 'vs/platform/launch/electron-main/launchService';
interface PostResult {
readonly blob_id: string;
}
class Endpoint {
private constructor(
public readonly url: string
) { }
public static getFromProduct(): Endpoint | undefined {
const logUploaderUrl = product.logUploaderUrl;
return logUploaderUrl ? new Endpoint(logUploaderUrl) : undefined;
}
}
export async function uploadLogs(
launchService: ILaunchService,
requestService: IRequestService,
environmentService: IEnvironmentService
): Promise<any> {
const endpoint = Endpoint.getFromProduct();
if (!endpoint) {
console.error(localize('invalidEndpoint', 'Invalid log uploader endpoint'));
return;
}
const logsPath = await launchService.getLogsPath();
if (await promptUserToConfirmLogUpload(logsPath, environmentService)) {
console.log(localize('beginUploading', 'Uploading...'));
const outZip = await zipLogs(logsPath);
const result = await postLogs(endpoint, outZip, requestService);
console.log(localize('didUploadLogs', 'Upload successful! Log file ID: {0}', result.blob_id));
}
}
function promptUserToConfirmLogUpload(
logsPath: string,
environmentService: IEnvironmentService
): boolean {
const confirmKey = 'iConfirmLogsUpload';
if ((environmentService.args['upload-logs'] || '').toLowerCase() === confirmKey.toLowerCase()) {
return true;
} else {
const message = localize('logUploadPromptHeader', 'You are about to upload your session logs to a secure Microsoft endpoint that only Microsoft\'s members of the VS Code team can access.')
+ '\n\n' + localize('logUploadPromptBody', 'Session logs may contain personal information such as full paths or file contents. Please review and redact your session log files here: \'{0}\'', logsPath)
+ '\n\n' + localize('logUploadPromptBodyDetails', 'By continuing you confirm that you have reviewed and redacted your session log files and that you agree to Microsoft using them to debug VS Code.')
+ '\n\n' + localize('logUploadPromptAcceptInstructions', 'Please run code with \'--upload-logs={0}\' to proceed with upload', confirmKey);
console.log(message);
return false;
}
}
async function postLogs(
endpoint: Endpoint,
outZip: string,
requestService: IRequestService
): Promise<PostResult> {
const dotter = setInterval(() => console.log('.'), 5000);
let result: IRequestContext;
try {
result = await requestService.request({
url: endpoint.url,
type: 'POST',
data: Buffer.from(fs.readFileSync(outZip)).toString('base64'),
headers: {
'Content-Type': 'application/zip'
}
}, CancellationToken.None);
} catch (e) {
clearInterval(dotter);
console.log(localize('postError', 'Error posting logs: {0}', e));
throw e;
}
return new Promise<PostResult>((resolve, reject) => {
const parts: Buffer[] = [];
result.stream.on('data', data => {
parts.push(data);
});
result.stream.on('end', () => {
clearInterval(dotter);
try {
const response = Buffer.concat(parts).toString('utf-8');
if (result.res.statusCode === 200) {
resolve(JSON.parse(response));
} else {
const errorMessage = localize('responseError', 'Error posting logs. Got {0} — {1}', result.res.statusCode, response);
console.log(errorMessage);
reject(new Error(errorMessage));
}
} catch (e) {
console.log(localize('parseError', 'Error parsing response'));
reject(e);
}
});
});
}
function zipLogs(
logsPath: string
): Promise<string> {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'vscode-log-upload'));
const outZip = path.join(tempDir, 'logs.zip');
return new Promise<string>((resolve, reject) => {
doZip(logsPath, outZip, tempDir, (err, stdout, stderr) => {
if (err) {
console.error(localize('zipError', 'Error zipping logs: {0}', err.message));
reject(err);
} else {
resolve(outZip);
}
});
});
}
function doZip(
logsPath: string,
outZip: string,
tempDir: string,
callback: (error: Error, stdout: string, stderr: string) => void
) {
switch (os.platform()) {
case 'win32':
// Copy directory first to avoid file locking issues
const sub = path.join(tempDir, 'sub');
return cp.execFile('powershell', ['-Command',
`[System.IO.Directory]::CreateDirectory("${sub}"); Copy-Item -recurse "${logsPath}" "${sub}"; Compress-Archive -Path "${sub}" -DestinationPath "${outZip}"`],
{ cwd: logsPath },
callback);
default:
return cp.execFile('zip', ['-r', outZip, '.'], { cwd: logsPath }, callback);
}
}

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/code/code.main';
import 'vs/platform/update/node/update.config.contribution';
import { app, dialog } from 'electron';
import { assign } from 'vs/base/common/objects';
import * as platform from 'vs/base/common/platform';
@@ -26,82 +26,191 @@ import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
import { IRequestService } from 'vs/platform/request/node/request';
import { IRequestService } from 'vs/platform/request/common/request';
import { RequestService } from 'vs/platform/request/electron-main/requestService';
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 { createSpdLogService } from 'vs/platform/log/node/spdlogService';
import { IDiagnosticsService, DiagnosticsService } from 'vs/platform/diagnostics/electron-main/diagnosticsService';
import { SpdLogService } from 'vs/platform/log/node/spdlogService';
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';
import { ISignService } from 'vs/platform/sign/common/sign';
import { SignService } from 'vs/platform/sign/node/signService';
import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsIpc';
class ExpectedError extends Error {
readonly isExpected = true;
}
function setupIPC(accessor: ServicesAccessor): Promise<Server> {
const logService = accessor.get(ILogService);
const environmentService = accessor.get(IEnvironmentService);
const instantiationService = accessor.get(IInstantiationService);
class CodeMain {
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);
main(): void {
try {
const { allowSetForegroundWindow } = require.__$__nodeRequire('windows-foreground-love');
allowSetForegroundWindow(processId);
} catch (e) {
// noop
}
});
// 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;
}
return promise;
// 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;
}
}
// Launch
this.startup(args);
}
function setup(retry: boolean): Promise<Server> {
return serve(environmentService.mainIPCHandle).then(server => {
private async startup(args: ParsedArgs): Promise<void> {
// 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...');
}
// 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();
// 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...');
}
const [instantiationService, instanceEnvironment] = this.createServices(args, bufferLogService);
try {
// dock might be hidden at this case due to a retry
if (platform.isMacintosh) {
app.dock.show();
}
// Init services
await instantiationService.invokeFunction(async accessor => {
const environmentService = accessor.get(IEnvironmentService);
const configurationService = accessor.get(IConfigurationService);
const stateService = accessor.get(IStateService);
// 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);
try {
await this.initServices(environmentService, configurationService as ConfigurationService, stateService as StateService);
} catch (error) {
return server;
}, err => {
// 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));
services.set(ILifecycleService, new SyncDescriptor(LifecycleService));
services.set(IStateService, new SyncDescriptor(StateService));
services.set(IRequestService, new SyncDescriptor(RequestService));
services.set(IThemeMainService, new SyncDescriptor(ThemeMainService));
services.set(ISignService, new SyncDescriptor(SignService));
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.fsPath
].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) {
// Handle unexpected errors (the only expected error is EADDRINUSE that
// indicates a second instance of Code is running)
if (err.code !== 'EADDRINUSE') {
if (error.code !== 'EADDRINUSE') {
// Show a dialog for errors that can be resolved by the user
handleStartupDataDirError(environmentService, err);
this.handleStartupDataDirError(environmentService, error);
// Any other runtime error is just printed to the console
return Promise.reject<Server>(err);
throw error;
}
// Since we are the second instance, we do not want to show the dock
@@ -110,263 +219,177 @@ function setupIPC(accessor: ServicesAccessor): Promise<Server> {
}
// there's a running instance, let's connect to it
return connect(environmentService.mainIPCHandle, 'main').then(
client => {
let client: Client<string>;
try {
client = await connect(environmentService.mainIPCHandle, 'main');
} catch (error) {
// 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));
// 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.")
);
}
// 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);
throw error;
}
// 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) {
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 => {
// Create a diagnostic service connected to the existing shared process
const sharedProcessClient = await connect(environmentService.sharedIPCHandle, 'main');
const diagnosticsChannel = sharedProcessClient.getChannel('diagnostics');
const diagnosticsService = new DiagnosticsService(diagnosticsChannel);
const mainProcessInfo = await launchClient.getMainProcessInfo();
const remoteDiagnostics = await launchClient.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true });
const diagnostics = await diagnosticsService.getDiagnostics(mainProcessInfo, remoteDiagnostics);
console.log(diagnostics);
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...');
}
// 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
});
}
return setup(true);
}
private async windowsAllowSetForegroundWindow(client: LaunchChannelClient, logService: ILogService): Promise<void> {
if (platform.isWindows) {
const processId = await client.getMainProcessId();
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
});
}
logService.trace('Sending some foreground love to the running instance:', processId);
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)
);
}
}
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);
try {
(await import('windows-foreground-love')).allowSetForegroundWindow(processId);
} catch (error) {
logService.error(error);
}
} else {
exitCode = 1; // signal error to the outside
}
}
if (reason.stack) {
logService.error(reason.stack);
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 {
logService.error(`Startup error: ${reason.toString()}`);
exitCode = 1; // signal error to the outside
if (reason.stack) {
logService.error(reason.stack);
} else {
logService.error(`Startup error: ${reason.toString()}`);
}
}
}
lifecycleService.kill(exitCode);
}
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();
// Main Startup
const code = new CodeMain();
code.main();

View File

@@ -11,9 +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 { IStateService } from 'vs/platform/state/common/state';
import { getBackgroundColor } from 'vs/code/electron-main/theme';
import { dispose, toDisposable, IDisposable } from 'vs/base/common/lifecycle';
import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
export class SharedProcess implements ISharedProcess {
@@ -26,19 +25,20 @@ export class SharedProcess implements ISharedProcess {
private userEnv: NodeJS.ProcessEnv,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@ILifecycleService private readonly lifecycleService: ILifecycleService,
@IStateService private readonly stateService: IStateService,
@ILogService private readonly logService: ILogService
@ILogService private readonly logService: ILogService,
@IThemeMainService private readonly themeMainService: IThemeMainService
) { }
@memoize
private get _whenReady(): Promise<void> {
this.window = new BrowserWindow({
show: false,
backgroundColor: getBackgroundColor(this.stateService),
backgroundColor: this.themeMainService.getBackgroundColor(),
webPreferences: {
images: false,
webaudio: false,
webgl: false,
nodeIntegration: true,
disableBlinkFeatures: 'Auxclick' // do NOT change, allows us to identify this window as shared-process in the process explorer
}
});
@@ -67,10 +67,10 @@ export class SharedProcess implements ISharedProcess {
this.window.on('close', onClose);
const disposables: IDisposable[] = [];
const disposables = new DisposableStore();
this.lifecycleService.onWillShutdown(() => {
dispose(disposables);
disposables.dispose();
// Shut the shared process down when we are quitting
//
@@ -104,7 +104,7 @@ export class SharedProcess implements ISharedProcess {
logLevel: this.logService.getLevel()
});
disposables.push(toDisposable(() => sender.send('handshake:goodbye')));
disposables.add(toDisposable(() => sender.send('handshake:goodbye')));
ipcMain.once('handshake:im ready', () => c(undefined));
});
});

View File

@@ -1,44 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isWindows, isMacintosh } from 'vs/base/common/platform';
import { systemPreferences } from 'electron';
import { IStateService } from 'vs/platform/state/common/state';
const DEFAULT_BG_LIGHT = '#FFFFFF';
const DEFAULT_BG_DARK = '#1E1E1E';
const DEFAULT_BG_HC_BLACK = '#000000';
const THEME_STORAGE_KEY = 'theme';
const THEME_BG_STORAGE_KEY = 'themeBackground';
export function storeBackgroundColor(stateService: IStateService, data: { baseTheme: string, background: string }): void {
stateService.setItem(THEME_STORAGE_KEY, data.baseTheme);
stateService.setItem(THEME_BG_STORAGE_KEY, data.background);
}
export function getBackgroundColor(stateService: IStateService): string {
if (isWindows && systemPreferences.isInvertedColorScheme()) {
return DEFAULT_BG_HC_BLACK;
}
let background = stateService.getItem<string | null>(THEME_BG_STORAGE_KEY, null);
if (!background) {
let baseTheme: string;
if (isWindows && systemPreferences.isInvertedColorScheme()) {
baseTheme = 'hc-black';
} else {
baseTheme = stateService.getItem<string>(THEME_STORAGE_KEY, 'vs-dark').split(' ')[0];
}
background = (baseTheme === 'hc-black') ? DEFAULT_BG_HC_BLACK : (baseTheme === 'vs' ? DEFAULT_BG_LIGHT : DEFAULT_BG_DARK);
}
if (isMacintosh && background.toUpperCase() === DEFAULT_BG_DARK) {
background = '#171717'; // https://github.com/electron/electron/issues/5150
}
return background;
}

View File

@@ -7,14 +7,13 @@ 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';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { parseArgs } from 'vs/platform/environment/node/argv';
import product from 'vs/platform/product/node/product';
import { IWindowSettings, MenuBarVisibility, IWindowConfiguration, ReadyState, IRunActionInWindowRequest, getTitleBarStyle } from 'vs/platform/windows/common/windows';
import { IWindowSettings, MenuBarVisibility, IWindowConfiguration, ReadyState, getTitleBarStyle } from 'vs/platform/windows/common/windows';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import { ICodeWindow, IWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows';
@@ -22,10 +21,14 @@ import { IWorkspaceIdentifier, IWorkspacesMainService } from 'vs/platform/worksp
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 { getBackgroundColor } from 'vs/code/electron-main/theme';
import { RunOnceScheduler } from 'vs/base/common/async';
import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/common/extensionGalleryService';
import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
import { endsWith } from 'vs/base/common/strings';
import { RunOnceScheduler } from 'vs/base/common/async';
import { IFileService } from 'vs/platform/files/common/files';
import pkg from 'vs/platform/product/node/package';
const RUN_TEXTMATE_IN_WORKER = false;
export interface IWindowCreationOptions {
state: IWindowState;
@@ -41,14 +44,6 @@ export const defaultWindowState = function (mode = WindowMode.Normal): IWindowSt
};
};
interface IWorkbenchEditorConfiguration {
workbench: {
editor: {
swipeToNavigate: boolean
}
};
}
interface ITouchBarSegment extends Electron.SegmentedControlSegment {
id: string;
}
@@ -83,8 +78,9 @@ export class CodeWindow extends Disposable implements ICodeWindow {
config: IWindowCreationOptions,
@ILogService private readonly logService: ILogService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IFileService private readonly fileService: IFileService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IStateService private readonly stateService: IStateService,
@IThemeMainService private readonly themeMainService: IThemeMainService,
@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
@IBackupMainService private readonly backupMainService: IBackupMainService,
) {
@@ -114,7 +110,8 @@ export class CodeWindow extends Disposable implements ICodeWindow {
private createBrowserWindow(config: IWindowCreationOptions): void {
// Load window state
this.windowState = this.restoreWindowState(config.state);
const [state, hasMultipleDisplays] = this.restoreWindowState(config.state);
this.windowState = state;
// in case we are maximized or fullscreen, only show later after the call to maximize/fullscreen (see below)
const isFullscreenOrMaximized = (this.windowState.mode === WindowMode.Maximized || this.windowState.mode === WindowMode.Fullscreen);
@@ -124,7 +121,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
height: this.windowState.height,
x: this.windowState.x,
y: this.windowState.y,
backgroundColor: getBackgroundColor(this.stateService),
backgroundColor: this.themeMainService.getBackgroundColor(),
minWidth: CodeWindow.MIN_WIDTH,
minHeight: CodeWindow.MIN_HEIGHT,
show: !isFullscreenOrMaximized,
@@ -134,7 +131,10 @@ export class CodeWindow extends Disposable implements ICodeWindow {
// want to enforce that Code stays in the foreground. This triggers a disable_hidden_
// flag that Electron provides via patch:
// https://github.com/electron/libchromiumcontent/blob/master/patches/common/chromium/disable_hidden.patch
backgroundThrottling: false
backgroundThrottling: false,
nodeIntegration: true,
nodeIntegrationInWorker: RUN_TEXTMATE_IN_WORKER,
webviewTag: true
}
};
@@ -156,7 +156,8 @@ export class CodeWindow extends Disposable implements ICodeWindow {
}
}
if (isMacintosh && windowConfig && windowConfig.nativeTabs === true) {
const useNativeTabs = isMacintosh && windowConfig && windowConfig.nativeTabs === true;
if (useNativeTabs) {
options.tabbingIdentifier = product.nameShort; // this opts in to sierra tabs
}
@@ -177,6 +178,24 @@ export class CodeWindow extends Disposable implements ICodeWindow {
this._win.setSheetOffset(22); // offset dialogs by the height of the custom title bar if we have any
}
// TODO@Ben (Electron 4 regression): when running on multiple displays where the target display
// to open the window has a larger resolution than the primary display, the window will not size
// correctly unless we set the bounds again (https://github.com/microsoft/vscode/issues/74872)
//
// However, when running with native tabs with multiple windows we cannot use this workaround
// because there is a potential that the new window will be added as native tab instead of being
// a window on its own. In that case calling setBounds() would cause https://github.com/microsoft/vscode/issues/75830
if (isMacintosh && hasMultipleDisplays && (!useNativeTabs || BrowserWindow.getAllWindows().length === 1)) {
if ([this.windowState.width, this.windowState.height, this.windowState.x, this.windowState.y].every(value => typeof value === 'number')) {
this._win.setBounds({
width: this.windowState.width!,
height: this.windowState.height!,
x: this.windowState.x!,
y: this.windowState.y!
});
}
}
if (isFullscreenOrMaximized) {
this._win.maximize();
@@ -204,12 +223,6 @@ 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;
}
@@ -297,13 +310,13 @@ export class CodeWindow extends Disposable implements ICodeWindow {
private handleMarketplaceRequests(): void {
// Resolve marketplace headers
this.marketplaceHeadersPromise = resolveMarketplaceHeaders(this.environmentService);
this.marketplaceHeadersPromise = resolveMarketplaceHeaders(pkg.version, this.environmentService, this.fileService);
// Inject headers when requests are incoming
const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*'];
this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) => {
this.marketplaceHeadersPromise.then(headers => {
const requestHeaders = objects.assign(details.requestHeaders, headers);
const requestHeaders = objects.assign(details.requestHeaders, headers) as { [key: string]: string | undefined };
if (!this.configurationService.getValue('extensions.disableExperimentalAzureSearch')) {
requestHeaders['Cookie'] = `${requestHeaders['Cookie'] ? requestHeaders['Cookie'] + ';' : ''}EnableExternalSearchForVSCode=true`;
}
@@ -327,12 +340,14 @@ export class CodeWindow extends Disposable implements ICodeWindow {
});
this._win.webContents.session.webRequest.onHeadersReceived(null!, (details, callback) => {
const contentType: string[] = (details.responseHeaders['content-type'] || details.responseHeaders['Content-Type']);
const responseHeaders = details.responseHeaders as { [key: string]: string[] };
const contentType: string[] = (responseHeaders['content-type'] || responseHeaders['Content-Type']);
if (contentType && Array.isArray(contentType) && contentType.some(x => x.toLowerCase().indexOf('image/svg') >= 0)) {
return callback({ cancel: true });
}
return callback({ cancel: false, responseHeaders: details.responseHeaders });
return callback({ cancel: false, responseHeaders });
});
// Remember that we loaded
@@ -358,9 +373,6 @@ export class CodeWindow extends Disposable implements ICodeWindow {
}
});
// App commands support
this.registerNavigationListenerOn('app-command', 'browser-backward', 'browser-forward', false);
// Window Focus
this._win.on('focus', () => {
this._lastFocusTime = Date.now();
@@ -446,30 +458,6 @@ export class CodeWindow extends Disposable implements ICodeWindow {
this.currentMenuBarVisibility = newMenuBarVisibility;
this.setMenuBarVisibility(newMenuBarVisibility);
}
// Swipe command support (macOS)
if (isMacintosh) {
const config = this.configurationService.getValue<IWorkbenchEditorConfiguration>();
if (config && config.workbench && config.workbench.editor && config.workbench.editor.swipeToNavigate) {
this.registerNavigationListenerOn('swipe', 'left', 'right', true);
} else {
this._win.removeAllListeners('swipe');
}
}
}
private registerNavigationListenerOn(command: 'swipe' | 'app-command', back: 'left' | 'browser-backward', forward: 'right' | 'browser-forward', acrossEditors: boolean) {
this._win.on(command as 'swipe' /* | 'app-command' */, (e: Electron.Event, cmd: string) => {
if (!this.isReady) {
return; // window must be ready
}
if (cmd === back) {
this.send('vscode:runAction', { id: acrossEditors ? 'workbench.action.openPreviousRecentlyUsedEditor' : 'workbench.action.navigateBack', from: 'mouse' } as IRunActionInWindowRequest);
} else if (cmd === forward) {
this.send('vscode:runAction', { id: acrossEditors ? 'workbench.action.openNextRecentlyUsedEditor' : 'workbench.action.navigateForward', from: 'mouse' } as IRunActionInWindowRequest);
}
});
}
addTabbedWindow(window: ICodeWindow): void {
@@ -599,9 +587,10 @@ export class CodeWindow extends Disposable implements ICodeWindow {
// Config (combination of process.argv and window configuration)
const environment = parseArgs(process.argv);
const config = objects.assign(environment, windowConfiguration);
for (let key in config) {
if (config[key] === undefined || config[key] === null || config[key] === '' || config[key] === false) {
delete config[key]; // only send over properties that have a true value
for (const key in config) {
const configValue = (config as any)[key];
if (configValue === undefined || configValue === null || configValue === '' || configValue === false) {
delete (config as any)[key]; // only send over properties that have a true value
}
}
@@ -675,7 +664,12 @@ export class CodeWindow extends Disposable implements ICodeWindow {
// only consider non-minimized window states
if (mode === WindowMode.Normal || mode === WindowMode.Maximized) {
const bounds = this.getBounds();
let bounds: Electron.Rectangle;
if (mode === WindowMode.Normal) {
bounds = this.getBounds();
} else {
bounds = this._win.getNormalBounds(); // make sure to persist the normal bounds when maximized to be able to restore them
}
state.x = bounds.x;
state.y = bounds.y;
@@ -686,18 +680,23 @@ export class CodeWindow extends Disposable implements ICodeWindow {
return state;
}
private restoreWindowState(state?: IWindowState): IWindowState {
private restoreWindowState(state?: IWindowState): [IWindowState, boolean? /* has multiple displays */] {
let hasMultipleDisplays = false;
if (state) {
try {
state = this.validateWindowState(state);
const displays = screen.getAllDisplays();
hasMultipleDisplays = displays.length > 1;
state = this.validateWindowState(state, displays);
} catch (err) {
this.logService.warn(`Unexpected error validating window state: ${err}\n${err.stack}`); // somehow display API can be picky about the state to validate
}
}
return state || defaultWindowState();
return [state || defaultWindowState(), hasMultipleDisplays];
}
private validateWindowState(state: IWindowState): IWindowState | undefined {
private validateWindowState(state: IWindowState, displays: Display[]): IWindowState | undefined {
if (typeof state.x !== 'number'
|| typeof state.y !== 'number'
|| typeof state.width !== 'number'
@@ -710,12 +709,10 @@ export class CodeWindow extends Disposable implements ICodeWindow {
return undefined;
}
const displays = screen.getAllDisplays();
// Single Monitor: be strict about x/y positioning
if (displays.length === 1) {
const displayWorkingArea = this.getWorkingArea(displays[0]);
if (state.mode !== WindowMode.Maximized && displayWorkingArea) {
if (displayWorkingArea) {
if (state.x < displayWorkingArea.x) {
state.x = displayWorkingArea.x; // prevent window from falling out of the screen to the left
}
@@ -741,10 +738,6 @@ export class CodeWindow extends Disposable implements ICodeWindow {
}
}
if (state.mode === WindowMode.Maximized) {
return defaultWindowState(WindowMode.Maximized); // when maximized, make sure we have good values when the user restores the window
}
return state;
}
@@ -772,14 +765,6 @@ export class CodeWindow extends Disposable implements ICodeWindow {
bounds.x + bounds.width > displayWorkingArea.x && // prevent window from falling out of the screen to the left
bounds.y + bounds.height > displayWorkingArea.y // prevent window from falling out of the scree nto the top
) {
if (state.mode === WindowMode.Maximized) {
const defaults = defaultWindowState(WindowMode.Maximized); // when maximized, make sure we have good values when the user restores the window
defaults.x = state.x; // carefull to keep x/y position so that the window ends up on the correct monitor
defaults.y = state.y;
return defaults;
}
return state;
}
@@ -853,16 +838,17 @@ export class CodeWindow extends Disposable implements ICodeWindow {
}
private useNativeFullScreen(): boolean {
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
if (!windowConfig || typeof windowConfig.nativeFullScreen !== 'boolean') {
return true; // default
}
return true; // TODO@ben enable simple fullscreen again (https://github.com/microsoft/vscode/issues/75054)
// const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
// if (!windowConfig || typeof windowConfig.nativeFullScreen !== 'boolean') {
// return true; // default
// }
if (windowConfig.nativeTabs) {
return true; // https://github.com/electron/electron/issues/16142
}
// if (windowConfig.nativeTabs) {
// return true; // https://github.com/electron/electron/issues/16142
// }
return windowConfig.nativeFullScreen !== false;
// return windowConfig.nativeFullScreen !== false;
}
isMinimized(): boolean {

View File

@@ -15,7 +15,7 @@ import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window';
import { hasArgs, asArray } from 'vs/platform/environment/node/argv';
import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences, FileFilter } from 'electron';
import { parseLineAndColumnAware } from 'vs/code/node/paths';
import { ILifecycleService, UnloadReason, LifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
import { ILifecycleService, UnloadReason, LifecycleService, LifecycleMainPhase } 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,6 +38,8 @@ 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,
@@ -153,15 +155,13 @@ interface IWorkspacePathToOpen {
label?: string;
}
export class WindowsManager implements IWindowsMainService {
export class WindowsManager extends Disposable implements IWindowsMainService {
_serviceBrand: any;
private static readonly windowsStateStorageKey = 'windowsState';
private static WINDOWS: ICodeWindow[] = [];
private initialUserEnv: IProcessEnvironment;
private static readonly WINDOWS: ICodeWindow[] = [];
private readonly windowsState: IWindowsState;
private lastClosedWindowState?: IWindowState;
@@ -169,20 +169,21 @@ export class WindowsManager implements IWindowsMainService {
private readonly dialogs: Dialogs;
private readonly workspacesManager: WorkspacesManager;
private _onWindowReady = new Emitter<ICodeWindow>();
onWindowReady: CommonEvent<ICodeWindow> = this._onWindowReady.event;
private readonly _onWindowReady = this._register(new Emitter<ICodeWindow>());
readonly onWindowReady: CommonEvent<ICodeWindow> = this._onWindowReady.event;
private _onWindowClose = new Emitter<number>();
onWindowClose: CommonEvent<number> = this._onWindowClose.event;
private readonly _onWindowClose = this._register(new Emitter<number>());
readonly onWindowClose: CommonEvent<number> = this._onWindowClose.event;
private _onWindowLoad = new Emitter<number>();
onWindowLoad: CommonEvent<number> = this._onWindowLoad.event;
private readonly _onWindowLoad = this._register(new Emitter<number>());
readonly onWindowLoad: CommonEvent<number> = this._onWindowLoad.event;
private _onWindowsCountChanged = new Emitter<IWindowsCountChangedEvent>();
onWindowsCountChanged: CommonEvent<IWindowsCountChangedEvent> = this._onWindowsCountChanged.event;
private readonly _onWindowsCountChanged = this._register(new Emitter<IWindowsCountChangedEvent>());
readonly 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,
@@ -194,6 +195,7 @@ export class WindowsManager 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);
@@ -203,12 +205,21 @@ export class WindowsManager 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.installWindowsMutex());
}
ready(initialUserEnv: IProcessEnvironment): void {
this.initialUserEnv = initialUserEnv;
this.registerListeners();
private installWindowsMutex(): void {
if (isWindows) {
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);
}
}
}
private registerListeners(): void {
@@ -543,6 +554,7 @@ export class WindowsManager 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);
@@ -639,7 +651,6 @@ export class WindowsManager 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
@@ -713,7 +724,9 @@ export class WindowsManager 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,
@@ -1289,7 +1302,7 @@ export class WindowsManager implements IWindowsMainService {
// For all other cases we first call into registerEmptyWindowBackupSync() to set it before
// loading the window.
if (options.emptyWindowBackupInfo) {
configuration.backupPath = join(this.environmentService.backupHome, options.emptyWindowBackupInfo.backupFolder);
configuration.backupPath = join(this.environmentService.backupHome.fsPath, options.emptyWindowBackupInfo.backupFolder);
}
let window: ICodeWindow | undefined;
@@ -1536,14 +1549,13 @@ export class WindowsManager implements IWindowsMainService {
return state;
}
reload(win: ICodeWindow, cli?: ParsedArgs): void {
async reload(win: ICodeWindow, cli?: ParsedArgs): Promise<void> {
// Only reload when the window has not vetoed this
this.lifecycleService.unload(win, UnloadReason.RELOAD).then(veto => {
if (!veto) {
win.reload(undefined, cli);
}
});
const veto = await this.lifecycleService.unload(win, UnloadReason.RELOAD);
if (!veto) {
win.reload(undefined, cli);
}
}
closeWorkspace(win: ICodeWindow): void {
@@ -1554,8 +1566,10 @@ export class WindowsManager implements IWindowsMainService {
});
}
enterWorkspace(win: ICodeWindow, path: URI): Promise<IEnterWorkspaceResult | undefined> {
return this.workspacesManager.enterWorkspace(win, path).then(result => result ? this.doEnterWorkspace(win, result) : undefined);
async enterWorkspace(win: ICodeWindow, path: URI): Promise<IEnterWorkspaceResult | undefined> {
const result = await this.workspacesManager.enterWorkspace(win, path);
return result ? this.doEnterWorkspace(win, result) : undefined;
}
private doEnterWorkspace(win: ICodeWindow, result: IEnterWorkspaceResult): IEnterWorkspaceResult {
@@ -1666,14 +1680,13 @@ export class WindowsManager implements IWindowsMainService {
private onWindowError(window: ICodeWindow, error: WindowError): void {
this.logService.error(error === WindowError.CRASHED ? '[VS Code]: render process crashed!' : '[VS Code]: detected unresponsive');
/* __GDPR__
"windowerror" : {
"type" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }
}
*/
this.telemetryService.publicLog('windowerror', { type: error });
type WindowErrorClassification = {
type: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
};
type WindowErrorEvent = {
type: WindowError;
};
this.telemetryService.publicLog2<WindowErrorEvent, WindowErrorClassification>('windowerror', { type: error });
// Unresponsive
if (error === WindowError.UNRESPONSIVE) {
if (window.isExtensionDevelopmentHost || window.isExtensionTestHost || (window.win && window.win.webContents && window.win.webContents.isDevToolsOpened())) {
@@ -1751,8 +1764,10 @@ export class WindowsManager 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(path => {
return dirExists(path).then(isDir => isDir ? { folderUri: URI.file(path) } : { fileUri: URI.file(path) });
const urisToOpen = await Promise.all(paths.map(async path => {
const isDir = await dirExists(path);
return isDir ? { folderUri: URI.file(path) } : { fileUri: URI.file(path) };
}));
this.open({
context: OpenContext.DIALOG,
@@ -1880,7 +1895,7 @@ class Dialogs {
this.noWindowDialogQueue = new Queue<void>();
}
pick(options: IInternalNativeOpenDialogOptions): Promise<string[] | undefined> {
async pick(options: IInternalNativeOpenDialogOptions): Promise<string[] | undefined> {
// Ensure dialog options
const dialogOptions: Electron.OpenDialogOptions = {
@@ -1913,16 +1928,16 @@ class Dialogs {
// Show Dialog
const focusedWindow = (typeof options.windowId === 'number' ? this.windowsMainService.getWindowById(options.windowId) : undefined) || this.windowsMainService.getFocusedWindow();
return this.showOpenDialog(dialogOptions, focusedWindow).then(paths => {
if (paths && paths.length > 0) {
const paths = await this.showOpenDialog(dialogOptions, focusedWindow);
if (paths && paths.length > 0) {
// Remember path in storage for next time
this.stateService.setItem(Dialogs.workingDirPickerStorageKey, dirname(paths[0]));
return paths;
}
// Remember path in storage for next time
this.stateService.setItem(Dialogs.workingDirPickerStorageKey, dirname(paths[0]));
return undefined;
});
return paths;
}
return undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-check
}
private getDialogQueue(window?: ICodeWindow): Queue<any> {
@@ -2028,28 +2043,26 @@ class WorkspacesManager {
private readonly windowsMainService: IWindowsMainService,
) { }
enterWorkspace(window: ICodeWindow, path: URI): Promise<IEnterWorkspaceResult | null> {
async enterWorkspace(window: ICodeWindow, path: URI): Promise<IEnterWorkspaceResult | null> {
if (!window || !window.win || !window.isReady) {
return Promise.resolve(null); // return early if the window is not ready or disposed
return null; // return early if the window is not ready or disposed
}
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);
});
const isValid = await this.isValidTargetWorkspacePath(window, path);
if (!isValid) {
return null; // return early if the workspace is not valid
}
return this.doOpenWorkspace(window, getWorkspaceIdentifier(path));
}
private isValidTargetWorkspacePath(window: ICodeWindow, path?: URI): Promise<boolean> {
private async isValidTargetWorkspacePath(window: ICodeWindow, path?: URI): Promise<boolean> {
if (!path) {
return Promise.resolve(true);
return true;
}
if (window.openedWorkspace && isEqual(window.openedWorkspace.configPath, path)) {
return Promise.resolve(false); // window is already opened on a workspace with that path
return false; // window is already opened on a workspace with that path
}
// Prevent overwriting a workspace that is currently opened in another window
@@ -2063,10 +2076,12 @@ class WorkspacesManager {
noLink: true
};
return this.windowsMainService.showMessageBox(options, this.windowsMainService.getFocusedWindow()).then(() => false);
await this.windowsMainService.showMessageBox(options, this.windowsMainService.getFocusedWindow());
return false;
}
return Promise.resolve(true); // OK
return true; // OK
}
private doOpenWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): IEnterWorkspaceResult {
@@ -2090,14 +2105,16 @@ class WorkspacesManager {
return { workspace, backupPath };
}
}
function resourceFromURIToOpen(u: IURIToOpen) {
function resourceFromURIToOpen(u: IURIToOpen): URI {
if (isWorkspaceToOpen(u)) {
return u.workspaceUri;
} else if (isFolderToOpen(u)) {
}
if (isFolderToOpen(u)) {
return u.folderUri;
}
return u.fileUri;
}

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { spawn, ChildProcess } from 'child_process';
import { spawn, ChildProcess, SpawnOptions } from 'child_process';
import { assign } from 'vs/base/common/objects';
import { buildHelpMessage, buildVersionMessage, addArg, createWaitMarkerFile } from 'vs/platform/environment/node/argv';
import { parseCLIProcessArgv } from 'vs/platform/environment/node/argvHelper';
@@ -19,13 +19,15 @@ import { resolveTerminalEncoding } from 'vs/base/node/encoding';
import * as iconv from 'iconv-lite';
import { isWindows } from 'vs/base/common/platform';
import { ProfilingSession, Target } from 'v8-inspect-profiler';
import { isString } from 'vs/base/common/types';
function shouldSpawnCliProcess(argv: ParsedArgs): boolean {
return !!argv['install-source']
|| !!argv['list-extensions']
|| !!argv['install-extension']
|| !!argv['uninstall-extension']
|| !!argv['locate-extension'];
|| !!argv['locate-extension']
|| !!argv['telemetry'];
}
interface IMainCli {
@@ -57,6 +59,7 @@ 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;
}
@@ -124,7 +127,7 @@ export async function main(argv: string[]): Promise<any> {
const processCallbacks: ((child: ChildProcess) => Promise<any>)[] = [];
const verbose = args.verbose || args.status || typeof args['upload-logs'] !== 'undefined';
const verbose = args.verbose || args.status;
if (verbose) {
env['ELECTRON_ENABLE_LOGGING'] = '1';
@@ -257,7 +260,7 @@ export async function main(argv: string[]): Promise<any> {
addArg(argv, `--prof-startup-prefix`, filenamePrefix);
addArg(argv, `--no-cached-data`);
fs.writeFileSync(filenamePrefix, argv.slice(-6).join('|'));
writeFileSync(filenamePrefix, argv.slice(-6).join('|'));
processCallbacks.push(async _child => {
@@ -329,7 +332,7 @@ export async function main(argv: string[]): Promise<any> {
await extHost.stop();
// re-create the marker file to signal that profiling is done
fs.writeFileSync(filenamePrefix, '');
writeFileSync(filenamePrefix, '');
} catch (e) {
console.error('Failed to profile startup. Make sure to quit Code first.');
@@ -337,21 +340,20 @@ export async function main(argv: string[]): Promise<any> {
});
}
if (args['js-flags']) {
const match = /max_old_space_size=(\d+)/g.exec(args['js-flags']);
const jsFlags = args['js-flags'];
if (isString(jsFlags)) {
const match = /max_old_space_size=(\d+)/g.exec(jsFlags);
if (match && !args['max-memory']) {
addArg(argv, `--max-memory=${match[1]}`);
}
}
const options = {
const options: SpawnOptions = {
detached: true,
env
};
if (typeof args['upload-logs'] !== 'undefined') {
options['stdio'] = ['pipe', 'pipe', 'pipe'];
} else if (!verbose) {
if (!verbose) {
options['stdio'] = 'ignore';
}

View File

@@ -17,12 +17,12 @@ import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { IExtensionManagementService, IExtensionGalleryService, IGalleryExtension, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { combinedAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
import { IRequestService } from 'vs/platform/request/node/request';
import { IRequestService } from 'vs/platform/request/common/request';
import { RequestService } from 'vs/platform/request/node/requestService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
@@ -31,7 +31,6 @@ 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';
@@ -41,10 +40,18 @@ 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';
import { buildTelemetryMessage } from 'vs/platform/telemetry/node/telemetry';
import { FileService } from 'vs/platform/files/common/fileService';
import { IFileService } from 'vs/platform/files/common/files';
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { IProductService } from 'vs/platform/product/common/product';
import { ProductService } from 'vs/platform/product/node/productService';
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, eg: {0}", 'ms-vscode.csharp');
const useId = localize('useId', "Make sure you use the full extension ID, including the publisher, e.g.: {0}", 'ms-vscode.csharp');
function getId(manifest: IExtensionManifest, withVersion?: boolean): string {
if (withVersion) {
@@ -84,7 +91,7 @@ export class Main {
} else if (argv['install-extension']) {
const arg = argv['install-extension'];
const args: string[] = typeof arg === 'string' ? [arg] : arg;
await this.installExtensions(args, argv['force']);
await this.installExtensions(args, !!argv['force']);
} else if (argv['uninstall-extension']) {
const arg = argv['uninstall-extension'];
@@ -94,6 +101,8 @@ export class Main {
const arg = argv['locate-extension'];
const ids: string[] = typeof arg === 'string' ? [arg] : arg;
await this.locateExtension(ids);
} else if (argv['telemetry']) {
console.log(buildTelemetryMessage(this.environmentService.appRoot, this.environmentService.extensionsPath));
}
}
@@ -275,59 +284,79 @@ export class Main {
const eventPrefix = 'monacoworkbench';
export function main(argv: ParsedArgs): Promise<void> {
export async function main(argv: ParsedArgs): Promise<void> {
const services = new ServiceCollection();
const disposables = new DisposableStore();
const environmentService = new EnvironmentService(argv, process.execPath);
const logService = createBufferSpdLogService('cli', getLogLevel(environmentService), environmentService.logsPath);
const logService: ILogService = new SpdLogService('cli', environmentService.logsPath, getLogLevel(environmentService));
process.once('exit', () => logService.dispose());
logService.info('main', argv);
await Promise.all([environmentService.appSettingsHome.fsPath, environmentService.extensionsPath].map(p => mkdirp(p)));
const configurationService = new ConfigurationService(environmentService.settingsResource);
disposables.add(configurationService);
await configurationService.initialize();
services.set(IEnvironmentService, environmentService);
services.set(ILogService, logService);
services.set(IConfigurationService, configurationService);
services.set(IStateService, new SyncDescriptor(StateService));
services.set(IProductService, new SyncDescriptor(ProductService));
// Files
const fileService = new FileService(logService);
disposables.add(fileService);
services.set(IFileService, fileService);
const diskFileSystemProvider = new DiskFileSystemProvider(logService);
disposables.add(diskFileSystemProvider);
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
const instantiationService: IInstantiationService = new InstantiationService(services);
return instantiationService.invokeFunction(accessor => {
return instantiationService.invokeFunction(async accessor => {
const envService = accessor.get(IEnvironmentService);
const stateService = accessor.get(IStateService);
return Promise.all([envService.appSettingsHome, envService.extensionsPath].map(p => mkdirp(p))).then(() => {
const { appRoot, extensionsPath, extensionDevelopmentLocationURI: extensionDevelopmentLocationURI, isBuilt, installSourcePath } = envService;
const { appRoot, extensionsPath, extensionDevelopmentLocationURI: extensionDevelopmentLocationURI, isBuilt, installSourcePath } = envService;
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 services = new ServiceCollection();
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));
}
services.set(IRequestService, new SyncDescriptor(RequestService));
services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
const config: ITelemetryServiceConfig = {
appender: combinedAppender(...appenders),
commonProperties: resolveCommonProperties(product.commit, pkg.version, stateService.getItem('telemetry.machineId'), installSourcePath),
piiPaths: [appRoot, extensionsPath]
};
const appenders: AppInsightsAppender[] = [];
if (isBuilt && !extensionDevelopmentLocationURI && !envService.args['disable-telemetry'] && product.enableTelemetry) {
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config]));
} else {
services.set(ITelemetryService, NullTelemetryService);
if (product.aiConfig && product.aiConfig.asimovKey) {
appenders.push(new AppInsightsAppender(eventPrefix, null, product.aiConfig.asimovKey, logService));
}
const instantiationService2 = instantiationService.createChild(services);
const main = instantiationService2.createInstance(Main);
const config: ITelemetryServiceConfig = {
appender: combinedAppender(...appenders),
commonProperties: resolveCommonProperties(product.commit, pkg.version, stateService.getItem('telemetry.machineId'), installSourcePath),
piiPaths: [appRoot, extensionsPath]
};
return main.run(argv).then(() => {
// Dispose the AI adapter so that remaining data gets flushed.
return combinedAppender(...appenders).dispose();
});
});
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config]));
// Dispose the AI adapter so that remaining data gets flushed.
disposables.add(combinedAppender(...appenders));
} else {
services.set(ITelemetryService, NullTelemetryService);
}
const instantiationService2 = instantiationService.createChild(services);
const main = instantiationService2.createInstance(Main);
try {
await main.run(argv);
} finally {
disposables.dispose();
}
});
}

View File

@@ -8,6 +8,7 @@ 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) => {
@@ -89,13 +90,16 @@ 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): Promise<typeof process.env> {
export function getShellEnvironment(logService: ILogService, environmentService: IEnvironmentService): Promise<typeof process.env> {
if (_shellEnv === undefined) {
if (isWindows) {
logService.trace('getShellEnvironment: runing on windows, skipping');
if (environmentService.args['disable-user-env-probe']) {
logService.trace('getShellEnvironment: disable-user-env-probe set, skipping');
_shellEnv = Promise.resolve({});
} else if (isWindows) {
logService.trace('getShellEnvironment: running on Windows, skipping');
_shellEnv = Promise.resolve({});
} else if (process.env['VSCODE_CLI'] === '1') {
logService.trace('getShellEnvironment: runing on CLI, skipping');
logService.trace('getShellEnvironment: running on CLI, skipping');
_shellEnv = Promise.resolve({});
} else {
logService.trace('getShellEnvironment: running on Unix');

View File

@@ -0,0 +1,28 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { isWindows } from 'vs/base/common/platform';
suite('Windows Native Helpers', () => {
test('windows-mutex', async () => {
if (!isWindows) {
return;
}
const mutex = await import('windows-mutex');
assert.ok(mutex, 'Unable to load windows-mutex dependency.');
assert.ok(typeof mutex.isActive === 'function', 'Unable to load windows-mutex dependency.');
});
test('windows-foreground-love', async () => {
if (!isWindows) {
return;
}
const foregroundLove = await import('windows-foreground-love');
assert.ok(foregroundLove, 'Unable to load windows-foreground-love dependency.');
});
});

View File

@@ -4,10 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { formatOptions, Option, addArg } from 'vs/platform/environment/node/argv';
import { ParsedArgs } from 'vs/platform/environment/common/environment';
suite('formatOptions', () => {
function o(id: string, description: string): Option {
function o(id: keyof ParsedArgs, description: string): Option {
return {
id, description, type: 'string'
};
@@ -16,30 +17,30 @@ suite('formatOptions', () => {
test('Text should display small columns correctly', () => {
assert.deepEqual(
formatOptions([
o('foo', 'bar')
o('add', 'bar')
], 80),
[' --foo bar']
[' --add bar']
);
assert.deepEqual(
formatOptions([
o('f', 'bar'),
o('fo', 'ba'),
o('foo', 'b')
o('add', 'bar'),
o('wait', 'ba'),
o('trace', 'b')
], 80),
[
' --f bar',
' --fo ba',
' --foo b'
' --add bar',
' --wait ba',
' --trace b'
]);
});
test('Text should wrap', () => {
assert.deepEqual(
formatOptions([
o('foo', (<any>'bar ').repeat(9))
o('add', (<any>'bar ').repeat(9))
], 40),
[
' --foo bar bar bar bar bar bar bar bar',
' --add bar bar bar bar bar bar bar bar',
' bar'
]);
});
@@ -47,10 +48,10 @@ suite('formatOptions', () => {
test('Text should revert to the condensed view when the terminal is too narrow', () => {
assert.deepEqual(
formatOptions([
o('foo', (<any>'bar ').repeat(9))
o('add', (<any>'bar ').repeat(9))
], 30),
[
' --foo',
' --add',
' bar bar bar bar bar bar bar bar bar '
]);
});