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:
Karl Burtram
2018-04-04 15:27:51 -07:00
committed by GitHub
parent 5fba3e31b4
commit dafb780987
9412 changed files with 141255 additions and 98813 deletions

View 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>

View 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();

View 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');
}

View 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>`;
}
}

View 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>`;

View 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;
}

View File

@@ -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 -->`);
});
});