mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-17 09:35:37 -05:00
Merge VS Code 1.23.1 (#1520)
This commit is contained in:
@@ -5,23 +5,26 @@
|
||||
'use strict';
|
||||
|
||||
function createModuleDescription(name, exclude) {
|
||||
var result= {};
|
||||
var result = {};
|
||||
var excludes = ['vs/css', 'vs/nls'];
|
||||
result.name= name;
|
||||
result.name = name;
|
||||
if (Array.isArray(exclude) && exclude.length > 0) {
|
||||
excludes = excludes.concat(exclude);
|
||||
}
|
||||
result.exclude= excludes;
|
||||
|
||||
result.exclude = excludes;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
exports.collectModules= function() {
|
||||
exports.collectModules = function () {
|
||||
return [
|
||||
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/issue/issueReporterMain', []),
|
||||
createModuleDescription('vs/code/electron-browser/sharedProcess/sharedProcessMain', []),
|
||||
createModuleDescription('vs/code/electron-browser/issue/issueReporterMain', [])
|
||||
createModuleDescription('vs/code/electron-browser/issue/issueReporterMain', []),
|
||||
createModuleDescription('vs/platform/driver/node/driver', []),
|
||||
createModuleDescription('vs/code/electron-browser/processExplorer/processExplorerMain', [])
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
@@ -9,6 +9,11 @@ const path = require('path');
|
||||
const fs = require('fs');
|
||||
const remote = require('electron').remote;
|
||||
|
||||
function assign(destination, source) {
|
||||
return Object.keys(source)
|
||||
.reduce(function (r, key) { r[key] = source[key]; return r; }, destination);
|
||||
}
|
||||
|
||||
function parseURLQueryArgs() {
|
||||
const search = window.location.search || '';
|
||||
|
||||
@@ -19,15 +24,6 @@ function parseURLQueryArgs() {
|
||||
.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) !== '/') {
|
||||
@@ -53,6 +49,8 @@ function main() {
|
||||
const args = parseURLQueryArgs();
|
||||
const configuration = JSON.parse(args['config'] || '{}') || {};
|
||||
|
||||
assign(process.env, configuration.userEnv);
|
||||
|
||||
//#region Add support for using node_modules.asar
|
||||
(function () {
|
||||
const path = require('path');
|
||||
@@ -65,10 +63,10 @@ function main() {
|
||||
const NODE_MODULES_ASAR_PATH = NODE_MODULES_PATH + '.asar';
|
||||
|
||||
const originalResolveLookupPaths = Module._resolveLookupPaths;
|
||||
Module._resolveLookupPaths = function (request, parent) {
|
||||
const result = originalResolveLookupPaths(request, parent);
|
||||
Module._resolveLookupPaths = function (request, parent, newReturn) {
|
||||
const result = originalResolveLookupPaths(request, parent, newReturn);
|
||||
|
||||
const paths = result[1];
|
||||
const paths = newReturn ? result : 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);
|
||||
@@ -143,31 +141,34 @@ function main() {
|
||||
|
||||
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
|
||||
// Load the loader
|
||||
const loaderFilename = configuration.appRoot + '/out/vs/loader.js';
|
||||
const loaderSource = fs.readFileSync(loaderFilename);
|
||||
require('vm').runInThisContext(loaderSource, { filename: loaderFilename });
|
||||
var define = global.define;
|
||||
global.define = undefined;
|
||||
|
||||
window.MonacoEnvironment = {};
|
||||
window.nodeRequire = require.__$__nodeRequire;
|
||||
|
||||
require.config({
|
||||
baseUrl: rootUrl,
|
||||
'vs/nls': nlsConfig,
|
||||
nodeCachedDataDir: configuration.nodeCachedDataDir,
|
||||
nodeModules: [/*BUILD->INSERT_NODE_MODULES*/]
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
if (nlsConfig.pseudo) {
|
||||
require(['vs/nls'], function (nlsPlugin) {
|
||||
nlsPlugin.setPseudoTranslation(nlsConfig.pseudo);
|
||||
});
|
||||
}
|
||||
|
||||
require(['vs/code/electron-browser/issue/issueReporterMain'], (issueReporter) => {
|
||||
issueReporter.startup(configuration);
|
||||
});
|
||||
require(['vs/code/electron-browser/issue/issueReporterMain'], (issueReporter) => {
|
||||
issueReporter.startup(configuration);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ 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';
|
||||
import { normalizeGitHubIssuesUrl } from 'vs/code/electron-browser/issue/issueReporterUtil';
|
||||
|
||||
const MAX_URL_LENGTH = platform.isWindows ? 2081 : 5400;
|
||||
|
||||
@@ -69,6 +70,7 @@ export class IssueReporter extends Disposable {
|
||||
private numberOfSearchResultsDisplayed = 0;
|
||||
private receivedSystemInfo = false;
|
||||
private receivedPerformanceInfo = false;
|
||||
private shouldQueueSearch = false;
|
||||
|
||||
constructor(configuration: IssueReporterConfiguration) {
|
||||
super();
|
||||
@@ -105,7 +107,9 @@ export class IssueReporter extends Disposable {
|
||||
});
|
||||
|
||||
ipcRenderer.send('issueSystemInfoRequest');
|
||||
ipcRenderer.send('issuePerformanceInfoRequest');
|
||||
if (configuration.data.issueType === IssueType.PerformanceIssue) {
|
||||
ipcRenderer.send('issuePerformanceInfoRequest');
|
||||
}
|
||||
this.logService.trace('issueReporter: Sent data requests');
|
||||
|
||||
if (window.document.documentElement.lang !== 'en') {
|
||||
@@ -160,7 +164,7 @@ export class IssueReporter extends Disposable {
|
||||
}
|
||||
|
||||
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; }`);
|
||||
content.push(`input[type='text']:focus, textarea:focus, select:focus, summary:focus, button:focus, a:focus, .workbenchCommand:focus { border: 1px solid ${styles.inputActiveBorder}; outline-style: none; }`);
|
||||
}
|
||||
|
||||
if (styles.textLinkColor) {
|
||||
@@ -208,14 +212,14 @@ export class IssueReporter extends Disposable {
|
||||
});
|
||||
|
||||
const numberOfThemeExtesions = themes && themes.length;
|
||||
this.issueReporterModel.update({ numberOfThemeExtesions, enabledNonThemeExtesions: nonThemes });
|
||||
this.issueReporterModel.update({ numberOfThemeExtesions, enabledNonThemeExtesions: nonThemes, allExtensions: extensions });
|
||||
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 });
|
||||
}
|
||||
|
||||
this.updateExtensionSelector(extensions);
|
||||
}
|
||||
|
||||
private handleSettingsSearchData(data: ISettingsSearchIssueReporterData): void {
|
||||
@@ -292,7 +296,11 @@ export class IssueReporter extends Disposable {
|
||||
|
||||
private setEventHandlers(): void {
|
||||
this.addEventListener('issue-type', 'change', (event: Event) => {
|
||||
this.issueReporterModel.update({ issueType: parseInt((<HTMLInputElement>event.target).value) });
|
||||
const issueType = parseInt((<HTMLInputElement>event.target).value);
|
||||
this.issueReporterModel.update({ issueType: issueType });
|
||||
if (issueType === IssueType.PerformanceIssue && !this.receivedPerformanceInfo) {
|
||||
ipcRenderer.send('issuePerformanceInfoRequest');
|
||||
}
|
||||
this.updatePreviewButtonState();
|
||||
this.render();
|
||||
});
|
||||
@@ -322,30 +330,33 @@ export class IssueReporter extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
this.addEventListener('reproducesWithoutExtensions', 'click', (e) => {
|
||||
this.issueReporterModel.update({ reprosWithoutExtensions: true });
|
||||
});
|
||||
this.addEventListener('issue-source', 'change', (event: Event) => {
|
||||
const fileOnExtension = JSON.parse((<HTMLInputElement>event.target).value);
|
||||
this.issueReporterModel.update({ fileOnExtension: fileOnExtension, includeExtensions: !fileOnExtension });
|
||||
this.render();
|
||||
|
||||
this.addEventListener('reproducesWithExtensions', 'click', (e) => {
|
||||
this.issueReporterModel.update({ reprosWithoutExtensions: false });
|
||||
const title = (<HTMLInputElement>document.getElementById('issue-title')).value;
|
||||
if (fileOnExtension) {
|
||||
this.searchExtensionIssues(title);
|
||||
} else {
|
||||
const description = this.issueReporterModel.getData().issueDescription;
|
||||
this.searchVSCodeIssues(title, description);
|
||||
}
|
||||
});
|
||||
|
||||
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();
|
||||
// Only search for extension issues on title change
|
||||
if (!this.issueReporterModel.fileOnExtension()) {
|
||||
const title = (<HTMLInputElement>document.getElementById('issue-title')).value;
|
||||
this.searchVSCodeIssues(title, issueDescription);
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
@@ -353,10 +364,11 @@ export class IssueReporter extends Disposable {
|
||||
hide(lengthValidationMessage);
|
||||
}
|
||||
|
||||
if (title || description) {
|
||||
this.searchDuplicates(title, description);
|
||||
if (this.issueReporterModel.fileOnExtension()) {
|
||||
this.searchExtensionIssues(title);
|
||||
} else {
|
||||
this.clearSearchResults();
|
||||
const description = this.issueReporterModel.getData().issueDescription;
|
||||
this.searchVSCodeIssues(title, description);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -367,43 +379,32 @@ export class IssueReporter extends Disposable {
|
||||
});
|
||||
|
||||
this.addEventListener('disableExtensions', 'keydown', (e: KeyboardEvent) => {
|
||||
e.stopPropagation();
|
||||
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');
|
||||
document.onkeydown = (e: KeyboardEvent) => {
|
||||
const cmdOrCtrlKey = platform.isMacintosh ? e.metaKey : e.ctrlKey;
|
||||
// Cmd/Ctrl+Enter previews issue and closes window
|
||||
if (cmdOrCtrlKey && e.keyCode === 13) {
|
||||
if (this.createIssue()) {
|
||||
remote.getCurrentWindow().close();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
// Cmd/Ctrl + zooms in
|
||||
if (cmdOrCtrlKey && e.keyCode === 187) {
|
||||
this.applyZoom(webFrame.getZoomLevel() + 1);
|
||||
}
|
||||
|
||||
prevKeyWasCommand = e.keyCode === 91 || e.keyCode === 93;
|
||||
};
|
||||
} else {
|
||||
document.onkeydown = (e: KeyboardEvent) => {
|
||||
if (e.ctrlKey && e.keyCode === 13) {
|
||||
if (this.createIssue()) {
|
||||
remote.getCurrentWindow().close();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
// Cmd/Ctrl - zooms out
|
||||
if (cmdOrCtrlKey && e.keyCode === 189) {
|
||||
this.applyZoom(webFrame.getZoomLevel() - 1);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private updatePreviewButtonState() {
|
||||
@@ -438,12 +439,77 @@ export class IssueReporter extends Disposable {
|
||||
return false;
|
||||
}
|
||||
|
||||
private getExtensionRepositoryUrl(): string {
|
||||
const selectedExtension = this.issueReporterModel.getData().selectedExtension;
|
||||
return selectedExtension && selectedExtension.manifest && selectedExtension.manifest.repository && selectedExtension.manifest.repository.url;
|
||||
}
|
||||
|
||||
private getExtensionBugsUrl(): string {
|
||||
const selectedExtension = this.issueReporterModel.getData().selectedExtension;
|
||||
return selectedExtension && selectedExtension.manifest && selectedExtension.manifest.bugs && selectedExtension.manifest.bugs.url;
|
||||
}
|
||||
|
||||
private searchVSCodeIssues(title: string, issueDescription: string): void {
|
||||
if (title) {
|
||||
this.searchDuplicates(title, issueDescription);
|
||||
} else {
|
||||
this.clearSearchResults();
|
||||
}
|
||||
}
|
||||
|
||||
private searchExtensionIssues(title: string): void {
|
||||
const url = this.getExtensionRepositoryUrl();
|
||||
if (title) {
|
||||
const matches = /^https?:\/\/github\.com\/(.*)(?:.git)/.exec(url);
|
||||
if (matches && matches.length) {
|
||||
const repo = matches[1];
|
||||
return this.searchGitHub(repo, title);
|
||||
}
|
||||
}
|
||||
|
||||
this.clearSearchResults();
|
||||
}
|
||||
|
||||
private clearSearchResults(): void {
|
||||
const similarIssues = document.getElementById('similar-issues');
|
||||
similarIssues.innerHTML = '';
|
||||
this.numberOfSearchResultsDisplayed = 0;
|
||||
}
|
||||
|
||||
@debounce(300)
|
||||
private searchGitHub(repo: string, title: string): void {
|
||||
const query = `is:issue+repo:${repo}+${title}`;
|
||||
const similarIssues = document.getElementById('similar-issues');
|
||||
|
||||
window.fetch(`https://api.github.com/search/issues?q=${query}`).then((response) => {
|
||||
response.json().then(result => {
|
||||
similarIssues.innerHTML = '';
|
||||
if (result && result.items) {
|
||||
this.displaySearchResults(result.items);
|
||||
} else {
|
||||
// If the items property isn't present, the rate limit has been hit
|
||||
const message = $('div.list-title');
|
||||
message.textContent = localize('rateLimited', "GitHub query limit exceeded. Please wait.");
|
||||
similarIssues.appendChild(message);
|
||||
|
||||
const resetTime = response.headers.get('X-RateLimit-Reset');
|
||||
const timeToWait = parseInt(resetTime) - Math.floor(Date.now() / 1000);
|
||||
if (this.shouldQueueSearch) {
|
||||
this.shouldQueueSearch = false;
|
||||
setTimeout(() => {
|
||||
this.searchGitHub(repo, title);
|
||||
this.shouldQueueSearch = true;
|
||||
}, timeToWait * 1000);
|
||||
}
|
||||
}
|
||||
}).catch(e => {
|
||||
this.logSearchError(e);
|
||||
});
|
||||
}).catch(e => {
|
||||
this.logSearchError(e);
|
||||
});
|
||||
}
|
||||
|
||||
@debounce(300)
|
||||
private searchDuplicates(title: string, body: string): void {
|
||||
const url = 'https://vscode-probot.westus.cloudapp.azure.com:7890/duplicate_candidates';
|
||||
@@ -516,7 +582,7 @@ export class IssueReporter extends Disposable {
|
||||
similarIssues.appendChild(issues);
|
||||
} else {
|
||||
const message = $('div.list-title');
|
||||
message.textContent = localize('noResults', "No results found");
|
||||
message.textContent = localize('noSimilarIssues', "No similar issues found");
|
||||
similarIssues.appendChild(message);
|
||||
}
|
||||
}
|
||||
@@ -542,8 +608,8 @@ export class IssueReporter extends Disposable {
|
||||
} else {
|
||||
typeSelect.innerHTML = [
|
||||
makeOption(IssueType.Bug, localize('bugReporter', "Bug Report")),
|
||||
makeOption(IssueType.PerformanceIssue, localize('performanceIssue', "Performance Issue")),
|
||||
makeOption(IssueType.FeatureRequest, localize('featureRequest', "Feature Request"))
|
||||
makeOption(IssueType.FeatureRequest, localize('featureRequest', "Feature Request")),
|
||||
makeOption(IssueType.PerformanceIssue, localize('performanceIssue', "Performance Issue"))
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
@@ -552,7 +618,8 @@ export class IssueReporter extends Disposable {
|
||||
|
||||
private renderBlocks(): void {
|
||||
// Depending on Issue Type, we render different blocks and text
|
||||
const { issueType } = this.issueReporterModel.getData();
|
||||
const { issueType, fileOnExtension } = this.issueReporterModel.getData();
|
||||
const blockContainer = document.getElementById('block-container');
|
||||
const systemBlock = document.querySelector('.block-system');
|
||||
const processBlock = document.querySelector('.block-process');
|
||||
const workspaceBlock = document.querySelector('.block-workspace');
|
||||
@@ -560,39 +627,64 @@ export class IssueReporter extends Disposable {
|
||||
const searchedExtensionsBlock = document.querySelector('.block-searchedExtensions');
|
||||
const settingsSearchResultsBlock = document.querySelector('.block-settingsSearchResults');
|
||||
|
||||
const disabledExtensions = document.getElementById('disabledExtensions');
|
||||
const problemSource = document.getElementById('problem-source');
|
||||
const problemSourceHelpText = document.getElementById('problem-source-help-text');
|
||||
const descriptionTitle = document.getElementById('issue-description-label');
|
||||
const descriptionSubtitle = document.getElementById('issue-description-subtitle');
|
||||
const extensionSelector = document.getElementById('extension-selection');
|
||||
|
||||
// Hide all by default
|
||||
hide(blockContainer);
|
||||
hide(systemBlock);
|
||||
hide(processBlock);
|
||||
hide(workspaceBlock);
|
||||
hide(extensionsBlock);
|
||||
hide(searchedExtensionsBlock);
|
||||
hide(settingsSearchResultsBlock);
|
||||
hide(disabledExtensions);
|
||||
hide(problemSource);
|
||||
hide(problemSourceHelpText);
|
||||
hide(extensionSelector);
|
||||
|
||||
if (issueType === IssueType.Bug) {
|
||||
show(blockContainer);
|
||||
show(systemBlock);
|
||||
show(extensionsBlock);
|
||||
show(disabledExtensions);
|
||||
show(problemSource);
|
||||
|
||||
if (fileOnExtension) {
|
||||
show(extensionSelector);
|
||||
} else {
|
||||
show(extensionsBlock);
|
||||
show(problemSourceHelpText);
|
||||
}
|
||||
|
||||
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(blockContainer);
|
||||
show(systemBlock);
|
||||
show(processBlock);
|
||||
show(workspaceBlock);
|
||||
show(extensionsBlock);
|
||||
show(disabledExtensions);
|
||||
show(problemSource);
|
||||
|
||||
if (fileOnExtension) {
|
||||
show(extensionSelector);
|
||||
} else {
|
||||
show(extensionsBlock);
|
||||
show(problemSourceHelpText);
|
||||
}
|
||||
|
||||
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.");
|
||||
show(problemSource);
|
||||
|
||||
if (fileOnExtension) {
|
||||
show(extensionSelector);
|
||||
}
|
||||
} else if (issueType === IssueType.SettingsSearchIssue) {
|
||||
show(blockContainer);
|
||||
show(searchedExtensionsBlock);
|
||||
show(settingsSearchResultsBlock);
|
||||
|
||||
@@ -614,11 +706,14 @@ export class IssueReporter extends Disposable {
|
||||
|
||||
private validateInputs(): boolean {
|
||||
let isValid = true;
|
||||
['issue-title', 'description'].forEach(elementId => {
|
||||
['issue-title', 'description', 'issue-source'].forEach(elementId => {
|
||||
isValid = this.validateInput(elementId) && isValid;
|
||||
|
||||
});
|
||||
|
||||
if (this.issueReporterModel.fileOnExtension()) {
|
||||
isValid = this.validateInput('extension-selector') && isValid;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
@@ -626,7 +721,10 @@ export class IssueReporter extends Disposable {
|
||||
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();
|
||||
const invalidInput = document.getElementsByClassName('invalid-input');
|
||||
if (invalidInput.length) {
|
||||
(<HTMLInputElement>invalidInput[0]).focus();
|
||||
}
|
||||
|
||||
document.getElementById('issue-title').addEventListener('input', (event) => {
|
||||
this.validateInput('issue-title');
|
||||
@@ -641,8 +739,8 @@ export class IssueReporter extends Disposable {
|
||||
|
||||
/* __GDPR__
|
||||
"issueReporterSubmit" : {
|
||||
"issueType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"numSimilarIssuesDisplayed" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
"issueType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
|
||||
"numSimilarIssuesDisplayed" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('issueReporterSubmit', { issueType: this.issueReporterModel.getData().issueType, numSimilarIssuesDisplayed: this.numberOfSearchResultsDisplayed });
|
||||
@@ -660,24 +758,88 @@ export class IssueReporter extends Disposable {
|
||||
return true;
|
||||
}
|
||||
|
||||
private getIssueUrlWithTitle(issueTitle: string) {
|
||||
private getIssueUrlWithTitle(issueTitle: string): string {
|
||||
let repositoryUrl = product.reportIssueUrl;
|
||||
if (this.issueReporterModel.fileOnExtension()) {
|
||||
const bugsUrl = this.getExtensionBugsUrl();
|
||||
const extensionUrl = this.getExtensionRepositoryUrl();
|
||||
// If given, try to match the extension's bug url
|
||||
if (bugsUrl && bugsUrl.match(/^https?:\/\/github\.com\/(.*)/)) {
|
||||
repositoryUrl = normalizeGitHubIssuesUrl(bugsUrl);
|
||||
} else if (extensionUrl && extensionUrl.match(/^https?:\/\/github\.com\/(.*)/)) {
|
||||
repositoryUrl = normalizeGitHubIssuesUrl(extensionUrl);
|
||||
}
|
||||
}
|
||||
|
||||
const queryStringPrefix = product.reportIssueUrl.indexOf('?') === -1 ? '?' : '&';
|
||||
return `${product.reportIssueUrl}${queryStringPrefix}title=${encodeURIComponent(issueTitle)}`;
|
||||
return `${repositoryUrl}${queryStringPrefix}title=${encodeURIComponent(issueTitle)}`;
|
||||
}
|
||||
|
||||
private updateSystemInfo = (state) => {
|
||||
const target = document.querySelector('.block-system .block-info');
|
||||
let tableHtml = '';
|
||||
Object.keys(state.systemInfo).forEach(k => {
|
||||
const data = typeof state.systemInfo[k] === 'object'
|
||||
? Object.keys(state.systemInfo[k]).map(key => `${key}: ${state.systemInfo[k][key]}`).join('<br>')
|
||||
: state.systemInfo[k];
|
||||
|
||||
tableHtml += `
|
||||
<tr>
|
||||
<td>${k}</td>
|
||||
<td>${state.systemInfo[k]}</td>
|
||||
<td>${data}</td>
|
||||
</tr>`;
|
||||
});
|
||||
target.innerHTML = `<table>${tableHtml}</table>`;
|
||||
}
|
||||
|
||||
private updateExtensionSelector(extensions: ILocalExtension[]): void {
|
||||
interface IOption {
|
||||
name: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
const extensionOptions: IOption[] = extensions.map(extension => {
|
||||
return {
|
||||
name: extension.manifest.displayName || extension.manifest.name || '',
|
||||
id: extension.identifier.id
|
||||
};
|
||||
});
|
||||
|
||||
// Sort extensions by name
|
||||
extensionOptions.sort((a, b) => {
|
||||
const aName = a.name.toLowerCase();
|
||||
const bName = b.name.toLowerCase();
|
||||
if (aName > bName) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (aName < bName) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
const makeOption = (extension: IOption) => `<option value="${extension.id}">${escape(extension.name)}</option>`;
|
||||
const extensionsSelector = document.getElementById('extension-selector');
|
||||
extensionsSelector.innerHTML = '<option></option>' + extensionOptions.map(makeOption).join('\n');
|
||||
|
||||
this.addEventListener('extension-selector', 'change', (e: Event) => {
|
||||
const selectedExtensionId = (<HTMLInputElement>e.target).value;
|
||||
const extensions = this.issueReporterModel.getData().allExtensions;
|
||||
const matches = extensions.filter(extension => extension.identifier.id === selectedExtensionId);
|
||||
if (matches.length) {
|
||||
this.issueReporterModel.update({ selectedExtension: matches[0] });
|
||||
|
||||
const title = (<HTMLInputElement>document.getElementById('issue-title')).value;
|
||||
this.searchExtensionIssues(title);
|
||||
} else {
|
||||
this.issueReporterModel.update({ selectedExtension: null });
|
||||
this.clearSearchResults();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private updateProcessInfo = (state) => {
|
||||
const target = document.querySelector('.block-process .block-info');
|
||||
target.innerHTML = `<code>${state.processInfo}</code>`;
|
||||
|
||||
@@ -26,9 +26,11 @@ export interface IssueReporterData {
|
||||
includeSettingsSearchDetails?: boolean;
|
||||
|
||||
numberOfThemeExtesions?: number;
|
||||
allExtensions?: ILocalExtension[];
|
||||
enabledNonThemeExtesions?: ILocalExtension[];
|
||||
extensionsDisabled?: boolean;
|
||||
reprosWithoutExtensions?: boolean;
|
||||
fileOnExtension?: boolean;
|
||||
selectedExtension?: ILocalExtension;
|
||||
actualSearchResults?: ISettingSearchResult[];
|
||||
query?: string;
|
||||
filterResultCount?: number;
|
||||
@@ -44,8 +46,7 @@ export class IssueReporterModel {
|
||||
includeProcessInfo: true,
|
||||
includeExtensions: true,
|
||||
includeSearchedExtensions: true,
|
||||
includeSettingsSearchDetails: true,
|
||||
reprosWithoutExtensions: false
|
||||
includeSettingsSearchDetails: true
|
||||
};
|
||||
|
||||
this._data = initialData ? assign(defaultData, initialData) : defaultData;
|
||||
@@ -73,6 +74,22 @@ ${this.getInfos()}
|
||||
<!-- generated by issue reporter -->`;
|
||||
}
|
||||
|
||||
fileOnExtension(): boolean {
|
||||
const fileOnExtensionSupported = this._data.issueType === IssueType.Bug
|
||||
|| this._data.issueType === IssueType.PerformanceIssue
|
||||
|| this._data.issueType === IssueType.FeatureRequest;
|
||||
|
||||
return fileOnExtensionSupported && this._data.fileOnExtension;
|
||||
}
|
||||
|
||||
private getExtensionVersion(): string {
|
||||
if (this.fileOnExtension()) {
|
||||
return `\nExtension version: ${this._data.selectedExtension.manifest.version}`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
private getIssueTypeTitle(): string {
|
||||
if (this._data.issueType === IssueType.Bug) {
|
||||
return 'Bug';
|
||||
@@ -109,8 +126,6 @@ ${this.getInfos()}
|
||||
if (this._data.includeExtensions) {
|
||||
info += this.generateExtensionsMd();
|
||||
}
|
||||
|
||||
info += this._data.reprosWithoutExtensions ? '\nReproduces without extensions' : '\nReproduces only with extensions';
|
||||
}
|
||||
|
||||
if (this._data.issueType === IssueType.SettingsSearchIssue) {
|
||||
@@ -136,7 +151,11 @@ ${this.getInfos()}
|
||||
`;
|
||||
|
||||
Object.keys(this._data.systemInfo).forEach(k => {
|
||||
md += `|${k}|${this._data.systemInfo[k]}|\n`;
|
||||
const data = typeof this._data.systemInfo[k] === 'object'
|
||||
? Object.keys(this._data.systemInfo[k]).map(key => `${key}: ${this._data.systemInfo[k][key]}`).join('<br>')
|
||||
: this._data.systemInfo[k];
|
||||
|
||||
md += `|${k}|${data}|\n`;
|
||||
});
|
||||
|
||||
md += '\n</details>';
|
||||
|
||||
@@ -19,6 +19,25 @@ export default (): string => `
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input-group" id="problem-source">
|
||||
<label class="inline-label" for="issue-source">${escape(localize('issueSourceLabel', "File on"))}</label>
|
||||
<select id="issue-source" class="inline-form-control">
|
||||
<!-- {{ SQL CARBON EDIT }} -->
|
||||
<option value="false">${escape(localize('sqlops', "SQL Operations Studio"))}</option>
|
||||
<option value="true">${escape(localize('extension', "An Extension"))}</option>
|
||||
</select>
|
||||
<div id="problem-source-help-text" class="instructions">${escape(localize('disableExtensionsLabelText', "Try to reproduce the problem after {0}. If the problem only reproduces when extensions are active, it is likely an issue with an extension."))
|
||||
.replace('{0}', `<span tabIndex=0 role="button" id="disableExtensions" class="workbenchCommand">${escape(localize('disableExtensions', "disabling all extensions and reloading the window"))}</span>`)}
|
||||
</div>
|
||||
|
||||
<div id="extension-selection">
|
||||
<label class="inline-label" for="extension-selector">${escape(localize('chooseExtension', "Extension"))} <span class="required-input">*</span></label>
|
||||
<select id="extension-selector" class="inline-form-control">
|
||||
<!-- To be dynamically filled -->
|
||||
</select>
|
||||
</div>
|
||||
</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>
|
||||
@@ -27,118 +46,93 @@ export default (): string => `
|
||||
<!-- 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 class="system-info" 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>
|
||||
</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>
|
||||
</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 class="input-group">
|
||||
<div class="input-group description-section">
|
||||
<label for="description" id="issue-description-label">
|
||||
<!-- To be dynamically filled -->
|
||||
</label>
|
||||
@@ -146,7 +140,7 @@ export default (): string => `
|
||||
<!-- 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>
|
||||
<textarea name="description" id="description" placeholder="${escape(localize('details', "Please enter details."))}" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
30
src/vs/code/electron-browser/issue/issueReporterUtil.ts
Normal file
30
src/vs/code/electron-browser/issue/issueReporterUtil.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { endsWith, rtrim } from 'vs/base/common/strings';
|
||||
|
||||
export function normalizeGitHubIssuesUrl(url: string): string {
|
||||
// If the url has a .git suffix, remove it
|
||||
if (endsWith(url, '.git')) {
|
||||
url = url.substr(0, url.length - 4);
|
||||
}
|
||||
|
||||
// Remove trailing slash
|
||||
url = rtrim(url, '/');
|
||||
|
||||
// If the url already ends with issues/new, it's beautiful, return it
|
||||
if (endsWith(url, 'issues/new')) {
|
||||
return url;
|
||||
}
|
||||
|
||||
// Add new segment if it does not exist
|
||||
if (endsWith(url, 'issues')) {
|
||||
return url + '/new';
|
||||
}
|
||||
|
||||
return url + '/issues/new';
|
||||
}
|
||||
@@ -18,16 +18,11 @@ 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 {
|
||||
@@ -39,7 +34,7 @@ td {
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 1.5em;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,21 +98,19 @@ textarea {
|
||||
html {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Ubuntu", "Droid Sans", sans-serif;
|
||||
color: #CCCCCC;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: scroll;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#block-container {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.block .block-info {
|
||||
width: 100%;
|
||||
font-family: 'Menlo', 'Courier New', 'Courier', monospace;
|
||||
@@ -144,11 +137,32 @@ button:disabled {
|
||||
max-width: 85vw;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 2em;
|
||||
padding-top: 2em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.description-section {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
textarea {
|
||||
flex-grow: 1;
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
.block-info-text {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
#github-submit-btn {
|
||||
float: right;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
@@ -166,31 +180,8 @@ button:disabled {
|
||||
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;
|
||||
#extension-selection {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.system-info {
|
||||
@@ -206,6 +197,7 @@ summary {
|
||||
border: 1px solid transparent;
|
||||
padding: 0 10px;
|
||||
margin-bottom: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.validation-error {
|
||||
@@ -250,8 +242,9 @@ input:disabled {
|
||||
margin-top: .5em;
|
||||
}
|
||||
|
||||
.workbenchCommand {
|
||||
a, .workbenchCommand {
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.workbenchCommand:disabled {
|
||||
@@ -309,17 +302,17 @@ button {
|
||||
}
|
||||
|
||||
#similar-issues {
|
||||
margin-left: 12%;
|
||||
margin-left: 13%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (max-width: 950px) {
|
||||
.section .inline-label {
|
||||
width: 12%;
|
||||
width: 13%;
|
||||
}
|
||||
|
||||
.section .inline-form-control {
|
||||
width: calc(88% - 5px);
|
||||
width: calc(87% - 5px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,7 +321,7 @@ button {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.inline-form-control {
|
||||
.section .inline-form-control {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -352,26 +345,28 @@ button {
|
||||
.issues-container {
|
||||
margin-left: 1.5em;
|
||||
margin-top: .5em;
|
||||
height: 92px;
|
||||
max-height: 92px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.issues-container > .issue {
|
||||
padding: 4px 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.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 .octicon {
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.issues-container > .issue > .issue-state {
|
||||
display: inline-block;
|
||||
width: 77px;
|
||||
padding: 3px 6px;
|
||||
margin-right: 5px;
|
||||
@@ -380,14 +375,8 @@ button {
|
||||
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;
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel';
|
||||
import { normalizeGitHubIssuesUrl } from 'vs/code/electron-browser/issue/issueReporterUtil';
|
||||
import { IssueType } from 'vs/platform/issue/common/issue';
|
||||
|
||||
suite('IssueReporter', () => {
|
||||
|
||||
@@ -18,8 +20,7 @@ suite('IssueReporter', () => {
|
||||
includeProcessInfo: true,
|
||||
includeExtensions: true,
|
||||
includeSearchedExtensions: true,
|
||||
includeSettingsSearchDetails: true,
|
||||
reprosWithoutExtensions: false
|
||||
includeSettingsSearchDetails: true
|
||||
});
|
||||
});
|
||||
|
||||
@@ -38,4 +39,75 @@ OS version: undefined
|
||||
|
||||
<!-- generated by issue reporter -->`);
|
||||
});
|
||||
|
||||
test('serializes GPU information when data is provided', () => {
|
||||
const issueReporterModel = new IssueReporterModel({
|
||||
issueType: 0,
|
||||
systemInfo: {
|
||||
'GPU Status': {
|
||||
'2d_canvas': 'enabled',
|
||||
'checker_imaging': 'disabled_off'
|
||||
}
|
||||
}
|
||||
});
|
||||
assert.equal(issueReporterModel.serialize(),
|
||||
// {{SQL CARBON EDIT}}
|
||||
`
|
||||
Issue Type: <b>Bug</b>
|
||||
|
||||
undefined
|
||||
|
||||
SQL Operations Studio version: undefined
|
||||
OS version: undefined
|
||||
|
||||
<details>
|
||||
<summary>System Info</summary>
|
||||
|
||||
|Item|Value|
|
||||
|---|---|
|
||||
|GPU Status|2d_canvas: enabled<br>checker_imaging: disabled_off|
|
||||
|
||||
</details>Extensions: none
|
||||
<!-- generated by issue reporter -->`);
|
||||
});
|
||||
|
||||
test('should normalize GitHub urls', () => {
|
||||
[
|
||||
'https://github.com/repo',
|
||||
'https://github.com/repo/',
|
||||
'https://github.com/repo.git',
|
||||
'https://github.com/repo/issues',
|
||||
'https://github.com/repo/issues/',
|
||||
'https://github.com/repo/issues/new',
|
||||
'https://github.com/repo/issues/new/'
|
||||
].forEach(url => {
|
||||
assert.equal('https://github.com/repo/issues/new', normalizeGitHubIssuesUrl(url));
|
||||
});
|
||||
});
|
||||
|
||||
test('should have support for filing on extensions for bugs, performance issues, and feature requests', () => {
|
||||
[
|
||||
IssueType.Bug,
|
||||
IssueType.FeatureRequest,
|
||||
IssueType.PerformanceIssue
|
||||
].forEach(type => {
|
||||
const issueReporterModel = new IssueReporterModel({
|
||||
issueType: type,
|
||||
fileOnExtension: true
|
||||
});
|
||||
|
||||
assert.equal(issueReporterModel.fileOnExtension(), true);
|
||||
});
|
||||
|
||||
[
|
||||
IssueType.SettingsSearchIssue
|
||||
].forEach(type => {
|
||||
const issueReporterModel = new IssueReporterModel({
|
||||
issueType: type,
|
||||
fileOnExtension: true
|
||||
});
|
||||
|
||||
assert.equal(issueReporterModel.fileOnExtension(), false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Ubuntu", "Droid Sans", sans-serif;
|
||||
font-size: 13px;
|
||||
color: #cccccc;
|
||||
}
|
||||
|
||||
.cpu {
|
||||
width: 45px;
|
||||
}
|
||||
|
||||
.pid {
|
||||
width: 50px
|
||||
}
|
||||
|
||||
.memory {
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
.process-item {
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
th {
|
||||
vertical-align: bottom;
|
||||
border-bottom: 1px solid #cccccc;
|
||||
padding: .5rem;
|
||||
border-top: 1px solid #cccccc;
|
||||
cursor: default;
|
||||
}
|
||||
td {
|
||||
padding: .25rem;
|
||||
vertical-align: top;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.centered {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nameLabel{
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.data {
|
||||
white-space: pre;
|
||||
padding-left: .5rem;
|
||||
}
|
||||
|
||||
tbody > tr:hover {
|
||||
background-color: #2A2D2E;
|
||||
}
|
||||
@@ -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:;">
|
||||
</head>
|
||||
|
||||
<body aria-label="">
|
||||
<div id="process-list"></div>
|
||||
</body>
|
||||
|
||||
<!-- Startup via processExplorer.js -->
|
||||
<script src="processExplorer.js"></script>
|
||||
|
||||
</html>
|
||||
175
src/vs/code/electron-browser/processExplorer/processExplorer.js
Normal file
175
src/vs/code/electron-browser/processExplorer/processExplorer.js
Normal file
@@ -0,0 +1,175 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 assign(destination, source) {
|
||||
return Object.keys(source)
|
||||
.reduce(function (r, key) { r[key] = source[key]; return r; }, destination);
|
||||
}
|
||||
|
||||
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 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'] || '{}') || {};
|
||||
|
||||
assign(process.env, configuration.userEnv);
|
||||
|
||||
//#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, newReturn) {
|
||||
const result = originalResolveLookupPaths(request, parent, newReturn);
|
||||
|
||||
const paths = newReturn ? result : 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
|
||||
|
||||
// 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);
|
||||
|
||||
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
|
||||
const loaderFilename = configuration.appRoot + '/out/vs/loader.js';
|
||||
const loaderSource = fs.readFileSync(loaderFilename);
|
||||
require('vm').runInThisContext(loaderSource, { filename: loaderFilename });
|
||||
var define = global.define;
|
||||
global.define = undefined;
|
||||
|
||||
window.nodeRequire = require.__$__nodeRequire;
|
||||
|
||||
define('fs', ['original-fs'], function (originalFS) { return originalFS; }); // replace the patched electron fs with the original node fs for all AMD code
|
||||
|
||||
window.MonacoEnvironment = {};
|
||||
const rootUrl = uriFromPath(configuration.appRoot) + '/out';
|
||||
|
||||
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/processExplorer/processExplorerMain'
|
||||
], function (processExplorer) {
|
||||
processExplorer.startup(configuration.data);
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,184 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/processExplorer';
|
||||
import { listProcesses, ProcessItem } from 'vs/base/node/ps';
|
||||
import { remote, webFrame } from 'electron';
|
||||
import { repeat } from 'vs/base/common/strings';
|
||||
import { totalmem } from 'os';
|
||||
import product from 'vs/platform/node/product';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ProcessExplorerStyles, ProcessExplorerData } from 'vs/platform/issue/common/issue';
|
||||
import * as browser from 'vs/base/browser/browser';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
|
||||
let processList: any[];
|
||||
|
||||
function getProcessList(rootProcess: ProcessItem) {
|
||||
const processes: any[] = [];
|
||||
|
||||
if (rootProcess) {
|
||||
getProcessItem(processes, rootProcess, 0);
|
||||
}
|
||||
|
||||
return processes;
|
||||
}
|
||||
|
||||
function getProcessItem(processes: any[], item: ProcessItem, indent: number): void {
|
||||
const isRoot = (indent === 0);
|
||||
|
||||
const MB = 1024 * 1024;
|
||||
|
||||
// Format name with indent
|
||||
const name = isRoot ? `${product.applicationName} main` : item.name;
|
||||
const formattedName = isRoot ? name : `${repeat(' ', indent)} ${name}`;
|
||||
const memory = process.platform === 'win32' ? item.mem : (totalmem() * (item.mem / 100));
|
||||
processes.push({
|
||||
cpu: Number(item.load.toFixed(0)),
|
||||
memory: Number((memory / MB).toFixed(0)),
|
||||
pid: Number((item.pid).toFixed(0)),
|
||||
name,
|
||||
formattedName,
|
||||
cmd: item.cmd
|
||||
});
|
||||
|
||||
// Recurse into children if any
|
||||
if (Array.isArray(item.children)) {
|
||||
item.children.forEach(child => getProcessItem(processes, child, indent + 1));
|
||||
}
|
||||
}
|
||||
|
||||
function getProcessIdWithHighestProperty(processList, propertyName: string) {
|
||||
let max = 0;
|
||||
let maxProcessId;
|
||||
processList.forEach(process => {
|
||||
if (process[propertyName] > max) {
|
||||
max = process[propertyName];
|
||||
maxProcessId = process.pid;
|
||||
}
|
||||
});
|
||||
|
||||
return maxProcessId;
|
||||
}
|
||||
|
||||
function updateProcessInfo(processList): void {
|
||||
const target = document.getElementById('process-list');
|
||||
const highestCPUProcess = getProcessIdWithHighestProperty(processList, 'cpu');
|
||||
const highestMemoryProcess = getProcessIdWithHighestProperty(processList, 'memory');
|
||||
|
||||
let tableHtml = `
|
||||
<tr>
|
||||
<th class="cpu">${localize('cpu', "CPU %")}</th>
|
||||
<th class="memory">${localize('memory', "Memory (MB)")}</th>
|
||||
<th class="pid">${localize('pid', "pid")}</th>
|
||||
<th class="nameLabel">${localize('name', "Name")}</th>
|
||||
</tr>`;
|
||||
|
||||
processList.forEach(p => {
|
||||
const cpuClass = p.pid === highestCPUProcess ? 'highest' : '';
|
||||
const memoryClass = p.pid === highestMemoryProcess ? 'highest' : '';
|
||||
|
||||
tableHtml += `
|
||||
<tr id=${p.pid}>
|
||||
<td class="centered ${cpuClass}">${p.cpu}</td>
|
||||
<td class="centered ${memoryClass}">${p.memory}</td>
|
||||
<td class="centered">${p.pid}</td>
|
||||
<td title="${p.name}" class="data">${p.formattedName}</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
target.innerHTML = `<table>${tableHtml}</table>`;
|
||||
}
|
||||
|
||||
function applyStyles(styles: ProcessExplorerStyles): void {
|
||||
const styleTag = document.createElement('style');
|
||||
const content: string[] = [];
|
||||
|
||||
if (styles.hoverBackground) {
|
||||
content.push(`tbody > tr:hover { background-color: ${styles.hoverBackground}; }`);
|
||||
}
|
||||
|
||||
if (styles.hoverForeground) {
|
||||
content.push(`tbody > tr:hover{ color: ${styles.hoverForeground}; }`);
|
||||
}
|
||||
|
||||
if (styles.highlightForeground) {
|
||||
content.push(`.highest { color: ${styles.highlightForeground}; }`);
|
||||
}
|
||||
|
||||
styleTag.innerHTML = content.join('\n');
|
||||
document.head.appendChild(styleTag);
|
||||
document.body.style.color = styles.color;
|
||||
}
|
||||
|
||||
function applyZoom(zoomLevel: number): void {
|
||||
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);
|
||||
}
|
||||
|
||||
function showContextMenu(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const pid = parseInt(e.currentTarget.id);
|
||||
if (pid && typeof pid === 'number') {
|
||||
const menu = new remote.Menu();
|
||||
menu.append(new remote.MenuItem({
|
||||
label: localize('killProcess', "Kill Process"),
|
||||
click() {
|
||||
process.kill(pid, 'SIGTERM');
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
menu.append(new remote.MenuItem({
|
||||
label: localize('forceKillProcess', "Force Kill Process"),
|
||||
click() {
|
||||
process.kill(pid, 'SIGKILL');
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
menu.popup(remote.getCurrentWindow());
|
||||
}
|
||||
}
|
||||
|
||||
export function startup(data: ProcessExplorerData): void {
|
||||
applyStyles(data.styles);
|
||||
applyZoom(data.zoomLevel);
|
||||
|
||||
setInterval(() => listProcesses(remote.process.pid).then(processes => {
|
||||
processList = getProcessList(processes);
|
||||
updateProcessInfo(processList);
|
||||
|
||||
const tableRows = document.getElementsByTagName('tr');
|
||||
for (let i = 0; i < tableRows.length; i++) {
|
||||
const tableRow = tableRows[i];
|
||||
tableRow.addEventListener('contextmenu', (e) => {
|
||||
showContextMenu(e);
|
||||
});
|
||||
}
|
||||
}), 1200);
|
||||
|
||||
|
||||
document.onkeydown = (e: KeyboardEvent) => {
|
||||
const cmdOrCtrlKey = platform.isMacintosh ? e.metaKey : e.ctrlKey;
|
||||
|
||||
// Cmd/Ctrl + zooms in
|
||||
if (cmdOrCtrlKey && e.keyCode === 187) {
|
||||
applyZoom(webFrame.getZoomLevel() + 1);
|
||||
}
|
||||
|
||||
// Cmd/Ctrl - zooms out
|
||||
if (cmdOrCtrlKey && e.keyCode === 189) {
|
||||
applyZoom(webFrame.getZoomLevel() - 1);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -69,10 +69,10 @@ function main() {
|
||||
const NODE_MODULES_ASAR_PATH = NODE_MODULES_PATH + '.asar';
|
||||
|
||||
const originalResolveLookupPaths = Module._resolveLookupPaths;
|
||||
Module._resolveLookupPaths = function (request, parent) {
|
||||
const result = originalResolveLookupPaths(request, parent);
|
||||
Module._resolveLookupPaths = function (request, parent, newReturn) {
|
||||
const result = originalResolveLookupPaths(request, parent, newReturn);
|
||||
|
||||
const paths = result[1];
|
||||
const paths = newReturn ? result : 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);
|
||||
|
||||
@@ -30,10 +30,9 @@ 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 { IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import { IWindowsService, ActiveWindowManager } 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/sharedProcess/contrib/contributions';
|
||||
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
|
||||
@@ -41,8 +40,8 @@ import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log
|
||||
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';
|
||||
import { DialogChannelClient } from 'vs/platform/dialogs/common/dialogIpc';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
|
||||
export interface ISharedProcessConfiguration {
|
||||
readonly machineId: string;
|
||||
@@ -58,28 +57,6 @@ interface ISharedProcessInitData {
|
||||
logLevel: LogLevel;
|
||||
}
|
||||
|
||||
class ActiveWindowManager implements IDisposable {
|
||||
private disposables: IDisposable[] = [];
|
||||
private _activeWindowId: number;
|
||||
|
||||
constructor( @IWindowsService windowsService: IWindowsService) {
|
||||
windowsService.onWindowOpen(this.setActiveWindow, this, this.disposables);
|
||||
windowsService.onWindowFocus(this.setActiveWindow, this, this.disposables);
|
||||
}
|
||||
|
||||
private setActiveWindow(windowId: number) {
|
||||
this._activeWindowId = windowId;
|
||||
}
|
||||
|
||||
public get activeClientId(): string {
|
||||
return `window:${this._activeWindowId}`;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
|
||||
const eventPrefix = 'monacoworkbench';
|
||||
|
||||
function main(server: Server, initData: ISharedProcessInitData, configuration: ISharedProcessConfiguration): void {
|
||||
@@ -102,13 +79,13 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
|
||||
services.set(IWindowsService, windowsService);
|
||||
|
||||
const activeWindowManager = new ActiveWindowManager(windowsService);
|
||||
const choiceChannel = server.getChannel('choice', {
|
||||
const dialogChannel = server.getChannel('dialog', {
|
||||
route: () => {
|
||||
logService.info('Routing choice request to the client', activeWindowManager.activeClientId);
|
||||
logService.info('Routing dialog request to the client', activeWindowManager.activeClientId);
|
||||
return activeWindowManager.activeClientId;
|
||||
}
|
||||
});
|
||||
services.set(IChoiceService, new ChoiceChannelClient(choiceChannel));
|
||||
services.set(IDialogService, new DialogChannelClient(dialogChannel));
|
||||
|
||||
const instantiationService = new InstantiationService(services);
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { app, ipcMain as ipc, BrowserWindow } from 'electron';
|
||||
import { app, ipcMain as ipc } 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';
|
||||
import { IWindowsService, OpenContext, ActiveWindowManager } from 'vs/platform/windows/common/windows';
|
||||
import { WindowsChannel } from 'vs/platform/windows/common/windowsIpc';
|
||||
import { WindowsService } from 'vs/platform/windows/electron-main/windowsService';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
|
||||
@@ -29,7 +29,7 @@ import { IStateService } from 'vs/platform/state/common/state';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IURLService } from 'vs/platform/url/common/url';
|
||||
import { URLChannel } from 'vs/platform/url/common/urlIpc';
|
||||
import { URLHandlerChannelClient, URLServiceChannel } from 'vs/platform/url/common/urlIpc';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { ITelemetryAppenderChannel, TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc';
|
||||
@@ -59,6 +59,8 @@ 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';
|
||||
import { ElectronURLListener } from 'vs/platform/url/electron-main/electronUrlListener';
|
||||
import { serve as serveDriver } from 'vs/platform/driver/electron-main/driver';
|
||||
|
||||
export class CodeApplication {
|
||||
|
||||
@@ -126,7 +128,7 @@ export class CodeApplication {
|
||||
return srcUri.startsWith(URI.file(this.environmentService.appRoot.toLowerCase()).toString());
|
||||
};
|
||||
|
||||
app.on('web-contents-created', (_event: any, contents) => {
|
||||
app.on('web-contents-created', (event: any, contents) => {
|
||||
contents.on('will-attach-webview', (event: Electron.Event, webPreferences, params) => {
|
||||
delete webPreferences.preload;
|
||||
webPreferences.nodeIntegration = false;
|
||||
@@ -181,15 +183,15 @@ export class CodeApplication {
|
||||
this.windowsMainService.openNewWindow(OpenContext.DESKTOP); //macOS native tab "+" button
|
||||
});
|
||||
|
||||
ipc.on('vscode:exit', (_event: any, code: number) => {
|
||||
ipc.on('vscode:exit', (event: any, code: number) => {
|
||||
this.logService.trace('IPC#vscode:exit', code);
|
||||
|
||||
this.dispose();
|
||||
this.lifecycleService.kill(code);
|
||||
});
|
||||
|
||||
ipc.on('vscode:fetchShellEnv', (_event: any, windowId: number) => {
|
||||
const { webContents } = BrowserWindow.fromId(windowId);
|
||||
ipc.on('vscode:fetchShellEnv', event => {
|
||||
const webContents = event.sender.webContents;
|
||||
getShellEnvironment().then(shellEnv => {
|
||||
if (!webContents.isDestroyed()) {
|
||||
webContents.send('vscode:acceptShellEnv', shellEnv);
|
||||
@@ -203,7 +205,7 @@ export class CodeApplication {
|
||||
});
|
||||
});
|
||||
|
||||
ipc.on('vscode:broadcast', (_event: any, windowId: number, broadcast: { channel: string; payload: any; }) => {
|
||||
ipc.on('vscode:broadcast', (event: any, windowId: number, broadcast: { channel: string; payload: any; }) => {
|
||||
if (this.windowsMainService && broadcast.channel && !isUndefinedOrNull(broadcast.payload)) {
|
||||
this.logService.trace('IPC#vscode:broadcast', broadcast.channel, broadcast.payload);
|
||||
|
||||
@@ -277,22 +279,34 @@ export class CodeApplication {
|
||||
this.logService.trace(`Resolved machine identifier: ${machineId}`);
|
||||
|
||||
// Spawn shared process
|
||||
this.sharedProcess = new SharedProcess(this.environmentService, machineId, this.userEnv, this.logService);
|
||||
this.toDispose.push(this.sharedProcess);
|
||||
this.sharedProcess = new SharedProcess(this.environmentService, this.lifecycleService, this.logService, machineId, this.userEnv);
|
||||
this.sharedProcessClient = this.sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main'));
|
||||
|
||||
// Services
|
||||
const appInstantiationService = this.initServices(machineId);
|
||||
|
||||
// Setup Auth Handler
|
||||
const authHandler = appInstantiationService.createInstance(ProxyAuthHandler);
|
||||
this.toDispose.push(authHandler);
|
||||
let promise: TPromise<any> = TPromise.as(null);
|
||||
|
||||
// Open Windows
|
||||
appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor));
|
||||
// Create driver
|
||||
if (this.environmentService.driverHandle) {
|
||||
serveDriver(this.electronIpcServer, this.environmentService.driverHandle, appInstantiationService).then(server => {
|
||||
this.logService.info('Driver started at:', this.environmentService.driverHandle);
|
||||
this.toDispose.push(server);
|
||||
});
|
||||
}
|
||||
|
||||
// Post Open Windows Tasks
|
||||
appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor));
|
||||
return promise.then(() => {
|
||||
|
||||
// Setup Auth Handler
|
||||
const authHandler = appInstantiationService.createInstance(ProxyAuthHandler);
|
||||
this.toDispose.push(authHandler);
|
||||
|
||||
// Open Windows
|
||||
appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor));
|
||||
|
||||
// Post Open Windows Tasks
|
||||
appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -325,7 +339,7 @@ export class CodeApplication {
|
||||
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));
|
||||
services.set(IIssueService, new SyncDescriptor(IssueService, machineId, this.userEnv));
|
||||
|
||||
// Telemtry
|
||||
if (this.environmentService.isBuilt && !this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
|
||||
@@ -346,16 +360,6 @@ export class CodeApplication {
|
||||
private openFirstWindow(accessor: ServicesAccessor): void {
|
||||
const appInstantiationService = accessor.get(IInstantiationService);
|
||||
|
||||
// TODO@Joao: unfold this
|
||||
this.windowsMainService = accessor.get(IWindowsMainService);
|
||||
|
||||
// TODO@Joao: so ugly...
|
||||
this.windowsMainService.onWindowsCountChanged(e => {
|
||||
if (!platform.isMacintosh && e.newCount === 0) {
|
||||
this.sharedProcess.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
// Register more Main IPC services
|
||||
const launchService = accessor.get(ILaunchService);
|
||||
const launchChannel = new LaunchChannel(launchService);
|
||||
@@ -366,10 +370,6 @@ export class CodeApplication {
|
||||
const updateChannel = new UpdateChannel(updateService);
|
||||
this.electronIpcServer.registerChannel('update', updateChannel);
|
||||
|
||||
const urlService = accessor.get(IURLService);
|
||||
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);
|
||||
@@ -383,6 +383,10 @@ export class CodeApplication {
|
||||
this.electronIpcServer.registerChannel('windows', windowsChannel);
|
||||
this.sharedProcessClient.done(client => client.registerChannel('windows', windowsChannel));
|
||||
|
||||
const urlService = accessor.get(IURLService);
|
||||
const urlChannel = new URLServiceChannel(urlService);
|
||||
this.electronIpcServer.registerChannel('url', urlChannel);
|
||||
|
||||
// Log level management
|
||||
const logLevelChannel = new LogLevelSetterChannel(accessor.get(ILogService));
|
||||
this.electronIpcServer.registerChannel('loglevel', logLevelChannel);
|
||||
@@ -392,15 +396,51 @@ export class CodeApplication {
|
||||
this.lifecycleService.ready();
|
||||
|
||||
// Propagate to clients
|
||||
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); // TODO@Joao: unfold this
|
||||
|
||||
const args = this.environmentService.args;
|
||||
|
||||
// Create a URL handler which forwards to the last active window
|
||||
const activeWindowManager = new ActiveWindowManager(windowsService);
|
||||
const urlHandlerChannel = this.electronIpcServer.getChannel('urlHandler', { route: () => activeWindowManager.activeClientId });
|
||||
const multiplexURLHandler = new URLHandlerChannelClient(urlHandlerChannel);
|
||||
|
||||
// On Mac, Code can be running without any open windows, so we must create a window to handle urls,
|
||||
// if there is none
|
||||
if (platform.isMacintosh) {
|
||||
const environmentService = accessor.get(IEnvironmentService);
|
||||
|
||||
urlService.registerHandler({
|
||||
async handleURL(uri: URI): TPromise<boolean> {
|
||||
if (windowsMainService.getWindowCount() === 0) {
|
||||
const cli = { ...environmentService.args, goto: true };
|
||||
const [window] = windowsMainService.open({ context: OpenContext.API, cli, forceEmpty: true });
|
||||
|
||||
return window.ready().then(() => urlService.open(uri));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Register the multiple URL handker
|
||||
urlService.registerHandler(multiplexURLHandler);
|
||||
|
||||
// Watch Electron URLs and forward them to the UrlService
|
||||
const urls = args['open-url'] ? args._urls : [];
|
||||
const urlListener = new ElectronURLListener(urls, urlService, this.windowsMainService);
|
||||
this.toDispose.push(urlListener);
|
||||
|
||||
this.windowsMainService.ready(this.userEnv);
|
||||
|
||||
// Open our first window
|
||||
const args = this.environmentService.args;
|
||||
const macOpenFiles = (<any>global).macOpenFiles as string[];
|
||||
const context = !!process.env['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP;
|
||||
if (args['new-window'] && args._.length === 0) {
|
||||
this.windowsMainService.open({ context, cli: args, forceNewWindow: true, forceEmpty: true, initialStartup: true }); // new window if "-n" was used without paths
|
||||
} else if (global.macOpenFiles && global.macOpenFiles.length && (!args._ || !args._.length)) {
|
||||
this.windowsMainService.open({ context: OpenContext.DOCK, cli: args, pathsToOpen: global.macOpenFiles, initialStartup: true }); // mac: open-file event received on startup
|
||||
} else if (macOpenFiles && macOpenFiles.length && (!args._ || !args._.length)) {
|
||||
this.windowsMainService.open({ context: OpenContext.DOCK, cli: args, pathsToOpen: macOpenFiles, initialStartup: true }); // mac: open-file event received on startup
|
||||
} else {
|
||||
this.windowsMainService.open({ context, cli: args, forceNewWindow: args['new-window'] || (!args._.length && args['unity-launch']), diffMode: args.diff, initialStartup: true }); // default: read paths from cli
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ export interface SystemInfo {
|
||||
VM: string;
|
||||
'Screen Reader': string;
|
||||
'Process Argv': string;
|
||||
'GPU Status': Electron.GPUFeatureStatus;
|
||||
}
|
||||
|
||||
export interface ProcessInfo {
|
||||
@@ -92,7 +93,8 @@ export function getSystemInfo(info: IMainProcessInfo): 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(' ')}`
|
||||
'Process Argv': `${info.mainArguments.join(' ')}`,
|
||||
'GPU Status': app.getGPUFeatureStatus()
|
||||
};
|
||||
|
||||
const cpus = os.cpus();
|
||||
@@ -208,7 +210,14 @@ function formatLaunchConfigs(configs: WorkspaceStatItem[]): string {
|
||||
return output.join('\n');
|
||||
}
|
||||
|
||||
function formatEnvironment(info: IMainProcessInfo): string {
|
||||
function expandGPUFeatures(): string {
|
||||
const gpuFeatures = app.getGPUFeatureStatus();
|
||||
const longestFeatureName = Math.max(...Object.keys(gpuFeatures).map(feature => feature.length));
|
||||
// Make columns aligned by adding spaces after feature name
|
||||
return Object.keys(gpuFeatures).map(feature => `${feature}: ${repeat(' ', longestFeatureName - feature.length)} ${gpuFeatures[feature]}`).join('\n ');
|
||||
}
|
||||
|
||||
export function formatEnvironment(info: IMainProcessInfo): string {
|
||||
const MB = 1024 * 1024;
|
||||
const GB = 1024 * MB;
|
||||
|
||||
@@ -226,6 +235,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(' ')}`);
|
||||
output.push(`GPU Status: ${expandGPUFeatures()}`);
|
||||
|
||||
return output.join('\n');
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import * as nativeKeymap from 'native-keymap';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IStateService } from 'vs/platform/state/common/state';
|
||||
import Event, { Emitter, once } from 'vs/base/common/event';
|
||||
import { Event, Emitter, once } from 'vs/base/common/event';
|
||||
import { ConfigWatcher } from 'vs/base/node/config';
|
||||
import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
@@ -20,7 +20,7 @@ export class KeyboardLayoutMonitor {
|
||||
|
||||
public static readonly INSTANCE = new KeyboardLayoutMonitor();
|
||||
|
||||
private _emitter: Emitter<void>;
|
||||
private readonly _emitter: Emitter<void>;
|
||||
private _registered: boolean;
|
||||
|
||||
private constructor() {
|
||||
|
||||
@@ -9,14 +9,16 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
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 { IProcessEnvironment, isMacintosh } from 'vs/base/common/platform';
|
||||
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 { OpenContext, IWindowSettings } 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';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
export const ID = 'launchService';
|
||||
export const ILaunchService = createDecorator<ILaunchService>(ID);
|
||||
@@ -38,6 +40,24 @@ export interface IMainProcessInfo {
|
||||
windows: IWindowInfo[];
|
||||
}
|
||||
|
||||
function parseOpenUrl(args: ParsedArgs): URI[] {
|
||||
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
|
||||
return args._urls
|
||||
.map(url => {
|
||||
try {
|
||||
return URI.parse(url);
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(uri => !!uri);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
export interface ILaunchService {
|
||||
_serviceBrand: any;
|
||||
start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void>;
|
||||
@@ -110,14 +130,32 @@ export class LaunchService implements ILaunchService {
|
||||
@IWindowsMainService private windowsMainService: IWindowsMainService,
|
||||
@IURLService private urlService: IURLService,
|
||||
@IWorkspacesMainService private workspacesMainService: IWorkspacesMainService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
) { }
|
||||
|
||||
public start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void> {
|
||||
this.logService.trace('Received data from other instance: ', args, userEnv);
|
||||
|
||||
const urlsToOpen = parseOpenUrl(args);
|
||||
|
||||
// Check early for open-url which is handled in URL service
|
||||
if (this.shouldOpenUrl(args)) {
|
||||
if (urlsToOpen.length) {
|
||||
let whenWindowReady = TPromise.as<any>(null);
|
||||
|
||||
// Create a window if there is none
|
||||
if (this.windowsMainService.getWindowCount() === 0) {
|
||||
const window = this.windowsMainService.openNewWindow(OpenContext.DESKTOP)[0];
|
||||
whenWindowReady = window.ready();
|
||||
}
|
||||
|
||||
// Make sure a window is open, ready to receive the url event
|
||||
whenWindowReady.then(() => {
|
||||
for (const url of urlsToOpen) {
|
||||
this.urlService.open(url);
|
||||
}
|
||||
});
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
@@ -125,28 +163,54 @@ export class LaunchService implements ILaunchService {
|
||||
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[];
|
||||
|
||||
// Special case extension development
|
||||
if (!!args.extensionDevelopmentPath) {
|
||||
this.windowsMainService.openExtensionDevelopmentHostWindow({ context, cli: args, userEnv });
|
||||
} else if (args._.length === 0 && (args['new-window'] || args['unity-launch'])) {
|
||||
usedWindows = this.windowsMainService.open({ context, cli: args, userEnv, forceNewWindow: true, forceEmpty: true });
|
||||
} else if (args._.length === 0) {
|
||||
usedWindows = [this.windowsMainService.focusLastActive(args, context)];
|
||||
} else {
|
||||
}
|
||||
|
||||
// Start without file/folder arguments
|
||||
else if (args._.length === 0) {
|
||||
let openNewWindow = false;
|
||||
|
||||
// Force new window
|
||||
if (args['new-window'] || args['unity-launch']) {
|
||||
openNewWindow = true;
|
||||
}
|
||||
|
||||
// Force reuse window
|
||||
else if (args['reuse-window']) {
|
||||
openNewWindow = false;
|
||||
}
|
||||
|
||||
// Otherwise check for settings
|
||||
else {
|
||||
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
|
||||
const openWithoutArgumentsInNewWindowConfig = (windowConfig && windowConfig.openWithoutArgumentsInNewWindow) || 'default' /* default */;
|
||||
switch (openWithoutArgumentsInNewWindowConfig) {
|
||||
case 'on':
|
||||
openNewWindow = true;
|
||||
break;
|
||||
case 'off':
|
||||
openNewWindow = false;
|
||||
break;
|
||||
default:
|
||||
openNewWindow = !isMacintosh; // prefer to restore running instance on macOS
|
||||
}
|
||||
}
|
||||
|
||||
if (openNewWindow) {
|
||||
usedWindows = this.windowsMainService.open({ context, cli: args, userEnv, forceNewWindow: true, forceEmpty: true });
|
||||
} else {
|
||||
usedWindows = [this.windowsMainService.focusLastActive(args, context)];
|
||||
}
|
||||
}
|
||||
|
||||
// Start with file/folder arguments
|
||||
else {
|
||||
usedWindows = this.windowsMainService.open({
|
||||
context,
|
||||
cli: args,
|
||||
|
||||
@@ -34,7 +34,7 @@ import { ConfigurationService } from 'vs/platform/configuration/node/configurati
|
||||
import { IRequestService } from 'vs/platform/request/node/request';
|
||||
import { RequestService } from 'vs/platform/request/electron-main/requestService';
|
||||
import { IURLService } from 'vs/platform/url/common/url';
|
||||
import { URLService } from 'vs/platform/url/electron-main/urlService';
|
||||
import { URLService } from 'vs/platform/url/common/urlService';
|
||||
import * as fs from 'original-fs';
|
||||
import { CodeApplication } from 'vs/code/electron-main/app';
|
||||
import { HistoryMainService } from 'vs/platform/history/electron-main/historyMainService';
|
||||
@@ -48,8 +48,8 @@ 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';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { CommandLineDialogService } from 'vs/platform/dialogs/node/dialogService';
|
||||
|
||||
function createServices(args: ParsedArgs, bufferLogService: BufferLogService): IInstantiationService {
|
||||
const services = new ServiceCollection();
|
||||
@@ -71,9 +71,9 @@ function createServices(args: ParsedArgs, bufferLogService: BufferLogService): I
|
||||
services.set(IStateService, new SyncDescriptor(StateService));
|
||||
services.set(IConfigurationService, new SyncDescriptor(ConfigurationService));
|
||||
services.set(IRequestService, new SyncDescriptor(RequestService));
|
||||
services.set(IURLService, new SyncDescriptor(URLService, args['open-url'] ? args._urls : []));
|
||||
services.set(IURLService, new SyncDescriptor(URLService));
|
||||
services.set(IBackupMainService, new SyncDescriptor(BackupMainService));
|
||||
services.set(IChoiceService, new SyncDescriptor(ChoiceCliService));
|
||||
services.set(IDialogService, new SyncDescriptor(CommandLineDialogService));
|
||||
|
||||
return new InstantiationService(services, true);
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ export class CodeMenu {
|
||||
this.windowsMainService.onWindowClose(() => this.updateWorkspaceMenuItems());
|
||||
|
||||
// Listen to extension viewlets
|
||||
ipc.on('vscode:extensionViewlets', (_event: any, rawExtensionViewlets: string) => {
|
||||
ipc.on('vscode:extensionViewlets', (event: any, rawExtensionViewlets: string) => {
|
||||
let extensionViewlets: IExtensionViewlet[] = [];
|
||||
try {
|
||||
extensionViewlets = JSON.parse(rawExtensionViewlets);
|
||||
@@ -465,7 +465,7 @@ export class CodeMenu {
|
||||
}
|
||||
|
||||
private getPreferencesMenu(): Electron.MenuItem {
|
||||
const settings = this.createMenuItem(nls.localize({ key: 'miOpenSettings', comment: ['&& denotes a mnemonic'] }, "&&Settings"), 'workbench.action.openGlobalSettings');
|
||||
const settings = this.createMenuItem(nls.localize({ key: 'miOpenSettings', comment: ['&& denotes a mnemonic'] }, "&&Settings"), 'workbench.action.openSettings');
|
||||
const kebindingSettings = this.createMenuItem(nls.localize({ key: 'miOpenKeymap', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts"), 'workbench.action.openGlobalKeybindings');
|
||||
// {{SQL CARBON EDIT}}
|
||||
// const keymapExtensions = this.createMenuItem(nls.localize({ key: 'miOpenKeymapExtensions', comment: ['&& denotes a mnemonic'] }, "&&Keymap Extensions"), 'workbench.extensions.action.showRecommendedKeymapExtensions');
|
||||
@@ -892,6 +892,7 @@ export class CodeMenu {
|
||||
breakpointsMenu.append(this.createMenuItem(nls.localize({ key: 'miConditionalBreakpoint', comment: ['&& denotes a mnemonic'] }, "&&Conditional Breakpoint..."), 'editor.debug.action.conditionalBreakpoint'));
|
||||
breakpointsMenu.append(this.createMenuItem(nls.localize({ key: 'miColumnBreakpoint', comment: ['&& denotes a mnemonic'] }, "C&&olumn Breakpoint"), 'editor.debug.action.toggleColumnBreakpoint'));
|
||||
breakpointsMenu.append(this.createMenuItem(nls.localize({ key: 'miFunctionBreakpoint', comment: ['&& denotes a mnemonic'] }, "&&Function Breakpoint..."), 'workbench.debug.viewlet.action.addFunctionBreakpointAction'));
|
||||
breakpointsMenu.append(this.createMenuItem(nls.localize({ key: 'miLogPoint', comment: ['&& denotes a mnemonic'] }, "&&Logpoint..."), 'editor.debug.action.toggleLogPoint'));
|
||||
const newBreakpoints = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miNewBreakpoint', comment: ['&& denotes a mnemonic'] }, "&&New Breakpoint")), submenu: breakpointsMenu });
|
||||
const enableAllBreakpoints = this.createMenuItem(nls.localize({ key: 'miEnableAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "Enable All Breakpoints"), 'workbench.debug.viewlet.action.enableAllBreakpoints');
|
||||
const disableAllBreakpoints = this.createMenuItem(nls.localize({ key: 'miDisableAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "Disable A&&ll Breakpoints"), 'workbench.debug.viewlet.action.disableAllBreakpoints');
|
||||
@@ -980,6 +981,8 @@ export class CodeMenu {
|
||||
}
|
||||
}, false));
|
||||
|
||||
const openProcessExplorer = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miOpenProcessExplorerer', comment: ['&& denotes a mnemonic'] }, "Open &&Process Explorer")), click: () => this.runActionInRenderer('workbench.action.openProcessExplorer') });
|
||||
|
||||
let reportIssuesItem: Electron.MenuItem = null;
|
||||
if (product.reportIssueUrl) {
|
||||
const label = nls.localize({ key: 'miReportIssue', comment: ['&& denotes a mnemonic', 'Translate this to "Report Issue in English" in all languages please!'] }, "Report &&Issue");
|
||||
@@ -1042,7 +1045,8 @@ export class CodeMenu {
|
||||
}) : null,
|
||||
(product.licenseUrl || product.privacyStatementUrl) ? __separator__() : null,
|
||||
toggleDevToolsItem,
|
||||
isWindows && product.quality !== 'stable' ? showAccessibilityOptions : null
|
||||
openProcessExplorer,
|
||||
isWindows && product.quality !== 'stable' ? showAccessibilityOptions : null,
|
||||
]).forEach(item => helpMenu.append(item));
|
||||
|
||||
if (!isMacintosh) {
|
||||
@@ -1085,7 +1089,7 @@ export class CodeMenu {
|
||||
}
|
||||
|
||||
private openAccessibilityOptions(): void {
|
||||
let win = new BrowserWindow({
|
||||
const win = new BrowserWindow({
|
||||
alwaysOnTop: true,
|
||||
skipTaskbar: true,
|
||||
resizable: false,
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IProcessEnvironment } from 'vs/base/common/platform';
|
||||
@@ -13,20 +12,22 @@ 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';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
|
||||
|
||||
export class SharedProcess implements ISharedProcess {
|
||||
|
||||
private barrier = new Barrier();
|
||||
|
||||
private window: Electron.BrowserWindow;
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
private environmentService: IEnvironmentService,
|
||||
private readonly environmentService: IEnvironmentService,
|
||||
private readonly lifecycleService: ILifecycleService,
|
||||
private readonly logService: ILogService,
|
||||
private readonly machineId: string,
|
||||
private readonly userEnv: IProcessEnvironment,
|
||||
private readonly logService: ILogService
|
||||
) { }
|
||||
) {
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get _whenReady(): TPromise<void> {
|
||||
@@ -50,16 +51,27 @@ export class SharedProcess implements ISharedProcess {
|
||||
|
||||
// Prevent the window from dying
|
||||
const onClose = (e: Event) => {
|
||||
this.logService.trace('SharedProcess#close prevented');
|
||||
|
||||
// We never allow to close the shared process unless we get explicitly disposed()
|
||||
e.preventDefault();
|
||||
|
||||
// Still hide the window though if visible
|
||||
if (this.window.isVisible()) {
|
||||
e.preventDefault();
|
||||
this.window.hide();
|
||||
}
|
||||
};
|
||||
|
||||
this.window.on('close', onClose);
|
||||
this.disposables.push(toDisposable(() => this.window.removeListener('close', onClose)));
|
||||
|
||||
this.disposables.push(toDisposable(() => {
|
||||
this.lifecycleService.onShutdown(() => {
|
||||
// Shut the shared process down when we are quitting
|
||||
//
|
||||
// Note: because we veto the window close, we must first remove our veto.
|
||||
// Otherwise the application would never quit because the shared process
|
||||
// window is refusing to close!
|
||||
//
|
||||
this.window.removeListener('close', onClose);
|
||||
|
||||
// Electron seems to crash on Windows without this setTimeout :|
|
||||
setTimeout(() => {
|
||||
@@ -71,7 +83,7 @@ export class SharedProcess implements ISharedProcess {
|
||||
|
||||
this.window = null;
|
||||
}, 0);
|
||||
}));
|
||||
});
|
||||
|
||||
return new TPromise<void>((c, e) => {
|
||||
ipcMain.once('handshake:hello', ({ sender }: { sender: any }) => {
|
||||
@@ -111,8 +123,4 @@ export class SharedProcess implements ISharedProcess {
|
||||
this.window.webContents.closeDevTools();
|
||||
this.window.hide();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import * as path from 'path';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import nls = require('vs/nls');
|
||||
import * as nls from 'vs/nls';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IStateService } from 'vs/platform/state/common/state';
|
||||
import { shell, screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage } from 'electron';
|
||||
@@ -20,35 +20,19 @@ import product from 'vs/platform/node/product';
|
||||
import { IWindowSettings, MenuBarVisibility, IWindowConfiguration, ReadyState, IRunActionInWindowRequest } from 'vs/platform/windows/common/windows';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
|
||||
import { ICodeWindow } from 'vs/platform/windows/electron-main/windows';
|
||||
import { ICodeWindow, IWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows';
|
||||
import { IWorkspaceIdentifier, IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IBackupMainService } from 'vs/platform/backup/common/backup';
|
||||
import { ICommandAction } from 'vs/platform/actions/common/actions';
|
||||
import { mark, exportEntries } from 'vs/base/common/performance';
|
||||
import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/node/extensionGalleryService';
|
||||
|
||||
export interface IWindowState {
|
||||
width?: number;
|
||||
height?: number;
|
||||
x?: number;
|
||||
y?: number;
|
||||
mode?: WindowMode;
|
||||
display?: number;
|
||||
}
|
||||
|
||||
export interface IWindowCreationOptions {
|
||||
state: IWindowState;
|
||||
extensionDevelopmentPath?: string;
|
||||
isExtensionTestHost?: boolean;
|
||||
}
|
||||
|
||||
export enum WindowMode {
|
||||
Maximized,
|
||||
Normal,
|
||||
Minimized, // not used anymore, but also cannot remove due to existing stored UI state (needs migration)
|
||||
Fullscreen
|
||||
}
|
||||
|
||||
export const defaultWindowState = function (mode = WindowMode.Normal): IWindowState {
|
||||
return {
|
||||
width: 1024,
|
||||
@@ -92,7 +76,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
private toDispose: IDisposable[];
|
||||
private representedFilename: string;
|
||||
|
||||
private whenReadyCallbacks: TValueCallback<CodeWindow>[];
|
||||
private whenReadyCallbacks: TValueCallback<ICodeWindow>[];
|
||||
|
||||
private currentConfig: IWindowConfiguration;
|
||||
private pendingLoadConfig: IWindowConfiguration;
|
||||
@@ -167,8 +151,16 @@ export class CodeWindow implements ICodeWindow {
|
||||
|
||||
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
|
||||
|
||||
if (isMacintosh) {
|
||||
options.acceptFirstMouse = true; // enabled by default
|
||||
|
||||
if (windowConfig && windowConfig.clickThroughInactive === false) {
|
||||
options.acceptFirstMouse = false;
|
||||
}
|
||||
}
|
||||
|
||||
let useNativeTabs = false;
|
||||
if (windowConfig && windowConfig.nativeTabs) {
|
||||
if (isMacintosh && windowConfig && windowConfig.nativeTabs === true) {
|
||||
options.tabbingIdentifier = product.nameShort; // this opts in to sierra tabs
|
||||
useNativeTabs = true;
|
||||
}
|
||||
@@ -219,15 +211,6 @@ export class CodeWindow implements ICodeWindow {
|
||||
this._win.setSheetOffset(22); // offset dialogs by the height of the custom title bar if we have any
|
||||
}
|
||||
|
||||
// Set relaunch command
|
||||
if (isWindows && product.win32AppUserModelId && typeof this._win.setAppDetails === 'function') {
|
||||
this._win.setAppDetails({
|
||||
appId: product.win32AppUserModelId,
|
||||
relaunchCommand: `"${process.execPath}" -n`,
|
||||
relaunchDisplayName: product.nameLong
|
||||
});
|
||||
}
|
||||
|
||||
if (isFullscreenOrMaximized) {
|
||||
this._win.maximize();
|
||||
|
||||
@@ -324,8 +307,8 @@ export class CodeWindow implements ICodeWindow {
|
||||
}
|
||||
}
|
||||
|
||||
public ready(): TPromise<CodeWindow> {
|
||||
return new TPromise<CodeWindow>((c) => {
|
||||
public ready(): TPromise<ICodeWindow> {
|
||||
return new TPromise<ICodeWindow>((c) => {
|
||||
if (this._readyState === ReadyState.READY) {
|
||||
return c(this);
|
||||
}
|
||||
@@ -443,6 +426,34 @@ export class CodeWindow implements ICodeWindow {
|
||||
|
||||
// Handle Workspace events
|
||||
this.toDispose.push(this.workspacesMainService.onUntitledWorkspaceDeleted(e => this.onUntitledWorkspaceDeleted(e)));
|
||||
|
||||
// TODO@Ben workaround for https://github.com/Microsoft/vscode/issues/13612
|
||||
// It looks like smooth scrolling disappears as soon as the window is minimized
|
||||
// and maximized again. Touching some window properties "fixes" it, like toggling
|
||||
// the visibility of the menu.
|
||||
if (isWindows) {
|
||||
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
|
||||
if (windowConfig && windowConfig.smoothScrollingWorkaround === true) {
|
||||
let minimized = false;
|
||||
|
||||
const restoreSmoothScrolling = () => {
|
||||
if (minimized) {
|
||||
const visibility = this.getMenuBarVisibility();
|
||||
const temporaryVisibility: MenuBarVisibility = (visibility === 'hidden' || visibility === 'toggle') ? 'default' : 'hidden';
|
||||
setTimeout(() => {
|
||||
this.doSetMenuBarVisibility(temporaryVisibility);
|
||||
this.doSetMenuBarVisibility(visibility);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
minimized = false;
|
||||
};
|
||||
|
||||
this._win.on('minimize', () => minimized = true);
|
||||
this._win.on('restore', () => restoreSmoothScrolling());
|
||||
this._win.on('maximize', () => restoreSmoothScrolling());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onUntitledWorkspaceDeleted(workspace: IWorkspaceIdentifier): void {
|
||||
@@ -599,7 +610,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
|
||||
// Perf Counters
|
||||
windowConfiguration.perfEntries = exportEntries();
|
||||
windowConfiguration.perfStartTime = global.perfStartTime;
|
||||
windowConfiguration.perfStartTime = (<any>global).perfStartTime;
|
||||
windowConfiguration.perfWindowLoadTime = Date.now();
|
||||
|
||||
// Config (combination of process.argv and window configuration)
|
||||
@@ -826,11 +837,32 @@ export class CodeWindow implements ICodeWindow {
|
||||
return menuBarVisibility;
|
||||
}
|
||||
|
||||
public setMenuBarVisibility(visibility: MenuBarVisibility, notify: boolean = true): void {
|
||||
private setMenuBarVisibility(visibility: MenuBarVisibility, notify: boolean = true): void {
|
||||
if (isMacintosh) {
|
||||
return; // ignore for macOS platform
|
||||
}
|
||||
|
||||
if (visibility === 'toggle') {
|
||||
if (notify) {
|
||||
this.send('vscode:showInfoMessage', nls.localize('hiddenMenuBar', "You can still access the menu bar by pressing the Alt-key."));
|
||||
}
|
||||
}
|
||||
|
||||
if (visibility === 'hidden') {
|
||||
// for some weird reason that I have no explanation for, the menu bar is not hiding when calling
|
||||
// this without timeout (see https://github.com/Microsoft/vscode/issues/19777). there seems to be
|
||||
// a timing issue with us opening the first window and the menu bar getting created. somehow the
|
||||
// fact that we want to hide the menu without being able to bring it back via Alt key makes Electron
|
||||
// still show the menu. Unable to reproduce from a simple Hello World application though...
|
||||
setTimeout(() => {
|
||||
this.doSetMenuBarVisibility(visibility);
|
||||
});
|
||||
} else {
|
||||
this.doSetMenuBarVisibility(visibility);
|
||||
}
|
||||
}
|
||||
|
||||
private doSetMenuBarVisibility(visibility: MenuBarVisibility): void {
|
||||
const isFullscreen = this._win.isFullScreen();
|
||||
|
||||
switch (visibility) {
|
||||
@@ -847,22 +879,11 @@ export class CodeWindow implements ICodeWindow {
|
||||
case ('toggle'):
|
||||
this._win.setMenuBarVisibility(false);
|
||||
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."));
|
||||
}
|
||||
break;
|
||||
|
||||
case ('hidden'):
|
||||
// for some weird reason that I have no explanation for, the menu bar is not hiding when calling
|
||||
// this without timeout (see https://github.com/Microsoft/vscode/issues/19777). there seems to be
|
||||
// a timing issue with us opening the first window and the menu bar getting created. somehow the
|
||||
// fact that we want to hide the menu without being able to bring it back via Alt key makes Electron
|
||||
// still show the menu. Unable to reproduce from a simple Hello World application though...
|
||||
setTimeout(() => {
|
||||
this._win.setMenuBarVisibility(false);
|
||||
this._win.setAutoHideMenuBar(false);
|
||||
});
|
||||
this._win.setMenuBarVisibility(false);
|
||||
this._win.setAutoHideMenuBar(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -907,7 +928,9 @@ export class CodeWindow implements ICodeWindow {
|
||||
}
|
||||
|
||||
public send(channel: string, ...args: any[]): void {
|
||||
this._win.webContents.send(channel, ...args);
|
||||
if (this._win) {
|
||||
this._win.webContents.send(channel, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
public updateTouchBar(groups: ICommandAction[][]): void {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { assign, mixin, equals } from 'vs/base/common/objects';
|
||||
import { IBackupMainService } from 'vs/platform/backup/common/backup';
|
||||
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
|
||||
import { IStateService } from 'vs/platform/state/common/state';
|
||||
import { CodeWindow, IWindowState as ISingleWindowState, defaultWindowState, WindowMode } from 'vs/code/electron-main/window';
|
||||
import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window';
|
||||
import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences, app } from 'electron';
|
||||
import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/node/paths';
|
||||
import { ILifecycleService, UnloadReason, IWindowUnloadEvent } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
|
||||
@@ -21,11 +21,11 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
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 { Event as 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, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
|
||||
import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } 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';
|
||||
@@ -36,6 +36,7 @@ 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';
|
||||
import { exists } from 'vs/base/node/pfs';
|
||||
|
||||
enum WindowError {
|
||||
UNRESPONSIVE,
|
||||
@@ -76,7 +77,7 @@ interface IOpenBrowserWindowOptions {
|
||||
filesToWait?: IPathsToWaitFor;
|
||||
|
||||
forceNewWindow?: boolean;
|
||||
windowToUse?: CodeWindow;
|
||||
windowToUse?: ICodeWindow;
|
||||
|
||||
emptyWindowBackupFolder?: string;
|
||||
}
|
||||
@@ -102,7 +103,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
|
||||
private static readonly windowsStateStorageKey = 'windowsState';
|
||||
|
||||
private static WINDOWS: CodeWindow[] = [];
|
||||
private static WINDOWS: ICodeWindow[] = [];
|
||||
|
||||
private initialUserEnv: IProcessEnvironment;
|
||||
|
||||
@@ -112,8 +113,8 @@ export class WindowsManager implements IWindowsMainService {
|
||||
private dialogs: Dialogs;
|
||||
private workspacesManager: WorkspacesManager;
|
||||
|
||||
private _onWindowReady = new Emitter<CodeWindow>();
|
||||
onWindowReady: CommonEvent<CodeWindow> = this._onWindowReady.event;
|
||||
private _onWindowReady = new Emitter<ICodeWindow>();
|
||||
onWindowReady: CommonEvent<ICodeWindow> = this._onWindowReady.event;
|
||||
|
||||
private _onWindowClose = new Emitter<number>();
|
||||
onWindowClose: CommonEvent<number> = this._onWindowClose.event;
|
||||
@@ -121,8 +122,8 @@ export class WindowsManager implements IWindowsMainService {
|
||||
private _onWindowLoad = new Emitter<number>();
|
||||
onWindowLoad: CommonEvent<number> = this._onWindowLoad.event;
|
||||
|
||||
private _onActiveWindowChanged = new Emitter<CodeWindow>();
|
||||
onActiveWindowChanged: CommonEvent<CodeWindow> = this._onActiveWindowChanged.event;
|
||||
private _onActiveWindowChanged = new Emitter<ICodeWindow>();
|
||||
onActiveWindowChanged: CommonEvent<ICodeWindow> = this._onActiveWindowChanged.event;
|
||||
|
||||
private _onWindowReload = new Emitter<number>();
|
||||
onWindowReload: CommonEvent<number> = this._onWindowReload.event;
|
||||
@@ -168,7 +169,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
});
|
||||
|
||||
// React to workbench loaded events from windows
|
||||
ipc.on('vscode:workbenchLoaded', (_event: any, windowId: number) => {
|
||||
ipc.on('vscode:workbenchLoaded', (event: any, windowId: number) => {
|
||||
this.logService.trace('IPC#vscode-workbenchLoaded');
|
||||
|
||||
const win = this.getWindowById(windowId);
|
||||
@@ -193,8 +194,8 @@ export class WindowsManager implements IWindowsMainService {
|
||||
|
||||
// Handle various lifecycle events around windows
|
||||
this.lifecycleService.onBeforeWindowUnload(e => this.onBeforeWindowUnload(e));
|
||||
this.lifecycleService.onBeforeWindowClose(win => this.onBeforeWindowClose(win as CodeWindow));
|
||||
this.lifecycleService.onBeforeQuit(() => this.onBeforeQuit());
|
||||
this.lifecycleService.onBeforeWindowClose(win => this.onBeforeWindowClose(win as ICodeWindow));
|
||||
this.lifecycleService.onBeforeShutdown(() => this.onBeforeShutdown());
|
||||
this.onWindowsCountChanged(e => {
|
||||
if (e.newCount - e.oldCount > 0) {
|
||||
// clear last closed window state when a new window opens. this helps on macOS where
|
||||
@@ -205,12 +206,12 @@ export class WindowsManager implements IWindowsMainService {
|
||||
});
|
||||
}
|
||||
|
||||
// Note that onBeforeQuit() and onBeforeWindowClose() are fired in different order depending on the OS:
|
||||
// Note that onBeforeShutdown() and onBeforeWindowClose() are fired in different order depending on the OS:
|
||||
// - macOS: since the app will not quit when closing the last window, you will always first get
|
||||
// the onBeforeQuit() event followed by N onbeforeWindowClose() events for each window
|
||||
// the onBeforeShutdown() event followed by N onbeforeWindowClose() events for each window
|
||||
// - other: on other OS, closing the last window will quit the app so the order depends on the
|
||||
// user interaction: closing the last window will first trigger onBeforeWindowClose()
|
||||
// and then onBeforeQuit(). Using the quit action however will first issue onBeforeQuit()
|
||||
// and then onBeforeShutdown(). Using the quit action however will first issue onBeforeShutdown()
|
||||
// and then onBeforeWindowClose().
|
||||
//
|
||||
// Here is the behaviour on different OS dependig on action taken (Electron 1.7.x):
|
||||
@@ -219,30 +220,30 @@ export class WindowsManager implements IWindowsMainService {
|
||||
// - quit(N): quit application with N windows opened
|
||||
// - close(1): close one window via the window close button
|
||||
// - closeAll: close all windows via the taskbar command
|
||||
// - onBeforeQuit(N): number of windows reported in this event handler
|
||||
// - onBeforeShutdown(N): number of windows reported in this event handler
|
||||
// - onBeforeWindowClose(N, M): number of windows reported and quitRequested boolean in this event handler
|
||||
//
|
||||
// macOS
|
||||
// - quit(1): onBeforeQuit(1), onBeforeWindowClose(1, true)
|
||||
// - quit(2): onBeforeQuit(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
|
||||
// - quit(0): onBeforeQuit(0)
|
||||
// - quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
|
||||
// - quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
|
||||
// - quit(0): onBeforeShutdown(0)
|
||||
// - close(1): onBeforeWindowClose(1, false)
|
||||
//
|
||||
// Windows
|
||||
// - quit(1): onBeforeQuit(1), onBeforeWindowClose(1, true)
|
||||
// - quit(2): onBeforeQuit(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
|
||||
// - quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
|
||||
// - quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
|
||||
// - close(1): onBeforeWindowClose(2, false)[not last window]
|
||||
// - close(1): onBeforeWindowClose(1, false), onBeforequit(0)[last window]
|
||||
// - closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeQuit(0)
|
||||
// - close(1): onBeforeWindowClose(1, false), onBeforeShutdown(0)[last window]
|
||||
// - closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0)
|
||||
//
|
||||
// Linux
|
||||
// - quit(1): onBeforeQuit(1), onBeforeWindowClose(1, true)
|
||||
// - quit(2): onBeforeQuit(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
|
||||
// - quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
|
||||
// - quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
|
||||
// - close(1): onBeforeWindowClose(2, false)[not last window]
|
||||
// - close(1): onBeforeWindowClose(1, false), onBeforequit(0)[last window]
|
||||
// - closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeQuit(0)
|
||||
// - close(1): onBeforeWindowClose(1, false), onBeforeShutdown(0)[last window]
|
||||
// - closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0)
|
||||
//
|
||||
private onBeforeQuit(): void {
|
||||
private onBeforeShutdown(): void {
|
||||
const currentWindowsState: IWindowsState = {
|
||||
openedWindows: [],
|
||||
lastPluginDevelopmentHostWindow: this.windowsState.lastPluginDevelopmentHostWindow,
|
||||
@@ -280,9 +281,9 @@ export class WindowsManager implements IWindowsMainService {
|
||||
this.stateService.setItem(WindowsManager.windowsStateStorageKey, currentWindowsState);
|
||||
}
|
||||
|
||||
// See note on #onBeforeQuit() for details how these events are flowing
|
||||
private onBeforeWindowClose(win: CodeWindow): void {
|
||||
if (this.lifecycleService.isQuitRequested()) {
|
||||
// See note on #onBeforeShutdown() for details how these events are flowing
|
||||
private onBeforeWindowClose(win: ICodeWindow): void {
|
||||
if (this.lifecycleService.isQuitRequested) {
|
||||
return; // during quit, many windows close in parallel so let it be handled in the before-quit handler
|
||||
}
|
||||
|
||||
@@ -313,7 +314,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}
|
||||
}
|
||||
|
||||
private toWindowState(win: CodeWindow): IWindowState {
|
||||
private toWindowState(win: ICodeWindow): IWindowState {
|
||||
return {
|
||||
workspace: win.openedWorkspace,
|
||||
folderPath: win.openedFolderPath,
|
||||
@@ -322,7 +323,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
};
|
||||
}
|
||||
|
||||
public open(openConfig: IOpenConfiguration): CodeWindow[] {
|
||||
public open(openConfig: IOpenConfiguration): ICodeWindow[] {
|
||||
openConfig = this.validateOpenConfig(openConfig);
|
||||
|
||||
let pathsToOpen = this.getPathsToOpen(openConfig);
|
||||
@@ -481,7 +482,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
filesToWait: IPathsToWaitFor,
|
||||
foldersToAdd: IPath[]
|
||||
) {
|
||||
const usedWindows: CodeWindow[] = [];
|
||||
const usedWindows: ICodeWindow[] = [];
|
||||
|
||||
// Settings can decide if files/folders open in new window or not
|
||||
let { openFolderInNewWindow, openFilesInNewWindow } = this.shouldOpenNewWindow(openConfig);
|
||||
@@ -536,7 +537,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
else {
|
||||
|
||||
// Do open files
|
||||
usedWindows.push(this.doOpenFilesInExistingWindow(bestWindowOrFolder, filesToOpen, filesToCreate, filesToDiff, filesToWait));
|
||||
usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, bestWindowOrFolder, filesToOpen, filesToCreate, filesToDiff, filesToWait));
|
||||
|
||||
// Reset these because we handled them
|
||||
filesToOpen = [];
|
||||
@@ -582,7 +583,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
const windowOnWorkspace = windowsOnWorkspace[0];
|
||||
|
||||
// Do open files
|
||||
usedWindows.push(this.doOpenFilesInExistingWindow(windowOnWorkspace, filesToOpen, filesToCreate, filesToDiff, filesToWait));
|
||||
usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnWorkspace, filesToOpen, filesToCreate, filesToDiff, filesToWait));
|
||||
|
||||
// Reset these because we handled them
|
||||
filesToOpen = [];
|
||||
@@ -622,7 +623,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
const windowOnFolderPath = windowsOnFolderPath[0];
|
||||
|
||||
// Do open files
|
||||
usedWindows.push(this.doOpenFilesInExistingWindow(windowOnFolderPath, filesToOpen, filesToCreate, filesToDiff, filesToWait));
|
||||
usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnFolderPath, filesToOpen, filesToCreate, filesToDiff, filesToWait));
|
||||
|
||||
// Reset these because we handled them
|
||||
filesToOpen = [];
|
||||
@@ -694,17 +695,18 @@ export class WindowsManager implements IWindowsMainService {
|
||||
return arrays.distinct(usedWindows);
|
||||
}
|
||||
|
||||
private doOpenFilesInExistingWindow(window: CodeWindow, filesToOpen: IPath[], filesToCreate: IPath[], filesToDiff: IPath[], filesToWait: IPathsToWaitFor): CodeWindow {
|
||||
private doOpenFilesInExistingWindow(configuration: IOpenConfiguration, window: ICodeWindow, filesToOpen: IPath[], filesToCreate: IPath[], filesToDiff: IPath[], filesToWait: IPathsToWaitFor): ICodeWindow {
|
||||
window.focus(); // make sure window has focus
|
||||
|
||||
window.ready().then(readyWindow => {
|
||||
readyWindow.send('vscode:openFiles', { filesToOpen, filesToCreate, filesToDiff, filesToWait });
|
||||
const termProgram = configuration.userEnv ? configuration.userEnv['TERM_PROGRAM'] : void 0;
|
||||
readyWindow.send('vscode:openFiles', { filesToOpen, filesToCreate, filesToDiff, filesToWait, termProgram });
|
||||
});
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
private doAddFoldersToExistingWidow(window: CodeWindow, foldersToAdd: IPath[]): CodeWindow {
|
||||
private doAddFoldersToExistingWidow(window: ICodeWindow, foldersToAdd: IPath[]): ICodeWindow {
|
||||
window.focus(); // make sure window has focus
|
||||
|
||||
window.ready().then(readyWindow => {
|
||||
@@ -714,7 +716,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
return window;
|
||||
}
|
||||
|
||||
private doOpenFolderOrWorkspace(openConfig: IOpenConfiguration, folderOrWorkspace: IPathToOpen, openInNewWindow: boolean, filesToOpen: IPath[], filesToCreate: IPath[], filesToDiff: IPath[], filesToWait: IPathsToWaitFor, windowToUse?: CodeWindow): CodeWindow {
|
||||
private doOpenFolderOrWorkspace(openConfig: IOpenConfiguration, folderOrWorkspace: IPathToOpen, openInNewWindow: boolean, filesToOpen: IPath[], filesToCreate: IPath[], filesToDiff: IPath[], filesToWait: IPathsToWaitFor, windowToUse?: ICodeWindow): ICodeWindow {
|
||||
const browserWindow = this.openInBrowserWindow({
|
||||
userEnv: openConfig.userEnv,
|
||||
cli: openConfig.cli,
|
||||
@@ -981,10 +983,22 @@ export class WindowsManager implements IWindowsMainService {
|
||||
if (openConfig.forceNewWindow || openConfig.forceReuseWindow) {
|
||||
openFilesInNewWindow = openConfig.forceNewWindow && !openConfig.forceReuseWindow;
|
||||
} else {
|
||||
if (openConfig.context === OpenContext.DOCK) {
|
||||
openFilesInNewWindow = true; // only on macOS do we allow to open files in a new window if this is triggered via DOCK context
|
||||
|
||||
// macOS: by default we open files in a new window if this is triggered via DOCK context
|
||||
if (isMacintosh) {
|
||||
if (openConfig.context === OpenContext.DOCK) {
|
||||
openFilesInNewWindow = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Linux/Windows: by default we open files in the new window unless triggered via DIALOG or MENU context
|
||||
else {
|
||||
if (openConfig.context !== OpenContext.DIALOG && openConfig.context !== OpenContext.MENU) {
|
||||
openFilesInNewWindow = true;
|
||||
}
|
||||
}
|
||||
|
||||
// finally check for overrides of default
|
||||
if (!openConfig.cli.extensionDevelopmentPath && (openFilesInNewWindowConfig === 'on' || openFilesInNewWindowConfig === 'off')) {
|
||||
openFilesInNewWindow = (openFilesInNewWindowConfig === 'on');
|
||||
}
|
||||
@@ -1024,7 +1038,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
this.open({ context: openConfig.context, cli: openConfig.cli, forceNewWindow: true, forceEmpty: openConfig.cli._.length === 0, userEnv: openConfig.userEnv });
|
||||
}
|
||||
|
||||
private openInBrowserWindow(options: IOpenBrowserWindowOptions): CodeWindow {
|
||||
private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow {
|
||||
|
||||
// Build IWindowConfiguration from config and options
|
||||
const configuration: IWindowConfiguration = mixin({}, options.cli); // inherit all properties from CLI
|
||||
@@ -1049,7 +1063,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
configuration.backupPath = join(this.environmentService.backupHome, options.emptyWindowBackupFolder);
|
||||
}
|
||||
|
||||
let window: CodeWindow;
|
||||
let window: ICodeWindow;
|
||||
if (!options.forceNewWindow) {
|
||||
window = options.windowToUse || this.getLastActiveWindow();
|
||||
if (window) {
|
||||
@@ -1217,9 +1231,12 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}
|
||||
}
|
||||
|
||||
// Compute x/y based on display bounds
|
||||
// Note: important to use Math.round() because Electron does not seem to be too happy about
|
||||
// display coordinates that are not absolute numbers.
|
||||
let state = defaultWindowState() as INewWindowState;
|
||||
state.x = displayToUse.bounds.x + (displayToUse.bounds.width / 2) - (state.width / 2);
|
||||
state.y = displayToUse.bounds.y + (displayToUse.bounds.height / 2) - (state.height / 2);
|
||||
state.x = Math.round(displayToUse.bounds.x + (displayToUse.bounds.width / 2) - (state.width / 2));
|
||||
state.y = Math.round(displayToUse.bounds.y + (displayToUse.bounds.height / 2) - (state.height / 2));
|
||||
|
||||
// Check for newWindowDimensions setting and adjust accordingly
|
||||
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
|
||||
@@ -1266,7 +1283,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
return state;
|
||||
}
|
||||
|
||||
public reload(win: CodeWindow, cli?: ParsedArgs): void {
|
||||
public reload(win: ICodeWindow, cli?: ParsedArgs): void {
|
||||
|
||||
// Only reload when the window has not vetoed this
|
||||
this.lifecycleService.unload(win, UnloadReason.RELOAD).done(veto => {
|
||||
@@ -1279,22 +1296,22 @@ export class WindowsManager implements IWindowsMainService {
|
||||
});
|
||||
}
|
||||
|
||||
public closeWorkspace(win: CodeWindow): void {
|
||||
public closeWorkspace(win: ICodeWindow): void {
|
||||
this.openInBrowserWindow({
|
||||
cli: this.environmentService.args,
|
||||
windowToUse: win
|
||||
});
|
||||
}
|
||||
|
||||
public saveAndEnterWorkspace(win: CodeWindow, path: string): TPromise<IEnterWorkspaceResult> {
|
||||
public saveAndEnterWorkspace(win: ICodeWindow, path: string): TPromise<IEnterWorkspaceResult> {
|
||||
return this.workspacesManager.saveAndEnterWorkspace(win, path).then(result => this.doEnterWorkspace(win, result));
|
||||
}
|
||||
|
||||
public createAndEnterWorkspace(win: CodeWindow, folders?: IWorkspaceFolderCreationData[], path?: string): TPromise<IEnterWorkspaceResult> {
|
||||
public createAndEnterWorkspace(win: ICodeWindow, folders?: IWorkspaceFolderCreationData[], path?: string): TPromise<IEnterWorkspaceResult> {
|
||||
return this.workspacesManager.createAndEnterWorkspace(win, folders, path).then(result => this.doEnterWorkspace(win, result));
|
||||
}
|
||||
|
||||
private doEnterWorkspace(win: CodeWindow, result: IEnterWorkspaceResult): IEnterWorkspaceResult {
|
||||
private doEnterWorkspace(win: ICodeWindow, result: IEnterWorkspaceResult): IEnterWorkspaceResult {
|
||||
|
||||
// Mark as recently opened
|
||||
this.historyMainService.addRecentlyOpened([result.workspace], []);
|
||||
@@ -1346,7 +1363,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}));
|
||||
}
|
||||
|
||||
public focusLastActive(cli: ParsedArgs, context: OpenContext): CodeWindow {
|
||||
public focusLastActive(cli: ParsedArgs, context: OpenContext): ICodeWindow {
|
||||
const lastActive = this.getLastActiveWindow();
|
||||
if (lastActive) {
|
||||
lastActive.focus();
|
||||
@@ -1358,12 +1375,12 @@ export class WindowsManager implements IWindowsMainService {
|
||||
return this.open({ context, cli, forceEmpty: true })[0];
|
||||
}
|
||||
|
||||
public getLastActiveWindow(): CodeWindow {
|
||||
public getLastActiveWindow(): ICodeWindow {
|
||||
return getLastActiveWindow(WindowsManager.WINDOWS);
|
||||
}
|
||||
|
||||
public openNewWindow(context: OpenContext): void {
|
||||
this.open({ context, cli: this.environmentService.args, forceNewWindow: true, forceEmpty: true });
|
||||
public openNewWindow(context: OpenContext): ICodeWindow[] {
|
||||
return this.open({ context, cli: this.environmentService.args, forceNewWindow: true, forceEmpty: true });
|
||||
}
|
||||
|
||||
public waitForWindowCloseOrLoad(windowId: number): TPromise<void> {
|
||||
@@ -1400,7 +1417,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
});
|
||||
}
|
||||
|
||||
public getFocusedWindow(): CodeWindow {
|
||||
public getFocusedWindow(): ICodeWindow {
|
||||
const win = BrowserWindow.getFocusedWindow();
|
||||
if (win) {
|
||||
return this.getWindowById(win.id);
|
||||
@@ -1409,7 +1426,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
return null;
|
||||
}
|
||||
|
||||
public getWindowById(windowId: number): CodeWindow {
|
||||
public getWindowById(windowId: number): ICodeWindow {
|
||||
const res = WindowsManager.WINDOWS.filter(w => w.id === windowId);
|
||||
if (res && res.length === 1) {
|
||||
return res[0];
|
||||
@@ -1418,7 +1435,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
return null;
|
||||
}
|
||||
|
||||
public getWindows(): CodeWindow[] {
|
||||
public getWindows(): ICodeWindow[] {
|
||||
return WindowsManager.WINDOWS;
|
||||
}
|
||||
|
||||
@@ -1426,7 +1443,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
return WindowsManager.WINDOWS.length;
|
||||
}
|
||||
|
||||
private onWindowError(window: CodeWindow, error: WindowError): void {
|
||||
private onWindowError(window: ICodeWindow, error: WindowError): void {
|
||||
this.logService.error(error === WindowError.CRASHED ? '[VS Code]: render process crashed!' : '[VS Code]: detected unresponsive');
|
||||
|
||||
// Unresponsive
|
||||
@@ -1476,7 +1493,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}
|
||||
}
|
||||
|
||||
private onWindowClosed(win: CodeWindow): void {
|
||||
private onWindowClosed(win: ICodeWindow): void {
|
||||
|
||||
// Tell window
|
||||
win.dispose();
|
||||
@@ -1524,6 +1541,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
|
||||
if (!internalOptions.telemetryEventName) {
|
||||
if (pickFolders && pickFiles) {
|
||||
// __GDPR__TODO__ classify event
|
||||
internalOptions.telemetryEventName = 'openFileFolder';
|
||||
} else if (pickFolders) {
|
||||
internalOptions.telemetryEventName = 'openFolder';
|
||||
@@ -1535,15 +1553,15 @@ export class WindowsManager implements IWindowsMainService {
|
||||
this.dialogs.pickAndOpen(internalOptions);
|
||||
}
|
||||
|
||||
public showMessageBox(options: Electron.MessageBoxOptions, win?: CodeWindow): TPromise<IMessageBoxResult> {
|
||||
public showMessageBox(options: Electron.MessageBoxOptions, win?: ICodeWindow): TPromise<IMessageBoxResult> {
|
||||
return this.dialogs.showMessageBox(options, win);
|
||||
}
|
||||
|
||||
public showSaveDialog(options: Electron.SaveDialogOptions, win?: CodeWindow): TPromise<string> {
|
||||
public showSaveDialog(options: Electron.SaveDialogOptions, win?: ICodeWindow): TPromise<string> {
|
||||
return this.dialogs.showSaveDialog(options, win);
|
||||
}
|
||||
|
||||
public showOpenDialog(options: Electron.OpenDialogOptions, win?: CodeWindow): TPromise<string[]> {
|
||||
public showOpenDialog(options: Electron.OpenDialogOptions, win?: ICodeWindow): TPromise<string[]> {
|
||||
return this.dialogs.showOpenDialog(options, win);
|
||||
}
|
||||
|
||||
@@ -1684,6 +1702,7 @@ class Dialogs {
|
||||
}
|
||||
|
||||
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
|
||||
@@ -1702,6 +1721,7 @@ class Dialogs {
|
||||
}
|
||||
|
||||
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
|
||||
@@ -1712,8 +1732,22 @@ class Dialogs {
|
||||
|
||||
return this.getDialogQueue(window).queue(() => {
|
||||
return new TPromise((c, e) => {
|
||||
dialog.showOpenDialog(window ? window.win : void 0, options, paths => {
|
||||
c(normalizePaths(paths));
|
||||
|
||||
// Ensure the path exists (if provided)
|
||||
let validatePathPromise: TPromise<void> = TPromise.as(void 0);
|
||||
if (options.defaultPath) {
|
||||
validatePathPromise = exists(options.defaultPath).then(exists => {
|
||||
if (!exists) {
|
||||
options.defaultPath = void 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Show dialog and wrap as promise
|
||||
validatePathPromise.then(() => {
|
||||
dialog.showOpenDialog(window ? window.win : void 0, options, paths => {
|
||||
c(normalizePaths(paths));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1730,7 +1764,7 @@ class WorkspacesManager {
|
||||
) {
|
||||
}
|
||||
|
||||
public saveAndEnterWorkspace(window: CodeWindow, path: string): TPromise<IEnterWorkspaceResult> {
|
||||
public saveAndEnterWorkspace(window: ICodeWindow, path: string): TPromise<IEnterWorkspaceResult> {
|
||||
if (!window || !window.win || window.readyState !== ReadyState.READY || !window.openedWorkspace || !path || !this.isValidTargetWorkspacePath(window, path)) {
|
||||
return TPromise.as(null); // return early if the window is not ready or disposed or does not have a workspace
|
||||
}
|
||||
@@ -1738,7 +1772,7 @@ class WorkspacesManager {
|
||||
return this.doSaveAndOpenWorkspace(window, window.openedWorkspace, path);
|
||||
}
|
||||
|
||||
public createAndEnterWorkspace(window: CodeWindow, folders?: IWorkspaceFolderCreationData[], path?: string): TPromise<IEnterWorkspaceResult> {
|
||||
public createAndEnterWorkspace(window: ICodeWindow, folders?: IWorkspaceFolderCreationData[], path?: string): TPromise<IEnterWorkspaceResult> {
|
||||
if (!window || !window.win || window.readyState !== ReadyState.READY) {
|
||||
return TPromise.as(null); // return early if the window is not ready or disposed
|
||||
}
|
||||
@@ -1755,7 +1789,7 @@ class WorkspacesManager {
|
||||
|
||||
}
|
||||
|
||||
private isValidTargetWorkspacePath(window: CodeWindow, path?: string): TPromise<boolean> {
|
||||
private isValidTargetWorkspacePath(window: ICodeWindow, path?: string): TPromise<boolean> {
|
||||
if (!path) {
|
||||
return TPromise.wrap(true);
|
||||
}
|
||||
@@ -1781,7 +1815,7 @@ class WorkspacesManager {
|
||||
return TPromise.wrap(true); // OK
|
||||
}
|
||||
|
||||
private doSaveAndOpenWorkspace(window: CodeWindow, workspace: IWorkspaceIdentifier, path?: string): TPromise<IEnterWorkspaceResult> {
|
||||
private doSaveAndOpenWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier, path?: string): TPromise<IEnterWorkspaceResult> {
|
||||
let savePromise: TPromise<IWorkspaceIdentifier>;
|
||||
if (path) {
|
||||
savePromise = this.workspacesMainService.saveWorkspace(workspace, path);
|
||||
|
||||
@@ -36,8 +36,8 @@ import { StateService } from 'vs/platform/state/node/stateService';
|
||||
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';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { CommandLineDialogService } from 'vs/platform/dialogs/node/dialogService';
|
||||
|
||||
const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id);
|
||||
const notInstalled = (id: string) => localize('notInstalled', "Extension '{0}' is not installed.", id);
|
||||
@@ -144,15 +144,15 @@ class Main {
|
||||
|
||||
return this.extensionManagementService.installFromGallery(extension)
|
||||
.then(
|
||||
() => console.log(localize('successInstall', "Extension '{0}' v{1} was successfully installed!", id, extension.version)),
|
||||
error => {
|
||||
if (isPromiseCanceledError(error)) {
|
||||
console.log(localize('cancelVsixInstall', "Cancelled installing Extension '{0}'.", id));
|
||||
return null;
|
||||
} else {
|
||||
return TPromise.wrapError(error);
|
||||
}
|
||||
});
|
||||
() => console.log(localize('successInstall', "Extension '{0}' v{1} was successfully installed!", id, extension.version)),
|
||||
error => {
|
||||
if (isPromiseCanceledError(error)) {
|
||||
console.log(localize('cancelVsixInstall', "Cancelled installing Extension '{0}'.", id));
|
||||
return null;
|
||||
} else {
|
||||
return TPromise.wrapError(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -219,7 +219,7 @@ export function main(argv: ParsedArgs): TPromise<void> {
|
||||
services.set(IRequestService, new SyncDescriptor(RequestService));
|
||||
services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
|
||||
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
|
||||
services.set(IChoiceService, new SyncDescriptor(ChoiceCliService));
|
||||
services.set(IDialogService, new SyncDescriptor(CommandLineDialogService));
|
||||
|
||||
if (isBuilt && !extensionDevelopmentPath && !envService.args['disable-telemetry'] && product.enableTelemetry) {
|
||||
const appenders: AppInsightsAppender[] = [];
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as paths from 'vs/base/common/paths';
|
||||
import { OpenContext } from 'vs/platform/windows/common/windows';
|
||||
@@ -35,22 +33,9 @@ export interface IBestWindowOrFolderOptions<W extends ISimpleWindow> {
|
||||
export function findBestWindowOrFolderForFile<W extends ISimpleWindow>({ windows, newWindow, reuseWindow, context, filePath, userHome, codeSettingsFolder, workspaceResolver }: IBestWindowOrFolderOptions<W>): W | string {
|
||||
if (!newWindow && filePath && (context === OpenContext.DESKTOP || context === OpenContext.CLI || context === OpenContext.DOCK)) {
|
||||
const windowOnFilePath = findWindowOnFilePath(windows, filePath, workspaceResolver);
|
||||
|
||||
// 1) window wins if it has a workspace opened
|
||||
if (windowOnFilePath && !!windowOnFilePath.openedWorkspace) {
|
||||
if (windowOnFilePath) {
|
||||
return windowOnFilePath;
|
||||
}
|
||||
|
||||
// 2) window wins if it has a folder opened that is more specific than settings folder
|
||||
const folderWithCodeSettings = !reuseWindow && findFolderWithCodeSettings(filePath, userHome, codeSettingsFolder);
|
||||
if (windowOnFilePath && !(folderWithCodeSettings && folderWithCodeSettings.length > windowOnFilePath.openedFolderPath.length)) {
|
||||
return windowOnFilePath;
|
||||
}
|
||||
|
||||
// 3) finally return path to folder with settings
|
||||
if (folderWithCodeSettings) {
|
||||
return folderWithCodeSettings;
|
||||
}
|
||||
}
|
||||
|
||||
return !newWindow ? getLastActiveWindow(windows) : null;
|
||||
@@ -77,41 +62,6 @@ function findWindowOnFilePath<W extends ISimpleWindow>(windows: W[], filePath: s
|
||||
return null;
|
||||
}
|
||||
|
||||
function findFolderWithCodeSettings(filePath: string, userHome?: string, codeSettingsFolder?: string): string {
|
||||
let folder = path.dirname(paths.normalize(filePath, true));
|
||||
let homeFolder = userHome && paths.normalize(userHome, true);
|
||||
if (!platform.isLinux) {
|
||||
homeFolder = homeFolder && homeFolder.toLowerCase();
|
||||
}
|
||||
|
||||
let previous = null;
|
||||
while (folder !== previous) {
|
||||
if (hasCodeSettings(folder, homeFolder, codeSettingsFolder)) {
|
||||
return folder;
|
||||
}
|
||||
|
||||
previous = folder;
|
||||
folder = path.dirname(folder);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
function hasCodeSettings(folder: string, normalizedUserHome?: string, codeSettingsFolder = '.sqlops') {
|
||||
try {
|
||||
if ((platform.isLinux ? folder : folder.toLowerCase()) === normalizedUserHome) {
|
||||
return fs.statSync(path.join(folder, codeSettingsFolder, 'settings.json')).isFile(); // ~/.vscode/extensions is used for extensions
|
||||
}
|
||||
|
||||
return fs.statSync(path.join(folder, codeSettingsFolder)).isDirectory();
|
||||
} catch (err) {
|
||||
// assume impossible to access
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getLastActiveWindow<W extends ISimpleWindow>(windows: W[]): W {
|
||||
const lastFocusedDate = Math.max.apply(Math, windows.map(window => window.lastFocusTime));
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import assert = require('assert');
|
||||
import * as assert from 'assert';
|
||||
import { formatOptions } from 'vs/platform/environment/node/argv';
|
||||
|
||||
suite('formatOptions', () => {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import assert = require('assert');
|
||||
import path = require('path');
|
||||
import * as assert from 'assert';
|
||||
import * as path from 'path';
|
||||
import { findBestWindowOrFolderForFile, ISimpleWindow, IBestWindowOrFolderOptions } from 'vs/code/node/windowsFinder';
|
||||
import { OpenContext } from 'vs/platform/windows/common/windows';
|
||||
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
@@ -48,25 +48,22 @@ suite('WindowsFinder', () => {
|
||||
})), null);
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt'),
|
||||
newWindow: true // We assume this implies 'editor' work mode, might need separate CLI option later.
|
||||
newWindow: true
|
||||
})), null);
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt'),
|
||||
reuseWindow: true // We assume this implies 'editor' work mode, might need separate CLI option later.
|
||||
reuseWindow: true
|
||||
})), null);
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt'),
|
||||
context: OpenContext.API
|
||||
})), null);
|
||||
});
|
||||
|
||||
test('New window with folder when no windows exist', () => {
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt')
|
||||
})), path.join(fixturesFolder, 'vscode_folder'));
|
||||
})), null);
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'new_folder', 'new_file.txt')
|
||||
})), path.join(fixturesFolder, 'vscode_folder'));
|
||||
})), null);
|
||||
});
|
||||
|
||||
test('New window without folder when windows exist', () => {
|
||||
@@ -106,19 +103,11 @@ suite('WindowsFinder', () => {
|
||||
windows,
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt')
|
||||
})), vscodeFolderWindow);
|
||||
});
|
||||
|
||||
test('Existing window wins over vscode folder if more specific', () => {
|
||||
const window = { lastFocusTime: 1, openedFolderPath: path.join(fixturesFolder, 'vscode_folder', 'nested_folder') };
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
windows: [window],
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_folder', 'subfolder', 'file.txt')
|
||||
})), window);
|
||||
// check
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
windows: [window],
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_folder2', 'subfolder', 'file.txt')
|
||||
})), path.join(fixturesFolder, 'vscode_folder'));
|
||||
});
|
||||
|
||||
test('More specific existing window wins', () => {
|
||||
@@ -130,37 +119,6 @@ suite('WindowsFinder', () => {
|
||||
})), nestedFolderWindow);
|
||||
});
|
||||
|
||||
test('VSCode folder wins over existing window if more specific', () => {
|
||||
const window = { lastFocusTime: 1, openedFolderPath: path.join(fixturesFolder, 'vscode_folder') };
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
windows: [window],
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_vscode_folder', 'subfolder', 'file.txt')
|
||||
})), path.join(fixturesFolder, 'vscode_folder', 'nested_vscode_folder'));
|
||||
// check
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
windows: [window],
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_folder', 'subfolder', 'file.txt')
|
||||
})), window);
|
||||
});
|
||||
|
||||
test('More specific VSCode folder wins', () => {
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_vscode_folder', 'subfolder', 'file.txt')
|
||||
})), path.join(fixturesFolder, 'vscode_folder', 'nested_vscode_folder'));
|
||||
});
|
||||
|
||||
test('VSCode folder in home folder needs settings.json', () => {
|
||||
// Because ~/.vscode/extensions is used for extensions, ~/.vscode is not enough as a hint.
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt'),
|
||||
userHome: path.join(fixturesFolder, 'vscode_folder')
|
||||
})), null);
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
filePath: path.join(fixturesFolder, 'vscode_home_folder', 'file.txt'),
|
||||
userHome: path.join(fixturesFolder, 'vscode_home_folder')
|
||||
})), path.join(fixturesFolder, 'vscode_home_folder'));
|
||||
});
|
||||
|
||||
test('Workspace folder wins', () => {
|
||||
const window = { lastFocusTime: 1, openedWorkspace: testWorkspace };
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
|
||||
Reference in New Issue
Block a user