mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-24 01:25:37 -05:00
Merge VS Code 1.21 source code (#1067)
* Initial VS Code 1.21 file copy with patches * A few more merges * Post npm install * Fix batch of build breaks * Fix more build breaks * Fix more build errors * Fix more build breaks * Runtime fixes 1 * Get connection dialog working with some todos * Fix a few packaging issues * Copy several node_modules to package build to fix loader issues * Fix breaks from master * A few more fixes * Make tests pass * First pass of license header updates * Second pass of license header updates * Fix restore dialog issues * Remove add additional themes menu items * fix select box issues where the list doesn't show up * formatting * Fix editor dispose issue * Copy over node modules to correct location on all platforms
This commit is contained in:
@@ -12,6 +12,7 @@ function createModuleDescription(name, exclude) {
|
||||
excludes = excludes.concat(exclude);
|
||||
}
|
||||
result.exclude= excludes;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -20,6 +21,7 @@ exports.collectModules= function() {
|
||||
createModuleDescription('vs/code/electron-main/main', []),
|
||||
createModuleDescription('vs/code/node/cli', []),
|
||||
createModuleDescription('vs/code/node/cliProcessMain', ['vs/code/node/cli']),
|
||||
createModuleDescription('vs/code/electron-browser/sharedProcessMain', [])
|
||||
createModuleDescription('vs/code/electron-browser/sharedProcess/sharedProcessMain', []),
|
||||
createModuleDescription('vs/code/electron-browser/issue/issueReporterMain', [])
|
||||
];
|
||||
};
|
||||
17
src/vs/code/electron-browser/issue/issueReporter.html
Normal file
17
src/vs/code/electron-browser/issue/issueReporter.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data:; media-src 'none'; child-src 'self'; object-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' https:; font-src 'self' https:;">
|
||||
<style>body{display: none}</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
</body>
|
||||
|
||||
<!-- Startup via issueReporter.js -->
|
||||
<script src="issueReporter.js"></script>
|
||||
|
||||
</html>
|
||||
174
src/vs/code/electron-browser/issue/issueReporter.js
Normal file
174
src/vs/code/electron-browser/issue/issueReporter.js
Normal file
@@ -0,0 +1,174 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const remote = require('electron').remote;
|
||||
|
||||
function parseURLQueryArgs() {
|
||||
const search = window.location.search || '';
|
||||
|
||||
return search.split(/[?&]/)
|
||||
.filter(function (param) { return !!param; })
|
||||
.map(function (param) { return param.split('='); })
|
||||
.filter(function (param) { return param.length === 2; })
|
||||
.reduce(function (r, param) { r[param[0]] = decodeURIComponent(param[1]); return r; }, {});
|
||||
}
|
||||
|
||||
function createScript(src, onload) {
|
||||
const script = document.createElement('script');
|
||||
script.src = src;
|
||||
script.addEventListener('load', onload);
|
||||
|
||||
const head = document.getElementsByTagName('head')[0];
|
||||
head.insertBefore(script, head.lastChild);
|
||||
}
|
||||
|
||||
function uriFromPath(_path) {
|
||||
var pathName = path.resolve(_path).replace(/\\/g, '/');
|
||||
if (pathName.length > 0 && pathName.charAt(0) !== '/') {
|
||||
pathName = '/' + pathName;
|
||||
}
|
||||
|
||||
return encodeURI('file://' + pathName);
|
||||
}
|
||||
|
||||
function readFile(file) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
fs.readFile(file, 'utf8', function(err, data) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function main() {
|
||||
const args = parseURLQueryArgs();
|
||||
const configuration = JSON.parse(args['config'] || '{}') || {};
|
||||
|
||||
//#region Add support for using node_modules.asar
|
||||
(function () {
|
||||
const path = require('path');
|
||||
const Module = require('module');
|
||||
let NODE_MODULES_PATH = path.join(configuration.appRoot, 'node_modules');
|
||||
if (/[a-z]\:/.test(NODE_MODULES_PATH)) {
|
||||
// Make drive letter uppercase
|
||||
NODE_MODULES_PATH = NODE_MODULES_PATH.charAt(0).toUpperCase() + NODE_MODULES_PATH.substr(1);
|
||||
}
|
||||
const NODE_MODULES_ASAR_PATH = NODE_MODULES_PATH + '.asar';
|
||||
|
||||
const originalResolveLookupPaths = Module._resolveLookupPaths;
|
||||
Module._resolveLookupPaths = function (request, parent) {
|
||||
const result = originalResolveLookupPaths(request, parent);
|
||||
|
||||
const paths = result[1];
|
||||
for (let i = 0, len = paths.length; i < len; i++) {
|
||||
if (paths[i] === NODE_MODULES_PATH) {
|
||||
paths.splice(i, 0, NODE_MODULES_ASAR_PATH);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
//#endregion
|
||||
|
||||
const extractKey = function (e) {
|
||||
return [
|
||||
e.ctrlKey ? 'ctrl-' : '',
|
||||
e.metaKey ? 'meta-' : '',
|
||||
e.altKey ? 'alt-' : '',
|
||||
e.shiftKey ? 'shift-' : '',
|
||||
e.keyCode
|
||||
].join('');
|
||||
};
|
||||
|
||||
const TOGGLE_DEV_TOOLS_KB = (process.platform === 'darwin' ? 'meta-alt-73' : 'ctrl-shift-73'); // mac: Cmd-Alt-I, rest: Ctrl-Shift-I
|
||||
const RELOAD_KB = (process.platform === 'darwin' ? 'meta-82' : 'ctrl-82'); // mac: Cmd-R, rest: Ctrl-R
|
||||
|
||||
window.addEventListener('keydown', function (e) {
|
||||
const key = extractKey(e);
|
||||
if (key === TOGGLE_DEV_TOOLS_KB) {
|
||||
remote.getCurrentWebContents().toggleDevTools();
|
||||
} else if (key === RELOAD_KB) {
|
||||
remote.getCurrentWindow().reload();
|
||||
}
|
||||
});
|
||||
|
||||
// Load the loader and start loading the workbench
|
||||
const rootUrl = uriFromPath(configuration.appRoot) + '/out';
|
||||
|
||||
// Get the nls configuration into the process.env as early as possible.
|
||||
var nlsConfig = { availableLanguages: {} };
|
||||
const config = process.env['VSCODE_NLS_CONFIG'];
|
||||
if (config) {
|
||||
process.env['VSCODE_NLS_CONFIG'] = config;
|
||||
try {
|
||||
nlsConfig = JSON.parse(config);
|
||||
} catch (e) { /*noop*/ }
|
||||
}
|
||||
|
||||
if (nlsConfig._resolvedLanguagePackCoreLocation) {
|
||||
let bundles = Object.create(null);
|
||||
nlsConfig.loadBundle = function(bundle, language, cb) {
|
||||
let result = bundles[bundle];
|
||||
if (result) {
|
||||
cb(undefined, result);
|
||||
return;
|
||||
}
|
||||
let bundleFile = path.join(nlsConfig._resolvedLanguagePackCoreLocation, bundle.replace(/\//g, '!') + '.nls.json');
|
||||
readFile(bundleFile).then(function (content) {
|
||||
let json = JSON.parse(content);
|
||||
bundles[bundle] = json;
|
||||
cb(undefined, json);
|
||||
})
|
||||
.catch(cb);
|
||||
};
|
||||
}
|
||||
|
||||
var locale = nlsConfig.availableLanguages['*'] || 'en';
|
||||
if (locale === 'zh-tw') {
|
||||
locale = 'zh-Hant';
|
||||
} else if (locale === 'zh-cn') {
|
||||
locale = 'zh-Hans';
|
||||
}
|
||||
|
||||
window.document.documentElement.setAttribute('lang', locale);
|
||||
|
||||
// In the bundled version the nls plugin is packaged with the loader so the NLS Plugins
|
||||
// loads as soon as the loader loads. To be able to have pseudo translation
|
||||
createScript(rootUrl + '/vs/loader.js', function () {
|
||||
var define = global.define;
|
||||
global.define = undefined;
|
||||
define('fs', ['original-fs'], function (originalFS) { return originalFS; }); // replace the patched electron fs with the original node fs for all AMD code
|
||||
|
||||
window.MonacoEnvironment = {};
|
||||
|
||||
require.config({
|
||||
baseUrl: rootUrl,
|
||||
'vs/nls': nlsConfig,
|
||||
nodeCachedDataDir: configuration.nodeCachedDataDir,
|
||||
nodeModules: [/*BUILD->INSERT_NODE_MODULES*/]
|
||||
});
|
||||
|
||||
if (nlsConfig.pseudo) {
|
||||
require(['vs/nls'], function (nlsPlugin) {
|
||||
nlsPlugin.setPseudoTranslation(nlsConfig.pseudo);
|
||||
});
|
||||
}
|
||||
|
||||
require(['vs/code/electron-browser/issue/issueReporterMain'], (issueReporter) => {
|
||||
issueReporter.startup(configuration);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
779
src/vs/code/electron-browser/issue/issueReporterMain.ts
Normal file
779
src/vs/code/electron-browser/issue/issueReporterMain.ts
Normal file
@@ -0,0 +1,779 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/issueReporter';
|
||||
import { shell, ipcRenderer, webFrame, remote, clipboard } from 'electron';
|
||||
import { localize } from 'vs/nls';
|
||||
import { $ } from 'vs/base/browser/dom';
|
||||
import * as collections from 'vs/base/common/collections';
|
||||
import * as browser from 'vs/base/browser/browser';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
import product from 'vs/platform/node/product';
|
||||
import pkg from 'vs/platform/node/package';
|
||||
import * as os from 'os';
|
||||
import { debounce } from 'vs/base/common/decorators';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Client as ElectronIPCClient } from 'vs/base/parts/ipc/electron-browser/ipc.electron-browser';
|
||||
import { getDelayedChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IWindowConfiguration, IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
|
||||
import { ITelemetryAppenderChannel, TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
|
||||
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
|
||||
import { WindowsChannelClient } from 'vs/platform/windows/common/windowsIpc';
|
||||
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
import { IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel';
|
||||
import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssueReporterData, IssueReporterFeatures } from 'vs/platform/issue/common/issue';
|
||||
import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage';
|
||||
import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
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';
|
||||
|
||||
const MAX_URL_LENGTH = platform.isWindows ? 2081 : 5400;
|
||||
|
||||
interface SearchResult {
|
||||
html_url: string;
|
||||
title: string;
|
||||
state?: string;
|
||||
}
|
||||
|
||||
export interface IssueReporterConfiguration extends IWindowConfiguration {
|
||||
data: IssueReporterData;
|
||||
features: IssueReporterFeatures;
|
||||
}
|
||||
|
||||
export function startup(configuration: IssueReporterConfiguration) {
|
||||
document.body.innerHTML = BaseHtml();
|
||||
const issueReporter = new IssueReporter(configuration);
|
||||
issueReporter.render();
|
||||
document.body.style.display = 'block';
|
||||
}
|
||||
|
||||
export class IssueReporter extends Disposable {
|
||||
private environmentService: IEnvironmentService;
|
||||
private telemetryService: ITelemetryService;
|
||||
private logService: ILogService;
|
||||
private issueReporterModel: IssueReporterModel;
|
||||
private numberOfSearchResultsDisplayed = 0;
|
||||
private receivedSystemInfo = false;
|
||||
private receivedPerformanceInfo = false;
|
||||
|
||||
constructor(configuration: IssueReporterConfiguration) {
|
||||
super();
|
||||
|
||||
this.initServices(configuration);
|
||||
|
||||
this.issueReporterModel = new IssueReporterModel({
|
||||
issueType: configuration.data.issueType || IssueType.Bug,
|
||||
versionInfo: {
|
||||
vscodeVersion: `${pkg.name} ${pkg.version} (${product.commit || 'Commit unknown'}, ${product.date || 'Date unknown'})`,
|
||||
os: `${os.type()} ${os.arch()} ${os.release()}`
|
||||
},
|
||||
extensionsDisabled: this.environmentService.disableExtensions,
|
||||
});
|
||||
|
||||
ipcRenderer.on('issuePerformanceInfoResponse', (event, info) => {
|
||||
this.logService.trace('issueReporter: Received performance data');
|
||||
this.issueReporterModel.update(info);
|
||||
this.receivedPerformanceInfo = true;
|
||||
|
||||
const state = this.issueReporterModel.getData();
|
||||
this.updateProcessInfo(state);
|
||||
this.updateWorkspaceInfo(state);
|
||||
this.updatePreviewButtonState();
|
||||
});
|
||||
|
||||
ipcRenderer.on('issueSystemInfoResponse', (event, info) => {
|
||||
this.logService.trace('issueReporter: Received system data');
|
||||
this.issueReporterModel.update({ systemInfo: info });
|
||||
this.receivedSystemInfo = true;
|
||||
|
||||
this.updateSystemInfo(this.issueReporterModel.getData());
|
||||
this.updatePreviewButtonState();
|
||||
});
|
||||
|
||||
ipcRenderer.send('issueSystemInfoRequest');
|
||||
ipcRenderer.send('issuePerformanceInfoRequest');
|
||||
this.logService.trace('issueReporter: Sent data requests');
|
||||
|
||||
if (window.document.documentElement.lang !== 'en') {
|
||||
show(document.getElementById('english'));
|
||||
}
|
||||
|
||||
this.setUpTypes();
|
||||
this.setEventHandlers();
|
||||
this.applyZoom(configuration.data.zoomLevel);
|
||||
this.applyStyles(configuration.data.styles);
|
||||
this.handleExtensionData(configuration.data.enabledExtensions);
|
||||
|
||||
if (configuration.data.issueType === IssueType.SettingsSearchIssue) {
|
||||
this.handleSettingsSearchData(<ISettingsSearchIssueReporterData>configuration.data);
|
||||
}
|
||||
}
|
||||
|
||||
render(): void {
|
||||
this.renderBlocks();
|
||||
}
|
||||
|
||||
private applyZoom(zoomLevel: number) {
|
||||
webFrame.setZoomLevel(zoomLevel);
|
||||
browser.setZoomFactor(webFrame.getZoomFactor());
|
||||
// See https://github.com/Microsoft/vscode/issues/26151
|
||||
// Cannot be trusted because the webFrame might take some time
|
||||
// until it really applies the new zoom level
|
||||
browser.setZoomLevel(webFrame.getZoomLevel(), /*isTrusted*/false);
|
||||
}
|
||||
|
||||
private applyStyles(styles: IssueReporterStyles) {
|
||||
const styleTag = document.createElement('style');
|
||||
const content: string[] = [];
|
||||
|
||||
if (styles.inputBackground) {
|
||||
content.push(`input[type="text"], textarea, select, .issues-container > .issue > .issue-state { background-color: ${styles.inputBackground}; }`);
|
||||
}
|
||||
|
||||
if (styles.inputBorder) {
|
||||
content.push(`input[type="text"], textarea, select { border: 1px solid ${styles.inputBorder}; }`);
|
||||
} else {
|
||||
content.push(`input[type="text"], textarea, select { border: 1px solid transparent; }`);
|
||||
}
|
||||
|
||||
if (styles.inputForeground) {
|
||||
content.push(`input[type="text"], textarea, select, .issues-container > .issue > .issue-state { color: ${styles.inputForeground}; }`);
|
||||
}
|
||||
|
||||
if (styles.inputErrorBorder) {
|
||||
content.push(`.invalid-input, .invalid-input:focus { border: 1px solid ${styles.inputErrorBorder} !important; }`);
|
||||
content.push(`.validation-error, .required-input { color: ${styles.inputErrorBorder}; }`);
|
||||
}
|
||||
|
||||
if (styles.inputActiveBorder) {
|
||||
content.push(`input[type='text']:focus, textarea:focus, select:focus, summary:focus, button:focus { border: 1px solid ${styles.inputActiveBorder}; outline-style: none; }`);
|
||||
}
|
||||
|
||||
if (styles.textLinkColor) {
|
||||
content.push(`a, .workbenchCommand { color: ${styles.textLinkColor}; }`);
|
||||
}
|
||||
|
||||
if (styles.buttonBackground) {
|
||||
content.push(`button { background-color: ${styles.buttonBackground}; }`);
|
||||
}
|
||||
|
||||
if (styles.buttonForeground) {
|
||||
content.push(`button { color: ${styles.buttonForeground}; }`);
|
||||
}
|
||||
|
||||
if (styles.buttonHoverBackground) {
|
||||
content.push(`#github-submit-btn:hover:enabled, #github-submit-btn:focus:enabled { background-color: ${styles.buttonHoverBackground}; }`);
|
||||
}
|
||||
|
||||
if (styles.textLinkColor) {
|
||||
content.push(`a { color: ${styles.textLinkColor}; }`);
|
||||
}
|
||||
|
||||
if (styles.sliderBackgroundColor) {
|
||||
content.push(`::-webkit-scrollbar-thumb { background-color: ${styles.sliderBackgroundColor}; }`);
|
||||
}
|
||||
|
||||
if (styles.sliderActiveColor) {
|
||||
content.push(`::-webkit-scrollbar-thumb:active { background-color: ${styles.sliderActiveColor}; }`);
|
||||
}
|
||||
|
||||
if (styles.sliderHoverColor) {
|
||||
content.push(`::--webkit-scrollbar-thumb:hover { background-color: ${styles.sliderHoverColor}; }`);
|
||||
}
|
||||
|
||||
styleTag.innerHTML = content.join('\n');
|
||||
document.head.appendChild(styleTag);
|
||||
document.body.style.color = styles.color;
|
||||
}
|
||||
|
||||
private handleExtensionData(extensions: ILocalExtension[]) {
|
||||
const { nonThemes, themes } = collections.groupBy(extensions, ext => {
|
||||
const manifestKeys = ext.manifest.contributes ? Object.keys(ext.manifest.contributes) : [];
|
||||
const onlyTheme = !ext.manifest.activationEvents && manifestKeys.length === 1 && manifestKeys[0] === 'themes';
|
||||
return onlyTheme ? 'themes' : 'nonThemes';
|
||||
});
|
||||
|
||||
const numberOfThemeExtesions = themes && themes.length;
|
||||
this.issueReporterModel.update({ numberOfThemeExtesions, enabledNonThemeExtesions: nonThemes });
|
||||
this.updateExtensionTable(nonThemes, numberOfThemeExtesions);
|
||||
|
||||
if (this.environmentService.disableExtensions || extensions.length === 0) {
|
||||
(<HTMLButtonElement>document.getElementById('disableExtensions')).disabled = true;
|
||||
(<HTMLInputElement>document.getElementById('reproducesWithoutExtensions')).checked = true;
|
||||
this.issueReporterModel.update({ reprosWithoutExtensions: true });
|
||||
}
|
||||
}
|
||||
|
||||
private handleSettingsSearchData(data: ISettingsSearchIssueReporterData): void {
|
||||
this.issueReporterModel.update({
|
||||
actualSearchResults: data.actualSearchResults,
|
||||
query: data.query,
|
||||
filterResultCount: data.filterResultCount
|
||||
});
|
||||
this.updateSearchedExtensionTable(data.enabledExtensions);
|
||||
this.updateSettingsSearchDetails(data);
|
||||
}
|
||||
|
||||
private updateSettingsSearchDetails(data: ISettingsSearchIssueReporterData): void {
|
||||
const target = document.querySelector('.block-settingsSearchResults .block-info');
|
||||
|
||||
const details = `
|
||||
<div class='block-settingsSearchResults-details'>
|
||||
<div>Query: "${data.query}"</div>
|
||||
<div>Literal match count: ${data.filterResultCount}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
let table = `
|
||||
<tr>
|
||||
<th>Setting</th>
|
||||
<th>Extension</th>
|
||||
<th>Score</th>
|
||||
</tr>`;
|
||||
|
||||
data.actualSearchResults
|
||||
.forEach(setting => {
|
||||
table += `
|
||||
<tr>
|
||||
<td>${setting.key}</td>
|
||||
<td>${setting.extensionId}</td>
|
||||
<td>${String(setting.score).slice(0, 5)}</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
target.innerHTML = `${details}<table>${table}</table>`;
|
||||
}
|
||||
|
||||
private initServices(configuration: IWindowConfiguration): void {
|
||||
const serviceCollection = new ServiceCollection();
|
||||
const mainProcessClient = new ElectronIPCClient(String(`window${configuration.windowId}`));
|
||||
|
||||
const windowsChannel = mainProcessClient.getChannel('windows');
|
||||
serviceCollection.set(IWindowsService, new WindowsChannelClient(windowsChannel));
|
||||
this.environmentService = new EnvironmentService(configuration, configuration.execPath);
|
||||
|
||||
const logService = createSpdLogService(`issuereporter${configuration.windowId}`, getLogLevel(this.environmentService), this.environmentService.logsPath);
|
||||
const logLevelClient = new LogLevelSetterChannelClient(mainProcessClient.getChannel('loglevel'));
|
||||
this.logService = new FollowerLogService(logLevelClient, logService);
|
||||
|
||||
const sharedProcess = (<IWindowsService>serviceCollection.get(IWindowsService)).whenSharedProcessReady()
|
||||
.then(() => connectNet(this.environmentService.sharedIPCHandle, `window:${configuration.windowId}`));
|
||||
|
||||
const instantiationService = new InstantiationService(serviceCollection, true);
|
||||
if (this.environmentService.isBuilt && !this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
|
||||
const channel = getDelayedChannel<ITelemetryAppenderChannel>(sharedProcess.then(c => c.getChannel('telemetryAppender')));
|
||||
const appender = new TelemetryAppenderClient(channel);
|
||||
const commonProperties = resolveCommonProperties(product.commit, pkg.version, configuration.machineId, this.environmentService.installSourcePath);
|
||||
const piiPaths = [this.environmentService.appRoot, this.environmentService.extensionsPath];
|
||||
const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths };
|
||||
|
||||
const telemetryService = instantiationService.createInstance(TelemetryService, config);
|
||||
this._register(telemetryService);
|
||||
|
||||
this.telemetryService = telemetryService;
|
||||
} else {
|
||||
this.telemetryService = NullTelemetryService;
|
||||
}
|
||||
}
|
||||
|
||||
private setEventHandlers(): void {
|
||||
this.addEventListener('issue-type', 'change', (event: Event) => {
|
||||
this.issueReporterModel.update({ issueType: parseInt((<HTMLInputElement>event.target).value) });
|
||||
this.updatePreviewButtonState();
|
||||
this.render();
|
||||
});
|
||||
|
||||
['includeSystemInfo', 'includeProcessInfo', 'includeWorkspaceInfo', 'includeExtensions', 'includeSearchedExtensions', 'includeSettingsSearchDetails'].forEach(elementId => {
|
||||
this.addEventListener(elementId, 'click', (event: Event) => {
|
||||
event.stopPropagation();
|
||||
this.issueReporterModel.update({ [elementId]: !this.issueReporterModel.getData()[elementId] });
|
||||
});
|
||||
});
|
||||
|
||||
const labelElements = document.getElementsByClassName('caption');
|
||||
for (let i = 0; i < labelElements.length; i++) {
|
||||
const label = labelElements.item(i);
|
||||
label.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
// Stop propgagation not working as expected in this case https://bugs.chromium.org/p/chromium/issues/detail?id=809801
|
||||
// preventDefault does prevent outer details tag from toggling, so use that and manually toggle the checkbox
|
||||
e.preventDefault();
|
||||
const containingDiv = (<HTMLLabelElement>e.target).parentElement;
|
||||
const checkbox = <HTMLInputElement>containingDiv.firstElementChild;
|
||||
if (checkbox) {
|
||||
checkbox.checked = !checkbox.checked;
|
||||
this.issueReporterModel.update({ [checkbox.id]: !this.issueReporterModel.getData()[checkbox.id] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.addEventListener('reproducesWithoutExtensions', 'click', (e) => {
|
||||
this.issueReporterModel.update({ reprosWithoutExtensions: true });
|
||||
});
|
||||
|
||||
this.addEventListener('reproducesWithExtensions', 'click', (e) => {
|
||||
this.issueReporterModel.update({ reprosWithoutExtensions: false });
|
||||
});
|
||||
|
||||
this.addEventListener('description', 'input', (event: Event) => {
|
||||
const issueDescription = (<HTMLInputElement>event.target).value;
|
||||
this.issueReporterModel.update({ issueDescription });
|
||||
|
||||
const title = (<HTMLInputElement>document.getElementById('issue-title')).value;
|
||||
if (title || issueDescription) {
|
||||
this.searchDuplicates(title, issueDescription);
|
||||
} else {
|
||||
this.clearSearchResults();
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener('issue-title', 'input', (e) => {
|
||||
const description = this.issueReporterModel.getData().issueDescription;
|
||||
const title = (<HTMLInputElement>event.target).value;
|
||||
|
||||
const lengthValidationMessage = document.getElementById('issue-title-length-validation-error');
|
||||
if (title && this.getIssueUrlWithTitle(title).length > MAX_URL_LENGTH) {
|
||||
show(lengthValidationMessage);
|
||||
} else {
|
||||
hide(lengthValidationMessage);
|
||||
}
|
||||
|
||||
if (title || description) {
|
||||
this.searchDuplicates(title, description);
|
||||
} else {
|
||||
this.clearSearchResults();
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener('github-submit-btn', 'click', () => this.createIssue());
|
||||
|
||||
this.addEventListener('disableExtensions', 'click', () => {
|
||||
ipcRenderer.send('workbenchCommand', 'workbench.action.reloadWindowWithExtensionsDisabled');
|
||||
});
|
||||
|
||||
this.addEventListener('disableExtensions', 'keydown', (e: KeyboardEvent) => {
|
||||
if (e.keyCode === 13 || e.keyCode === 32) {
|
||||
ipcRenderer.send('workbenchCommand', 'workbench.extensions.action.disableAll');
|
||||
ipcRenderer.send('workbenchCommand', 'workbench.action.reloadWindow');
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener('showRunning', 'click', () => {
|
||||
ipcRenderer.send('workbenchCommand', 'workbench.action.showRuntimeExtensions');
|
||||
});
|
||||
|
||||
this.addEventListener('showRunning', 'keydown', (e: KeyboardEvent) => {
|
||||
if (e.keyCode === 13 || e.keyCode === 32) {
|
||||
ipcRenderer.send('workbenchCommand', 'workbench.action.showRuntimeExtensions');
|
||||
}
|
||||
});
|
||||
|
||||
// Cmd+Enter or Mac or Ctrl+Enter on other platforms previews issue and closes window
|
||||
if (platform.isMacintosh) {
|
||||
let prevKeyWasCommand = false;
|
||||
document.onkeydown = (e: KeyboardEvent) => {
|
||||
if (prevKeyWasCommand && e.keyCode === 13) {
|
||||
if (this.createIssue()) {
|
||||
remote.getCurrentWindow().close();
|
||||
}
|
||||
}
|
||||
|
||||
prevKeyWasCommand = e.keyCode === 91 || e.keyCode === 93;
|
||||
};
|
||||
} else {
|
||||
document.onkeydown = (e: KeyboardEvent) => {
|
||||
if (e.ctrlKey && e.keyCode === 13) {
|
||||
if (this.createIssue()) {
|
||||
remote.getCurrentWindow().close();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private updatePreviewButtonState() {
|
||||
const submitButton = <HTMLButtonElement>document.getElementById('github-submit-btn');
|
||||
if (this.isPreviewEnabled()) {
|
||||
submitButton.disabled = false;
|
||||
submitButton.textContent = localize('previewOnGitHub', "Preview on GitHub");
|
||||
} else {
|
||||
submitButton.disabled = true;
|
||||
submitButton.textContent = localize('loadingData', "Loading data...");
|
||||
}
|
||||
}
|
||||
|
||||
private isPreviewEnabled() {
|
||||
const issueType = this.issueReporterModel.getData().issueType;
|
||||
if (issueType === IssueType.Bug && this.receivedSystemInfo) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (issueType === IssueType.PerformanceIssue && this.receivedSystemInfo && this.receivedPerformanceInfo) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (issueType === IssueType.FeatureRequest) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (issueType === IssueType.SettingsSearchIssue) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private clearSearchResults(): void {
|
||||
const similarIssues = document.getElementById('similar-issues');
|
||||
similarIssues.innerHTML = '';
|
||||
this.numberOfSearchResultsDisplayed = 0;
|
||||
}
|
||||
|
||||
@debounce(300)
|
||||
private searchDuplicates(title: string, body: string): void {
|
||||
const url = 'https://vscode-probot.westus.cloudapp.azure.com:7890/duplicate_candidates';
|
||||
const init = {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
title,
|
||||
body
|
||||
}),
|
||||
headers: new Headers({
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
};
|
||||
|
||||
window.fetch(url, init).then((response) => {
|
||||
response.json().then(result => {
|
||||
this.clearSearchResults();
|
||||
|
||||
if (result && result.candidates) {
|
||||
this.displaySearchResults(result.candidates);
|
||||
} else {
|
||||
throw new Error('Unexpected response, no candidates property');
|
||||
}
|
||||
}).catch((error) => {
|
||||
this.logSearchError(error);
|
||||
});
|
||||
}).catch((error) => {
|
||||
this.logSearchError(error);
|
||||
});
|
||||
}
|
||||
|
||||
private displaySearchResults(results: SearchResult[]) {
|
||||
const similarIssues = document.getElementById('similar-issues');
|
||||
if (results.length) {
|
||||
const issues = $('div.issues-container');
|
||||
const issuesText = $('div.list-title');
|
||||
issuesText.textContent = localize('similarIssues', "Similar issues");
|
||||
|
||||
this.numberOfSearchResultsDisplayed = results.length < 5 ? results.length : 5;
|
||||
for (let i = 0; i < this.numberOfSearchResultsDisplayed; i++) {
|
||||
const issue = results[i];
|
||||
const link = $('a.issue-link', { href: issue.html_url });
|
||||
link.textContent = issue.title;
|
||||
link.title = issue.title;
|
||||
link.addEventListener('click', (e) => this.openLink(e));
|
||||
link.addEventListener('auxclick', (e) => this.openLink(<MouseEvent>e));
|
||||
|
||||
let issueState: HTMLElement;
|
||||
if (issue.state) {
|
||||
issueState = $('span.issue-state');
|
||||
|
||||
const issueIcon = $('span.issue-icon');
|
||||
const octicon = new OcticonLabel(issueIcon);
|
||||
octicon.text = issue.state === 'open' ? '$(issue-opened)' : '$(issue-closed)';
|
||||
|
||||
const issueStateLabel = $('span.issue-state.label');
|
||||
issueStateLabel.textContent = issue.state === 'open' ? localize('open', "Open") : localize('closed', "Closed");
|
||||
|
||||
issueState.title = issue.state === 'open' ? localize('open', "Open") : localize('closed', "Closed");
|
||||
issueState.appendChild(issueIcon);
|
||||
issueState.appendChild(issueStateLabel);
|
||||
}
|
||||
|
||||
const item = $('div.issue', {}, issueState, link);
|
||||
issues.appendChild(item);
|
||||
}
|
||||
|
||||
similarIssues.appendChild(issuesText);
|
||||
similarIssues.appendChild(issues);
|
||||
} else {
|
||||
const message = $('div.list-title');
|
||||
message.textContent = localize('noResults', "No results found");
|
||||
similarIssues.appendChild(message);
|
||||
}
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
private setUpTypes(): void {
|
||||
const makeOption = (issueType: IssueType, description: string) => `<option value="${issueType.valueOf()}">${escape(description)}</option>`;
|
||||
|
||||
const typeSelect = (<HTMLSelectElement>document.getElementById('issue-type'));
|
||||
const { issueType } = this.issueReporterModel.getData();
|
||||
if (issueType === IssueType.SettingsSearchIssue) {
|
||||
typeSelect.innerHTML = makeOption(IssueType.SettingsSearchIssue, localize('settingsSearchIssue', "Settings Search Issue"));
|
||||
typeSelect.disabled = true;
|
||||
} else {
|
||||
typeSelect.innerHTML = [
|
||||
makeOption(IssueType.Bug, localize('bugReporter', "Bug Report")),
|
||||
makeOption(IssueType.PerformanceIssue, localize('performanceIssue', "Performance Issue")),
|
||||
makeOption(IssueType.FeatureRequest, localize('featureRequest', "Feature Request"))
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
typeSelect.value = issueType.toString();
|
||||
}
|
||||
|
||||
private renderBlocks(): void {
|
||||
// Depending on Issue Type, we render different blocks and text
|
||||
const { issueType } = this.issueReporterModel.getData();
|
||||
const systemBlock = document.querySelector('.block-system');
|
||||
const processBlock = document.querySelector('.block-process');
|
||||
const workspaceBlock = document.querySelector('.block-workspace');
|
||||
const extensionsBlock = document.querySelector('.block-extensions');
|
||||
const searchedExtensionsBlock = document.querySelector('.block-searchedExtensions');
|
||||
const settingsSearchResultsBlock = document.querySelector('.block-settingsSearchResults');
|
||||
|
||||
const disabledExtensions = document.getElementById('disabledExtensions');
|
||||
const descriptionTitle = document.getElementById('issue-description-label');
|
||||
const descriptionSubtitle = document.getElementById('issue-description-subtitle');
|
||||
|
||||
// Hide all by default
|
||||
hide(systemBlock);
|
||||
hide(processBlock);
|
||||
hide(workspaceBlock);
|
||||
hide(extensionsBlock);
|
||||
hide(searchedExtensionsBlock);
|
||||
hide(settingsSearchResultsBlock);
|
||||
hide(disabledExtensions);
|
||||
|
||||
if (issueType === IssueType.Bug) {
|
||||
show(systemBlock);
|
||||
show(extensionsBlock);
|
||||
show(disabledExtensions);
|
||||
|
||||
descriptionTitle.innerHTML = `${localize('stepsToReproduce', "Steps to Reproduce")} <span class="required-input">*</span>`;
|
||||
descriptionSubtitle.innerHTML = localize('bugDescription', "Share the steps needed to reliably reproduce the problem. Please include actual and expected results. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.");
|
||||
} else if (issueType === IssueType.PerformanceIssue) {
|
||||
show(systemBlock);
|
||||
show(processBlock);
|
||||
show(workspaceBlock);
|
||||
show(extensionsBlock);
|
||||
show(disabledExtensions);
|
||||
|
||||
descriptionTitle.innerHTML = `${localize('stepsToReproduce', "Steps to Reproduce")} <span class="required-input">*</span>`;
|
||||
descriptionSubtitle.innerHTML = localize('performanceIssueDesciption', "When did this performance issue happen? Does it occur on startup or after a specific series of actions? We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.");
|
||||
} else if (issueType === IssueType.FeatureRequest) {
|
||||
descriptionTitle.innerHTML = `${localize('description', "Description")} <span class="required-input">*</span>`;
|
||||
descriptionSubtitle.innerHTML = localize('featureRequestDescription', "Please describe the feature you would like to see. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.");
|
||||
} else if (issueType === IssueType.SettingsSearchIssue) {
|
||||
show(searchedExtensionsBlock);
|
||||
show(settingsSearchResultsBlock);
|
||||
|
||||
descriptionTitle.innerHTML = `${localize('expectedResults', "Expected Results")} <span class="required-input">*</span>`;
|
||||
descriptionSubtitle.innerHTML = localize('settingsSearchResultsDescription', "Please list the results that you were expecting to see when you searched with this query. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.");
|
||||
}
|
||||
}
|
||||
|
||||
private validateInput(inputId: string): boolean {
|
||||
const inputElement = (<HTMLInputElement>document.getElementById(inputId));
|
||||
if (!inputElement.value) {
|
||||
inputElement.classList.add('invalid-input');
|
||||
return false;
|
||||
} else {
|
||||
inputElement.classList.remove('invalid-input');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private validateInputs(): boolean {
|
||||
let isValid = true;
|
||||
['issue-title', 'description'].forEach(elementId => {
|
||||
isValid = this.validateInput(elementId) && isValid;
|
||||
|
||||
});
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
private createIssue(): boolean {
|
||||
if (!this.validateInputs()) {
|
||||
// If inputs are invalid, set focus to the first one and add listeners on them
|
||||
// to detect further changes
|
||||
(<HTMLInputElement>document.getElementsByClassName('invalid-input')[0]).focus();
|
||||
|
||||
document.getElementById('issue-title').addEventListener('input', (event) => {
|
||||
this.validateInput('issue-title');
|
||||
});
|
||||
|
||||
document.getElementById('description').addEventListener('input', (event) => {
|
||||
this.validateInput('description');
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* __GDPR__
|
||||
"issueReporterSubmit" : {
|
||||
"issueType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"numSimilarIssuesDisplayed" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('issueReporterSubmit', { issueType: this.issueReporterModel.getData().issueType, numSimilarIssuesDisplayed: this.numberOfSearchResultsDisplayed });
|
||||
|
||||
const baseUrl = this.getIssueUrlWithTitle((<HTMLInputElement>document.getElementById('issue-title')).value);
|
||||
const issueBody = this.issueReporterModel.serialize();
|
||||
let url = baseUrl + `&body=${encodeURIComponent(issueBody)}`;
|
||||
|
||||
if (url.length > MAX_URL_LENGTH) {
|
||||
clipboard.writeText(issueBody);
|
||||
url = baseUrl + `&body=${encodeURIComponent(localize('pasteData', "We have written the needed data into your clipboard because it was too large to send. Please paste."))}`;
|
||||
}
|
||||
|
||||
shell.openExternal(url);
|
||||
return true;
|
||||
}
|
||||
|
||||
private getIssueUrlWithTitle(issueTitle: string) {
|
||||
const queryStringPrefix = product.reportIssueUrl.indexOf('?') === -1 ? '?' : '&';
|
||||
return `${product.reportIssueUrl}${queryStringPrefix}title=${encodeURIComponent(issueTitle)}`;
|
||||
}
|
||||
|
||||
private updateSystemInfo = (state) => {
|
||||
const target = document.querySelector('.block-system .block-info');
|
||||
let tableHtml = '';
|
||||
Object.keys(state.systemInfo).forEach(k => {
|
||||
tableHtml += `
|
||||
<tr>
|
||||
<td>${k}</td>
|
||||
<td>${state.systemInfo[k]}</td>
|
||||
</tr>`;
|
||||
});
|
||||
target.innerHTML = `<table>${tableHtml}</table>`;
|
||||
}
|
||||
|
||||
private updateProcessInfo = (state) => {
|
||||
const target = document.querySelector('.block-process .block-info');
|
||||
target.innerHTML = `<code>${state.processInfo}</code>`;
|
||||
}
|
||||
|
||||
private updateWorkspaceInfo = (state) => {
|
||||
document.querySelector('.block-workspace .block-info code').textContent = '\n' + state.workspaceInfo;
|
||||
}
|
||||
|
||||
private updateExtensionTable(extensions: ILocalExtension[], numThemeExtensions: number): void {
|
||||
const target = document.querySelector('.block-extensions .block-info');
|
||||
|
||||
if (this.environmentService.disableExtensions) {
|
||||
target.innerHTML = localize('disabledExtensions', "Extensions are disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
const themeExclusionStr = numThemeExtensions ? `\n(${numThemeExtensions} theme extensions excluded)` : '';
|
||||
extensions = extensions || [];
|
||||
|
||||
if (!extensions.length) {
|
||||
target.innerHTML = 'Extensions: none' + themeExclusionStr;
|
||||
return;
|
||||
}
|
||||
|
||||
const table = this.getExtensionTableHtml(extensions);
|
||||
target.innerHTML = `<table>${table}</table>${themeExclusionStr}`;
|
||||
}
|
||||
|
||||
private updateSearchedExtensionTable(extensions: ILocalExtension[]): void {
|
||||
const target = document.querySelector('.block-searchedExtensions .block-info');
|
||||
|
||||
if (!extensions.length) {
|
||||
target.innerHTML = 'Extensions: none';
|
||||
return;
|
||||
}
|
||||
|
||||
const table = this.getExtensionTableHtml(extensions);
|
||||
target.innerHTML = `<table>${table}</table>`;
|
||||
}
|
||||
|
||||
private getExtensionTableHtml(extensions: ILocalExtension[]): string {
|
||||
let table = `
|
||||
<tr>
|
||||
<th>Extension</th>
|
||||
<th>Author (truncated)</th>
|
||||
<th>Version</th>
|
||||
</tr>`;
|
||||
|
||||
table += extensions.map(extension => {
|
||||
return `
|
||||
<tr>
|
||||
<td>${extension.manifest.name}</td>
|
||||
<td>${extension.manifest.publisher.substr(0, 3)}</td>
|
||||
<td>${extension.manifest.version}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
private openLink(event: MouseEvent): void {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
// Exclude right click
|
||||
if (event.which < 3) {
|
||||
shell.openExternal((<HTMLAnchorElement>event.target).href);
|
||||
|
||||
/* __GDPR__
|
||||
"issueReporterViewSimilarIssue" : { }
|
||||
*/
|
||||
this.telemetryService.publicLog('issueReporterViewSimilarIssue');
|
||||
}
|
||||
}
|
||||
|
||||
private addEventListener(elementId: string, eventType: string, handler: (event: Event) => void): void {
|
||||
const element = document.getElementById(elementId);
|
||||
if (element) {
|
||||
element.addEventListener(eventType, handler);
|
||||
} else {
|
||||
const error = new Error(`${elementId} not found.`);
|
||||
this.logService.error(error);
|
||||
/* __GDPR__
|
||||
"issueReporterAddEventListenerError" : {
|
||||
"message" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('issueReporterAddEventListenerError', { message: error.message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// helper functions
|
||||
|
||||
function hide(el) {
|
||||
el.classList.add('hidden');
|
||||
}
|
||||
function show(el) {
|
||||
el.classList.remove('hidden');
|
||||
}
|
||||
224
src/vs/code/electron-browser/issue/issueReporterModel.ts
Normal file
224
src/vs/code/electron-browser/issue/issueReporterModel.ts
Normal file
@@ -0,0 +1,224 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IssueType, ISettingSearchResult } from 'vs/platform/issue/common/issue';
|
||||
|
||||
export interface IssueReporterData {
|
||||
issueType?: IssueType;
|
||||
issueDescription?: string;
|
||||
|
||||
versionInfo?: any;
|
||||
systemInfo?: any;
|
||||
processInfo?: any;
|
||||
workspaceInfo?: any;
|
||||
|
||||
includeSystemInfo?: boolean;
|
||||
includeWorkspaceInfo?: boolean;
|
||||
includeProcessInfo?: boolean;
|
||||
includeExtensions?: boolean;
|
||||
includeSearchedExtensions?: boolean;
|
||||
includeSettingsSearchDetails?: boolean;
|
||||
|
||||
numberOfThemeExtesions?: number;
|
||||
enabledNonThemeExtesions?: ILocalExtension[];
|
||||
extensionsDisabled?: boolean;
|
||||
reprosWithoutExtensions?: boolean;
|
||||
actualSearchResults?: ISettingSearchResult[];
|
||||
query?: string;
|
||||
filterResultCount?: number;
|
||||
}
|
||||
|
||||
export class IssueReporterModel {
|
||||
private _data: IssueReporterData;
|
||||
|
||||
constructor(initialData?: IssueReporterData) {
|
||||
const defaultData = {
|
||||
includeSystemInfo: true,
|
||||
includeWorkspaceInfo: true,
|
||||
includeProcessInfo: true,
|
||||
includeExtensions: true,
|
||||
includeSearchedExtensions: true,
|
||||
includeSettingsSearchDetails: true,
|
||||
reprosWithoutExtensions: false
|
||||
};
|
||||
|
||||
this._data = initialData ? assign(defaultData, initialData) : defaultData;
|
||||
}
|
||||
|
||||
getData(): IssueReporterData {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
update(newData: IssueReporterData): void {
|
||||
assign(this._data, newData);
|
||||
}
|
||||
|
||||
serialize(): string {
|
||||
return `
|
||||
Issue Type: <b>${this.getIssueTypeTitle()}</b>
|
||||
|
||||
${this._data.issueDescription}
|
||||
|
||||
VS Code version: ${this._data.versionInfo && this._data.versionInfo.vscodeVersion}
|
||||
OS version: ${this._data.versionInfo && this._data.versionInfo.os}
|
||||
|
||||
${this.getInfos()}
|
||||
<!-- generated by issue reporter -->`;
|
||||
}
|
||||
|
||||
private getIssueTypeTitle(): string {
|
||||
if (this._data.issueType === IssueType.Bug) {
|
||||
return 'Bug';
|
||||
} else if (this._data.issueType === IssueType.PerformanceIssue) {
|
||||
return 'Performance Issue';
|
||||
} else if (this._data.issueType === IssueType.SettingsSearchIssue) {
|
||||
return 'Settings Search Issue';
|
||||
} else {
|
||||
return 'Feature Request';
|
||||
}
|
||||
}
|
||||
|
||||
private getInfos(): string {
|
||||
let info = '';
|
||||
|
||||
if (this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue) {
|
||||
if (this._data.includeSystemInfo) {
|
||||
info += this.generateSystemInfoMd();
|
||||
}
|
||||
}
|
||||
|
||||
if (this._data.issueType === IssueType.PerformanceIssue) {
|
||||
|
||||
if (this._data.includeProcessInfo) {
|
||||
info += this.generateProcessInfoMd();
|
||||
}
|
||||
|
||||
if (this._data.includeWorkspaceInfo) {
|
||||
info += this.generateWorkspaceInfoMd();
|
||||
}
|
||||
}
|
||||
|
||||
if (this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue) {
|
||||
if (this._data.includeExtensions) {
|
||||
info += this.generateExtensionsMd();
|
||||
}
|
||||
|
||||
info += this._data.reprosWithoutExtensions ? '\nReproduces without extensions' : '\nReproduces only with extensions';
|
||||
}
|
||||
|
||||
if (this._data.issueType === IssueType.SettingsSearchIssue) {
|
||||
if (this._data.includeSearchedExtensions) {
|
||||
info += this.generateExtensionsMd();
|
||||
}
|
||||
|
||||
if (this._data.includeSettingsSearchDetails) {
|
||||
info += this.generateSettingSearchResultsMd();
|
||||
info += '\n' + this.generateSettingsSearchResultDetailsMd();
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private generateSystemInfoMd(): string {
|
||||
let md = `<details>
|
||||
<summary>System Info</summary>
|
||||
|
||||
|Item|Value|
|
||||
|---|---|
|
||||
`;
|
||||
|
||||
Object.keys(this._data.systemInfo).forEach(k => {
|
||||
md += `|${k}|${this._data.systemInfo[k]}|\n`;
|
||||
});
|
||||
|
||||
md += '\n</details>';
|
||||
|
||||
return md;
|
||||
}
|
||||
|
||||
private generateProcessInfoMd(): string {
|
||||
return `<details>
|
||||
<summary>Process Info</summary>
|
||||
|
||||
\`\`\`
|
||||
${this._data.processInfo}
|
||||
\`\`\`
|
||||
|
||||
</details>
|
||||
`;
|
||||
}
|
||||
|
||||
private generateWorkspaceInfoMd(): string {
|
||||
return `<details>
|
||||
<summary>Workspace Info</summary>
|
||||
|
||||
\`\`\`
|
||||
${this._data.workspaceInfo};
|
||||
\`\`\`
|
||||
|
||||
</details>
|
||||
`;
|
||||
}
|
||||
|
||||
private generateExtensionsMd(): string {
|
||||
if (this._data.extensionsDisabled) {
|
||||
return 'Extensions disabled';
|
||||
}
|
||||
|
||||
const themeExclusionStr = this._data.numberOfThemeExtesions ? `\n(${this._data.numberOfThemeExtesions} theme extensions excluded)` : '';
|
||||
|
||||
if (!this._data.enabledNonThemeExtesions) {
|
||||
return 'Extensions: none' + themeExclusionStr;
|
||||
}
|
||||
|
||||
let tableHeader = `Extension|Author (truncated)|Version
|
||||
---|---|---`;
|
||||
const table = this._data.enabledNonThemeExtesions.map(e => {
|
||||
return `${e.manifest.name}|${e.manifest.publisher.substr(0, 3)}|${e.manifest.version}`;
|
||||
}).join('\n');
|
||||
|
||||
return `<details><summary>Extensions (${this._data.enabledNonThemeExtesions.length})</summary>
|
||||
|
||||
${tableHeader}
|
||||
${table}
|
||||
${themeExclusionStr}
|
||||
|
||||
</details>`;
|
||||
}
|
||||
|
||||
private generateSettingsSearchResultDetailsMd(): string {
|
||||
return `
|
||||
Query: ${this._data.query}
|
||||
Literal matches: ${this._data.filterResultCount}`;
|
||||
}
|
||||
|
||||
private generateSettingSearchResultsMd(): string {
|
||||
if (!this._data.actualSearchResults) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!this._data.actualSearchResults.length) {
|
||||
return `No fuzzy results`;
|
||||
}
|
||||
|
||||
let tableHeader = `Setting|Extension|Score
|
||||
---|---|---`;
|
||||
const table = this._data.actualSearchResults.map(setting => {
|
||||
return `${setting.key}|${setting.extensionId}|${String(setting.score).slice(0, 5)}`;
|
||||
}).join('\n');
|
||||
|
||||
return `<details><summary>Results</summary>
|
||||
|
||||
${tableHeader}
|
||||
${table}
|
||||
|
||||
</details>`;
|
||||
}
|
||||
}
|
||||
154
src/vs/code/electron-browser/issue/issueReporterPage.ts
Normal file
154
src/vs/code/electron-browser/issue/issueReporterPage.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export default (): string => `
|
||||
<div id="issue-reporter">
|
||||
<div id="english" class="input-group hidden">${escape(localize('completeInEnglish', "Please complete the form in English."))}</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="input-group">
|
||||
<label class="inline-label" for="issue-type">${escape(localize('issueTypeLabel', "This is a"))}</label>
|
||||
<select id="issue-type" class="inline-form-control">
|
||||
<!-- To be dynamically filled -->
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<label class="inline-label" for="issue-title">${escape(localize('issueTitleLabel', "Title"))} <span class="required-input">*</span></label>
|
||||
<input id="issue-title" type="text" class="inline-form-control" placeholder="${escape(localize('issueTitleRequired', "Please enter a title."))}" required>
|
||||
<div id="issue-title-length-validation-error" class="validation-error hidden" role="alert">${escape(localize('titleLengthValidation', "The title is too long."))}</div>
|
||||
<small id="similar-issues">
|
||||
<!-- To be dynamically filled -->
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="system-info">
|
||||
<div id="block-container">
|
||||
<div class="block block-system">
|
||||
<details>
|
||||
<summary>${escape(localize('systemInfo', "My System Info"))}
|
||||
<div class="include-data">
|
||||
<input class="sendData" type="checkbox" id="includeSystemInfo" checked/>
|
||||
<label class="caption" for="includeSystemInfo">${escape(localize('sendData', "Send my data"))}</label>
|
||||
</div>
|
||||
</summary>
|
||||
<div class="block-info">
|
||||
<!-- To be dynamically filled -->
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<div class="block block-process">
|
||||
<details>
|
||||
<summary>${escape(localize('processes', "Currently Running Processes"))}
|
||||
<div class="include-data">
|
||||
<input class="sendData" type="checkbox" id="includeProcessInfo" checked/>
|
||||
<label class="caption" for="includeProcessInfo">${escape(localize('sendData', "Send my data"))}</label>
|
||||
</div>
|
||||
</summary>
|
||||
<pre class="block-info">
|
||||
<!-- To be dynamically filled -->
|
||||
</pre>
|
||||
</details>
|
||||
</div>
|
||||
<div class="block block-workspace">
|
||||
<details>
|
||||
<summary>${escape(localize('workspaceStats', "My Workspace Stats"))}
|
||||
<div class="include-data">
|
||||
<input class="sendData" type="checkbox" id="includeWorkspaceInfo" checked/>
|
||||
<label class="caption" for="includeWorkspaceInfo">${escape(localize('sendData', "Send my data"))}</label>
|
||||
</div>
|
||||
</summary>
|
||||
<pre class="block-info">
|
||||
<code>
|
||||
<!-- To be dynamically filled -->
|
||||
</code>
|
||||
</pre>
|
||||
</details>
|
||||
</div>
|
||||
<div class="block block-extensions">
|
||||
<details>
|
||||
<summary>${escape(localize('extensions', "My Extensions"))}
|
||||
<div class="include-data">
|
||||
<input class="sendData" type="checkbox" id="includeExtensions" checked/>
|
||||
<label class="caption" for="includeExtensions">${escape(localize('sendData', "Send my data"))}</label>
|
||||
</div>
|
||||
</summary>
|
||||
<div class="block-info">
|
||||
<!-- To be dynamically filled -->
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<div class="block block-searchedExtensions">
|
||||
<details>
|
||||
<summary>${escape(localize('searchedExtensions', "Searched Extensions"))}
|
||||
<div class="include-data">
|
||||
<input class="sendData" type="checkbox" id="includeSearchedExtensions" checked/>
|
||||
<label class="caption" for="includeSearchedExtensions">${escape(localize('sendData', "Send my data"))}</label>
|
||||
</div>
|
||||
</summary>
|
||||
<div class="block-info">
|
||||
<!-- To be dynamically filled -->
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<div class="block block-settingsSearchResults">
|
||||
<details>
|
||||
<summary>${escape(localize('settingsSearchDetails', "Settings Search Details"))}
|
||||
<div class="include-data">
|
||||
<input class="sendData" type="checkbox" id="includeSettingsSearchDetails" checked/>
|
||||
<label class="caption" for="includeSettingsSearchDetails">${escape(localize('sendData', "Send my data"))}</label>
|
||||
</div>
|
||||
</summary>
|
||||
<div class="block-info">
|
||||
<!-- To be dynamically filled -->
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div id="disabledExtensions">
|
||||
<div class="extensions-form">
|
||||
<label>${escape(localize('tryDisablingExtensions', "Is the problem reproducible when extensions are disabled?"))}</label>
|
||||
<div class="form-buttons">
|
||||
<div class="choice">
|
||||
<input type="radio" id="reproducesWithoutExtensions" value=true name="reprosWithoutExtensions" />
|
||||
<label for="reproducesWithoutExtensions">${escape(localize('yes', "Yes"))}</label>
|
||||
</div>
|
||||
<div class="choice">
|
||||
<input type="radio" id="reproducesWithExtensions" value=false name="reprosWithoutExtensions" checked/>
|
||||
<label for="reproducesWithExtensions">${escape(localize('no', "No"))}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="instructions">${escape(localize('disableExtensionsLabelText', "Try to reproduce the problem after {0}."))
|
||||
.replace('{0}', `<span tabIndex=0 role="button" id="disableExtensions" class="workbenchCommand">${escape(localize('disableExtensions', "disabling all extensions and reloading the window"))}</span>`)}
|
||||
</div>
|
||||
<div class="instructions">${escape(localize('showRunningExtensionsLabelText', "If you suspect it's an extension issue, {0} to report the issue on the extension."))
|
||||
.replace('{0}', `<span tabIndex=0 role="button"id="showRunning" class="workbenchCommand">${escape(localize('showRunningExtensions', "view all running extensions"))}</span>`)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<label for="description" id="issue-description-label">
|
||||
<!-- To be dynamically filled -->
|
||||
</label>
|
||||
<div class="instructions" id="issue-description-subtitle">
|
||||
<!-- To be dynamically filled -->
|
||||
</div>
|
||||
<div class="block-info-text">
|
||||
<textarea name="description" id="description" cols="100" rows="12" placeholder="${escape(localize('details', "Please enter details."))}" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="github-submit-btn" disabled>${escape(localize('loadingData', "Loading data..."))}</button>
|
||||
</div>`;
|
||||
394
src/vs/code/electron-browser/issue/media/issueReporter.css
Normal file
394
src/vs/code/electron-browser/issue/media/issueReporter.css
Normal file
@@ -0,0 +1,394 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* Table
|
||||
*/
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
background-color: transparent;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
th {
|
||||
vertical-align: bottom;
|
||||
border-bottom: 2px solid #e9ecef;
|
||||
padding: .75rem;
|
||||
border-top: 1px solid #e9ecef;
|
||||
text-align: inherit;
|
||||
}
|
||||
tr:nth-of-type(even) {
|
||||
background-color: rgba(0,0,0,.05);
|
||||
}
|
||||
td {
|
||||
padding: .75rem;
|
||||
vertical-align: top;
|
||||
border-top: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.block-settingsSearchResults-details {
|
||||
padding-bottom: .5rem;
|
||||
}
|
||||
|
||||
.block-settingsSearchResults-details > div {
|
||||
padding: .5rem .75rem;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forms
|
||||
*/
|
||||
input[type="text"], textarea {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: .375rem .75rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
color: #495057;
|
||||
background-color: #fff;
|
||||
border-radius: .25rem;
|
||||
border: 1px solid #ced4da;
|
||||
}
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/**
|
||||
* Button
|
||||
*/
|
||||
button {
|
||||
display: inline-block;
|
||||
font-weight: 400;
|
||||
line-height: 1.25;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
user-select: none;
|
||||
padding: .5rem 1rem;
|
||||
font-size: 1rem;
|
||||
border-radius: .25rem;
|
||||
background: none;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
select {
|
||||
height: calc(2.25rem + 2px);
|
||||
display: inline-block;
|
||||
padding: 3px 3px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: #495057;
|
||||
background-color: #fff;
|
||||
border-radius: 0.25rem;
|
||||
border: none;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
textarea {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Ubuntu", "Droid Sans", sans-serif;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Ubuntu", "Droid Sans", sans-serif;
|
||||
color: #CCCCCC;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#block-container {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.block .block-info {
|
||||
width: 100%;
|
||||
font-family: 'Menlo', 'Courier New', 'Courier', monospace;
|
||||
font-size: 12px;
|
||||
overflow: auto;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
pre {
|
||||
margin: 10px 20px;
|
||||
}
|
||||
pre code {
|
||||
font-family: 'Menlo', 'Courier New', 'Courier', monospace;
|
||||
}
|
||||
|
||||
button:hover:enabled {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
#issue-reporter {
|
||||
max-width: 85vw;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
#github-submit-btn {
|
||||
float: right;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.two-col {
|
||||
display: inline-block;
|
||||
width: 49%;
|
||||
}
|
||||
|
||||
#vscode-version {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.extensions-form {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.extensions-form > .form-buttons {
|
||||
display: flex;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.extensions-form > .form-buttons > .choice {
|
||||
margin-right: 35px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.extensions-form > .form-buttons > .choice > label, .extensions-form > .form-buttons > .choice > input {
|
||||
cursor: pointer;
|
||||
height: 100%;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.extensions-form > .form-buttons > .choice > label {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: -50%;
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
.system-info {
|
||||
margin-bottom: 1.25em;
|
||||
}
|
||||
|
||||
select, input, textarea {
|
||||
border: 1px solid transparent;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
summary {
|
||||
border: 1px solid transparent;
|
||||
padding: 0 10px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.validation-error {
|
||||
font-size: 12px;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.include-data {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.include-data > .caption {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sendData {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
margin-top: 0;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input:disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.list-title {
|
||||
margin-top: 1em;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.instructions {
|
||||
font-size: 12px;
|
||||
margin-left: 1em;
|
||||
margin-top: .5em;
|
||||
}
|
||||
|
||||
.workbenchCommand {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.workbenchCommand:disabled {
|
||||
color: #868e96;
|
||||
cursor: default
|
||||
}
|
||||
|
||||
.block-extensions .block-info {
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
/* Default styles, overwritten if a theme is provided */
|
||||
input, select, textarea {
|
||||
background-color: #3c3c3c;
|
||||
border: none;
|
||||
color: #cccccc;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #CCCCCC;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.invalid-input {
|
||||
border: 1px solid #be1100;
|
||||
}
|
||||
|
||||
.required-input, .validation-error {
|
||||
color: #be1100;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #007ACC;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.section .input-group .validation-error {
|
||||
margin-left: 13%;
|
||||
}
|
||||
|
||||
.section .inline-form-control, .section .inline-label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.section .inline-label {
|
||||
width: 95px;
|
||||
}
|
||||
|
||||
.section .inline-form-control {
|
||||
width: calc(100% - 100px);
|
||||
}
|
||||
|
||||
#issue-type {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#similar-issues {
|
||||
margin-left: 12%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (max-width: 950px) {
|
||||
.section .inline-label {
|
||||
width: 12%;
|
||||
}
|
||||
|
||||
.section .inline-form-control {
|
||||
width: calc(88% - 5px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 620px) {
|
||||
.section .inline-label {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.inline-form-control {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#similar-issues, .section .input-group .validation-error {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.issues-container {
|
||||
margin-left: 1.5em;
|
||||
margin-top: .5em;
|
||||
height: 92px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.issues-container > .issue {
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.issues-container > .issue > .issue-link {
|
||||
display: inline-block;
|
||||
width: calc(100% - 82px);
|
||||
vertical-align: top;
|
||||
overflow: hidden;
|
||||
padding-top: 3px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.issues-container > .issue > .issue-state {
|
||||
display: inline-block;
|
||||
width: 77px;
|
||||
padding: 3px 6px;
|
||||
margin-right: 5px;
|
||||
color: #CCCCCC;
|
||||
background-color: #3c3c3c;
|
||||
border-radius: .25rem;
|
||||
}
|
||||
|
||||
.issues-container > .issue > .issue-state .octicon {
|
||||
vertical-align: top;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.issues-container > .issue .label {
|
||||
margin-left: 5px;
|
||||
display: inline-block;
|
||||
width: 44px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel';
|
||||
|
||||
suite('IssueReporter', () => {
|
||||
|
||||
test('sets defaults to include all data', () => {
|
||||
const issueReporterModel = new IssueReporterModel();
|
||||
assert.deepEqual(issueReporterModel.getData(), {
|
||||
includeSystemInfo: true,
|
||||
includeWorkspaceInfo: true,
|
||||
includeProcessInfo: true,
|
||||
includeExtensions: true,
|
||||
includeSearchedExtensions: true,
|
||||
includeSettingsSearchDetails: true,
|
||||
reprosWithoutExtensions: false
|
||||
});
|
||||
});
|
||||
|
||||
test('serializes model skeleton when no data is provided', () => {
|
||||
const issueReporterModel = new IssueReporterModel();
|
||||
assert.equal(issueReporterModel.serialize(),
|
||||
`
|
||||
Issue Type: <b>Feature Request</b>
|
||||
|
||||
undefined
|
||||
|
||||
VS Code version: undefined
|
||||
OS version: undefined
|
||||
|
||||
|
||||
<!-- generated by issue reporter -->`);
|
||||
});
|
||||
});
|
||||
@@ -4,10 +4,14 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
|
||||
import { NodeCachedDataCleaner } from 'vs/code/electron-browser/contrib/nodeCachedDataCleaner';
|
||||
import { NodeCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner';
|
||||
import { LanguagePackCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export function createSharedProcessContributions(service: IInstantiationService): void {
|
||||
service.createInstance(NodeCachedDataCleaner);
|
||||
export function createSharedProcessContributions(service: IInstantiationService): IDisposable {
|
||||
return combinedDisposable([
|
||||
service.createInstance(NodeCachedDataCleaner),
|
||||
service.createInstance(LanguagePackCachedDataCleaner)
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as path from 'path';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import product from 'vs/platform/node/product';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
|
||||
interface ExtensionEntry {
|
||||
version: string;
|
||||
extensionIdentifier: {
|
||||
id: string;
|
||||
uuid: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface LanguagePackEntry {
|
||||
hash: string;
|
||||
extensions: ExtensionEntry[];
|
||||
}
|
||||
|
||||
interface LanguagePackFile {
|
||||
[locale: string]: LanguagePackEntry;
|
||||
}
|
||||
|
||||
export class LanguagePackCachedDataCleaner {
|
||||
|
||||
private _disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
|
||||
@ILogService private readonly _logService: ILogService
|
||||
) {
|
||||
// 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) {
|
||||
this._manageCachedDataSoon();
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._disposables = dispose(this._disposables);
|
||||
}
|
||||
|
||||
private _manageCachedDataSoon(): void {
|
||||
let handle = setTimeout(async () => {
|
||||
handle = undefined;
|
||||
this._logService.info('Starting to clean up unused language packs.');
|
||||
const maxAge = product.nameLong.indexOf('Insiders') >= 0
|
||||
? 1000 * 60 * 60 * 24 * 7 // roughly 1 week
|
||||
: 1000 * 60 * 60 * 24 * 30 * 3; // roughly 3 months
|
||||
try {
|
||||
let installed: IStringDictionary<boolean> = Object.create(null);
|
||||
const metaData: LanguagePackFile = JSON.parse(await pfs.readFile(path.join(this._environmentService.userDataPath, 'languagepacks.json'), 'utf8'));
|
||||
for (let locale of Object.keys(metaData)) {
|
||||
let entry = metaData[locale];
|
||||
installed[`${entry.hash}.${locale}`] = true;
|
||||
}
|
||||
// Cleanup entries for language packs that aren't installed anymore
|
||||
const cacheDir = path.join(this._environmentService.userDataPath, 'clp');
|
||||
for (let entry of await pfs.readdir(cacheDir)) {
|
||||
if (installed[entry]) {
|
||||
this._logService.info(`Skipping directory ${entry}. Language pack still in use`);
|
||||
continue;
|
||||
}
|
||||
this._logService.info('Removing unused language pack:', entry);
|
||||
await pfs.rimraf(path.join(cacheDir, entry));
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
for (let packEntry of Object.keys(installed)) {
|
||||
const folder = path.join(cacheDir, packEntry);
|
||||
for (let entry of await pfs.readdir(folder)) {
|
||||
if (entry === 'tcf.json') {
|
||||
continue;
|
||||
}
|
||||
const candidate = path.join(folder, entry);
|
||||
const stat = await pfs.stat(candidate);
|
||||
if (stat.isDirectory()) {
|
||||
const diff = now - stat.mtime.getTime();
|
||||
if (diff > maxAge) {
|
||||
this._logService.info('Removing language pack cache entry: ', path.join(packEntry, entry));
|
||||
await pfs.rimraf(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
onUnexpectedError(error);
|
||||
}
|
||||
}, 40 * 1000);
|
||||
|
||||
this._disposables.push({
|
||||
dispose() {
|
||||
if (handle !== void 0) {
|
||||
clearTimeout(handle);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
Shared Process
|
||||
</body>
|
||||
|
||||
<!-- Startup via index.js -->
|
||||
<!-- Startup via sharedProcess.js -->
|
||||
<script src="sharedProcess.js"></script>
|
||||
|
||||
</html>
|
||||
@@ -6,6 +6,7 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
function assign(destination, source) {
|
||||
return Object.keys(source)
|
||||
@@ -40,10 +41,50 @@ function uriFromPath(_path) {
|
||||
return encodeURI('file://' + pathName);
|
||||
}
|
||||
|
||||
function readFile(file) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
fs.readFile(file, 'utf8', function(err, data) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function main() {
|
||||
const args = parseURLQueryArgs();
|
||||
const configuration = JSON.parse(args['config'] || '{}') || {};
|
||||
|
||||
//#region Add support for using node_modules.asar
|
||||
(function () {
|
||||
const path = require('path');
|
||||
const Module = require('module');
|
||||
let NODE_MODULES_PATH = path.join(configuration.appRoot, 'node_modules');
|
||||
if (/[a-z]\:/.test(NODE_MODULES_PATH)) {
|
||||
// Make drive letter uppercase
|
||||
NODE_MODULES_PATH = NODE_MODULES_PATH.charAt(0).toUpperCase() + NODE_MODULES_PATH.substr(1);
|
||||
}
|
||||
const NODE_MODULES_ASAR_PATH = NODE_MODULES_PATH + '.asar';
|
||||
|
||||
const originalResolveLookupPaths = Module._resolveLookupPaths;
|
||||
Module._resolveLookupPaths = function (request, parent) {
|
||||
const result = originalResolveLookupPaths(request, parent);
|
||||
|
||||
const paths = result[1];
|
||||
for (let i = 0, len = paths.length; i < len; i++) {
|
||||
if (paths[i] === NODE_MODULES_PATH) {
|
||||
paths.splice(i, 0, NODE_MODULES_ASAR_PATH);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
//#endregion
|
||||
|
||||
// Correctly inherit the parent's environment
|
||||
assign(process.env, configuration.userEnv);
|
||||
|
||||
@@ -57,6 +98,24 @@ function main() {
|
||||
} catch (e) { /*noop*/ }
|
||||
}
|
||||
|
||||
if (nlsConfig._resolvedLanguagePackCoreLocation) {
|
||||
let bundles = Object.create(null);
|
||||
nlsConfig.loadBundle = function(bundle, language, cb) {
|
||||
let result = bundles[bundle];
|
||||
if (result) {
|
||||
cb(undefined, result);
|
||||
return;
|
||||
}
|
||||
let bundleFile = path.join(nlsConfig._resolvedLanguagePackCoreLocation, bundle.replace(/\//g, '!') + '.nls.json');
|
||||
readFile(bundleFile).then(function (content) {
|
||||
let json = JSON.parse(content);
|
||||
bundles[bundle] = json;
|
||||
cb(undefined, json);
|
||||
})
|
||||
.catch(cb);
|
||||
};
|
||||
}
|
||||
|
||||
var locale = nlsConfig.availableLanguages['*'] || 'en';
|
||||
if (locale === 'zh-tw') {
|
||||
locale = 'zh-Hant';
|
||||
@@ -72,6 +131,8 @@ function main() {
|
||||
// In the bundled version the nls plugin is packaged with the loader so the NLS Plugins
|
||||
// loads as soon as the loader loads. To be able to have pseudo translation
|
||||
createScript(rootUrl + '/vs/loader.js', function () {
|
||||
var define = global.define;
|
||||
global.define = undefined;
|
||||
define('fs', ['original-fs'], function (originalFS) { return originalFS; }); // replace the patched electron fs with the original node fs for all AMD code
|
||||
|
||||
window.MonacoEnvironment = {};
|
||||
@@ -89,7 +150,7 @@ function main() {
|
||||
});
|
||||
}
|
||||
|
||||
require(['vs/code/electron-browser/sharedProcessMain'], function (sharedProcess) {
|
||||
require(['vs/code/electron-browser/sharedProcess/sharedProcessMain'], function (sharedProcess) {
|
||||
sharedProcess.startup({
|
||||
machineId: configuration.machineId
|
||||
});
|
||||
@@ -3,6 +3,8 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import product from 'vs/platform/node/product';
|
||||
@@ -28,15 +30,19 @@ import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProper
|
||||
import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc';
|
||||
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
|
||||
import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
|
||||
import { IChoiceService } from 'vs/platform/message/common/message';
|
||||
import { ChoiceChannelClient } from 'vs/platform/message/common/messageIpc';
|
||||
import { IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import { WindowsChannelClient } from 'vs/platform/windows/common/windowsIpc';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { createSharedProcessContributions } from 'vs/code/electron-browser/contrib/contributions';
|
||||
import { createLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { createSharedProcessContributions } from 'vs/code/electron-browser/sharedProcess/contrib/contributions';
|
||||
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
|
||||
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/common/localizationsIpc';
|
||||
import { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { ChoiceChannelClient } from 'vs/platform/dialogs/common/choiceIpc';
|
||||
|
||||
export interface ISharedProcessConfiguration {
|
||||
readonly machineId: string;
|
||||
@@ -49,6 +55,7 @@ export function startup(configuration: ISharedProcessConfiguration) {
|
||||
interface ISharedProcessInitData {
|
||||
sharedIPCHandle: string;
|
||||
args: ParsedArgs;
|
||||
logLevel: LogLevel;
|
||||
}
|
||||
|
||||
class ActiveWindowManager implements IDisposable {
|
||||
@@ -79,7 +86,8 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
|
||||
const services = new ServiceCollection();
|
||||
|
||||
const environmentService = new EnvironmentService(initData.args, process.execPath);
|
||||
const logService = createLogService('sharedprocess', environmentService);
|
||||
const logLevelClient = new LogLevelSetterChannelClient(server.getChannel('loglevel', { route: () => 'main' }));
|
||||
const logService = new FollowerLogService(logLevelClient, createSpdLogService('sharedprocess', initData.logLevel, environmentService.logsPath));
|
||||
process.once('exit', () => logService.dispose());
|
||||
|
||||
logService.info('main', JSON.stringify(configuration));
|
||||
@@ -94,8 +102,12 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
|
||||
services.set(IWindowsService, windowsService);
|
||||
|
||||
const activeWindowManager = new ActiveWindowManager(windowsService);
|
||||
|
||||
const choiceChannel = server.getChannel('choice', { route: () => activeWindowManager.activeClientId });
|
||||
const choiceChannel = server.getChannel('choice', {
|
||||
route: () => {
|
||||
logService.info('Routing choice request to the client', activeWindowManager.activeClientId);
|
||||
return activeWindowManager.activeClientId;
|
||||
}
|
||||
});
|
||||
services.set(IChoiceService, new ChoiceChannelClient(choiceChannel));
|
||||
|
||||
const instantiationService = new InstantiationService(services);
|
||||
@@ -132,6 +144,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));
|
||||
|
||||
const instantiationService2 = instantiationService.createChild(services);
|
||||
|
||||
@@ -143,6 +156,10 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
|
||||
// clean up deprecated extensions
|
||||
(extensionManagementService as ExtensionManagementService).removeDeprecatedExtensions();
|
||||
|
||||
const localizationsService = accessor.get(ILocalizationsService);
|
||||
const localizationsChannel = new LocalizationsChannel(localizationsService);
|
||||
server.registerChannel('localizations', localizationsChannel);
|
||||
|
||||
createSharedProcessContributions(instantiationService2);
|
||||
});
|
||||
});
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { app, ipcMain as ipc, BrowserWindow, dialog } from 'electron';
|
||||
import { app, ipcMain as ipc, BrowserWindow } from 'electron';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { WindowsManager } from 'vs/code/electron-main/windows';
|
||||
import { IWindowsService, OpenContext } from 'vs/platform/windows/common/windows';
|
||||
@@ -16,7 +16,6 @@ import { CodeMenu } from 'vs/code/electron-main/menus';
|
||||
import { getShellEnvironment } from 'vs/code/node/shellEnv';
|
||||
import { IUpdateService } from 'vs/platform/update/common/update';
|
||||
import { UpdateChannel } from 'vs/platform/update/common/updateIpc';
|
||||
import { UpdateService } from 'vs/platform/update/electron-main/updateService';
|
||||
import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron-main';
|
||||
import { Server, connect, Client } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { SharedProcess } from 'vs/code/electron-main/sharedProcess';
|
||||
@@ -51,13 +50,18 @@ import { KeyboardLayoutMonitor } from 'vs/code/electron-main/keyboard';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { WorkspacesChannel } from 'vs/platform/workspaces/common/workspacesIpc';
|
||||
import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { dirname, join } from 'path';
|
||||
import { touch } from 'vs/base/node/pfs';
|
||||
import { getMachineId } from 'vs/base/node/id';
|
||||
import { Win32UpdateService } from 'vs/platform/update/electron-main/updateService.win32';
|
||||
import { LinuxUpdateService } from 'vs/platform/update/electron-main/updateService.linux';
|
||||
import { DarwinUpdateService } from 'vs/platform/update/electron-main/updateService.darwin';
|
||||
import { IIssueService } from 'vs/platform/issue/common/issue';
|
||||
import { IssueChannel } from 'vs/platform/issue/common/issueIpc';
|
||||
import { IssueService } from 'vs/platform/issue/electron-main/issueService';
|
||||
import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc';
|
||||
import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
|
||||
|
||||
export class CodeApplication {
|
||||
|
||||
private static readonly APP_ICON_REFRESH_KEY = 'macOSAppIconRefresh3';
|
||||
private static readonly MACHINE_ID_KEY = 'telemetry.machineId';
|
||||
|
||||
private toDispose: IDisposable[];
|
||||
@@ -87,26 +91,8 @@ export class CodeApplication {
|
||||
private registerListeners(): void {
|
||||
|
||||
// We handle uncaught exceptions here to prevent electron from opening a dialog to the user
|
||||
process.on('uncaughtException', (err: any) => {
|
||||
if (err) {
|
||||
|
||||
// take only the message and stack property
|
||||
const friendlyError = {
|
||||
message: err.message,
|
||||
stack: err.stack
|
||||
};
|
||||
|
||||
// handle on client side
|
||||
if (this.windowsMainService) {
|
||||
this.windowsMainService.sendToFocused('vscode:reportError', JSON.stringify(friendlyError));
|
||||
}
|
||||
}
|
||||
|
||||
this.logService.error(`[uncaught exception in main]: ${err}`);
|
||||
if (err.stack) {
|
||||
this.logService.error(err.stack);
|
||||
}
|
||||
});
|
||||
setUnexpectedErrorHandler(err => this.onUnexpectedError(err));
|
||||
process.on('uncaughtException', err => this.onUnexpectedError(err));
|
||||
|
||||
app.on('will-quit', () => {
|
||||
this.logService.trace('App#will-quit: disposing resources');
|
||||
@@ -129,8 +115,16 @@ export class CodeApplication {
|
||||
}
|
||||
});
|
||||
|
||||
const isValidWebviewSource = (source: string) =>
|
||||
!source || (URI.parse(source.toLowerCase()).toString() as any).startsWith(URI.file(this.environmentService.appRoot.toLowerCase()).toString());
|
||||
const isValidWebviewSource = (source: string): boolean => {
|
||||
if (!source) {
|
||||
return false;
|
||||
}
|
||||
if (source === 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%09%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E') {
|
||||
return true;
|
||||
}
|
||||
const srcUri: any = URI.parse(source.toLowerCase()).toString();
|
||||
return srcUri.startsWith(URI.file(this.environmentService.appRoot.toLowerCase()).toString());
|
||||
};
|
||||
|
||||
app.on('web-contents-created', (_event: any, contents) => {
|
||||
contents.on('will-attach-webview', (event: Electron.Event, webPreferences, params) => {
|
||||
@@ -229,6 +223,27 @@ export class CodeApplication {
|
||||
});
|
||||
}
|
||||
|
||||
private onUnexpectedError(err: Error): void {
|
||||
if (err) {
|
||||
|
||||
// take only the message and stack property
|
||||
const friendlyError = {
|
||||
message: err.message,
|
||||
stack: err.stack
|
||||
};
|
||||
|
||||
// handle on client side
|
||||
if (this.windowsMainService) {
|
||||
this.windowsMainService.sendToFocused('vscode:reportError', JSON.stringify(friendlyError));
|
||||
}
|
||||
}
|
||||
|
||||
this.logService.error(`[uncaught exception in main]: ${err}`);
|
||||
if (err.stack) {
|
||||
this.logService.error(err.stack);
|
||||
}
|
||||
}
|
||||
|
||||
private onBroadcast(event: string, payload: any): void {
|
||||
|
||||
// Theme changes
|
||||
@@ -262,7 +277,7 @@ export class CodeApplication {
|
||||
this.logService.trace(`Resolved machine identifier: ${machineId}`);
|
||||
|
||||
// Spawn shared process
|
||||
this.sharedProcess = new SharedProcess(this.environmentService, machineId, this.userEnv);
|
||||
this.sharedProcess = new SharedProcess(this.environmentService, machineId, this.userEnv, this.logService);
|
||||
this.toDispose.push(this.sharedProcess);
|
||||
this.sharedProcessClient = this.sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main'));
|
||||
|
||||
@@ -299,10 +314,18 @@ export class CodeApplication {
|
||||
private initServices(machineId: string): IInstantiationService {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
services.set(IUpdateService, new SyncDescriptor(UpdateService));
|
||||
if (process.platform === 'win32') {
|
||||
services.set(IUpdateService, new SyncDescriptor(Win32UpdateService));
|
||||
} else if (process.platform === 'linux') {
|
||||
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));
|
||||
|
||||
// Telemtry
|
||||
if (this.environmentService.isBuilt && !this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
|
||||
@@ -347,6 +370,10 @@ export class CodeApplication {
|
||||
const urlChannel = appInstantiationService.createInstance(URLChannel, urlService);
|
||||
this.electronIpcServer.registerChannel('url', urlChannel);
|
||||
|
||||
const issueService = accessor.get(IIssueService);
|
||||
const issueChannel = new IssueChannel(issueService);
|
||||
this.electronIpcServer.registerChannel('issue', issueChannel);
|
||||
|
||||
const workspacesService = accessor.get(IWorkspacesMainService);
|
||||
const workspacesChannel = appInstantiationService.createInstance(WorkspacesChannel, workspacesService);
|
||||
this.electronIpcServer.registerChannel('workspaces', workspacesChannel);
|
||||
@@ -356,6 +383,11 @@ export class CodeApplication {
|
||||
this.electronIpcServer.registerChannel('windows', windowsChannel);
|
||||
this.sharedProcessClient.done(client => client.registerChannel('windows', windowsChannel));
|
||||
|
||||
// Log level management
|
||||
const logLevelChannel = new LogLevelSetterChannel(accessor.get(ILogService));
|
||||
this.electronIpcServer.registerChannel('loglevel', logLevelChannel);
|
||||
this.sharedProcessClient.done(client => client.registerChannel('loglevel', logLevelChannel));
|
||||
|
||||
// Lifecycle
|
||||
this.lifecycleService.ready();
|
||||
|
||||
@@ -376,6 +408,7 @@ export class CodeApplication {
|
||||
|
||||
private afterWindowOpen(accessor: ServicesAccessor): void {
|
||||
const appInstantiationService = accessor.get(IInstantiationService);
|
||||
const windowsMainService = accessor.get(IWindowsMainService);
|
||||
|
||||
let windowsMutex: Mutex = null;
|
||||
if (platform.isWindows) {
|
||||
@@ -387,7 +420,7 @@ export class CodeApplication {
|
||||
this.toDispose.push({ dispose: () => windowsMutex.release() });
|
||||
} catch (e) {
|
||||
if (!this.environmentService.isBuilt) {
|
||||
dialog.showMessageBox({
|
||||
windowsMainService.showMessageBox({
|
||||
title: product.nameLong,
|
||||
type: 'warning',
|
||||
message: 'Failed to load windows-mutex!',
|
||||
@@ -403,7 +436,7 @@ export class CodeApplication {
|
||||
<any>require.__$__nodeRequire('windows-foreground-love');
|
||||
} catch (e) {
|
||||
if (!this.environmentService.isBuilt) {
|
||||
dialog.showMessageBox({
|
||||
windowsMainService.showMessageBox({
|
||||
title: product.nameLong,
|
||||
type: 'warning',
|
||||
message: 'Failed to load windows-foreground-love!',
|
||||
@@ -423,20 +456,6 @@ export class CodeApplication {
|
||||
|
||||
// Start shared process here
|
||||
this.sharedProcess.spawn();
|
||||
|
||||
// Helps application icon refresh after an update with new icon is installed (macOS)
|
||||
// TODO@Ben remove after a couple of releases
|
||||
if (platform.isMacintosh) {
|
||||
if (!this.stateService.getItem(CodeApplication.APP_ICON_REFRESH_KEY)) {
|
||||
this.stateService.setItem(CodeApplication.APP_ICON_REFRESH_KEY, true);
|
||||
|
||||
// 'exe' => /Applications/Visual Studio Code - Insiders.app/Contents/MacOS/Electron
|
||||
const appPath = dirname(dirname(dirname(app.getPath('exe'))));
|
||||
const infoPlistPath = join(appPath, 'Contents', 'Info.plist');
|
||||
touch(appPath).done(null, error => { /* ignore */ });
|
||||
touch(infoPlistPath).done(null, error => { /* ignore */ });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private dispose(): void {
|
||||
|
||||
@@ -68,7 +68,7 @@ export class ProxyAuthHandler {
|
||||
|
||||
const win = new BrowserWindow(opts);
|
||||
const config = {};
|
||||
const baseUrl = require.toUrl('./auth.html');
|
||||
const baseUrl = require.toUrl('vs/code/electron-browser/proxy/auth.html');
|
||||
const url = `${baseUrl}?config=${encodeURIComponent(JSON.stringify(config))}`;
|
||||
const proxyUrl = `${authInfo.host}:${authInfo.port}`;
|
||||
const title = localize('authRequire', "Proxy Authentication Required");
|
||||
|
||||
8
src/vs/code/electron-main/contributions.ts
Normal file
8
src/vs/code/electron-main/contributions.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/platform/update/node/update.config.contribution';
|
||||
@@ -17,6 +17,97 @@ import { isWindows } from 'vs/base/common/platform';
|
||||
import { app } from 'electron';
|
||||
import { basename } from 'path';
|
||||
|
||||
export interface VersionInfo {
|
||||
vscodeVersion: string;
|
||||
os: string;
|
||||
}
|
||||
|
||||
export interface SystemInfo {
|
||||
CPUs?: string;
|
||||
'Memory (System)': string;
|
||||
'Load (avg)'?: string;
|
||||
VM: string;
|
||||
'Screen Reader': string;
|
||||
'Process Argv': string;
|
||||
}
|
||||
|
||||
export interface ProcessInfo {
|
||||
cpu: number;
|
||||
memory: number;
|
||||
pid: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface PerformanceInfo {
|
||||
processInfo?: string;
|
||||
workspaceInfo?: string;
|
||||
}
|
||||
|
||||
export function getPerformanceInfo(info: IMainProcessInfo): Promise<PerformanceInfo> {
|
||||
return listProcesses(info.mainPID).then(rootProcess => {
|
||||
const workspaceInfoMessages = [];
|
||||
|
||||
// Workspace Stats
|
||||
if (info.windows.some(window => window.folders && window.folders.length > 0)) {
|
||||
info.windows.forEach(window => {
|
||||
if (window.folders.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
workspaceInfoMessages.push(`| Window (${window.title})`);
|
||||
|
||||
window.folders.forEach(folder => {
|
||||
try {
|
||||
const stats = collectWorkspaceStats(folder, ['node_modules', '.git']);
|
||||
let countMessage = `${stats.fileCount} files`;
|
||||
if (stats.maxFilesReached) {
|
||||
countMessage = `more than ${countMessage}`;
|
||||
}
|
||||
workspaceInfoMessages.push(`| Folder (${basename(folder)}): ${countMessage}`);
|
||||
workspaceInfoMessages.push(formatWorkspaceStats(stats));
|
||||
|
||||
const launchConfigs = collectLaunchConfigs(folder);
|
||||
if (launchConfigs.length > 0) {
|
||||
workspaceInfoMessages.push(formatLaunchConfigs(launchConfigs));
|
||||
}
|
||||
} catch (error) {
|
||||
workspaceInfoMessages.push(`| Error: Unable to collect workpsace stats for folder ${folder} (${error.toString()})`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
processInfo: formatProcessList(info, rootProcess),
|
||||
workspaceInfo: workspaceInfoMessages.join('\n')
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function getSystemInfo(info: IMainProcessInfo): SystemInfo {
|
||||
const MB = 1024 * 1024;
|
||||
const GB = 1024 * MB;
|
||||
|
||||
const systemInfo: SystemInfo = {
|
||||
'Memory (System)': `${(os.totalmem() / GB).toFixed(2)}GB (${(os.freemem() / GB).toFixed(2)}GB free)`,
|
||||
VM: `${Math.round((virtualMachineHint.value() * 100))}%`,
|
||||
'Screen Reader': `${app.isAccessibilitySupportEnabled() ? 'yes' : 'no'}`,
|
||||
'Process Argv': `${info.mainArguments.join(' ')}`
|
||||
};
|
||||
|
||||
const cpus = os.cpus();
|
||||
if (cpus && cpus.length > 0) {
|
||||
systemInfo.CPUs = `${cpus[0].model} (${cpus.length} x ${cpus[0].speed})`;
|
||||
}
|
||||
|
||||
if (!isWindows) {
|
||||
systemInfo['Load (avg)'] = `${os.loadavg().map(l => Math.round(l)).join(', ')}`;
|
||||
}
|
||||
|
||||
|
||||
return systemInfo;
|
||||
}
|
||||
|
||||
export function printDiagnostics(info: IMainProcessInfo): Promise<any> {
|
||||
return listProcesses(info.mainPID).then(rootProcess => {
|
||||
|
||||
@@ -83,7 +174,6 @@ function formatWorkspaceStats(workspaceStats: WorkspaceStats): string {
|
||||
line += item;
|
||||
};
|
||||
|
||||
|
||||
// File Types
|
||||
let line = '| File types:';
|
||||
const maxShown = 10;
|
||||
@@ -124,7 +214,7 @@ function formatEnvironment(info: IMainProcessInfo): string {
|
||||
|
||||
const output: string[] = [];
|
||||
output.push(`Version: ${pkg.name} ${pkg.version} (${product.commit || 'Commit unknown'}, ${product.date || 'Date unknown'})`);
|
||||
output.push(`OS Version: ${os.type()} ${os.arch()} ${os.release()})`);
|
||||
output.push(`OS Version: ${os.type()} ${os.arch()} ${os.release()}`);
|
||||
const cpus = os.cpus();
|
||||
if (cpus && cpus.length > 0) {
|
||||
output.push(`CPUs: ${cpus[0].model} (${cpus.length} x ${cpus[0].speed})`);
|
||||
@@ -135,6 +225,7 @@ function formatEnvironment(info: IMainProcessInfo): string {
|
||||
}
|
||||
output.push(`VM: ${Math.round((virtualMachineHint.value() * 100))}%`);
|
||||
output.push(`Screen Reader: ${app.isAccessibilitySupportEnabled() ? 'yes' : 'no'}`);
|
||||
output.push(`Process Argv: ${info.mainArguments.join(' ')}`);
|
||||
|
||||
return output.join('\n');
|
||||
}
|
||||
@@ -145,9 +236,11 @@ function formatProcessList(info: IMainProcessInfo, rootProcess: ProcessItem): st
|
||||
|
||||
const output: string[] = [];
|
||||
|
||||
output.push('CPU %\tMem MB\tProcess');
|
||||
output.push('CPU %\tMem MB\t PID\tProcess');
|
||||
|
||||
formatProcessItem(mapPidToWindowTitle, output, rootProcess, 0);
|
||||
if (rootProcess) {
|
||||
formatProcessItem(mapPidToWindowTitle, output, rootProcess, 0);
|
||||
}
|
||||
|
||||
return output.join('\n');
|
||||
}
|
||||
@@ -169,10 +262,10 @@ function formatProcessItem(mapPidToWindowTitle: Map<number, string>, output: str
|
||||
}
|
||||
}
|
||||
const memory = process.platform === 'win32' ? item.mem : (os.totalmem() * (item.mem / 100));
|
||||
output.push(`${pad(Number(item.load.toFixed(0)), 5, ' ')}\t${pad(Number((memory / MB).toFixed(0)), 6, ' ')}\t${name}`);
|
||||
output.push(`${pad(Number(item.load.toFixed(0)), 5, ' ')}\t${pad(Number((memory / MB).toFixed(0)), 6, ' ')}\t${pad(Number((item.pid).toFixed(0)), 6, ' ')}\t${name}`);
|
||||
|
||||
// Recurse into children if any
|
||||
if (Array.isArray(item.children)) {
|
||||
item.children.forEach(child => formatProcessItem(mapPidToWindowTitle, output, child, indent + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,13 @@ import { IChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IURLService } from 'vs/platform/url/common/url';
|
||||
import { IProcessEnvironment } from 'vs/base/common/platform';
|
||||
import { ParsedArgs } from 'vs/platform/environment/common/environment';
|
||||
import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { OpenContext } from 'vs/platform/windows/common/windows';
|
||||
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
|
||||
import { whenDeleted } from 'vs/base/node/pfs';
|
||||
import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
export const ID = 'launchService';
|
||||
export const ILaunchService = createDecorator<ILaunchService>(ID);
|
||||
@@ -33,6 +34,7 @@ export interface IWindowInfo {
|
||||
|
||||
export interface IMainProcessInfo {
|
||||
mainPID: number;
|
||||
mainArguments: string[];
|
||||
windows: IWindowInfo[];
|
||||
}
|
||||
|
||||
@@ -41,12 +43,14 @@ export interface ILaunchService {
|
||||
start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void>;
|
||||
getMainProcessId(): TPromise<number>;
|
||||
getMainProcessInfo(): TPromise<IMainProcessInfo>;
|
||||
getLogsPath(): TPromise<string>;
|
||||
}
|
||||
|
||||
export interface ILaunchChannel extends IChannel {
|
||||
call(command: 'start', arg: IStartArguments): TPromise<void>;
|
||||
call(command: 'get-main-process-id', arg: null): TPromise<any>;
|
||||
call(command: 'get-main-process-info', arg: null): TPromise<any>;
|
||||
call(command: 'get-logs-path', arg: null): TPromise<string>;
|
||||
call(command: string, arg: any): TPromise<any>;
|
||||
}
|
||||
|
||||
@@ -65,6 +69,9 @@ export class LaunchChannel implements ILaunchChannel {
|
||||
|
||||
case 'get-main-process-info':
|
||||
return this.service.getMainProcessInfo();
|
||||
|
||||
case 'get-logs-path':
|
||||
return this.service.getLogsPath();
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@@ -88,6 +95,10 @@ export class LaunchChannelClient implements ILaunchService {
|
||||
public getMainProcessInfo(): TPromise<IMainProcessInfo> {
|
||||
return this.channel.call('get-main-process-info', null);
|
||||
}
|
||||
|
||||
public getLogsPath(): TPromise<string> {
|
||||
return this.channel.call('get-logs-path', null);
|
||||
}
|
||||
}
|
||||
|
||||
export class LaunchService implements ILaunchService {
|
||||
@@ -98,21 +109,35 @@ export class LaunchService implements ILaunchService {
|
||||
@ILogService private logService: ILogService,
|
||||
@IWindowsMainService private windowsMainService: IWindowsMainService,
|
||||
@IURLService private urlService: IURLService,
|
||||
@IWorkspacesMainService private workspacesMainService: IWorkspacesMainService
|
||||
@IWorkspacesMainService private workspacesMainService: IWorkspacesMainService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService
|
||||
) { }
|
||||
|
||||
public start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void> {
|
||||
this.logService.trace('Received data from other instance: ', args, userEnv);
|
||||
|
||||
// Check early for open-url which is handled in URL service
|
||||
const openUrl = (args['open-url'] ? args._urls : []) || [];
|
||||
if (openUrl.length > 0) {
|
||||
openUrl.forEach(url => this.urlService.open(url));
|
||||
|
||||
if (this.shouldOpenUrl(args)) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
// Otherwise handle in windows service
|
||||
return this.startOpenWindow(args, userEnv);
|
||||
}
|
||||
|
||||
private shouldOpenUrl(args: ParsedArgs): boolean {
|
||||
if (args['open-url'] && args._urls && args._urls.length > 0) {
|
||||
// --open-url must contain -- followed by the url(s)
|
||||
// process.argv is used over args._ as args._ are resolved to file paths at this point
|
||||
args._urls.forEach(url => this.urlService.open(url));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private startOpenWindow(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void> {
|
||||
const context = !!userEnv['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP;
|
||||
let usedWindows: ICodeWindow[];
|
||||
if (!!args.extensionDevelopmentPath) {
|
||||
@@ -158,12 +183,19 @@ export class LaunchService implements ILaunchService {
|
||||
|
||||
return TPromise.wrap({
|
||||
mainPID: process.pid,
|
||||
mainArguments: process.argv,
|
||||
windows: this.windowsMainService.getWindows().map(window => {
|
||||
return this.getWindowInfo(window);
|
||||
})
|
||||
} as IMainProcessInfo);
|
||||
}
|
||||
|
||||
public getLogsPath(): TPromise<string> {
|
||||
this.logService.trace('Received request for logs path from other instance.');
|
||||
|
||||
return TPromise.as(this.environmentService.logsPath);
|
||||
}
|
||||
|
||||
private getWindowInfo(window: ICodeWindow): IWindowInfo {
|
||||
const folders: string[] = [];
|
||||
|
||||
@@ -172,7 +204,7 @@ export class LaunchService implements ILaunchService {
|
||||
} else if (window.openedWorkspace) {
|
||||
const rootFolders = this.workspacesMainService.resolveWorkspaceSync(window.openedWorkspace.configPath).folders;
|
||||
rootFolders.forEach(root => {
|
||||
if (root.uri.scheme === 'file') {
|
||||
if (root.uri.scheme === Schemas.file) { // todo@remote signal remote folders?
|
||||
folders.push(root.uri.fsPath);
|
||||
}
|
||||
});
|
||||
@@ -184,4 +216,4 @@ export class LaunchService implements ILaunchService {
|
||||
folders
|
||||
} as IWindowInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
155
src/vs/code/electron-main/logUploader.ts
Normal file
155
src/vs/code/electron-main/logUploader.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as os from 'os';
|
||||
import * as cp from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { ILaunchChannel } from 'vs/code/electron-main/launch';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import product from 'vs/platform/node/product';
|
||||
import { IRequestService } from 'vs/platform/request/node/request';
|
||||
import { IRequestContext } from 'vs/base/node/request';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
|
||||
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(
|
||||
channel: ILaunchChannel,
|
||||
requestService: IRequestService,
|
||||
environmentService: IEnvironmentService
|
||||
): TPromise<any> {
|
||||
const endpoint = Endpoint.getFromProduct();
|
||||
if (!endpoint) {
|
||||
console.error(localize('invalidEndpoint', 'Invalid log uploader endpoint'));
|
||||
return;
|
||||
}
|
||||
|
||||
const logsPath = await channel.call('get-logs-path', null);
|
||||
|
||||
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
|
||||
): TPromise<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'
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
clearInterval(dotter);
|
||||
console.log(localize('postError', 'Error posting logs: {0}', e));
|
||||
throw e;
|
||||
}
|
||||
|
||||
return new TPromise<PostResult>((res, 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) {
|
||||
res(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
|
||||
): TPromise<string> {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'vscode-log-upload'));
|
||||
const outZip = path.join(tempDir, 'logs.zip');
|
||||
return new TPromise<string>((resolve, reject) => {
|
||||
doZip(logsPath, outZip, tempDir, (err, stdout, stderr) => {
|
||||
if (err) {
|
||||
console.error(localize('zipError', 'Error zipping logs: {0}', err));
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/code/electron-main/contributions';
|
||||
import { app, dialog } from 'electron';
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
@@ -21,7 +22,7 @@ import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiati
|
||||
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { ILogService, ConsoleLogMainService, MultiplexLogService } from 'vs/platform/log/common/log';
|
||||
import { ILogService, ConsoleLogMainService, MultiplexLogService, getLogLevel } from 'vs/platform/log/common/log';
|
||||
import { StateService } from 'vs/platform/state/node/stateService';
|
||||
import { IStateService } from 'vs/platform/state/common/state';
|
||||
import { IBackupMainService } from 'vs/platform/backup/common/backup';
|
||||
@@ -42,16 +43,20 @@ import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/work
|
||||
import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { localize } from 'vs/nls';
|
||||
import { mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { createLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { printDiagnostics } from 'vs/code/electron-main/diagnostics';
|
||||
import { BufferLogService } from 'vs/platform/log/common/bufferLog';
|
||||
import { uploadLogs } from 'vs/code/electron-main/logUploader';
|
||||
import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
|
||||
import { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { ChoiceCliService } from 'vs/platform/dialogs/node/choiceCli';
|
||||
|
||||
function createServices(args: ParsedArgs): IInstantiationService {
|
||||
function createServices(args: ParsedArgs, bufferLogService: BufferLogService): IInstantiationService {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
const environmentService = new EnvironmentService(args, process.execPath);
|
||||
const spdlogService = createLogService('main', environmentService);
|
||||
const consoleLogService = new ConsoleLogMainService(environmentService);
|
||||
const logService = new MultiplexLogService([consoleLogService, spdlogService]);
|
||||
const consoleLogService = new ConsoleLogMainService(getLogLevel(environmentService));
|
||||
const logService = new MultiplexLogService([consoleLogService, bufferLogService]);
|
||||
|
||||
process.once('exit', () => logService.dispose());
|
||||
|
||||
@@ -68,6 +73,7 @@ function createServices(args: ParsedArgs): IInstantiationService {
|
||||
services.set(IRequestService, new SyncDescriptor(RequestService));
|
||||
services.set(IURLService, new SyncDescriptor(URLService, args['open-url'] ? args._urls : []));
|
||||
services.set(IBackupMainService, new SyncDescriptor(BackupMainService));
|
||||
services.set(IChoiceService, new SyncDescriptor(ChoiceCliService));
|
||||
|
||||
return new InstantiationService(services, true);
|
||||
}
|
||||
@@ -104,6 +110,7 @@ class ExpectedError extends Error {
|
||||
function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
|
||||
const logService = accessor.get(ILogService);
|
||||
const environmentService = accessor.get(IEnvironmentService);
|
||||
const requestService = accessor.get(IRequestService);
|
||||
|
||||
function allowSetForegroundWindow(service: LaunchChannelClient): TPromise<void> {
|
||||
let promise = TPromise.wrap<void>(void 0);
|
||||
@@ -133,6 +140,12 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
|
||||
throw new ExpectedError('Terminating...');
|
||||
}
|
||||
|
||||
// Log uploader usage info
|
||||
if (typeof environmentService.args['upload-logs'] !== 'undefined') {
|
||||
logService.warn('Warning: The --upload-logs argument can only be used if Code is already running. Please run it again after Code has started.');
|
||||
throw new ExpectedError('Terminating...');
|
||||
}
|
||||
|
||||
// dock might be hidden at this case due to a retry
|
||||
if (platform.isMacintosh) {
|
||||
app.dock.show();
|
||||
@@ -170,7 +183,7 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
|
||||
// Skip this if we are running with --wait where it is expected that we wait for a while.
|
||||
// Also skip when gathering diagnostics (--status) which can take a longer time.
|
||||
let startupWarningDialogHandle: number;
|
||||
if (!environmentService.wait && !environmentService.status) {
|
||||
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),
|
||||
@@ -189,6 +202,12 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
|
||||
});
|
||||
}
|
||||
|
||||
// Log uploader
|
||||
if (typeof environmentService.args['upload-logs'] !== 'undefined') {
|
||||
return uploadLogs(channel, requestService, environmentService)
|
||||
.then(() => TPromise.wrapError(new ExpectedError()));
|
||||
}
|
||||
|
||||
logService.trace('Sending env to running instance...');
|
||||
|
||||
return allowSetForegroundWindow(service)
|
||||
@@ -236,7 +255,7 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
|
||||
}
|
||||
|
||||
function showStartupWarningDialog(message: string, detail: string): void {
|
||||
dialog.showMessageBox(null, {
|
||||
dialog.showMessageBox({
|
||||
title: product.nameLong,
|
||||
type: 'warning',
|
||||
buttons: [mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
|
||||
@@ -272,8 +291,12 @@ function quit(accessor: ServicesAccessor, reason?: ExpectedError | Error): void
|
||||
}
|
||||
|
||||
function main() {
|
||||
let args: ParsedArgs;
|
||||
|
||||
// Set the error handler early enough so that we are not getting the
|
||||
// default electron error dialog popping up
|
||||
setUnexpectedErrorHandler(err => console.error(err));
|
||||
|
||||
let args: ParsedArgs;
|
||||
try {
|
||||
args = parseMainProcessArgv(process.argv);
|
||||
args = validatePaths(args);
|
||||
@@ -284,7 +307,12 @@ function main() {
|
||||
return;
|
||||
}
|
||||
|
||||
const instantiationService = createServices(args);
|
||||
// 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);
|
||||
|
||||
return instantiationService.invokeFunction(accessor => {
|
||||
|
||||
@@ -300,7 +328,10 @@ function main() {
|
||||
// Startup
|
||||
return instantiationService.invokeFunction(a => createPaths(a.get(IEnvironmentService)))
|
||||
.then(() => instantiationService.invokeFunction(setupIPC))
|
||||
.then(mainIpcServer => instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnv).startup());
|
||||
.then(mainIpcServer => {
|
||||
bufferLogService.logger = createSpdLogService('main', bufferLogService.getLevel(), environmentService.logsPath);
|
||||
return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnv).startup();
|
||||
});
|
||||
}).done(null, err => instantiationService.invokeFunction(quit, err));
|
||||
}
|
||||
|
||||
|
||||
@@ -9,16 +9,16 @@ import * as nls from 'vs/nls';
|
||||
import { isMacintosh, isLinux, isWindows, language } from 'vs/base/common/platform';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { ipcMain as ipc, app, shell, dialog, Menu, MenuItem, BrowserWindow, clipboard } from 'electron';
|
||||
import { OpenContext, IRunActionInWindowRequest } from 'vs/platform/windows/common/windows';
|
||||
import { ipcMain as ipc, app, shell, Menu, MenuItem, BrowserWindow } from 'electron';
|
||||
import { OpenContext, IRunActionInWindowRequest, IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
import { AutoSaveConfiguration } from 'vs/platform/files/common/files';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IUpdateService, State as UpdateState } from 'vs/platform/update/common/update';
|
||||
import { IUpdateService, StateType } from 'vs/platform/update/common/update';
|
||||
import product from 'vs/platform/node/product';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { mnemonicMenuLabel as baseMnemonicLabel, unmnemonicLabel, getPathLabel, mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { mnemonicMenuLabel as baseMnemonicLabel, unmnemonicLabel, getPathLabel } from 'vs/base/common/labels';
|
||||
import { KeybindingsResolver } from 'vs/code/electron-main/keyboard';
|
||||
import { IWindowsMainService, IWindowsCountChangedEvent } from 'vs/platform/windows/electron-main/windows';
|
||||
import { IHistoryMainService } from 'vs/platform/history/common/history';
|
||||
@@ -69,6 +69,7 @@ export class CodeMenu {
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IWindowsMainService private windowsMainService: IWindowsMainService,
|
||||
@IWindowsService private windowsService: IWindowsService,
|
||||
@IEnvironmentService private environmentService: IEnvironmentService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService,
|
||||
@IHistoryMainService private historyMainService: IHistoryMainService
|
||||
@@ -246,6 +247,7 @@ export class CodeMenu {
|
||||
const editMenu = new Menu();
|
||||
const editMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mEdit', comment: ['&& denotes a mnemonic'] }, "&&Edit")), submenu: editMenu });
|
||||
this.setEditMenu(editMenu);
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
// Selection
|
||||
// const selectionMenu = new Menu();
|
||||
@@ -333,7 +335,7 @@ export class CodeMenu {
|
||||
const showAll = new MenuItem({ label: nls.localize('mShowAll', "Show All"), role: 'unhide' });
|
||||
const quit = new MenuItem(this.likeAction('workbench.action.quit', {
|
||||
label: nls.localize('miQuit', "Quit {0}", product.nameLong), click: () => {
|
||||
if (this.windowsMainService.getWindowCount() === 0 || !!this.windowsMainService.getFocusedWindow()) {
|
||||
if (this.windowsMainService.getWindowCount() === 0 || !!BrowserWindow.getFocusedWindow()) {
|
||||
this.windowsMainService.quit(); // fix for https://github.com/Microsoft/vscode/issues/39191
|
||||
}
|
||||
}
|
||||
@@ -709,9 +711,11 @@ export class CodeMenu {
|
||||
}
|
||||
|
||||
const commands = this.createMenuItem(nls.localize({ key: 'miCommandPalette', comment: ['&& denotes a mnemonic'] }, "&&Command Palette..."), 'workbench.action.showCommands');
|
||||
const openView = this.createMenuItem(nls.localize({ key: 'miOpenView', comment: ['&& denotes a mnemonic'] }, "&&Open View..."), 'workbench.action.openView');
|
||||
|
||||
const fullscreen = new MenuItem(this.withKeybinding('workbench.action.toggleFullScreen', { label: this.mnemonicLabel(nls.localize({ key: 'miToggleFullScreen', comment: ['&& denotes a mnemonic'] }, "Toggle &&Full Screen")), click: () => this.windowsMainService.getLastActiveWindow().toggleFullScreen(), enabled: this.windowsMainService.getWindowCount() > 0 }));
|
||||
const toggleZenMode = this.createMenuItem(nls.localize('miToggleZenMode', "Toggle Zen Mode"), 'workbench.action.toggleZenMode');
|
||||
const toggleCenteredLayout = this.createMenuItem(nls.localize('miToggleCenteredLayout', "Toggle Centered Layout"), 'workbench.action.toggleCenteredLayout');
|
||||
const toggleMenuBar = this.createMenuItem(nls.localize({ key: 'miToggleMenuBar', comment: ['&& denotes a mnemonic'] }, "Toggle Menu &&Bar"), 'workbench.action.toggleMenuBar');
|
||||
// {{SQL CARBON EDIT}}
|
||||
//const splitEditor = this.createMenuItem(nls.localize({ key: 'miSplitEditor', comment: ['&& denotes a mnemonic'] }, "Split &&Editor"), 'workbench.action.splitEditor');
|
||||
@@ -758,6 +762,7 @@ export class CodeMenu {
|
||||
|
||||
arrays.coalesce([
|
||||
commands,
|
||||
openView,
|
||||
__separator__(),
|
||||
servers,
|
||||
tasks,
|
||||
@@ -777,6 +782,7 @@ export class CodeMenu {
|
||||
__separator__(),
|
||||
fullscreen,
|
||||
toggleZenMode,
|
||||
toggleCenteredLayout,
|
||||
isWindows || isLinux ? toggleMenuBar : void 0,
|
||||
__separator__(),
|
||||
// {{SQL CARBON EDIT}}
|
||||
@@ -979,7 +985,7 @@ export class CodeMenu {
|
||||
const label = nls.localize({ key: 'miReportIssue', comment: ['&& denotes a mnemonic', 'Translate this to "Report Issue in English" in all languages please!'] }, "Report &&Issue");
|
||||
|
||||
if (this.windowsMainService.getWindowCount() > 0) {
|
||||
reportIssuesItem = this.createMenuItem(label, 'workbench.action.reportIssues');
|
||||
reportIssuesItem = this.createMenuItem(label, 'workbench.action.openIssueReporter');
|
||||
} else {
|
||||
reportIssuesItem = new MenuItem({ label: this.mnemonicLabel(label), click: () => this.openUrl(product.reportIssueUrl, 'openReportIssues') });
|
||||
}
|
||||
@@ -1037,7 +1043,7 @@ export class CodeMenu {
|
||||
}
|
||||
|
||||
helpMenu.append(__separator__());
|
||||
helpMenu.append(new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miAbout', comment: ['&& denotes a mnemonic'] }, "&&About")), click: () => this.openAboutDialog() }));
|
||||
helpMenu.append(new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miAbout', comment: ['&& denotes a mnemonic'] }, "&&About")), click: () => this.windowsService.openAboutDialog() }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1085,45 +1091,54 @@ export class CodeMenu {
|
||||
}
|
||||
|
||||
private getUpdateMenuItems(): Electron.MenuItem[] {
|
||||
switch (this.updateService.state) {
|
||||
case UpdateState.Uninitialized:
|
||||
const state = this.updateService.state;
|
||||
|
||||
switch (state.type) {
|
||||
case StateType.Uninitialized:
|
||||
return [];
|
||||
|
||||
case UpdateState.UpdateDownloaded:
|
||||
case StateType.Idle:
|
||||
return [new MenuItem({
|
||||
label: nls.localize('miCheckForUpdates', "Check for Updates..."), click: () => setTimeout(() => {
|
||||
this.reportMenuActionTelemetry('CheckForUpdate');
|
||||
|
||||
const focusedWindow = this.windowsMainService.getFocusedWindow();
|
||||
const context = focusedWindow ? { windowId: focusedWindow.id } : null;
|
||||
this.updateService.checkForUpdates(context);
|
||||
}, 0)
|
||||
})];
|
||||
|
||||
case StateType.CheckingForUpdates:
|
||||
return [new MenuItem({ label: nls.localize('miCheckingForUpdates', "Checking For Updates..."), enabled: false })];
|
||||
|
||||
case StateType.AvailableForDownload:
|
||||
return [new MenuItem({
|
||||
label: nls.localize('miDownloadUpdate', "Download Available Update"), click: () => {
|
||||
this.updateService.downloadUpdate();
|
||||
}
|
||||
})];
|
||||
|
||||
case StateType.Downloading:
|
||||
return [new MenuItem({ label: nls.localize('miDownloadingUpdate', "Downloading Update..."), enabled: false })];
|
||||
|
||||
case StateType.Downloaded:
|
||||
return [new MenuItem({
|
||||
label: nls.localize('miInstallUpdate', "Install Update..."), click: () => {
|
||||
this.reportMenuActionTelemetry('InstallUpdate');
|
||||
this.updateService.applyUpdate();
|
||||
}
|
||||
})];
|
||||
|
||||
case StateType.Updating:
|
||||
return [new MenuItem({ label: nls.localize('miInstallingUpdate', "Installing Update..."), enabled: false })];
|
||||
|
||||
case StateType.Ready:
|
||||
return [new MenuItem({
|
||||
label: nls.localize('miRestartToUpdate', "Restart to Update..."), click: () => {
|
||||
this.reportMenuActionTelemetry('RestartToUpdate');
|
||||
this.updateService.quitAndInstall();
|
||||
}
|
||||
})];
|
||||
|
||||
case UpdateState.CheckingForUpdate:
|
||||
return [new MenuItem({ label: nls.localize('miCheckingForUpdates', "Checking For Updates..."), enabled: false })];
|
||||
|
||||
case UpdateState.UpdateAvailable:
|
||||
if (isLinux) {
|
||||
return [new MenuItem({
|
||||
label: nls.localize('miDownloadUpdate', "Download Available Update"), click: () => {
|
||||
this.updateService.quitAndInstall();
|
||||
}
|
||||
})];
|
||||
}
|
||||
|
||||
const updateAvailableLabel = isWindows
|
||||
? nls.localize('miDownloadingUpdate', "Downloading Update...")
|
||||
: nls.localize('miInstallingUpdate', "Installing Update...");
|
||||
|
||||
return [new MenuItem({ label: updateAvailableLabel, enabled: false })];
|
||||
|
||||
default:
|
||||
const result = [new MenuItem({
|
||||
label: nls.localize('miCheckForUpdates', "Check for Updates..."), click: () => setTimeout(() => {
|
||||
this.reportMenuActionTelemetry('CheckForUpdate');
|
||||
this.updateService.checkForUpdates(true);
|
||||
}, 0)
|
||||
})];
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1142,11 +1157,6 @@ export class CodeMenu {
|
||||
const enabled = typeof arg3 === 'boolean' ? arg3 : this.windowsMainService.getWindowCount() > 0;
|
||||
const checked = typeof arg4 === 'boolean' ? arg4 : false;
|
||||
|
||||
let commandId: string;
|
||||
if (typeof arg2 === 'string') {
|
||||
commandId = arg2;
|
||||
}
|
||||
|
||||
const options: Electron.MenuItemConstructorOptions = {
|
||||
label,
|
||||
click,
|
||||
@@ -1158,6 +1168,13 @@ export class CodeMenu {
|
||||
options['checked'] = checked;
|
||||
}
|
||||
|
||||
let commandId: string;
|
||||
if (typeof arg2 === 'string') {
|
||||
commandId = arg2;
|
||||
} else if (Array.isArray(arg2)) {
|
||||
commandId = arg2[0];
|
||||
}
|
||||
|
||||
return new MenuItem(this.withKeybinding(commandId, options));
|
||||
}
|
||||
|
||||
@@ -1241,41 +1258,6 @@ export class CodeMenu {
|
||||
return options;
|
||||
}
|
||||
|
||||
private openAboutDialog(): void {
|
||||
const lastActiveWindow = this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow();
|
||||
|
||||
const detail = nls.localize('aboutDetail',
|
||||
"Version {0}\nCommit {1}\nDate {2}\nShell {3}\nRenderer {4}\nNode {5}\nArchitecture {6}",
|
||||
app.getVersion(),
|
||||
product.commit || 'Unknown',
|
||||
product.date || 'Unknown',
|
||||
process.versions['electron'],
|
||||
process.versions['chrome'],
|
||||
process.versions['node'],
|
||||
process.arch
|
||||
);
|
||||
|
||||
const buttons = [nls.localize('okButton', "OK")];
|
||||
if (isWindows) {
|
||||
buttons.push(mnemonicButtonLabel(nls.localize({ key: 'copy', comment: ['&& denotes a mnemonic'] }, "&&Copy"))); // https://github.com/Microsoft/vscode/issues/37608
|
||||
}
|
||||
|
||||
const result = dialog.showMessageBox(lastActiveWindow && lastActiveWindow.win, {
|
||||
title: product.nameLong,
|
||||
type: 'info',
|
||||
message: product.nameLong,
|
||||
detail: `\n${detail}`,
|
||||
buttons,
|
||||
noLink: true
|
||||
});
|
||||
|
||||
if (isWindows && result === 1) {
|
||||
clipboard.writeText(detail);
|
||||
}
|
||||
|
||||
this.reportMenuActionTelemetry('showAboutDialog');
|
||||
}
|
||||
|
||||
private openUrl(url: string, id: string): void {
|
||||
shell.openExternal(url);
|
||||
this.reportMenuActionTelemetry(id);
|
||||
|
||||
@@ -12,6 +12,7 @@ import { IProcessEnvironment } from 'vs/base/common/platform';
|
||||
import { BrowserWindow, ipcMain } from 'electron';
|
||||
import { ISharedProcess } from 'vs/platform/windows/electron-main/windows';
|
||||
import { Barrier } from 'vs/base/common/async';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
export class SharedProcess implements ISharedProcess {
|
||||
|
||||
@@ -23,7 +24,8 @@ export class SharedProcess implements ISharedProcess {
|
||||
constructor(
|
||||
private environmentService: IEnvironmentService,
|
||||
private readonly machineId: string,
|
||||
private readonly userEnv: IProcessEnvironment
|
||||
private readonly userEnv: IProcessEnvironment,
|
||||
private readonly logService: ILogService
|
||||
) { }
|
||||
|
||||
@memoize
|
||||
@@ -43,7 +45,7 @@ export class SharedProcess implements ISharedProcess {
|
||||
userEnv: this.userEnv
|
||||
});
|
||||
|
||||
const url = `${require.toUrl('vs/code/electron-browser/sharedProcess.html')}?config=${encodeURIComponent(JSON.stringify(config))}`;
|
||||
const url = `${require.toUrl('vs/code/electron-browser/sharedProcess/sharedProcess.html')}?config=${encodeURIComponent(JSON.stringify(config))}`;
|
||||
this.window.loadURL(url);
|
||||
|
||||
// Prevent the window from dying
|
||||
@@ -75,7 +77,8 @@ export class SharedProcess implements ISharedProcess {
|
||||
ipcMain.once('handshake:hello', ({ sender }: { sender: any }) => {
|
||||
sender.send('handshake:hey there', {
|
||||
sharedIPCHandle: this.environmentService.sharedIPCHandle,
|
||||
args: this.environmentService.args
|
||||
args: this.environmentService.args,
|
||||
logLevel: this.logService.getLevel()
|
||||
});
|
||||
|
||||
ipcMain.once('handshake:im ready', () => c(null));
|
||||
|
||||
@@ -198,7 +198,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
this._win = new BrowserWindow(options);
|
||||
this._id = this._win.id;
|
||||
|
||||
// TODO@Ben Bug in Electron (https://github.com/electron/electron/issues/10862). On multi-monitor setups,
|
||||
// Bug in Electron (https://github.com/electron/electron/issues/10862). On multi-monitor setups,
|
||||
// it can happen that the position we set to the window is not the correct one on the display.
|
||||
// To workaround, we ask the window for its position and set it again if not matching.
|
||||
// This only applies if the window is not fullscreen or maximized and multiple monitors are used.
|
||||
@@ -559,6 +559,10 @@ export class CodeWindow implements ICodeWindow {
|
||||
configuration['extensions-dir'] = cli['extensions-dir'];
|
||||
}
|
||||
|
||||
if (cli) {
|
||||
configuration['disable-extensions'] = cli['disable-extensions'];
|
||||
}
|
||||
|
||||
configuration.isInitialStartup = false; // since this is a reload
|
||||
|
||||
// Load config
|
||||
@@ -567,6 +571,10 @@ export class CodeWindow implements ICodeWindow {
|
||||
|
||||
private getUrl(windowConfiguration: IWindowConfiguration): string {
|
||||
|
||||
// Set window ID
|
||||
windowConfiguration.windowId = this._win.id;
|
||||
windowConfiguration.logLevel = this.logService.getLevel();
|
||||
|
||||
// Set zoomlevel
|
||||
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
|
||||
const zoomLevel = windowConfig && windowConfig.zoomLevel;
|
||||
@@ -578,7 +586,11 @@ export class CodeWindow implements ICodeWindow {
|
||||
windowConfiguration.fullscreen = this._win.isFullScreen();
|
||||
|
||||
// Set Accessibility Config
|
||||
windowConfiguration.highContrast = isWindows && systemPreferences.isInvertedColorScheme() && (!windowConfig || windowConfig.autoDetectHighContrast);
|
||||
let autoDetectHighContrast = true;
|
||||
if (windowConfig && windowConfig.autoDetectHighContrast === false) {
|
||||
autoDetectHighContrast = false;
|
||||
}
|
||||
windowConfiguration.highContrast = isWindows && autoDetectHighContrast && systemPreferences.isInvertedColorScheme();
|
||||
windowConfiguration.accessibilitySupport = app.isAccessibilitySupportEnabled();
|
||||
|
||||
// Theme
|
||||
@@ -588,14 +600,13 @@ export class CodeWindow implements ICodeWindow {
|
||||
// Perf Counters
|
||||
windowConfiguration.perfEntries = exportEntries();
|
||||
windowConfiguration.perfStartTime = global.perfStartTime;
|
||||
windowConfiguration.perfAppReady = global.perfAppReady;
|
||||
windowConfiguration.perfWindowLoadTime = Date.now();
|
||||
|
||||
// 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]) {
|
||||
if (config[key] === void 0 || config[key] === null || config[key] === '') {
|
||||
delete config[key]; // only send over properties that have a true value
|
||||
}
|
||||
}
|
||||
@@ -838,7 +849,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
this._win.setAutoHideMenuBar(true);
|
||||
|
||||
if (notify) {
|
||||
this.send('vscode:showInfoMessage', nls.localize('hiddenMenuBar', "You can still access the menu bar by pressing the **Alt** key."));
|
||||
this.send('vscode:showInfoMessage', nls.localize('hiddenMenuBar', "You can still access the menu bar by pressing the Alt-key."));
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -955,7 +966,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
const segments: ITouchBarSegment[] = items.map(item => {
|
||||
let icon: Electron.NativeImage;
|
||||
if (item.iconPath) {
|
||||
icon = nativeImage.createFromPath(item.iconPath);
|
||||
icon = nativeImage.createFromPath(item.iconPath.dark);
|
||||
if (icon.isEmpty()) {
|
||||
icon = void 0;
|
||||
}
|
||||
|
||||
@@ -19,13 +19,13 @@ import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/node/pa
|
||||
import { ILifecycleService, UnloadReason, IWindowUnloadEvent } 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, ReadyState, IPathsToWaitFor, IEnterWorkspaceResult } from 'vs/platform/windows/common/windows';
|
||||
import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, INativeOpenDialogOptions, ReadyState, IPathsToWaitFor, IEnterWorkspaceResult, IMessageBoxResult } from 'vs/platform/windows/common/windows';
|
||||
import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderPath } from 'vs/code/node/windowsFinder';
|
||||
import CommonEvent, { Emitter } from 'vs/base/common/event';
|
||||
import product from 'vs/platform/node/product';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { isEqual } from 'vs/base/common/paths';
|
||||
import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent } from 'vs/platform/windows/electron-main/windows';
|
||||
import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
|
||||
import { IHistoryMainService } from 'vs/platform/history/common/history';
|
||||
import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
@@ -35,6 +35,7 @@ import { mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { normalizeNFC } from 'vs/base/common/strings';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { Queue } from 'vs/base/common/async';
|
||||
|
||||
enum WindowError {
|
||||
UNRESPONSIVE,
|
||||
@@ -45,10 +46,6 @@ interface INewWindowState extends ISingleWindowState {
|
||||
hasDefaultState?: boolean;
|
||||
}
|
||||
|
||||
interface ILegacyWindowState extends IWindowState {
|
||||
workspacePath?: string;
|
||||
}
|
||||
|
||||
interface IWindowState {
|
||||
workspace?: IWorkspaceIdentifier;
|
||||
folderPath?: string;
|
||||
@@ -56,10 +53,6 @@ interface IWindowState {
|
||||
uiState: ISingleWindowState;
|
||||
}
|
||||
|
||||
interface ILegacyWindowsState extends IWindowsState {
|
||||
openedFolders?: IWindowState[];
|
||||
}
|
||||
|
||||
interface IWindowsState {
|
||||
lastActiveWindow?: IWindowState;
|
||||
lastPluginDevelopmentHostWindow?: IWindowState;
|
||||
@@ -116,7 +109,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
private windowsState: IWindowsState;
|
||||
private lastClosedWindowState: IWindowState;
|
||||
|
||||
private fileDialog: FileDialog;
|
||||
private dialogs: Dialogs;
|
||||
private workspacesManager: WorkspacesManager;
|
||||
|
||||
private _onWindowReady = new Emitter<CodeWindow>();
|
||||
@@ -151,39 +144,12 @@ export class WindowsManager implements IWindowsMainService {
|
||||
@IInstantiationService private instantiationService: IInstantiationService
|
||||
) {
|
||||
this.windowsState = this.stateService.getItem<IWindowsState>(WindowsManager.windowsStateStorageKey) || { openedWindows: [] };
|
||||
|
||||
this.fileDialog = new FileDialog(environmentService, telemetryService, stateService, this);
|
||||
this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, environmentService, this);
|
||||
|
||||
this.migrateLegacyWindowState();
|
||||
}
|
||||
|
||||
private migrateLegacyWindowState(): void {
|
||||
const state: ILegacyWindowsState = this.windowsState;
|
||||
|
||||
// TODO@Ben migration from previous openedFolders to new openedWindows property
|
||||
if (Array.isArray(state.openedFolders) && state.openedFolders.length > 0) {
|
||||
state.openedWindows = state.openedFolders;
|
||||
state.openedFolders = void 0;
|
||||
} else if (!state.openedWindows) {
|
||||
state.openedWindows = [];
|
||||
if (!Array.isArray(this.windowsState.openedWindows)) {
|
||||
this.windowsState.openedWindows = [];
|
||||
}
|
||||
|
||||
// TODO@Ben migration from previous workspacePath in window state to folderPath
|
||||
const states: ILegacyWindowState[] = [];
|
||||
states.push(state.lastActiveWindow);
|
||||
states.push(state.lastPluginDevelopmentHostWindow);
|
||||
states.push(...state.openedWindows);
|
||||
states.forEach(state => {
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof state.workspacePath === 'string') {
|
||||
state.folderPath = state.workspacePath;
|
||||
state.workspacePath = void 0;
|
||||
}
|
||||
});
|
||||
this.dialogs = new Dialogs(environmentService, telemetryService, stateService, this);
|
||||
this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, environmentService, this);
|
||||
}
|
||||
|
||||
public ready(initialUserEnv: IProcessEnvironment): void {
|
||||
@@ -277,7 +243,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
// - closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeQuit(0)
|
||||
//
|
||||
private onBeforeQuit(): void {
|
||||
const currentWindowsState: ILegacyWindowsState = {
|
||||
const currentWindowsState: IWindowsState = {
|
||||
openedWindows: [],
|
||||
lastPluginDevelopmentHostWindow: this.windowsState.lastPluginDevelopmentHostWindow,
|
||||
lastActiveWindow: this.lastClosedWindowState
|
||||
@@ -403,7 +369,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
let foldersToRestore: string[] = [];
|
||||
let workspacesToRestore: IWorkspaceIdentifier[] = [];
|
||||
let emptyToRestore: string[] = [];
|
||||
if (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath) {
|
||||
if (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath && !openConfig.cli['disable-restore-windows']) {
|
||||
foldersToRestore = this.backupMainService.getFolderBackupPaths();
|
||||
|
||||
workspacesToRestore = this.backupMainService.getWorkspaceBackups(); // collect from workspaces with hot-exit backups
|
||||
@@ -825,12 +791,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
noLink: true
|
||||
};
|
||||
|
||||
const activeWindow = BrowserWindow.getFocusedWindow();
|
||||
if (activeWindow) {
|
||||
dialog.showMessageBox(activeWindow, options);
|
||||
} else {
|
||||
dialog.showMessageBox(options);
|
||||
}
|
||||
this.dialogs.showMessageBox(options, this.getFocusedWindow());
|
||||
}
|
||||
|
||||
return path;
|
||||
@@ -940,10 +901,6 @@ export class WindowsManager implements IWindowsMainService {
|
||||
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
|
||||
restoreWindows = ((windowConfig && windowConfig.restoreWindows) || 'one') as RestoreWindowsSetting;
|
||||
|
||||
if (restoreWindows === 'one' /* default */ && windowConfig && windowConfig.reopenFolders) {
|
||||
restoreWindows = windowConfig.reopenFolders; // TODO@Ben migration from deprecated window.reopenFolders setting
|
||||
}
|
||||
|
||||
if (['all', 'folders', 'one', 'none'].indexOf(restoreWindows) === -1) {
|
||||
restoreWindows = 'one';
|
||||
}
|
||||
@@ -987,10 +944,14 @@ export class WindowsManager implements IWindowsMainService {
|
||||
};
|
||||
}
|
||||
|
||||
// Folder
|
||||
return {
|
||||
folderPath: candidate
|
||||
};
|
||||
// Folder (we check for isDirectory() because e.g. paths like /dev/null
|
||||
// are neither file nor folder but some external tools might pass them
|
||||
// over to us)
|
||||
else if (candidateStat.isDirectory()) {
|
||||
return {
|
||||
folderPath: candidate
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.historyMainService.removeFromRecentlyOpened([candidate]); // since file does not seem to exist anymore, remove from recent
|
||||
@@ -1361,7 +1322,10 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}
|
||||
|
||||
if (e.window.config && !!e.window.config.extensionDevelopmentPath) {
|
||||
return; // do not ask to save workspace when doing extension development
|
||||
// do not ask to save workspace when doing extension development
|
||||
// but still delete it.
|
||||
this.workspacesMainService.deleteUntitledWorkspaceSync(workspace);
|
||||
return;
|
||||
}
|
||||
|
||||
if (windowClosing && !isMacintosh && this.getWindowCount() === 1) {
|
||||
@@ -1369,7 +1333,17 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}
|
||||
|
||||
// Handle untitled workspaces with prompt as needed
|
||||
this.workspacesManager.promptToSaveUntitledWorkspace(e, workspace);
|
||||
e.veto(this.workspacesManager.promptToSaveUntitledWorkspace(this.getWindowById(e.window.id), workspace).then(veto => {
|
||||
if (veto) {
|
||||
return veto;
|
||||
}
|
||||
|
||||
// Bug in electron: somehow we need this timeout so that the window closes properly. That
|
||||
// might be related to the fact that the untitled workspace prompt shows up async and this
|
||||
// code can execute before the dialog is fully closed which then blocks the window from closing.
|
||||
// Issue: https://github.com/Microsoft/vscode/issues/41989
|
||||
return TPromise.timeout(0).then(() => veto);
|
||||
}));
|
||||
}
|
||||
|
||||
public focusLastActive(cli: ParsedArgs, context: OpenContext): CodeWindow {
|
||||
@@ -1457,48 +1431,48 @@ export class WindowsManager implements IWindowsMainService {
|
||||
|
||||
// Unresponsive
|
||||
if (error === WindowError.UNRESPONSIVE) {
|
||||
const result = dialog.showMessageBox(window.win, {
|
||||
this.dialogs.showMessageBox({
|
||||
title: product.nameLong,
|
||||
type: 'warning',
|
||||
buttons: [mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'wait', comment: ['&& denotes a mnemonic'] }, "&&Keep Waiting")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
|
||||
message: localize('appStalled', "The window is no longer responding"),
|
||||
detail: localize('appStalledDetail', "You can reopen or close the window or keep waiting."),
|
||||
noLink: true
|
||||
}, window).then(result => {
|
||||
if (!window.win) {
|
||||
return; // Return early if the window has been going down already
|
||||
}
|
||||
|
||||
if (result.button === 0) {
|
||||
window.reload();
|
||||
} else if (result.button === 2) {
|
||||
this.onBeforeWindowClose(window); // 'close' event will not be fired on destroy(), so run it manually
|
||||
window.win.destroy(); // make sure to destroy the window as it is unresponsive
|
||||
}
|
||||
});
|
||||
|
||||
if (!window.win) {
|
||||
return; // Return early if the window has been going down already
|
||||
}
|
||||
|
||||
if (result === 0) {
|
||||
window.reload();
|
||||
} else if (result === 2) {
|
||||
this.onBeforeWindowClose(window); // 'close' event will not be fired on destroy(), so run it manually
|
||||
window.win.destroy(); // make sure to destroy the window as it is unresponsive
|
||||
}
|
||||
}
|
||||
|
||||
// Crashed
|
||||
else {
|
||||
const result = dialog.showMessageBox(window.win, {
|
||||
this.dialogs.showMessageBox({
|
||||
title: product.nameLong,
|
||||
type: 'warning',
|
||||
buttons: [mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
|
||||
message: localize('appCrashed', "The window has crashed"),
|
||||
detail: localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."),
|
||||
noLink: true
|
||||
}, window).then(result => {
|
||||
if (!window.win) {
|
||||
return; // Return early if the window has been going down already
|
||||
}
|
||||
|
||||
if (result.button === 0) {
|
||||
window.reload();
|
||||
} else if (result.button === 1) {
|
||||
this.onBeforeWindowClose(window); // 'close' event will not be fired on destroy(), so run it manually
|
||||
window.win.destroy(); // make sure to destroy the window as it has crashed
|
||||
}
|
||||
});
|
||||
|
||||
if (!window.win) {
|
||||
return; // Return early if the window has been going down already
|
||||
}
|
||||
|
||||
if (result === 0) {
|
||||
window.reload();
|
||||
} else if (result === 1) {
|
||||
this.onBeforeWindowClose(window); // 'close' event will not be fired on destroy(), so run it manually
|
||||
window.win.destroy(); // make sure to destroy the window as it has crashed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1558,7 +1532,19 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}
|
||||
}
|
||||
|
||||
this.fileDialog.pickAndOpen(internalOptions);
|
||||
this.dialogs.pickAndOpen(internalOptions);
|
||||
}
|
||||
|
||||
public showMessageBox(options: Electron.MessageBoxOptions, win?: CodeWindow): TPromise<IMessageBoxResult> {
|
||||
return this.dialogs.showMessageBox(options, win);
|
||||
}
|
||||
|
||||
public showSaveDialog(options: Electron.SaveDialogOptions, win?: CodeWindow): TPromise<string> {
|
||||
return this.dialogs.showSaveDialog(options, win);
|
||||
}
|
||||
|
||||
public showOpenDialog(options: Electron.OpenDialogOptions, win?: CodeWindow): TPromise<string[]> {
|
||||
return this.dialogs.showOpenDialog(options, win);
|
||||
}
|
||||
|
||||
public quit(): void {
|
||||
@@ -1584,20 +1570,25 @@ interface IInternalNativeOpenDialogOptions extends INativeOpenDialogOptions {
|
||||
pickFiles?: boolean;
|
||||
}
|
||||
|
||||
class FileDialog {
|
||||
class Dialogs {
|
||||
|
||||
private static readonly workingDirPickerStorageKey = 'pickerWorkingDir';
|
||||
|
||||
private mapWindowToDialogQueue: Map<number, Queue<any>>;
|
||||
private noWindowDialogQueue: Queue<any>;
|
||||
|
||||
constructor(
|
||||
private environmentService: IEnvironmentService,
|
||||
private telemetryService: ITelemetryService,
|
||||
private stateService: IStateService,
|
||||
private windowsMainService: IWindowsMainService
|
||||
private windowsMainService: IWindowsMainService,
|
||||
) {
|
||||
this.mapWindowToDialogQueue = new Map<number, Queue<any>>();
|
||||
this.noWindowDialogQueue = new Queue<any>();
|
||||
}
|
||||
|
||||
public pickAndOpen(options: INativeOpenDialogOptions): void {
|
||||
this.getFileOrFolderPaths(options, (paths: string[]) => {
|
||||
this.getFileOrFolderPaths(options).then(paths => {
|
||||
const numberOfPaths = paths ? paths.length : 0;
|
||||
|
||||
// Telemetry
|
||||
@@ -1623,7 +1614,7 @@ class FileDialog {
|
||||
});
|
||||
}
|
||||
|
||||
private getFileOrFolderPaths(options: IInternalNativeOpenDialogOptions, clb: (paths: string[]) => void): void {
|
||||
private getFileOrFolderPaths(options: IInternalNativeOpenDialogOptions): TPromise<string[]> {
|
||||
|
||||
// Ensure dialog options
|
||||
if (!options.dialogOptions) {
|
||||
@@ -1632,7 +1623,7 @@ class FileDialog {
|
||||
|
||||
// Ensure defaultPath
|
||||
if (!options.dialogOptions.defaultPath) {
|
||||
options.dialogOptions.defaultPath = this.stateService.getItem<string>(FileDialog.workingDirPickerStorageKey);
|
||||
options.dialogOptions.defaultPath = this.stateService.getItem<string>(Dialogs.workingDirPickerStorageKey);
|
||||
}
|
||||
|
||||
// Ensure properties
|
||||
@@ -1654,28 +1645,86 @@ class FileDialog {
|
||||
|
||||
// Show Dialog
|
||||
const focusedWindow = this.windowsMainService.getWindowById(options.windowId) || this.windowsMainService.getFocusedWindow();
|
||||
let paths = dialog.showOpenDialog(focusedWindow && focusedWindow.win, options.dialogOptions);
|
||||
if (paths && paths.length > 0) {
|
||||
if (isMacintosh) {
|
||||
|
||||
return this.showOpenDialog(options.dialogOptions, focusedWindow).then(paths => {
|
||||
if (paths && paths.length > 0) {
|
||||
|
||||
// Remember path in storage for next time
|
||||
this.stateService.setItem(Dialogs.workingDirPickerStorageKey, dirname(paths[0]));
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
return void 0;
|
||||
});
|
||||
}
|
||||
|
||||
private getDialogQueue(window?: ICodeWindow): Queue<any> {
|
||||
if (!window) {
|
||||
return this.noWindowDialogQueue;
|
||||
}
|
||||
|
||||
let windowDialogQueue = this.mapWindowToDialogQueue.get(window.id);
|
||||
if (!windowDialogQueue) {
|
||||
windowDialogQueue = new Queue<any>();
|
||||
this.mapWindowToDialogQueue.set(window.id, windowDialogQueue);
|
||||
}
|
||||
|
||||
return windowDialogQueue;
|
||||
}
|
||||
|
||||
public showMessageBox(options: Electron.MessageBoxOptions, window?: ICodeWindow): TPromise<IMessageBoxResult> {
|
||||
return this.getDialogQueue(window).queue(() => {
|
||||
return new TPromise((c, e) => {
|
||||
dialog.showMessageBox(window ? window.win : void 0, options, (response: number, checkboxChecked: boolean) => {
|
||||
c({ button: response, checkboxChecked });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public showSaveDialog(options: Electron.SaveDialogOptions, window?: ICodeWindow): TPromise<string> {
|
||||
function normalizePath(path: string): string {
|
||||
if (path && isMacintosh) {
|
||||
path = normalizeNFC(path); // normalize paths returned from the OS
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
return this.getDialogQueue(window).queue(() => {
|
||||
return new TPromise((c, e) => {
|
||||
dialog.showSaveDialog(window ? window.win : void 0, options, path => {
|
||||
c(normalizePath(path));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public showOpenDialog(options: Electron.OpenDialogOptions, window?: ICodeWindow): TPromise<string[]> {
|
||||
function normalizePaths(paths: string[]): string[] {
|
||||
if (paths && paths.length > 0 && isMacintosh) {
|
||||
paths = paths.map(path => normalizeNFC(path)); // normalize paths returned from the OS
|
||||
}
|
||||
|
||||
// Remember path in storage for next time
|
||||
this.stateService.setItem(FileDialog.workingDirPickerStorageKey, dirname(paths[0]));
|
||||
|
||||
// Return
|
||||
return clb(paths);
|
||||
return paths;
|
||||
}
|
||||
|
||||
return clb(void (0));
|
||||
return this.getDialogQueue(window).queue(() => {
|
||||
return new TPromise((c, e) => {
|
||||
dialog.showOpenDialog(window ? window.win : void 0, options, paths => {
|
||||
c(normalizePaths(paths));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class WorkspacesManager {
|
||||
|
||||
constructor(
|
||||
private workspacesService: IWorkspacesMainService,
|
||||
private backupService: IBackupMainService,
|
||||
private workspacesMainService: IWorkspacesMainService,
|
||||
private backupMainService: IBackupMainService,
|
||||
private environmentService: IEnvironmentService,
|
||||
private windowsMainService: IWindowsMainService
|
||||
) {
|
||||
@@ -1690,26 +1739,33 @@ class WorkspacesManager {
|
||||
}
|
||||
|
||||
public createAndEnterWorkspace(window: CodeWindow, folders?: IWorkspaceFolderCreationData[], path?: string): TPromise<IEnterWorkspaceResult> {
|
||||
if (!window || !window.win || window.readyState !== ReadyState.READY || !this.isValidTargetWorkspacePath(window, path)) {
|
||||
if (!window || !window.win || window.readyState !== ReadyState.READY) {
|
||||
return TPromise.as(null); // return early if the window is not ready or disposed
|
||||
}
|
||||
|
||||
return this.workspacesService.createWorkspace(folders).then(workspace => {
|
||||
return this.doSaveAndOpenWorkspace(window, workspace, path);
|
||||
return this.isValidTargetWorkspacePath(window, path).then(isValid => {
|
||||
if (!isValid) {
|
||||
return TPromise.as(null); // return early if the workspace is not valid
|
||||
}
|
||||
|
||||
return this.workspacesMainService.createWorkspace(folders).then(workspace => {
|
||||
return this.doSaveAndOpenWorkspace(window, workspace, path);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private isValidTargetWorkspacePath(window: CodeWindow, path?: string): boolean {
|
||||
private isValidTargetWorkspacePath(window: CodeWindow, path?: string): TPromise<boolean> {
|
||||
if (!path) {
|
||||
return true;
|
||||
return TPromise.wrap(true);
|
||||
}
|
||||
|
||||
if (window.openedWorkspace && window.openedWorkspace.configPath === path) {
|
||||
return false; // window is already opened on a workspace with that path
|
||||
return TPromise.wrap(false); // window is already opened on a workspace with that path
|
||||
}
|
||||
|
||||
// Prevent overwriting a workspace that is currently opened in another window
|
||||
if (findWindowOnWorkspace(this.windowsMainService.getWindows(), { id: this.workspacesService.getWorkspaceId(path), configPath: path })) {
|
||||
if (findWindowOnWorkspace(this.windowsMainService.getWindows(), { id: this.workspacesMainService.getWorkspaceId(path), configPath: path })) {
|
||||
const options: Electron.MessageBoxOptions = {
|
||||
title: product.nameLong,
|
||||
type: 'info',
|
||||
@@ -1719,23 +1775,16 @@ class WorkspacesManager {
|
||||
noLink: true
|
||||
};
|
||||
|
||||
const activeWindow = BrowserWindow.getFocusedWindow();
|
||||
if (activeWindow) {
|
||||
dialog.showMessageBox(activeWindow, options);
|
||||
} else {
|
||||
dialog.showMessageBox(options);
|
||||
}
|
||||
|
||||
return false;
|
||||
return this.windowsMainService.showMessageBox(options, this.windowsMainService.getFocusedWindow()).then(() => false);
|
||||
}
|
||||
|
||||
return true; // OK
|
||||
return TPromise.wrap(true); // OK
|
||||
}
|
||||
|
||||
private doSaveAndOpenWorkspace(window: CodeWindow, workspace: IWorkspaceIdentifier, path?: string): TPromise<IEnterWorkspaceResult> {
|
||||
let savePromise: TPromise<IWorkspaceIdentifier>;
|
||||
if (path) {
|
||||
savePromise = this.workspacesService.saveWorkspace(workspace, path);
|
||||
savePromise = this.workspacesMainService.saveWorkspace(workspace, path);
|
||||
} else {
|
||||
savePromise = TPromise.as(workspace);
|
||||
}
|
||||
@@ -1746,7 +1795,7 @@ class WorkspacesManager {
|
||||
// Register window for backups and migrate current backups over
|
||||
let backupPath: string;
|
||||
if (!window.config.extensionDevelopmentPath) {
|
||||
backupPath = this.backupService.registerWorkspaceBackupSync(workspace, window.config.backupPath);
|
||||
backupPath = this.backupMainService.registerWorkspaceBackupSync(workspace, window.config.backupPath);
|
||||
}
|
||||
|
||||
// Update window configuration properly based on transition to workspace
|
||||
@@ -1776,7 +1825,7 @@ class WorkspacesManager {
|
||||
});
|
||||
}
|
||||
|
||||
public promptToSaveUntitledWorkspace(e: IWindowUnloadEvent, workspace: IWorkspaceIdentifier): void {
|
||||
public promptToSaveUntitledWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): TPromise<boolean> {
|
||||
enum ConfirmResult {
|
||||
SAVE,
|
||||
DONT_SAVE,
|
||||
@@ -1810,41 +1859,35 @@ class WorkspacesManager {
|
||||
options.defaultId = 2;
|
||||
}
|
||||
|
||||
const res = dialog.showMessageBox(e.window.win, options);
|
||||
return this.windowsMainService.showMessageBox(options, window).then(res => {
|
||||
switch (buttons[res.button].result) {
|
||||
|
||||
switch (buttons[res].result) {
|
||||
// Cancel: veto unload
|
||||
case ConfirmResult.CANCEL:
|
||||
return true;
|
||||
|
||||
// Cancel: veto unload
|
||||
case ConfirmResult.CANCEL:
|
||||
e.veto(true);
|
||||
break;
|
||||
// Don't Save: delete workspace
|
||||
case ConfirmResult.DONT_SAVE:
|
||||
this.workspacesMainService.deleteUntitledWorkspaceSync(workspace);
|
||||
return false;
|
||||
|
||||
// Don't Save: delete workspace
|
||||
case ConfirmResult.DONT_SAVE:
|
||||
this.workspacesService.deleteUntitledWorkspaceSync(workspace);
|
||||
e.veto(false);
|
||||
break;
|
||||
// Save: save workspace, but do not veto unload
|
||||
case ConfirmResult.SAVE: {
|
||||
return this.windowsMainService.showSaveDialog({
|
||||
buttonLabel: mnemonicButtonLabel(localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")),
|
||||
title: localize('saveWorkspace', "Save Workspace"),
|
||||
filters: WORKSPACE_FILTER,
|
||||
defaultPath: this.getUntitledWorkspaceSaveDialogDefaultPath(workspace)
|
||||
}, window).then(target => {
|
||||
if (target) {
|
||||
return this.workspacesMainService.saveWorkspace(workspace, target).then(() => false, () => false);
|
||||
}
|
||||
|
||||
// Save: save workspace, but do not veto unload
|
||||
case ConfirmResult.SAVE: {
|
||||
let target = dialog.showSaveDialog(e.window.win, {
|
||||
buttonLabel: mnemonicButtonLabel(localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")),
|
||||
title: localize('saveWorkspace', "Save Workspace"),
|
||||
filters: WORKSPACE_FILTER,
|
||||
defaultPath: this.getUntitledWorkspaceSaveDialogDefaultPath(workspace)
|
||||
});
|
||||
|
||||
if (target) {
|
||||
if (isMacintosh) {
|
||||
target = normalizeNFC(target); // normalize paths returned from the OS
|
||||
}
|
||||
|
||||
e.veto(this.workspacesService.saveWorkspace(workspace, target).then(() => false, () => false));
|
||||
} else {
|
||||
e.veto(true); // keep veto if no target was provided
|
||||
return true; // keep veto if no target was provided
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getUntitledWorkspaceSaveDialogDefaultPath(workspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier): string {
|
||||
@@ -1853,7 +1896,7 @@ class WorkspacesManager {
|
||||
return dirname(workspace);
|
||||
}
|
||||
|
||||
const resolvedWorkspace = this.workspacesService.resolveWorkspaceSync(workspace.configPath);
|
||||
const resolvedWorkspace = this.workspacesMainService.resolveWorkspaceSync(workspace.configPath);
|
||||
if (resolvedWorkspace && resolvedWorkspace.folders.length > 0) {
|
||||
for (const folder of resolvedWorkspace.folders) {
|
||||
if (folder.uri.scheme === Schemas.file) {
|
||||
|
||||
@@ -17,6 +17,7 @@ import { whenDeleted } from 'vs/base/node/pfs';
|
||||
import { findFreePort } from 'vs/base/node/ports';
|
||||
import { resolveTerminalEncoding } from 'vs/base/node/encoding';
|
||||
import * as iconv from 'iconv-lite';
|
||||
import { writeFileAndFlushSync } from 'vs/base/node/extfs';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
|
||||
function shouldSpawnCliProcess(argv: ParsedArgs): boolean {
|
||||
@@ -56,6 +57,61 @@ export async function main(argv: string[]): TPromise<any> {
|
||||
return mainCli.then(cli => cli.main(args));
|
||||
}
|
||||
|
||||
// Write File
|
||||
else if (args['file-write']) {
|
||||
const source = args._[0];
|
||||
const target = args._[1];
|
||||
|
||||
// Validate
|
||||
if (
|
||||
!source || !target || source === target || // make sure source and target are provided and are not the same
|
||||
!paths.isAbsolute(source) || !paths.isAbsolute(target) || // make sure both source and target are absolute paths
|
||||
!fs.existsSync(source) || !fs.statSync(source).isFile() || // make sure source exists as file
|
||||
!fs.existsSync(target) || !fs.statSync(target).isFile() // make sure target exists as file
|
||||
) {
|
||||
return TPromise.wrapError(new Error('Using --file-write with invalid arguments.'));
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
// Check for readonly status and chmod if so if we are told so
|
||||
let targetMode: number;
|
||||
let restoreMode = false;
|
||||
if (!!args['file-chmod']) {
|
||||
targetMode = fs.statSync(target).mode;
|
||||
if (!(targetMode & 128) /* readonly */) {
|
||||
fs.chmodSync(target, targetMode | 128);
|
||||
restoreMode = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Write source to target
|
||||
const data = fs.readFileSync(source);
|
||||
try {
|
||||
writeFileAndFlushSync(target, data);
|
||||
} catch (error) {
|
||||
// On Windows and if the file exists with an EPERM error, we try a different strategy of saving the file
|
||||
// by first truncating the file and then writing with r+ mode. This helps to save hidden files on Windows
|
||||
// (see https://github.com/Microsoft/vscode/issues/931)
|
||||
if (isWindows && error.code === 'EPERM') {
|
||||
fs.truncateSync(target, 0);
|
||||
writeFileAndFlushSync(target, data, { flag: 'r+' });
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Restore previous mode as needed
|
||||
if (restoreMode) {
|
||||
fs.chmodSync(target, targetMode);
|
||||
}
|
||||
} catch (error) {
|
||||
return TPromise.wrapError(new Error(`Using --file-write resulted in an error: ${error}`));
|
||||
}
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
// Just Code
|
||||
else {
|
||||
const env = assign({}, process.env, {
|
||||
@@ -67,7 +123,7 @@ export async function main(argv: string[]): TPromise<any> {
|
||||
|
||||
const processCallbacks: ((child: ChildProcess) => Thenable<any>)[] = [];
|
||||
|
||||
const verbose = args.verbose || args.status;
|
||||
const verbose = args.verbose || args.status || typeof args['upload-logs'] !== 'undefined';
|
||||
if (verbose) {
|
||||
env['ELECTRON_ENABLE_LOGGING'] = '1';
|
||||
|
||||
@@ -86,17 +142,20 @@ export async function main(argv: string[]): TPromise<any> {
|
||||
// Windows workaround for https://github.com/nodejs/node/issues/11656
|
||||
}
|
||||
|
||||
const readFromStdin = args._.some(a => a === '-');
|
||||
if (readFromStdin) {
|
||||
// remove the "-" argument when we read from stdin
|
||||
args._ = args._.filter(a => a !== '-');
|
||||
argv = argv.filter(a => a !== '-');
|
||||
}
|
||||
|
||||
let stdinFilePath: string;
|
||||
if (stdinWithoutTty) {
|
||||
|
||||
// Read from stdin: we require a single "-" argument to be passed in order to start reading from
|
||||
// stdin. We do this because there is no reliable way to find out if data is piped to stdin. Just
|
||||
// checking for stdin being connected to a TTY is not enough (https://github.com/Microsoft/vscode/issues/40351)
|
||||
if (args._.length === 1 && args._[0] === '-') {
|
||||
|
||||
// remove the "-" argument when we read from stdin
|
||||
args._ = [];
|
||||
argv = argv.filter(a => a !== '-');
|
||||
if (args._.length === 0 && readFromStdin) {
|
||||
|
||||
// prepare temp file to read stdin to
|
||||
stdinFilePath = paths.join(os.tmpdir(), `code-stdin-${Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 3)}.txt`);
|
||||
@@ -247,12 +306,21 @@ export async function main(argv: string[]): TPromise<any> {
|
||||
});
|
||||
}
|
||||
|
||||
if (args['js-flags']) {
|
||||
const match = /max_old_space_size=(\d+)/g.exec(args['js-flags']);
|
||||
if (match && !args['max-memory']) {
|
||||
argv.push(`--max-memory=${match[1]}`);
|
||||
}
|
||||
}
|
||||
|
||||
const options = {
|
||||
detached: true,
|
||||
env
|
||||
};
|
||||
|
||||
if (!verbose) {
|
||||
if (typeof args['upload-logs'] !== undefined) {
|
||||
options['stdio'] = ['pipe', 'pipe', 'pipe'];
|
||||
} else if (!verbose) {
|
||||
options['stdio'] = 'ignore';
|
||||
}
|
||||
|
||||
@@ -288,6 +356,6 @@ function eventuallyExit(code: number): void {
|
||||
main(process.argv)
|
||||
.then(() => eventuallyExit(0))
|
||||
.then(null, err => {
|
||||
console.error(err.stack ? err.stack : err);
|
||||
console.error(err.message || err.stack || err);
|
||||
eventuallyExit(1);
|
||||
});
|
||||
|
||||
@@ -30,14 +30,14 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
|
||||
import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
|
||||
import { mkdirp, writeFile } from 'vs/base/node/pfs';
|
||||
import { IChoiceService } from 'vs/platform/message/common/message';
|
||||
import { ChoiceCliService } from 'vs/platform/message/node/messageCli';
|
||||
import { getBaseLabel } from 'vs/base/common/labels';
|
||||
import { IStateService } from 'vs/platform/state/common/state';
|
||||
import { StateService } from 'vs/platform/state/node/stateService';
|
||||
import { createLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { ILogService, getLogLevel } from 'vs/platform/log/common/log';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { ChoiceCliService } from 'vs/platform/dialogs/node/choiceCli';
|
||||
|
||||
const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id);
|
||||
const notInstalled = (id: string) => localize('notInstalled', "Extension '{0}' is not installed.", id);
|
||||
@@ -196,7 +196,7 @@ export function main(argv: ParsedArgs): TPromise<void> {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
const environmentService = new EnvironmentService(argv, process.execPath);
|
||||
const logService = createLogService('cli', environmentService);
|
||||
const logService = createSpdLogService('cli', getLogLevel(environmentService), environmentService.logsPath);
|
||||
process.once('exit', () => logService.dispose());
|
||||
|
||||
logService.info('main', argv);
|
||||
|
||||
Reference in New Issue
Block a user