mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-18 09:35:39 -05:00
Merge VS Code 1.26.1 (#2394)
* Squash merge commits for 1.26 (#1) (#2323) * Polish tag search as per feedback (#55269) * Polish tag search as per feedback * Updated regex * Allow users to opt-out of features that send online requests in the background (#55097) * settings sweep #54690 * Minor css tweaks to enable eoverflow elipsis in more places (#55277) * fix an issue with titlebarheight when not scaling with zoom * Settings descriptions update #54690 * fixes #55209 * Settings editor - many padding fixes * More space above level 2 label * Fixing Cannot debug npm script using Yarn #55103 * Settings editor - show ellipsis when description overflows * Settings editor - ... fix measuring around links, relayout * Setting descriptions * Settings editor - fix ... for some short lines, fix select container width * Settings editor - overlay trees so scrollable shadow is full width * Fix #54133 - missing extension settings after reload * Settings color token description tweak * Settings editor - disable overflow indicator temporarily, needs to be faster * Added command to Run the selected npm script * fixes #54452 * fixes #54929 * fixes #55248 * prefix command with extension name * Contribute run selected to the context menu * node-debug@1.26.6 * Allow terminal rendererType to be swapped out at runtime Part of #53274 Fixes #55344 * Settings editor - fix not focusing search when restoring editor setInput must be actually async. Will be fixed naturally when we aren't using winJS promises... * Settings editor - TOC should only expand the section with a selected item * Bump node-debug2 * Settings editor - Tree focus outlines * Settings editor - don't blink the scrollbar when toc selection changes And hide TOC correctly when the editor is narrow * Settings editor - header rows should not be selectable * fixes #54877 * change debug assignee to isi * Settings sweep (#54690) * workaround for #55051 * Settings sweep (#54690) * settings sweep #54690 * Don't try closing tags when you type > after another > * Describe what implementation code lens does Fixes #55370 * fix javadoc formatter setting description * fixes #55325 * update to officical TS version * Settings editor - Even more padding, use semibold instead of bold * Fix #55357 - fix TOC twistie * fixes #55288 * explorer: refresh on di change file system provider registration fixes #53256 * Disable push to Linux repo to test standalone publisher * New env var to notify log level to extensions #54001 * Disable snippets in extension search (when not in suggest dropdown) (#55281) * Disable snippits in extension search (when not in suggest dropdown) * Add monaco input contributions * Fix bug preventing snippetSuggestions from taking effect in sub-editors * Latest emmet helper to fix #52366 * Fix comment updates for threads within same file * Allow extensions to log telemetry to log files #54001 * Pull latest css grammar * files.exclude control - use same style for "add" vs "edit" * files.exclude control - focus/keyboard behavior * don't show menubar too early * files.exclude - better styling * Place cursor at end of extensions search box on autofill (#55254) * Place cursor at end of extensions search box on autofill * Use position instead of selection * fix linux build issue (empty if block) * Settings editor - fix extension category prefixes * Settings editor - add simple ellipsis for first line that overflows, doesn't cover case when first line does not overflow but there is more text, TODO * File/Text search provider docs * Fixes #52655 * Include epoch (#55008) * Fixes #53385 * Fixes #49480 * VS Code Insiders (Users) not opening Fixes #55353 * Better handling of the case when the extension host fails to start * Fixes #53966 * Remove confusing Start from wordPartLeft commands ID * vscode-xterm@3.6.0-beta12 Fixes #55488 * Initial size is set to infinity!! Fixes #55461 * Polish embeddedEditorBackground * configuration service misses event * Fix #55224 - fix duplicate results in multiroot workspace from splitting the diskseach query * Select all not working in issue reporter on mac, fixes #55424 * Disable fuzzy matching for extensions autosuggest (#55498) * Fix clipping of extensions search border in some third party themes (#55504) * fixes #55538 * Fix bug causing an aria alert to not be shown the third time (and odd numbers thereafter) * Settings editor - work around rendering glitch with webkit-line-clamp * Settings editor - revert earlier '...' changes * Settings editor - move enumDescription to its own div, because it disturbs -webkit-line-clamp for some reason * Settings editor - better overflow indicator * Don't show existing filters in autocomplete (#55495) * Dont show existing filters in autocomplete * Simplify * Settings Editor: Add aria labels for input elements Fixes: #54836 (#55543) * fixes #55223 * Update vscode-css-languageservice to 3.0.10-next.1 * Fix #55509 - settings navigation * Fix #55519 * Fix #55520 * FIx #55524 * Fix #55556 - include wordSeparators in all search queries, so findTextInFiles can respect isWordMatch correctly * oss updates for endgame * Fix unit tests * fixes #55522 * Avoid missing manifest error from bubbling up #54757 * Settings format crawl * Search provider - Fix FileSearchProvider to return array, not progress * Fix #55598 * Settings editor - fix NPE rendering settings with no description * dont render inden guides in search box (#55600) * fixes #55454 * More settings crawl * Another change for #55598 - maxResults applies to FileSearch and TextSearch but not FileIndex * Fix FileSearchProvider unit tests for progress change * fixes #55561 * Settings description update for #54690 * Update setting descriptions for online services * Minor edits * fixes #55513 * fixes #55451 * Fix #55612 - fix findTextInFiles cancellation * fixes #55539 * More setting description tweaks * Setting to disable online experiments #54354 * fixes #55507 * fixes #55515 * Show online services action only in Insiders for now * Settings editor - change toc behavior default to 'filter' * Settings editor - nicer filter count style during search * Fix #55617 - search viewlet icons * Settings editor - better styling for element count indicator * SearchProvider - fix NPE when searching extraFileResources * Allow extends to work without json suffix Fixes #16905 * Remove accessability options logic entirely Follow up on #55451 * use latest version of DAP * fixes #55490 * fixes #55122 * fixes #52332 * Avoid assumptions about git: URIs (fixes #36236) * relative path for descriptions * resourece: get rid of isFile context key fixes #48275 * Register previous ids for compatibility (#53497) * more tuning for #48275 * no need to always re-read "files explorer" fixes #52003 * read out active composites properly fixes #51967 * Update link colors for hc theme to meet color contrast ratio, fixes #55651 Also updated link color for `textLinkActiveForeground` to be the same as `textLinkForeground` as it wasn't properly updated * detect 'winpty-agent.exe'; fixes #55672 * node-debug@1.26.7 * reset counter on new label * Settings editor - fix multiple setting links in one description * Settings editor - color code blocks in setting descriptions, fix #55532 * Settings editor - hover color in TOC * Settings editor - fix navigation NPE * Settings editor - fix text control width * Settings editor - maybe fix #55684 * Fix bug causing cursor to not move on paste * fixes #53582 * Use ctrlCmd instead of ctrl for go down from search box * fixes #55264 * fixes #55456 * filter for spcaes before triggering search (#55611) * Fix #55698 - don't lose filtered TOC counts when refreshing TOC * fixes #55421 * fixes #28979 * fixes #55576 * only add check for updates to windows/linux help * readonly files: append decoration to label fixes #53022 * debug: do not show toolbar while initialising fixes #55026 * Opening launch.json should not activate debug extensions fixes #55029 * fixes #55435 * fixes #55434 * fixes #55439 * trigger menu only on altkey up * Fix #50555 - fix settings editor memory leak * Fix #55712 - no need to focus 'a' anymore when restoring control focus after tree render * fixes #55335 * proper fix for readonly model fixes #53022 * improve FoldingRangeKind spec (for #55686) * Use class with static fields (fixes #55494) * Fixes #53671 * fixes #54630 * [html] should disable ionic suggestions by default. Currently forces deprecated Ionic v1 suggestions in .html files while typing. Fixes #53324 * cleanup deps * debug issues back to andre * update electron for smoketest * Fix #55757 - prevent settings tabs from overflowing * Fix #53897 - revert setting menu defaults to old editor * Add enum descriptions to `typescript.preferences.importModuleSpecifier` * Fix #55767 - leaking style elements from settings editor * Fix #55521 - prevent flashing when clicking in exclude control * Update Git modified color for contrast ratio, fixes #53140 * Revert "Merge branch 'master' of github.com:Microsoft/vscode" This reverts commit bf46b6bfbae0cab99c2863e1244a916181fa9fbc, reversing changes made to e275a424483dfb4ed33b428c97d5e2c441d6b917. * Revert "Revert "Merge branch 'master' of github.com:Microsoft/vscode"" This reverts commit 53949d963f39e40757557c6526332354a31d9154. * don't ask to install an incomplete menu * Fix NPE in terminal AccessibilityManager Fixes #55744 * don't display fallback menu unless we've closed the last window * fixes #55547 * Fix smoke tests for extension search box * Update OSSREADME.json for Electron 2.0.5 * Update distro Includes Chromium license changes * fix #55455 * fix #55865 * fixes #55893 * Fix bug causing workspace recommendations to go away upon ignoring a recommendation (#55805) * Fix bug causing workspace recommendations to go away upon ignoring a recommendation * ONly show on @recommended or @recommended:workspace * Make more consistant * Fix #55911 * Understand json activity (#55926) * Understand json file activity * Refactoring * adding composer.json * Distro update for experiments * use terminal.processId for auto-attach; fixes #55918 * Reject invalid URI with vscode.openFolder (for #55891) * improve win32 setup system vs user detection fixes #55840 fixes #55840 delay winreg import related to #55840 show notification earlier related to #55840 fix #55840 update inno setup message related to #55840 * Fix #55593 - this code only operates on local paths, so use fsPath and Uri.file instead * Bring back the old menu due to electron 2.0 issues (#55913) * add the old menu back for native menus * make menu labels match * `vscode.openFolder`: treat missing URI schema gracefully (for #55891) * delay EH reattach; fixes #55955 * Mark all json files under appSettingsHome as settings * Use localized strings for telemetry opt-out * Exception when saving file editor opened from remote file provider (fixes #55051) * Remove terminal menu from stable Fixes 56003 * VSCode Insiders crashes on open with TypeError: Cannot read property 'lastIndexOf' of undefined. Fixes #54933 * improve fix for #55891 * fix #55916 * Improve #55891 * increase EH debugging restart delay; fixes #55955 * Revert "Don't include non-resource entries in history quick pick" This reverts commit 37209a838e9f7e9abe6dc53ed73cdf1e03b72060. * Diff editor: horizontal scrollbar height is smaller (fixes #56062) * improve openFolder uri fix (correctly treat backslashes) * fixes #56116 repair ipc for native menubar keybindings * Fix #56240 - Open the JSON settings editor instead of the UI editor * Fix #55536 * uriDisplay: if no formatter is registered fall back to getPathlabel fixes #56104 * VSCode hangs when opening python file. Fixes #56377 * VS Code Hangs When Opening Specific PowerShell File. Fixes #56430 * Fix #56433 - search extraFileResources even when no folders open * Workaround #55649 * Fix in master #56371 * Fix tests #56371 * Fix in master #56317 * increase version to 1.26.1 * Fixes #56387: Handle SIGPIPE in extension host * fixes #56185 * Fix merge issues (part 1) * Fix build breaks (part 1) * Build breaks (part 2) * Build breaks (part 3) * More build breaks (part 4) * Fix build breaks (part 5) * WIP * Fix menus * Render query result and message panels (#2363) * Put back query editor hot exit changes * Fix grid changes that broke profiler (#2365) * Update APIs for saving query editor state * Fix restore view state for profiler and edit data * Updating custom default themes to support 4.5:1 contrast ratio * Test updates * Fix Extension Manager and Windows Setup * Update license headers * Add appveyor and travis files back * Fix hidden modal dropdown issue
This commit is contained in:
@@ -45,6 +45,8 @@ function readFile(file) {
|
||||
});
|
||||
}
|
||||
|
||||
const writeFile = (file, content) => new Promise((c, e) => fs.writeFile(file, content, 'utf8', err => err ? e(err) : c()));
|
||||
|
||||
function main() {
|
||||
const args = parseURLQueryArgs();
|
||||
const configuration = JSON.parse(args['config'] || '{}') || {};
|
||||
@@ -127,8 +129,15 @@ function main() {
|
||||
let json = JSON.parse(content);
|
||||
bundles[bundle] = json;
|
||||
cb(undefined, json);
|
||||
})
|
||||
.catch(cb);
|
||||
}).catch((error) => {
|
||||
try {
|
||||
if (nlsConfig._corruptedFile) {
|
||||
writeFile(nlsConfig._corruptedFile, 'corrupted').catch(function (error) { console.error(error); });
|
||||
}
|
||||
} finally {
|
||||
cb(error, undefined);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import { getDelayedChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IWindowConfiguration, IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
|
||||
import { ITelemetryAppenderChannel, TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc';
|
||||
@@ -40,7 +40,8 @@ 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';
|
||||
import { normalizeGitHubUrl } from 'vs/code/electron-browser/issue/issueReporterUtil';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
|
||||
const MAX_URL_LENGTH = platform.isWindows ? 2081 : 5400;
|
||||
|
||||
@@ -72,6 +73,8 @@ export class IssueReporter extends Disposable {
|
||||
private receivedPerformanceInfo = false;
|
||||
private shouldQueueSearch = false;
|
||||
|
||||
private previewButton: Button;
|
||||
|
||||
constructor(configuration: IssueReporterConfiguration) {
|
||||
super();
|
||||
|
||||
@@ -83,9 +86,11 @@ export class IssueReporter extends Disposable {
|
||||
vscodeVersion: `${pkg.name} ${pkg.version} (${product.commit || 'Commit unknown'}, ${product.date || 'Date unknown'})`,
|
||||
os: `${os.type()} ${os.arch()} ${os.release()}`
|
||||
},
|
||||
extensionsDisabled: this.environmentService.disableExtensions,
|
||||
extensionsDisabled: !!this.environmentService.disableExtensions,
|
||||
});
|
||||
|
||||
this.previewButton = new Button(document.getElementById('issue-reporter'));
|
||||
|
||||
ipcRenderer.on('issuePerformanceInfoResponse', (event, info) => {
|
||||
this.logService.trace('issueReporter: Received performance data');
|
||||
this.issueReporterModel.update(info);
|
||||
@@ -145,7 +150,7 @@ export class IssueReporter extends Disposable {
|
||||
const content: string[] = [];
|
||||
|
||||
if (styles.inputBackground) {
|
||||
content.push(`input[type="text"], textarea, select, .issues-container > .issue > .issue-state { background-color: ${styles.inputBackground}; }`);
|
||||
content.push(`input[type="text"], textarea, select, .issues-container > .issue > .issue-state, .block-info { background-color: ${styles.inputBackground}; }`);
|
||||
}
|
||||
|
||||
if (styles.inputBorder) {
|
||||
@@ -155,7 +160,7 @@ export class IssueReporter extends Disposable {
|
||||
}
|
||||
|
||||
if (styles.inputForeground) {
|
||||
content.push(`input[type="text"], textarea, select, .issues-container > .issue > .issue-state { color: ${styles.inputForeground}; }`);
|
||||
content.push(`input[type="text"], textarea, select, .issues-container > .issue > .issue-state, .block-info { color: ${styles.inputForeground}; }`);
|
||||
}
|
||||
|
||||
if (styles.inputErrorBorder) {
|
||||
@@ -171,22 +176,14 @@ export class IssueReporter extends Disposable {
|
||||
content.push(`a, .workbenchCommand { color: ${styles.textLinkColor}; }`);
|
||||
}
|
||||
|
||||
if (styles.buttonBackground) {
|
||||
content.push(`button { background-color: ${styles.buttonBackground}; }`);
|
||||
}
|
||||
|
||||
if (styles.buttonForeground) {
|
||||
content.push(`button { color: ${styles.buttonForeground}; }`);
|
||||
}
|
||||
|
||||
if (styles.buttonHoverBackground) {
|
||||
content.push(`#github-submit-btn:hover:enabled, #github-submit-btn:focus:enabled { background-color: ${styles.buttonHoverBackground}; }`);
|
||||
}
|
||||
|
||||
if (styles.textLinkColor) {
|
||||
content.push(`a { color: ${styles.textLinkColor}; }`);
|
||||
}
|
||||
|
||||
if (styles.textLinkActiveForeground) {
|
||||
content.push(`a:hover, .workbenchCommand:hover { color: ${styles.textLinkActiveForeground}; }`);
|
||||
}
|
||||
|
||||
if (styles.sliderBackgroundColor) {
|
||||
content.push(`::-webkit-scrollbar-thumb { background-color: ${styles.sliderBackgroundColor}; }`);
|
||||
}
|
||||
@@ -199,6 +196,18 @@ export class IssueReporter extends Disposable {
|
||||
content.push(`::--webkit-scrollbar-thumb:hover { background-color: ${styles.sliderHoverColor}; }`);
|
||||
}
|
||||
|
||||
if (styles.buttonBackground) {
|
||||
content.push(`.monaco-text-button { background-color: ${styles.buttonBackground} !important; }`);
|
||||
}
|
||||
|
||||
if (styles.buttonForeground) {
|
||||
content.push(`.monaco-text-button { color: ${styles.buttonForeground} !important; }`);
|
||||
}
|
||||
|
||||
if (styles.buttonHoverBackground) {
|
||||
content.push(`.monaco-text-button:hover, .monaco-text-button:focus { background-color: ${styles.buttonHoverBackground} !important; }`);
|
||||
}
|
||||
|
||||
styleTag.innerHTML = content.join('\n');
|
||||
document.head.appendChild(styleTag);
|
||||
document.body.style.color = styles.color;
|
||||
@@ -278,9 +287,9 @@ export class IssueReporter extends Disposable {
|
||||
.then(() => connectNet(this.environmentService.sharedIPCHandle, `window:${configuration.windowId}`));
|
||||
|
||||
const instantiationService = new InstantiationService(serviceCollection, true);
|
||||
if (this.environmentService.isBuilt && !this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
|
||||
if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
|
||||
const channel = getDelayedChannel<ITelemetryAppenderChannel>(sharedProcess.then(c => c.getChannel('telemetryAppender')));
|
||||
const appender = new TelemetryAppenderClient(channel);
|
||||
const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(logService));
|
||||
const commonProperties = resolveCommonProperties(product.commit, pkg.version, configuration.machineId, this.environmentService.installSourcePath);
|
||||
const piiPaths = [this.environmentService.appRoot, this.environmentService.extensionsPath];
|
||||
const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths };
|
||||
@@ -312,20 +321,20 @@ export class IssueReporter extends Disposable {
|
||||
});
|
||||
});
|
||||
|
||||
const labelElements = document.getElementsByClassName('caption');
|
||||
for (let i = 0; i < labelElements.length; i++) {
|
||||
const label = labelElements.item(i);
|
||||
label.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
// Stop propgagation not working as expected in this case https://bugs.chromium.org/p/chromium/issues/detail?id=809801
|
||||
// preventDefault does prevent outer details tag from toggling, so use that and manually toggle the checkbox
|
||||
const showInfoElements = document.getElementsByClassName('showInfo');
|
||||
for (let i = 0; i < showInfoElements.length; i++) {
|
||||
const showInfo = showInfoElements.item(i);
|
||||
showInfo.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const containingDiv = (<HTMLLabelElement>e.target).parentElement;
|
||||
const checkbox = <HTMLInputElement>containingDiv.firstElementChild;
|
||||
if (checkbox) {
|
||||
checkbox.checked = !checkbox.checked;
|
||||
this.issueReporterModel.update({ [checkbox.id]: !this.issueReporterModel.getData()[checkbox.id] });
|
||||
const label = (<HTMLDivElement>e.target);
|
||||
const containingElement = label.parentElement.parentElement;
|
||||
const info = containingElement.lastElementChild;
|
||||
if (info.classList.contains('hidden')) {
|
||||
show(info);
|
||||
label.textContent = localize('hide', "hide");
|
||||
} else {
|
||||
hide(info);
|
||||
label.textContent = localize('show', "show");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -372,7 +381,7 @@ export class IssueReporter extends Disposable {
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener('github-submit-btn', 'click', () => this.createIssue());
|
||||
this.previewButton.onDidClick(() => this.createIssue());
|
||||
|
||||
this.addEventListener('disableExtensions', 'click', () => {
|
||||
ipcRenderer.send('workbenchCommand', 'workbench.action.reloadWindowWithExtensionsDisabled');
|
||||
@@ -404,17 +413,26 @@ export class IssueReporter extends Disposable {
|
||||
if (cmdOrCtrlKey && e.keyCode === 189) {
|
||||
this.applyZoom(webFrame.getZoomLevel() - 1);
|
||||
}
|
||||
|
||||
// With latest electron upgrade, cmd+a is no longer propagating correctly for inputs in this window on mac
|
||||
// Manually perform the selection
|
||||
if (platform.isMacintosh) {
|
||||
if (cmdOrCtrlKey && e.keyCode === 65 && e.target) {
|
||||
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
|
||||
(<HTMLInputElement>e.target).select();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private updatePreviewButtonState() {
|
||||
const submitButton = <HTMLButtonElement>document.getElementById('github-submit-btn');
|
||||
if (this.isPreviewEnabled()) {
|
||||
submitButton.disabled = false;
|
||||
submitButton.textContent = localize('previewOnGitHub', "Preview on GitHub");
|
||||
this.previewButton.label = localize('previewOnGitHub', "Preview on GitHub");
|
||||
this.previewButton.enabled = true;
|
||||
} else {
|
||||
submitButton.disabled = true;
|
||||
submitButton.textContent = localize('loadingData', "Loading data...");
|
||||
this.previewButton.enabled = false;
|
||||
this.previewButton.label = localize('loadingData', "Loading data...");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,13 +476,20 @@ export class IssueReporter extends Disposable {
|
||||
}
|
||||
|
||||
private searchExtensionIssues(title: string): void {
|
||||
const url = this.getExtensionRepositoryUrl();
|
||||
const url = this.getExtensionGitHubUrl();
|
||||
if (title) {
|
||||
const matches = /^https?:\/\/github\.com\/(.*)(?:.git)/.exec(url);
|
||||
const matches = /^https?:\/\/github\.com\/(.*)/.exec(url);
|
||||
if (matches && matches.length) {
|
||||
const repo = matches[1];
|
||||
return this.searchGitHub(repo, title);
|
||||
}
|
||||
|
||||
// If the extension has no repository, display empty search results
|
||||
if (this.issueReporterModel.getData().selectedExtension) {
|
||||
this.clearSearchResults();
|
||||
return this.displaySearchResults([]);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
this.clearSearchResults();
|
||||
@@ -734,6 +759,12 @@ export class IssueReporter extends Disposable {
|
||||
this.validateInput('description');
|
||||
});
|
||||
|
||||
if (this.issueReporterModel.fileOnExtension()) {
|
||||
document.getElementById('extension-selector').addEventListener('change', (event) => {
|
||||
this.validateInput('extension-selector');
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -758,16 +789,26 @@ export class IssueReporter extends Disposable {
|
||||
return true;
|
||||
}
|
||||
|
||||
private getExtensionGitHubUrl(): string {
|
||||
let repositoryUrl = '';
|
||||
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 = normalizeGitHubUrl(bugsUrl);
|
||||
} else if (extensionUrl && extensionUrl.match(/^https?:\/\/github\.com\/(.*)/)) {
|
||||
repositoryUrl = normalizeGitHubUrl(extensionUrl);
|
||||
}
|
||||
|
||||
return repositoryUrl;
|
||||
}
|
||||
|
||||
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 extensionGitHubUrl = this.getExtensionGitHubUrl();
|
||||
if (extensionGitHubUrl) {
|
||||
repositoryUrl = extensionGitHubUrl + '/issues/new';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,89 +49,6 @@ export default (): string => `
|
||||
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</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 description-section">
|
||||
<label for="description" id="issue-description-label">
|
||||
<!-- To be dynamically filled -->
|
||||
@@ -144,5 +61,70 @@ export default (): string => `
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="github-submit-btn" disabled>${escape(localize('loadingData', "Loading data..."))}</button>
|
||||
<div class="system-info" id="block-container">
|
||||
<div class="block block-system">
|
||||
<input class="sendData" type="checkbox" id="includeSystemInfo" checked/>
|
||||
<label class="caption" for="includeSystemInfo">${escape(localize({
|
||||
key: 'sendSystemInfo',
|
||||
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibililty of the system information']
|
||||
}, "Include my system information ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
|
||||
<div class="block-info hidden">
|
||||
<!-- To be dynamically filled -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="block block-process">
|
||||
<input class="sendData" type="checkbox" id="includeProcessInfo" checked/>
|
||||
<label class="caption" for="includeProcessInfo">${escape(localize({
|
||||
key: 'sendProcessInfo',
|
||||
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibililty of the process info']
|
||||
}, "Include my currently running processes ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
|
||||
<pre class="block-info hidden">
|
||||
<code>
|
||||
<!-- To be dynamically filled -->
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
<div class="block block-workspace">
|
||||
<input class="sendData" type="checkbox" id="includeWorkspaceInfo" checked/>
|
||||
<label class="caption" for="includeWorkspaceInfo">${escape(localize({
|
||||
key: 'sendWorkspaceInfo',
|
||||
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibililty of the workspace information']
|
||||
}, "Include my workspace metadata ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
|
||||
<pre id="systemInfo" class="block-info hidden">
|
||||
<code>
|
||||
<!-- To be dynamically filled -->
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
<div class="block block-extensions">
|
||||
<input class="sendData" type="checkbox" id="includeExtensions" checked/>
|
||||
<label class="caption" for="includeExtensions">${escape(localize({
|
||||
key: 'sendExtensions',
|
||||
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibililty of the enabled extensions list']
|
||||
}, "Include my enabled extensions ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
|
||||
<div id="systemInfo" class="block-info hidden">
|
||||
<!-- To be dynamically filled -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="block block-searchedExtensions">
|
||||
<input class="sendData" type="checkbox" id="includeSearchedExtensions" checked/>
|
||||
<label class="caption" for="includeSearchedExtensions">${escape(localize({
|
||||
key: 'sendSearchedExtensions',
|
||||
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibililty of the searched extensions']
|
||||
}, "Send searched extensions ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
|
||||
<div class="block-info hidden">
|
||||
<!-- To be dynamically filled -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="block block-settingsSearchResults">
|
||||
<input class="sendData" type="checkbox" id="includeSettingsSearchDetails" checked/>
|
||||
<label class="caption" for="includeSettingsSearchDetails">${escape(localize({
|
||||
key: 'sendSettingsSearchDetails',
|
||||
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibililty of the search details']
|
||||
}, "Send settings search details ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
|
||||
<div class="block-info hidden">
|
||||
<!-- To be dynamically filled -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import { endsWith, rtrim } from 'vs/base/common/strings';
|
||||
|
||||
export function normalizeGitHubIssuesUrl(url: string): string {
|
||||
export function normalizeGitHubUrl(url: string): string {
|
||||
// If the url has a .git suffix, remove it
|
||||
if (endsWith(url, '.git')) {
|
||||
url = url.substr(0, url.length - 4);
|
||||
@@ -16,15 +16,13 @@ export function normalizeGitHubIssuesUrl(url: string): string {
|
||||
// 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;
|
||||
if (endsWith(url, '/new')) {
|
||||
url = rtrim(url, '/new');
|
||||
}
|
||||
|
||||
// Add new segment if it does not exist
|
||||
if (endsWith(url, 'issues')) {
|
||||
return url + '/new';
|
||||
if (endsWith(url, '/issues')) {
|
||||
url = rtrim(url, '/issues');
|
||||
}
|
||||
|
||||
return url + '/issues/new';
|
||||
return url;
|
||||
}
|
||||
@@ -10,18 +10,17 @@
|
||||
table {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
background-color: transparent;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
th {
|
||||
vertical-align: bottom;
|
||||
border-bottom: 2px solid #e9ecef;
|
||||
padding: .75rem;
|
||||
border-bottom: 1px solid;
|
||||
padding: 5px;
|
||||
text-align: inherit;
|
||||
}
|
||||
td {
|
||||
padding: .75rem;
|
||||
padding: 5px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
@@ -48,7 +47,6 @@ input[type="text"], textarea {
|
||||
line-height: 1.5;
|
||||
color: #495057;
|
||||
background-color: #fff;
|
||||
border-radius: .25rem;
|
||||
border: 1px solid #ced4da;
|
||||
}
|
||||
|
||||
@@ -60,19 +58,13 @@ textarea {
|
||||
/**
|
||||
* Button
|
||||
*/
|
||||
button {
|
||||
display: inline-block;
|
||||
font-weight: 400;
|
||||
line-height: 1.25;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
user-select: none;
|
||||
padding: .5rem 1rem;
|
||||
font-size: 1rem;
|
||||
border-radius: .25rem;
|
||||
background: none;
|
||||
border: 1px solid transparent;
|
||||
|
||||
.monaco-text-button {
|
||||
display: block;
|
||||
width: auto;
|
||||
padding: 4px 10px;
|
||||
align-self: flex-end;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
select {
|
||||
@@ -83,7 +75,6 @@ select {
|
||||
line-height: 1.5;
|
||||
color: #495057;
|
||||
background-color: #fff;
|
||||
border-radius: 0.25rem;
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -103,7 +94,7 @@ html {
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: scroll;
|
||||
overflow-y: scroll;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -111,26 +102,22 @@ body {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.block {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.block .block-info {
|
||||
width: 100%;
|
||||
font-family: 'Menlo', 'Courier New', 'Courier', monospace;
|
||||
font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback";
|
||||
font-size: 12px;
|
||||
overflow: auto;
|
||||
overflow-wrap: break-word;
|
||||
margin: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
pre {
|
||||
margin: 10px 20px;
|
||||
}
|
||||
|
||||
pre code {
|
||||
font-family: 'Menlo', 'Courier New', 'Courier', monospace;
|
||||
}
|
||||
|
||||
button:hover:enabled {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
cursor: auto;
|
||||
font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback";
|
||||
}
|
||||
|
||||
#issue-reporter {
|
||||
@@ -138,6 +125,7 @@ button:disabled {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-top: 2em;
|
||||
padding-bottom: 2em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
@@ -184,40 +172,17 @@ textarea {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.system-info {
|
||||
margin-bottom: 1.25em;
|
||||
}
|
||||
|
||||
select, input, textarea {
|
||||
border: 1px solid transparent;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
summary {
|
||||
border: 1px solid transparent;
|
||||
padding: 0 10px;
|
||||
margin-bottom: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.validation-error {
|
||||
font-size: 12px;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.include-data {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.include-data > .caption {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sendData {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
width: auto;
|
||||
@@ -238,7 +203,6 @@ input:disabled {
|
||||
|
||||
.instructions {
|
||||
font-size: 12px;
|
||||
margin-left: 1em;
|
||||
margin-top: .5em;
|
||||
}
|
||||
|
||||
@@ -276,13 +240,8 @@ a {
|
||||
color: #be1100;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #007ACC;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.section .input-group .validation-error {
|
||||
margin-left: 13%;
|
||||
margin-left: 15%;
|
||||
}
|
||||
|
||||
.section .inline-form-control, .section .inline-label {
|
||||
@@ -302,17 +261,25 @@ button {
|
||||
}
|
||||
|
||||
#similar-issues {
|
||||
margin-left: 13%;
|
||||
margin-left: 15%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#problem-source-help-text {
|
||||
margin-left: calc(15% + 1em);
|
||||
}
|
||||
|
||||
@media (max-width: 950px) {
|
||||
.section .inline-label {
|
||||
width: 13%;
|
||||
width: 15%;
|
||||
}
|
||||
|
||||
#problem-source-help-text {
|
||||
margin-left: calc(15% + 1em);
|
||||
}
|
||||
|
||||
.section .inline-form-control {
|
||||
width: calc(87% - 5px);
|
||||
width: calc(85% - 5px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,6 +288,10 @@ button {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#problem-source-help-text {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.section .inline-form-control {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel';
|
||||
import { normalizeGitHubIssuesUrl } from 'vs/code/electron-browser/issue/issueReporterUtil';
|
||||
import { normalizeGitHubUrl } from 'vs/code/electron-browser/issue/issueReporterUtil';
|
||||
import { IssueType } from 'vs/platform/issue/common/issue';
|
||||
|
||||
suite('IssueReporter', () => {
|
||||
@@ -81,7 +81,7 @@ OS version: undefined
|
||||
'https://github.com/repo/issues/new',
|
||||
'https://github.com/repo/issues/new/'
|
||||
].forEach(url => {
|
||||
assert.equal('https://github.com/repo/issues/new', normalizeGitHubIssuesUrl(url));
|
||||
assert.equal('https://github.com/repo', normalizeGitHubUrl(url));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ function readFile(file) {
|
||||
});
|
||||
}
|
||||
|
||||
const writeFile = (file, content) => new Promise((c, e) => fs.writeFile(file, content, 'utf8', err => err ? e(err) : c()));
|
||||
|
||||
function main() {
|
||||
const args = parseURLQueryArgs();
|
||||
const configuration = JSON.parse(args['config'] || '{}') || {};
|
||||
@@ -102,8 +104,15 @@ function main() {
|
||||
let json = JSON.parse(content);
|
||||
bundles[bundle] = json;
|
||||
cb(undefined, json);
|
||||
})
|
||||
.catch(cb);
|
||||
}).catch((error) => {
|
||||
try {
|
||||
if (nlsConfig._corruptedFile) {
|
||||
writeFile(nlsConfig._corruptedFile, 'corrupted').catch(function (error) { console.error(error); });
|
||||
}
|
||||
} finally {
|
||||
cb(error, undefined);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import 'vs/css!./media/processExplorer';
|
||||
import { listProcesses, ProcessItem } from 'vs/base/node/ps';
|
||||
import { remote, webFrame } from 'electron';
|
||||
import { remote, webFrame, ipcRenderer, clipboard } from 'electron';
|
||||
import { repeat } from 'vs/base/common/strings';
|
||||
import { totalmem } from 'os';
|
||||
import product from 'vs/platform/node/product';
|
||||
@@ -17,6 +17,7 @@ import * as browser from 'vs/base/browser/browser';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
|
||||
let processList: any[];
|
||||
let mapPidToWindowTitle = new Map<number, string>();
|
||||
|
||||
function getProcessList(rootProcess: ProcessItem) {
|
||||
const processes: any[] = [];
|
||||
@@ -33,8 +34,17 @@ function getProcessItem(processes: any[], item: ProcessItem, indent: number): vo
|
||||
|
||||
const MB = 1024 * 1024;
|
||||
|
||||
let name = item.name;
|
||||
if (isRoot) {
|
||||
name = `${product.applicationName} main`;
|
||||
}
|
||||
|
||||
if (name === 'window') {
|
||||
const windowTitle = mapPidToWindowTitle.get(item.pid);
|
||||
name = windowTitle !== undefined ? `${name} (${mapPidToWindowTitle.get(item.pid)})` : name;
|
||||
}
|
||||
|
||||
// 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({
|
||||
@@ -127,45 +137,88 @@ function applyZoom(zoomLevel: number): void {
|
||||
function showContextMenu(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const menu = new remote.Menu();
|
||||
|
||||
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());
|
||||
menu.append(new remote.MenuItem({
|
||||
type: 'separator'
|
||||
}));
|
||||
|
||||
menu.append(new remote.MenuItem({
|
||||
label: localize('copy', "Copy"),
|
||||
click() {
|
||||
const row = document.getElementById(pid.toString());
|
||||
if (row) {
|
||||
clipboard.writeText(row.innerText);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
menu.append(new remote.MenuItem({
|
||||
label: localize('copyAll', "Copy All"),
|
||||
click() {
|
||||
const processList = document.getElementById('process-list');
|
||||
if (processList) {
|
||||
clipboard.writeText(processList.innerText);
|
||||
}
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
menu.append(new remote.MenuItem({
|
||||
label: localize('copyAll', "Copy All"),
|
||||
click() {
|
||||
const processList = document.getElementById('process-list');
|
||||
if (processList) {
|
||||
clipboard.writeText(processList.innerText);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
menu.popup({ window: 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);
|
||||
// Map window process pids to titles, annotate process names with this when rendering to distinguish between them
|
||||
ipcRenderer.on('windowsInfoResponse', (event, windows) => {
|
||||
mapPidToWindowTitle = new Map<number, string>();
|
||||
windows.forEach(window => mapPidToWindowTitle.set(window.pid, window.title));
|
||||
});
|
||||
|
||||
const tableRows = document.getElementsByTagName('tr');
|
||||
for (let i = 0; i < tableRows.length; i++) {
|
||||
const tableRow = tableRows[i];
|
||||
tableRow.addEventListener('contextmenu', (e) => {
|
||||
showContextMenu(e);
|
||||
});
|
||||
}
|
||||
}), 1200);
|
||||
setInterval(() => {
|
||||
ipcRenderer.send('windowsInfoRequest');
|
||||
|
||||
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) => {
|
||||
|
||||
@@ -66,9 +66,13 @@ export class LanguagePackCachedDataCleaner {
|
||||
}
|
||||
// Cleanup entries for language packs that aren't installed anymore
|
||||
const cacheDir = path.join(this._environmentService.userDataPath, 'clp');
|
||||
let exists = await pfs.exists(cacheDir);
|
||||
if (!exists) {
|
||||
return;
|
||||
}
|
||||
for (let entry of await pfs.readdir(cacheDir)) {
|
||||
if (installed[entry]) {
|
||||
this._logService.info(`Skipping directory ${entry}. Language pack still in use`);
|
||||
this._logService.info(`Skipping directory ${entry}. Language pack still in use.`);
|
||||
continue;
|
||||
}
|
||||
this._logService.info('Removing unused language pack:', entry);
|
||||
|
||||
@@ -53,6 +53,8 @@ function readFile(file) {
|
||||
});
|
||||
}
|
||||
|
||||
const writeFile = (file, content) => new Promise((c, e) => fs.writeFile(file, content, 'utf8', err => err ? e(err) : c()));
|
||||
|
||||
function main() {
|
||||
const args = parseURLQueryArgs();
|
||||
const configuration = JSON.parse(args['config'] || '{}') || {};
|
||||
@@ -111,8 +113,15 @@ function main() {
|
||||
let json = JSON.parse(content);
|
||||
bundles[bundle] = json;
|
||||
cb(undefined, json);
|
||||
})
|
||||
.catch(cb);
|
||||
}).catch((error) => {
|
||||
try {
|
||||
if (nlsConfig._corruptedFile) {
|
||||
writeFile(nlsConfig._corruptedFile, 'corrupted').catch(function (error) { console.error(error); });
|
||||
}
|
||||
} finally {
|
||||
cb(error, undefined);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import { ConfigurationService } from 'vs/platform/configuration/node/configurati
|
||||
import { IRequestService } from 'vs/platform/request/node/request';
|
||||
import { RequestService } from 'vs/platform/request/electron-browser/requestService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { combinedAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { combinedAppender, NullTelemetryService, ITelemetryAppender, NullAppender, LogAppender } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
|
||||
import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc';
|
||||
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
|
||||
@@ -42,6 +42,7 @@ import { ILocalizationsService } from 'vs/platform/localizations/common/localiza
|
||||
import { LocalizationsChannel } from 'vs/platform/localizations/common/localizationsIpc';
|
||||
import { DialogChannelClient } from 'vs/platform/dialogs/common/dialogIpc';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface ISharedProcessConfiguration {
|
||||
readonly machineId: string;
|
||||
@@ -61,11 +62,14 @@ const eventPrefix = 'monacoworkbench';
|
||||
|
||||
function main(server: Server, initData: ISharedProcessInitData, configuration: ISharedProcessConfiguration): void {
|
||||
const services = new ServiceCollection();
|
||||
const disposables: IDisposable[] = [];
|
||||
process.once('exit', () => dispose(disposables));
|
||||
|
||||
const environmentService = new EnvironmentService(initData.args, process.execPath);
|
||||
const logLevelClient = new LogLevelSetterChannelClient(server.getChannel('loglevel', { route: () => 'main' }));
|
||||
const mainRoute = () => TPromise.as('main');
|
||||
const logLevelClient = new LogLevelSetterChannelClient(server.getChannel('loglevel', { routeCall: mainRoute, routeEvent: mainRoute }));
|
||||
const logService = new FollowerLogService(logLevelClient, createSpdLogService('sharedprocess', initData.logLevel, environmentService.logsPath));
|
||||
process.once('exit', () => logService.dispose());
|
||||
disposables.push(logService);
|
||||
|
||||
logService.info('main', JSON.stringify(configuration));
|
||||
|
||||
@@ -74,42 +78,33 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
|
||||
services.set(IConfigurationService, new SyncDescriptor(ConfigurationService));
|
||||
services.set(IRequestService, new SyncDescriptor(RequestService));
|
||||
|
||||
const windowsChannel = server.getChannel('windows', { route: () => 'main' });
|
||||
const windowsChannel = server.getChannel('windows', { routeCall: mainRoute, routeEvent: mainRoute });
|
||||
const windowsService = new WindowsChannelClient(windowsChannel);
|
||||
services.set(IWindowsService, windowsService);
|
||||
|
||||
const activeWindowManager = new ActiveWindowManager(windowsService);
|
||||
const dialogChannel = server.getChannel('dialog', {
|
||||
route: () => {
|
||||
logService.info('Routing dialog request to the client', activeWindowManager.activeClientId);
|
||||
return activeWindowManager.activeClientId;
|
||||
}
|
||||
});
|
||||
const route = () => activeWindowManager.getActiveClientId();
|
||||
const dialogChannel = server.getChannel('dialog', { routeCall: route, routeEvent: route });
|
||||
services.set(IDialogService, new DialogChannelClient(dialogChannel));
|
||||
|
||||
const instantiationService = new InstantiationService(services);
|
||||
|
||||
instantiationService.invokeFunction(accessor => {
|
||||
const appenders: AppInsightsAppender[] = [];
|
||||
|
||||
if (product.aiConfig && product.aiConfig.asimovKey) {
|
||||
appenders.push(new AppInsightsAppender(eventPrefix, null, product.aiConfig.asimovKey));
|
||||
}
|
||||
|
||||
// It is important to dispose the AI adapter properly because
|
||||
// only then they flush remaining data.
|
||||
process.once('exit', () => appenders.forEach(a => a.dispose()));
|
||||
|
||||
const appender = combinedAppender(...appenders);
|
||||
server.registerChannel('telemetryAppender', new TelemetryAppenderChannel(appender));
|
||||
|
||||
const services = new ServiceCollection();
|
||||
const environmentService = accessor.get(IEnvironmentService);
|
||||
const { appRoot, extensionsPath, extensionDevelopmentPath, isBuilt, installSourcePath } = environmentService;
|
||||
const telemetryLogService = new FollowerLogService(logLevelClient, createSpdLogService('telemetry', initData.logLevel, environmentService.logsPath));
|
||||
|
||||
if (isBuilt && !extensionDevelopmentPath && !environmentService.args['disable-telemetry'] && product.enableTelemetry) {
|
||||
let appInsightsAppender: ITelemetryAppender = NullAppender;
|
||||
if (product.aiConfig && product.aiConfig.asimovKey && isBuilt) {
|
||||
appInsightsAppender = new AppInsightsAppender(eventPrefix, null, product.aiConfig.asimovKey, telemetryLogService);
|
||||
disposables.push(appInsightsAppender); // Ensure the AI appender is disposed so that it flushes remaining data
|
||||
}
|
||||
server.registerChannel('telemetryAppender', new TelemetryAppenderChannel(appInsightsAppender));
|
||||
|
||||
if (!extensionDevelopmentPath && !environmentService.args['disable-telemetry'] && product.enableTelemetry) {
|
||||
const config: ITelemetryServiceConfig = {
|
||||
appender,
|
||||
appender: combinedAppender(appInsightsAppender, new LogAppender(logService)),
|
||||
commonProperties: resolveCommonProperties(product.commit, pkg.version, configuration.machineId, installSourcePath),
|
||||
piiPaths: [appRoot, extensionsPath]
|
||||
};
|
||||
@@ -138,6 +133,7 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
|
||||
server.registerChannel('localizations', localizationsChannel);
|
||||
|
||||
createSharedProcessContributions(instantiationService2);
|
||||
disposables.push(extensionManagementService as ExtensionManagementService);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,14 +5,13 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { app, ipcMain as ipc } from 'electron';
|
||||
import { app, ipcMain as ipc, systemPreferences } from 'electron';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { WindowsManager } from 'vs/code/electron-main/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';
|
||||
import { CodeMenu } from 'vs/code/electron-main/menus';
|
||||
import { getShellEnvironment } from 'vs/code/node/shellEnv';
|
||||
import { IUpdateService } from 'vs/platform/update/common/update';
|
||||
import { UpdateChannel } from 'vs/platform/update/common/updateIpc';
|
||||
@@ -31,7 +30,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||
import { IURLService } from 'vs/platform/url/common/url';
|
||||
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 { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { ITelemetryAppenderChannel, TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc';
|
||||
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
|
||||
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
|
||||
@@ -58,9 +57,14 @@ import { IIssueService } from 'vs/platform/issue/common/issue';
|
||||
import { IssueChannel } from 'vs/platform/issue/common/issueIpc';
|
||||
import { IssueService } from 'vs/platform/issue/electron-main/issueService';
|
||||
import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc';
|
||||
import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
|
||||
import * as errors 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';
|
||||
import { IMenubarService } from 'vs/platform/menubar/common/menubar';
|
||||
import { MenubarService } from 'vs/platform/menubar/electron-main/menubarService';
|
||||
import { MenubarChannel } from 'vs/platform/menubar/common/menubarIpc';
|
||||
import { IUriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay';
|
||||
import { CodeMenu } from 'vs/code/electron-main/menus';
|
||||
|
||||
export class CodeApplication {
|
||||
|
||||
@@ -81,9 +85,10 @@ export class CodeApplication {
|
||||
@ILogService private logService: ILogService,
|
||||
@IEnvironmentService private environmentService: IEnvironmentService,
|
||||
@ILifecycleService private lifecycleService: ILifecycleService,
|
||||
@IConfigurationService configurationService: ConfigurationService,
|
||||
@IConfigurationService private configurationService: ConfigurationService,
|
||||
@IStateService private stateService: IStateService,
|
||||
@IHistoryMainService private historyMainService: IHistoryMainService
|
||||
@IHistoryMainService private historyMainService: IHistoryMainService,
|
||||
@IUriDisplayService private uriDisplayService: IUriDisplayService
|
||||
) {
|
||||
this.toDispose = [mainIpcServer, configurationService];
|
||||
|
||||
@@ -93,8 +98,9 @@ export class CodeApplication {
|
||||
private registerListeners(): void {
|
||||
|
||||
// We handle uncaught exceptions here to prevent electron from opening a dialog to the user
|
||||
setUnexpectedErrorHandler(err => this.onUnexpectedError(err));
|
||||
errors.setUnexpectedErrorHandler(err => this.onUnexpectedError(err));
|
||||
process.on('uncaughtException', err => this.onUnexpectedError(err));
|
||||
process.on('unhandledRejection', (reason: any, promise: Promise<any>) => errors.onUnexpectedError(reason));
|
||||
|
||||
app.on('will-quit', () => {
|
||||
this.logService.trace('App#will-quit: disposing resources');
|
||||
@@ -149,14 +155,14 @@ export class CodeApplication {
|
||||
});
|
||||
});
|
||||
|
||||
let macOpenFiles: string[] = [];
|
||||
let macOpenFileURIs: URI[] = [];
|
||||
let runningTimeout: number = null;
|
||||
app.on('open-file', (event: Event, path: string) => {
|
||||
this.logService.trace('App#open-file: ', path);
|
||||
event.preventDefault();
|
||||
|
||||
// Keep in array because more might come!
|
||||
macOpenFiles.push(path);
|
||||
macOpenFileURIs.push(URI.file(path));
|
||||
|
||||
// Clear previous handler if any
|
||||
if (runningTimeout !== null) {
|
||||
@@ -170,10 +176,10 @@ export class CodeApplication {
|
||||
this.windowsMainService.open({
|
||||
context: OpenContext.DOCK /* can also be opening from finder while app is running */,
|
||||
cli: this.environmentService.args,
|
||||
pathsToOpen: macOpenFiles,
|
||||
urisToOpen: macOpenFileURIs,
|
||||
preferNewWindow: true /* dropping on the dock or opening from finder prefers to open in a new window */
|
||||
});
|
||||
macOpenFiles = [];
|
||||
macOpenFileURIs = [];
|
||||
runningTimeout = null;
|
||||
}
|
||||
}, 100);
|
||||
@@ -217,6 +223,10 @@ export class CodeApplication {
|
||||
}
|
||||
});
|
||||
|
||||
ipc.on('vscode:uriDisplayRegisterFormater', (event: any, { scheme, formater }) => {
|
||||
this.uriDisplayService.registerFormater(scheme, formater);
|
||||
});
|
||||
|
||||
// Keyboard layout changes
|
||||
KeyboardLayoutMonitor.INSTANCE.onDidChangeKeyboardLayout(() => {
|
||||
if (this.windowsMainService) {
|
||||
@@ -257,7 +267,7 @@ export class CodeApplication {
|
||||
}
|
||||
}
|
||||
|
||||
public startup(): TPromise<void> {
|
||||
startup(): TPromise<void> {
|
||||
this.logService.debug('Starting VS Code');
|
||||
this.logService.debug(`from: ${this.environmentService.appRoot}`);
|
||||
this.logService.debug('args:', this.environmentService.args);
|
||||
@@ -270,6 +280,20 @@ export class CodeApplication {
|
||||
app.setAppUserModelId(product.win32AppUserModelId);
|
||||
}
|
||||
|
||||
// Fix native tabs on macOS 10.13
|
||||
// macOS enables a compatibility patch for any bundle ID beginning with
|
||||
// "com.microsoft.", which breaks native tabs for VS Code when using this
|
||||
// identifier (from the official build).
|
||||
// Explicitly opt out of the patch here before creating any windows.
|
||||
// See: https://github.com/Microsoft/vscode/issues/35361#issuecomment-399794085
|
||||
try {
|
||||
if (platform.isMacintosh && this.configurationService.getValue<boolean>('window.nativeTabs') === true && !systemPreferences.getUserDefault('NSUseImprovedLayoutPass', 'boolean')) {
|
||||
systemPreferences.registerDefaults({ NSUseImprovedLayoutPass: true });
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
|
||||
// Create Electron IPC Server
|
||||
this.electronIpcServer = new ElectronIPCServer();
|
||||
|
||||
@@ -289,7 +313,7 @@ export class CodeApplication {
|
||||
|
||||
// Create driver
|
||||
if (this.environmentService.driverHandle) {
|
||||
serveDriver(this.electronIpcServer, this.environmentService.driverHandle, appInstantiationService).then(server => {
|
||||
serveDriver(this.electronIpcServer, this.environmentService.driverHandle, this.environmentService, appInstantiationService).then(server => {
|
||||
this.logService.info('Driver started at:', this.environmentService.driverHandle);
|
||||
this.toDispose.push(server);
|
||||
});
|
||||
@@ -340,11 +364,12 @@ export class CodeApplication {
|
||||
services.set(IWindowsService, new SyncDescriptor(WindowsService, this.sharedProcess));
|
||||
services.set(ILaunchService, new SyncDescriptor(LaunchService));
|
||||
services.set(IIssueService, new SyncDescriptor(IssueService, machineId, this.userEnv));
|
||||
services.set(IMenubarService, new SyncDescriptor(MenubarService));
|
||||
|
||||
// Telemtry
|
||||
if (this.environmentService.isBuilt && !this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
|
||||
if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
|
||||
const channel = getDelayedChannel<ITelemetryAppenderChannel>(this.sharedProcessClient.then(c => c.getChannel('telemetryAppender')));
|
||||
const appender = new TelemetryAppenderClient(channel);
|
||||
const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(this.logService));
|
||||
const commonProperties = resolveCommonProperties(product.commit, pkg.version, machineId, this.environmentService.installSourcePath);
|
||||
const piiPaths = [this.environmentService.appRoot, this.environmentService.extensionsPath];
|
||||
const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths };
|
||||
@@ -383,6 +408,10 @@ export class CodeApplication {
|
||||
this.electronIpcServer.registerChannel('windows', windowsChannel);
|
||||
this.sharedProcessClient.done(client => client.registerChannel('windows', windowsChannel));
|
||||
|
||||
const menubarService = accessor.get(IMenubarService);
|
||||
const menubarChannel = new MenubarChannel(menubarService);
|
||||
this.electronIpcServer.registerChannel('menubar', menubarChannel);
|
||||
|
||||
const urlService = accessor.get(IURLService);
|
||||
const urlChannel = new URLServiceChannel(urlService);
|
||||
this.electronIpcServer.registerChannel('url', urlChannel);
|
||||
@@ -402,7 +431,8 @@ export class CodeApplication {
|
||||
|
||||
// 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 route = () => activeWindowManager.getActiveClientId();
|
||||
const urlHandlerChannel = this.electronIpcServer.getChannel('urlHandler', { routeCall: route, routeEvent: route });
|
||||
const multiplexURLHandler = new URLHandlerChannelClient(urlHandlerChannel);
|
||||
|
||||
// On Mac, Code can be running without any open windows, so we must create a window to handle urls,
|
||||
@@ -411,7 +441,7 @@ export class CodeApplication {
|
||||
const environmentService = accessor.get(IEnvironmentService);
|
||||
|
||||
urlService.registerHandler({
|
||||
async handleURL(uri: URI): TPromise<boolean> {
|
||||
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 });
|
||||
@@ -419,7 +449,7 @@ export class CodeApplication {
|
||||
return window.ready().then(() => urlService.open(uri));
|
||||
}
|
||||
|
||||
return false;
|
||||
return TPromise.as(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -437,17 +467,16 @@ export class CodeApplication {
|
||||
// Open our first window
|
||||
const macOpenFiles = (<any>global).macOpenFiles as string[];
|
||||
const context = !!process.env['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP;
|
||||
if (args['new-window'] && args._.length === 0) {
|
||||
if (args['new-window'] && args._.length === 0 && (args['folder-uri'] || []).length === 0) {
|
||||
this.windowsMainService.open({ context, cli: args, forceNewWindow: true, forceEmpty: true, initialStartup: true }); // new window if "-n" was used without paths
|
||||
} 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 if (macOpenFiles && macOpenFiles.length && (!args._ || !args._.length || !args['folder-uri'] || !args['folder-uri'].length)) {
|
||||
this.windowsMainService.open({ context: OpenContext.DOCK, cli: args, urisToOpen: macOpenFiles.map(file => URI.file(file)), 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
|
||||
}
|
||||
}
|
||||
|
||||
private afterWindowOpen(accessor: ServicesAccessor): void {
|
||||
const appInstantiationService = accessor.get(IInstantiationService);
|
||||
const windowsMainService = accessor.get(IWindowsMainService);
|
||||
|
||||
let windowsMutex: Mutex = null;
|
||||
@@ -487,15 +516,20 @@ export class CodeApplication {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO@sbatten: Remove when switching back to dynamic menu
|
||||
// Install Menu
|
||||
appInstantiationService.createInstance(CodeMenu);
|
||||
const instantiationService = accessor.get(IInstantiationService);
|
||||
const configurationService = accessor.get(IConfigurationService);
|
||||
if (platform.isMacintosh || configurationService.getValue<string>('window.titleBarStyle') !== 'custom') {
|
||||
instantiationService.createInstance(CodeMenu);
|
||||
}
|
||||
|
||||
// Jump List
|
||||
this.historyMainService.updateWindowsJumpList();
|
||||
this.historyMainService.onRecentlyOpenedChange(() => this.historyMainService.updateWindowsJumpList());
|
||||
|
||||
// Start shared process here
|
||||
this.sharedProcess.spawn();
|
||||
// Start shared process after a while
|
||||
TPromise.timeout(3000).then(() => this.sharedProcess.spawn());
|
||||
}
|
||||
|
||||
private dispose(): void {
|
||||
|
||||
@@ -56,7 +56,10 @@ export class ProxyAuthHandler {
|
||||
width: 450,
|
||||
height: 220,
|
||||
show: true,
|
||||
title: 'VS Code'
|
||||
title: 'VS Code',
|
||||
webPreferences: {
|
||||
disableBlinkFeatures: 'Auxclick'
|
||||
}
|
||||
};
|
||||
|
||||
const focusedWindow = this.windowsMainService.getFocusedWindow();
|
||||
|
||||
@@ -16,6 +16,7 @@ import { repeat, pad } from 'vs/base/common/strings';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { app } from 'electron';
|
||||
import { basename } from 'path';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
export interface VersionInfo {
|
||||
vscodeVersion: string;
|
||||
@@ -49,39 +50,51 @@ export function getPerformanceInfo(info: IMainProcessInfo): Promise<PerformanceI
|
||||
const workspaceInfoMessages = [];
|
||||
|
||||
// Workspace Stats
|
||||
if (info.windows.some(window => window.folders && window.folders.length > 0)) {
|
||||
const workspaceStatPromises = [];
|
||||
if (info.windows.some(window => window.folderURIs && window.folderURIs.length > 0)) {
|
||||
info.windows.forEach(window => {
|
||||
if (window.folders.length === 0) {
|
||||
if (window.folderURIs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
workspaceInfoMessages.push(`| Window (${window.title})`);
|
||||
|
||||
window.folders.forEach(folder => {
|
||||
try {
|
||||
const stats = collectWorkspaceStats(folder, ['node_modules', '.git']);
|
||||
let countMessage = `${stats.fileCount} files`;
|
||||
if (stats.maxFilesReached) {
|
||||
countMessage = `more than ${countMessage}`;
|
||||
}
|
||||
workspaceInfoMessages.push(`| Folder (${basename(folder)}): ${countMessage}`);
|
||||
workspaceInfoMessages.push(formatWorkspaceStats(stats));
|
||||
window.folderURIs.forEach(uriComponents => {
|
||||
const folderUri = URI.revive(uriComponents);
|
||||
if (folderUri.scheme === 'file') {
|
||||
const folder = folderUri.fsPath;
|
||||
workspaceStatPromises.push(collectWorkspaceStats(folder, ['node_modules', '.git']).then(async stats => {
|
||||
|
||||
const launchConfigs = collectLaunchConfigs(folder);
|
||||
if (launchConfigs.length > 0) {
|
||||
workspaceInfoMessages.push(formatLaunchConfigs(launchConfigs));
|
||||
}
|
||||
} catch (error) {
|
||||
workspaceInfoMessages.push(`| Error: Unable to collect workpsace stats for folder ${folder} (${error.toString()})`);
|
||||
let countMessage = `${stats.fileCount} files`;
|
||||
if (stats.maxFilesReached) {
|
||||
countMessage = `more than ${countMessage}`;
|
||||
}
|
||||
workspaceInfoMessages.push(`| Folder (${basename(folder)}): ${countMessage}`);
|
||||
workspaceInfoMessages.push(formatWorkspaceStats(stats));
|
||||
|
||||
const launchConfigs = await collectLaunchConfigs(folder);
|
||||
if (launchConfigs.length > 0) {
|
||||
workspaceInfoMessages.push(formatLaunchConfigs(launchConfigs));
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
workspaceInfoMessages.push(`| Folder (${folderUri.toString()}): RPerformance stats not available.`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
processInfo: formatProcessList(info, rootProcess),
|
||||
workspaceInfo: workspaceInfoMessages.join('\n')
|
||||
};
|
||||
return Promise.all(workspaceStatPromises).then(() => {
|
||||
return {
|
||||
processInfo: formatProcessList(info, rootProcess),
|
||||
workspaceInfo: workspaceInfoMessages.join('\n')
|
||||
};
|
||||
}).catch(error => {
|
||||
return {
|
||||
processInfo: formatProcessList(info, rootProcess),
|
||||
workspaceInfo: `Unable to calculate workspace stats: ${error}`
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -122,38 +135,48 @@ export function printDiagnostics(info: IMainProcessInfo): Promise<any> {
|
||||
console.log(formatProcessList(info, rootProcess));
|
||||
|
||||
// Workspace Stats
|
||||
if (info.windows.some(window => window.folders && window.folders.length > 0)) {
|
||||
const workspaceStatPromises = [];
|
||||
if (info.windows.some(window => window.folderURIs && window.folderURIs.length > 0)) {
|
||||
console.log('');
|
||||
console.log('Workspace Stats: ');
|
||||
info.windows.forEach(window => {
|
||||
if (window.folders.length === 0) {
|
||||
if (window.folderURIs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`| Window (${window.title})`);
|
||||
|
||||
window.folders.forEach(folder => {
|
||||
try {
|
||||
const stats = collectWorkspaceStats(folder, ['node_modules', '.git']);
|
||||
let countMessage = `${stats.fileCount} files`;
|
||||
if (stats.maxFilesReached) {
|
||||
countMessage = `more than ${countMessage}`;
|
||||
}
|
||||
console.log(`| Folder (${basename(folder)}): ${countMessage}`);
|
||||
console.log(formatWorkspaceStats(stats));
|
||||
window.folderURIs.forEach(uriComponents => {
|
||||
const folderUri = URI.revive(uriComponents);
|
||||
if (folderUri.scheme === 'file') {
|
||||
const folder = folderUri.fsPath;
|
||||
workspaceStatPromises.push(collectWorkspaceStats(folder, ['node_modules', '.git']).then(async stats => {
|
||||
let countMessage = `${stats.fileCount} files`;
|
||||
if (stats.maxFilesReached) {
|
||||
countMessage = `more than ${countMessage}`;
|
||||
}
|
||||
console.log(`| Folder (${basename(folder)}): ${countMessage}`);
|
||||
console.log(formatWorkspaceStats(stats));
|
||||
|
||||
const launchConfigs = collectLaunchConfigs(folder);
|
||||
if (launchConfigs.length > 0) {
|
||||
console.log(formatLaunchConfigs(launchConfigs));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`| Error: Unable to collect workpsace stats for folder ${folder} (${error.toString()})`);
|
||||
await collectLaunchConfigs(folder).then(launchConfigs => {
|
||||
if (launchConfigs.length > 0) {
|
||||
console.log(formatLaunchConfigs(launchConfigs));
|
||||
}
|
||||
});
|
||||
}).catch(error => {
|
||||
console.log(`| Error: Unable to collect workspace stats for folder ${folder} (${error.toString()})`);
|
||||
}));
|
||||
} else {
|
||||
console.log(`| Folder (${folderUri.toString()}): Workspace stats not available.`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
console.log('');
|
||||
console.log('');
|
||||
|
||||
return Promise.all(workspaceStatPromises).then(() => {
|
||||
console.log('');
|
||||
console.log('');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,10 @@ 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';
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { BrowserWindow } from 'electron';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export const ID = 'launchService';
|
||||
export const ILaunchService = createDecorator<ILaunchService>(ID);
|
||||
@@ -31,7 +32,7 @@ export interface IStartArguments {
|
||||
export interface IWindowInfo {
|
||||
pid: number;
|
||||
title: string;
|
||||
folders: string[];
|
||||
folderURIs: UriComponents[];
|
||||
}
|
||||
|
||||
export interface IMainProcessInfo {
|
||||
@@ -78,7 +79,11 @@ export class LaunchChannel implements ILaunchChannel {
|
||||
|
||||
constructor(private service: ILaunchService) { }
|
||||
|
||||
public call(command: string, arg: any): TPromise<any> {
|
||||
listen<T>(event: string): Event<T> {
|
||||
throw new Error('No event found');
|
||||
}
|
||||
|
||||
call(command: string, arg: any): TPromise<any> {
|
||||
switch (command) {
|
||||
case 'start':
|
||||
const { args, userEnv } = arg as IStartArguments;
|
||||
@@ -104,19 +109,19 @@ export class LaunchChannelClient implements ILaunchService {
|
||||
|
||||
constructor(private channel: ILaunchChannel) { }
|
||||
|
||||
public start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void> {
|
||||
start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void> {
|
||||
return this.channel.call('start', { args, userEnv });
|
||||
}
|
||||
|
||||
public getMainProcessId(): TPromise<number> {
|
||||
getMainProcessId(): TPromise<number> {
|
||||
return this.channel.call('get-main-process-id', null);
|
||||
}
|
||||
|
||||
public getMainProcessInfo(): TPromise<IMainProcessInfo> {
|
||||
getMainProcessInfo(): TPromise<IMainProcessInfo> {
|
||||
return this.channel.call('get-main-process-info', null);
|
||||
}
|
||||
|
||||
public getLogsPath(): TPromise<string> {
|
||||
getLogsPath(): TPromise<string> {
|
||||
return this.channel.call('get-logs-path', null);
|
||||
}
|
||||
}
|
||||
@@ -134,7 +139,7 @@ export class LaunchService implements ILaunchService {
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
) { }
|
||||
|
||||
public start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void> {
|
||||
start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void> {
|
||||
this.logService.trace('Received data from other instance: ', args, userEnv);
|
||||
|
||||
const urlsToOpen = parseOpenUrl(args);
|
||||
@@ -173,7 +178,7 @@ export class LaunchService implements ILaunchService {
|
||||
}
|
||||
|
||||
// Start without file/folder arguments
|
||||
else if (args._.length === 0) {
|
||||
else if (args._.length === 0 && (args['folder-uri'] || []).length === 0) {
|
||||
let openNewWindow = false;
|
||||
|
||||
// Force new window
|
||||
@@ -236,48 +241,58 @@ export class LaunchService implements ILaunchService {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public getMainProcessId(): TPromise<number> {
|
||||
getMainProcessId(): TPromise<number> {
|
||||
this.logService.trace('Received request for process ID from other instance.');
|
||||
|
||||
return TPromise.as(process.pid);
|
||||
}
|
||||
|
||||
public getMainProcessInfo(): TPromise<IMainProcessInfo> {
|
||||
getMainProcessInfo(): TPromise<IMainProcessInfo> {
|
||||
this.logService.trace('Received request for main process info from other instance.');
|
||||
|
||||
const windows: IWindowInfo[] = [];
|
||||
BrowserWindow.getAllWindows().forEach(window => {
|
||||
const codeWindow = this.windowsMainService.getWindowById(window.id);
|
||||
if (codeWindow) {
|
||||
windows.push(this.codeWindowToInfo(codeWindow));
|
||||
} else {
|
||||
windows.push(this.browserWindowToInfo(window));
|
||||
}
|
||||
});
|
||||
|
||||
return TPromise.wrap({
|
||||
mainPID: process.pid,
|
||||
mainArguments: process.argv,
|
||||
windows: this.windowsMainService.getWindows().map(window => {
|
||||
return this.getWindowInfo(window);
|
||||
})
|
||||
windows
|
||||
} as IMainProcessInfo);
|
||||
}
|
||||
|
||||
public getLogsPath(): TPromise<string> {
|
||||
getLogsPath(): TPromise<string> {
|
||||
this.logService.trace('Received request for logs path from other instance.');
|
||||
|
||||
return TPromise.as(this.environmentService.logsPath);
|
||||
}
|
||||
|
||||
private getWindowInfo(window: ICodeWindow): IWindowInfo {
|
||||
const folders: string[] = [];
|
||||
private codeWindowToInfo(window: ICodeWindow): IWindowInfo {
|
||||
const folderURIs: URI[] = [];
|
||||
|
||||
if (window.openedFolderPath) {
|
||||
folders.push(window.openedFolderPath);
|
||||
if (window.openedFolderUri) {
|
||||
folderURIs.push(window.openedFolderUri);
|
||||
} else if (window.openedWorkspace) {
|
||||
const rootFolders = this.workspacesMainService.resolveWorkspaceSync(window.openedWorkspace.configPath).folders;
|
||||
rootFolders.forEach(root => {
|
||||
if (root.uri.scheme === Schemas.file) { // todo@remote signal remote folders?
|
||||
folders.push(root.uri.fsPath);
|
||||
}
|
||||
folderURIs.push(root.uri);
|
||||
});
|
||||
}
|
||||
|
||||
return this.browserWindowToInfo(window.win, folderURIs);
|
||||
}
|
||||
|
||||
private browserWindowToInfo(win: BrowserWindow, folderURIs: URI[] = []): IWindowInfo {
|
||||
return {
|
||||
pid: window.win.webContents.getOSProcessId(),
|
||||
title: window.win.getTitle(),
|
||||
folders
|
||||
pid: win.webContents.getOSProcessId(),
|
||||
title: win.getTitle(),
|
||||
folderURIs
|
||||
} as IWindowInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export async function uploadLogs(
|
||||
channel: ILaunchChannel,
|
||||
requestService: IRequestService,
|
||||
environmentService: IEnvironmentService
|
||||
): TPromise<any> {
|
||||
): Promise<any> {
|
||||
const endpoint = Endpoint.getFromProduct();
|
||||
if (!endpoint) {
|
||||
console.error(localize('invalidEndpoint', 'Invalid log uploader endpoint'));
|
||||
@@ -75,7 +75,7 @@ async function postLogs(
|
||||
endpoint: Endpoint,
|
||||
outZip: string,
|
||||
requestService: IRequestService
|
||||
): TPromise<PostResult> {
|
||||
): Promise<PostResult> {
|
||||
const dotter = setInterval(() => console.log('.'), 5000);
|
||||
let result: IRequestContext;
|
||||
try {
|
||||
@@ -152,4 +152,4 @@ function doZip(
|
||||
default:
|
||||
return cp.execFile('zip', ['-r', outZip, '.'], { cwd: logsPath }, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ 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/common/urlService';
|
||||
import * as fs from 'original-fs';
|
||||
import * as fs from 'fs';
|
||||
import { CodeApplication } from 'vs/code/electron-main/app';
|
||||
import { HistoryMainService } from 'vs/platform/history/electron-main/historyMainService';
|
||||
import { IHistoryMainService } from 'vs/platform/history/common/history';
|
||||
@@ -50,6 +50,7 @@ import { uploadLogs } from 'vs/code/electron-main/logUploader';
|
||||
import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { CommandLineDialogService } from 'vs/platform/dialogs/node/dialogService';
|
||||
import { IUriDisplayService, UriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay';
|
||||
|
||||
function createServices(args: ParsedArgs, bufferLogService: BufferLogService): IInstantiationService {
|
||||
const services = new ServiceCollection();
|
||||
@@ -57,6 +58,7 @@ function createServices(args: ParsedArgs, bufferLogService: BufferLogService): I
|
||||
const environmentService = new EnvironmentService(args, process.execPath);
|
||||
const consoleLogService = new ConsoleLogMainService(getLogLevel(environmentService));
|
||||
const logService = new MultiplexLogService([consoleLogService, bufferLogService]);
|
||||
const uriDisplayService = new UriDisplayService(environmentService, undefined);
|
||||
|
||||
process.once('exit', () => logService.dispose());
|
||||
|
||||
@@ -64,6 +66,7 @@ function createServices(args: ParsedArgs, bufferLogService: BufferLogService): I
|
||||
setTimeout(() => cleanupOlderLogs(environmentService).then(null, err => console.error(err)), 10000);
|
||||
|
||||
services.set(IEnvironmentService, environmentService);
|
||||
services.set(IUriDisplayService, uriDisplayService);
|
||||
services.set(ILogService, logService);
|
||||
services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService));
|
||||
services.set(IHistoryMainService, new SyncDescriptor(HistoryMainService));
|
||||
@@ -81,7 +84,7 @@ function createServices(args: ParsedArgs, bufferLogService: BufferLogService): I
|
||||
/**
|
||||
* Cleans up older logs, while keeping the 10 most recent ones.
|
||||
*/
|
||||
async function cleanupOlderLogs(environmentService: EnvironmentService): TPromise<void> {
|
||||
async function cleanupOlderLogs(environmentService: EnvironmentService): Promise<void> {
|
||||
const currentLog = path.basename(environmentService.logsPath);
|
||||
const logsRoot = path.dirname(environmentService.logsPath);
|
||||
const children = await readdir(logsRoot);
|
||||
@@ -323,6 +326,11 @@ function main() {
|
||||
VSCODE_NLS_CONFIG: process.env['VSCODE_NLS_CONFIG'],
|
||||
VSCODE_LOGS: process.env['VSCODE_LOGS']
|
||||
};
|
||||
|
||||
if (process.env['VSCODE_PORTABLE']) {
|
||||
instanceEnv['VSCODE_PORTABLE'] = process.env['VSCODE_PORTABLE'];
|
||||
}
|
||||
|
||||
assign(process.env, instanceEnv);
|
||||
|
||||
// Startup
|
||||
|
||||
799
src/vs/code/electron-main/menubar.ts
Normal file
799
src/vs/code/electron-main/menubar.ts
Normal file
@@ -0,0 +1,799 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { isMacintosh, language } from 'vs/base/common/platform';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { app, shell, Menu, MenuItem, BrowserWindow } from 'electron';
|
||||
import { OpenContext, IRunActionInWindowRequest } from 'vs/platform/windows/common/windows';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IUpdateService, StateType } from 'vs/platform/update/common/update';
|
||||
import product from 'vs/platform/node/product';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { mnemonicMenuLabel as baseMnemonicLabel, unmnemonicLabel } from 'vs/base/common/labels';
|
||||
import { IWindowsMainService, IWindowsCountChangedEvent } from 'vs/platform/windows/electron-main/windows';
|
||||
import { IHistoryMainService } from 'vs/platform/history/common/history';
|
||||
import { IWorkspaceIdentifier, getWorkspaceLabel, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IMenubarData, IMenubarKeybinding, MenubarMenuItem, isMenubarMenuItemSeparator, isMenubarMenuItemSubmenu, isMenubarMenuItemAction } from 'vs/platform/menubar/common/menubar';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IUriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay';
|
||||
|
||||
const telemetryFrom = 'menu';
|
||||
|
||||
export class Menubar {
|
||||
|
||||
private static readonly MAX_MENU_RECENT_ENTRIES = 10;
|
||||
private isQuitting: boolean;
|
||||
private appMenuInstalled: boolean;
|
||||
private closedLastWindow: boolean;
|
||||
|
||||
private menuUpdater: RunOnceScheduler;
|
||||
|
||||
private nativeTabMenuItems: Electron.MenuItem[];
|
||||
|
||||
private menubarMenus: IMenubarData;
|
||||
|
||||
private keybindings: { [commandId: string]: IMenubarKeybinding };
|
||||
|
||||
constructor(
|
||||
@IUpdateService private updateService: IUpdateService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IWindowsMainService private windowsMainService: IWindowsMainService,
|
||||
@IEnvironmentService private environmentService: IEnvironmentService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService,
|
||||
@IHistoryMainService private historyMainService: IHistoryMainService,
|
||||
@IUriDisplayService private uriDisplayService: IUriDisplayService
|
||||
) {
|
||||
this.menuUpdater = new RunOnceScheduler(() => this.doUpdateMenu(), 0);
|
||||
|
||||
this.keybindings = Object.create(null);
|
||||
|
||||
this.closedLastWindow = false;
|
||||
|
||||
this.install();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// Keep flag when app quits
|
||||
app.on('will-quit', () => {
|
||||
this.isQuitting = true;
|
||||
});
|
||||
|
||||
// // Listen to some events from window service to update menu
|
||||
this.historyMainService.onRecentlyOpenedChange(() => this.scheduleUpdateMenu());
|
||||
this.windowsMainService.onWindowsCountChanged(e => this.onWindowsCountChanged(e));
|
||||
// this.windowsMainService.onActiveWindowChanged(() => this.updateWorkspaceMenuItems());
|
||||
// this.windowsMainService.onWindowReady(() => this.updateWorkspaceMenuItems());
|
||||
// this.windowsMainService.onWindowClose(() => this.updateWorkspaceMenuItems());
|
||||
|
||||
// Listen to extension viewlets
|
||||
// ipc.on('vscode:extensionViewlets', (event: any, rawExtensionViewlets: string) => {
|
||||
// let extensionViewlets: IExtensionViewlet[] = [];
|
||||
// try {
|
||||
// extensionViewlets = JSON.parse(rawExtensionViewlets);
|
||||
// } catch (error) {
|
||||
// // Should not happen
|
||||
// }
|
||||
|
||||
// if (extensionViewlets.length) {
|
||||
// this.extensionViewlets = extensionViewlets;
|
||||
// this.updateMenu();
|
||||
// }
|
||||
// });
|
||||
|
||||
// Update when auto save config changes
|
||||
// this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e));
|
||||
|
||||
// Listen to update service
|
||||
// this.updateService.onStateChange(() => this.updateMenu());
|
||||
}
|
||||
|
||||
private get currentEnableMenuBarMnemonics(): boolean {
|
||||
let enableMenuBarMnemonics = this.configurationService.getValue<boolean>('window.enableMenuBarMnemonics');
|
||||
if (typeof enableMenuBarMnemonics !== 'boolean') {
|
||||
enableMenuBarMnemonics = true;
|
||||
}
|
||||
|
||||
return enableMenuBarMnemonics;
|
||||
}
|
||||
|
||||
private get currentEnableNativeTabs(): boolean {
|
||||
let enableNativeTabs = this.configurationService.getValue<boolean>('window.nativeTabs');
|
||||
if (typeof enableNativeTabs !== 'boolean') {
|
||||
enableNativeTabs = false;
|
||||
}
|
||||
return enableNativeTabs;
|
||||
}
|
||||
|
||||
updateMenu(menus: IMenubarData, windowId: number, additionalKeybindings?: Array<IMenubarKeybinding>) {
|
||||
this.menubarMenus = menus;
|
||||
if (additionalKeybindings) {
|
||||
additionalKeybindings.forEach(keybinding => {
|
||||
this.keybindings[keybinding.id] = keybinding;
|
||||
});
|
||||
}
|
||||
|
||||
this.scheduleUpdateMenu();
|
||||
}
|
||||
|
||||
|
||||
private scheduleUpdateMenu(): void {
|
||||
this.menuUpdater.schedule(); // buffer multiple attempts to update the menu
|
||||
}
|
||||
|
||||
private doUpdateMenu(): void {
|
||||
|
||||
// Due to limitations in Electron, it is not possible to update menu items dynamically. The suggested
|
||||
// workaround from Electron is to set the application menu again.
|
||||
// See also https://github.com/electron/electron/issues/846
|
||||
//
|
||||
// Run delayed to prevent updating menu while it is open
|
||||
if (!this.isQuitting) {
|
||||
setTimeout(() => {
|
||||
if (!this.isQuitting) {
|
||||
this.install();
|
||||
}
|
||||
}, 10 /* delay this because there is an issue with updating a menu when it is open */);
|
||||
}
|
||||
}
|
||||
|
||||
private onWindowsCountChanged(e: IWindowsCountChangedEvent): void {
|
||||
if (!isMacintosh) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update menu if window count goes from N > 0 or 0 > N to update menu item enablement
|
||||
if ((e.oldCount === 0 && e.newCount > 0) || (e.oldCount > 0 && e.newCount === 0)) {
|
||||
this.closedLastWindow = e.newCount === 0;
|
||||
this.scheduleUpdateMenu();
|
||||
}
|
||||
|
||||
// Update specific items that are dependent on window count
|
||||
else if (this.currentEnableNativeTabs) {
|
||||
this.nativeTabMenuItems.forEach(item => {
|
||||
if (item) {
|
||||
item.enabled = e.newCount > 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private install(): void {
|
||||
|
||||
// Menus
|
||||
const menubar = new Menu();
|
||||
|
||||
// Mac: Application
|
||||
let macApplicationMenuItem: Electron.MenuItem;
|
||||
if (isMacintosh) {
|
||||
const applicationMenu = new Menu();
|
||||
macApplicationMenuItem = new MenuItem({ label: product.nameShort, submenu: applicationMenu });
|
||||
this.setMacApplicationMenu(applicationMenu);
|
||||
menubar.append(macApplicationMenuItem);
|
||||
}
|
||||
|
||||
// Mac: Dock
|
||||
if (isMacintosh && !this.appMenuInstalled) {
|
||||
this.appMenuInstalled = true;
|
||||
|
||||
const dockMenu = new Menu();
|
||||
dockMenu.append(new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window")), click: () => this.windowsMainService.openNewWindow(OpenContext.DOCK) }));
|
||||
|
||||
app.dock.setMenu(dockMenu);
|
||||
}
|
||||
|
||||
// File
|
||||
const fileMenu = new Menu();
|
||||
const fileMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mFile', comment: ['&& denotes a mnemonic'] }, "&&File")), submenu: fileMenu });
|
||||
|
||||
if (this.shouldDrawMenu('File')) {
|
||||
if (this.shouldFallback('File')) {
|
||||
this.setFallbackMenuById(fileMenu, 'File');
|
||||
} else {
|
||||
this.setMenuById(fileMenu, 'File');
|
||||
}
|
||||
|
||||
menubar.append(fileMenuItem);
|
||||
}
|
||||
|
||||
|
||||
// Edit
|
||||
const editMenu = new Menu();
|
||||
const editMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mEdit', comment: ['&& denotes a mnemonic'] }, "&&Edit")), submenu: editMenu });
|
||||
|
||||
if (this.shouldDrawMenu('Edit')) {
|
||||
this.setMenuById(editMenu, 'Edit');
|
||||
menubar.append(editMenuItem);
|
||||
}
|
||||
|
||||
// Selection
|
||||
const selectionMenu = new Menu();
|
||||
const selectionMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mSelection', comment: ['&& denotes a mnemonic'] }, "&&Selection")), submenu: selectionMenu });
|
||||
|
||||
if (this.shouldDrawMenu('Selection')) {
|
||||
this.setMenuById(selectionMenu, 'Selection');
|
||||
menubar.append(selectionMenuItem);
|
||||
}
|
||||
|
||||
// View
|
||||
const viewMenu = new Menu();
|
||||
const viewMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mView', comment: ['&& denotes a mnemonic'] }, "&&View")), submenu: viewMenu });
|
||||
|
||||
if (this.shouldDrawMenu('View')) {
|
||||
this.setMenuById(viewMenu, 'View');
|
||||
menubar.append(viewMenuItem);
|
||||
}
|
||||
|
||||
// Layout
|
||||
const layoutMenu = new Menu();
|
||||
const layoutMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mLayout', comment: ['&& denotes a mnemonic'] }, "&&Layout")), submenu: layoutMenu });
|
||||
|
||||
if (this.shouldDrawMenu('Layout')) {
|
||||
this.setMenuById(layoutMenu, 'Layout');
|
||||
menubar.append(layoutMenuItem);
|
||||
}
|
||||
|
||||
// Go
|
||||
const gotoMenu = new Menu();
|
||||
const gotoMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go")), submenu: gotoMenu });
|
||||
|
||||
if (this.shouldDrawMenu('Go')) {
|
||||
this.setMenuById(gotoMenu, 'Go');
|
||||
menubar.append(gotoMenuItem);
|
||||
}
|
||||
|
||||
// Debug
|
||||
const debugMenu = new Menu();
|
||||
const debugMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug")), submenu: debugMenu });
|
||||
|
||||
if (this.shouldDrawMenu('Debug')) {
|
||||
this.setMenuById(debugMenu, 'Debug');
|
||||
menubar.append(debugMenuItem);
|
||||
}
|
||||
|
||||
// Tasks
|
||||
const taskMenu = new Menu();
|
||||
const taskMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mTask', comment: ['&& denotes a mnemonic'] }, "&&Tasks")), submenu: taskMenu });
|
||||
|
||||
if (this.shouldDrawMenu('Tasks')) {
|
||||
this.setMenuById(taskMenu, 'Tasks');
|
||||
menubar.append(taskMenuItem);
|
||||
}
|
||||
|
||||
// Mac: Window
|
||||
let macWindowMenuItem: Electron.MenuItem;
|
||||
if (this.shouldDrawMenu('Window')) {
|
||||
const windowMenu = new Menu();
|
||||
macWindowMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize('mWindow', "Window")), submenu: windowMenu, role: 'window' });
|
||||
this.setMacWindowMenu(windowMenu);
|
||||
}
|
||||
|
||||
if (macWindowMenuItem) {
|
||||
menubar.append(macWindowMenuItem);
|
||||
}
|
||||
|
||||
// Preferences
|
||||
if (!isMacintosh) {
|
||||
const preferencesMenu = new Menu();
|
||||
const preferencesMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mPreferences', comment: ['&& denotes a mnemonic'] }, "&&Preferences")), submenu: preferencesMenu });
|
||||
|
||||
if (this.shouldDrawMenu('Preferences')) {
|
||||
if (this.shouldFallback('Preferences')) {
|
||||
this.setFallbackMenuById(preferencesMenu, 'Preferences');
|
||||
} else {
|
||||
this.setMenuById(preferencesMenu, 'Preferences');
|
||||
}
|
||||
menubar.append(preferencesMenuItem);
|
||||
}
|
||||
}
|
||||
|
||||
// Help
|
||||
const helpMenu = new Menu();
|
||||
const helpMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mHelp', comment: ['&& denotes a mnemonic'] }, "&&Help")), submenu: helpMenu, role: 'help' });
|
||||
|
||||
if (this.shouldDrawMenu('Help')) {
|
||||
if (this.shouldFallback('Help')) {
|
||||
this.setFallbackMenuById(helpMenu, 'Help');
|
||||
} else {
|
||||
this.setMenuById(helpMenu, 'Help');
|
||||
}
|
||||
menubar.append(helpMenuItem);
|
||||
}
|
||||
|
||||
if (menubar.items && menubar.items.length > 0) {
|
||||
Menu.setApplicationMenu(menubar);
|
||||
} else {
|
||||
Menu.setApplicationMenu(null);
|
||||
}
|
||||
}
|
||||
|
||||
private setMacApplicationMenu(macApplicationMenu: Electron.Menu): void {
|
||||
const about = new MenuItem({ label: nls.localize('mAbout', "About {0}", product.nameLong), role: 'about' });
|
||||
const checkForUpdates = this.getUpdateMenuItems();
|
||||
|
||||
let preferences;
|
||||
if (this.shouldDrawMenu('Preferences')) {
|
||||
const preferencesMenu = new Menu();
|
||||
this.setMenuById(preferencesMenu, 'Preferences');
|
||||
preferences = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miPreferences', comment: ['&& denotes a mnemonic'] }, "&&Preferences")), submenu: preferencesMenu });
|
||||
}
|
||||
|
||||
const servicesMenu = new Menu();
|
||||
const services = new MenuItem({ label: nls.localize('mServices', "Services"), role: 'services', submenu: servicesMenu });
|
||||
const hide = new MenuItem({ label: nls.localize('mHide', "Hide {0}", product.nameLong), role: 'hide', accelerator: 'Command+H' });
|
||||
const hideOthers = new MenuItem({ label: nls.localize('mHideOthers', "Hide Others"), role: 'hideothers', accelerator: 'Command+Alt+H' });
|
||||
const showAll = new MenuItem({ label: nls.localize('mShowAll', "Show All"), role: 'unhide' });
|
||||
const quit = new MenuItem(this.likeAction('workbench.action.quit', {
|
||||
label: nls.localize('miQuit', "Quit {0}", product.nameLong), click: () => {
|
||||
if (this.windowsMainService.getWindowCount() === 0 || !!BrowserWindow.getFocusedWindow()) {
|
||||
this.windowsMainService.quit(); // fix for https://github.com/Microsoft/vscode/issues/39191
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
const actions = [about];
|
||||
actions.push(...checkForUpdates);
|
||||
|
||||
if (preferences) {
|
||||
actions.push(...[
|
||||
__separator__(),
|
||||
preferences
|
||||
]);
|
||||
}
|
||||
|
||||
actions.push(...[
|
||||
__separator__(),
|
||||
services,
|
||||
__separator__(),
|
||||
hide,
|
||||
hideOthers,
|
||||
showAll,
|
||||
__separator__(),
|
||||
quit
|
||||
]);
|
||||
|
||||
actions.forEach(i => macApplicationMenu.append(i));
|
||||
}
|
||||
|
||||
private shouldDrawMenu(menuId: string): boolean {
|
||||
// We need to draw an empty menu to override the electron default
|
||||
if (!isMacintosh && this.configurationService.getValue('window.titleBarStyle') === 'custom') {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (menuId) {
|
||||
case 'File':
|
||||
case 'Help':
|
||||
if (isMacintosh) {
|
||||
return (this.windowsMainService.getWindowCount() === 0 && this.closedLastWindow) || (!!this.menubarMenus && !!this.menubarMenus[menuId]);
|
||||
}
|
||||
|
||||
case 'Window':
|
||||
if (isMacintosh) {
|
||||
return (this.windowsMainService.getWindowCount() === 0 && this.closedLastWindow) || !!this.menubarMenus;
|
||||
}
|
||||
|
||||
default:
|
||||
return this.windowsMainService.getWindowCount() > 0 && (!!this.menubarMenus && !!this.menubarMenus[menuId]);
|
||||
}
|
||||
}
|
||||
|
||||
private shouldFallback(menuId: string): boolean {
|
||||
return this.shouldDrawMenu(menuId) && (this.windowsMainService.getWindowCount() === 0 && this.closedLastWindow && isMacintosh);
|
||||
}
|
||||
|
||||
private setFallbackMenuById(menu: Electron.Menu, menuId: string): void {
|
||||
switch (menuId) {
|
||||
case 'File':
|
||||
const newFile = new MenuItem(this.likeAction('workbench.action.files.newUntitledFile', { label: this.mnemonicLabel(nls.localize({ key: 'miNewFile', comment: ['&& denotes a mnemonic'] }, "&&New File")), click: () => this.windowsMainService.openNewWindow(OpenContext.MENU) }));
|
||||
|
||||
const newWindow = new MenuItem(this.likeAction('workbench.action.newWindow', { label: this.mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window")), click: () => this.windowsMainService.openNewWindow(OpenContext.MENU) }));
|
||||
|
||||
const open = new MenuItem(this.likeAction('workbench.action.files.openFileFolder', { label: this.mnemonicLabel(nls.localize({ key: 'miOpen', comment: ['&& denotes a mnemonic'] }, "&&Open...")), click: (menuItem, win, event) => this.windowsMainService.pickFileFolderAndOpen({ forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }) }));
|
||||
|
||||
const openWorkspace = new MenuItem(this.likeAction('workbench.action.openWorkspace', { label: this.mnemonicLabel(nls.localize({ key: 'miOpenWorkspace', comment: ['&& denotes a mnemonic'] }, "Open Wor&&kspace...")), click: (menuItem, win, event) => this.windowsMainService.pickWorkspaceAndOpen({ forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }) }));
|
||||
|
||||
const openRecentMenu = new Menu();
|
||||
this.setFallbackMenuById(openRecentMenu, 'Recent');
|
||||
const openRecent = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miOpenRecent', comment: ['&& denotes a mnemonic'] }, "Open &&Recent")), submenu: openRecentMenu });
|
||||
|
||||
menu.append(newFile);
|
||||
menu.append(newWindow);
|
||||
menu.append(__separator__());
|
||||
menu.append(open);
|
||||
menu.append(openWorkspace);
|
||||
menu.append(openRecent);
|
||||
|
||||
break;
|
||||
|
||||
case 'Recent':
|
||||
menu.append(this.createMenuItem(nls.localize({ key: 'miReopenClosedEditor', comment: ['&& denotes a mnemonic'] }, "&&Reopen Closed Editor"), 'workbench.action.reopenClosedEditor'));
|
||||
|
||||
this.insertRecentMenuItems(menu);
|
||||
|
||||
menu.append(__separator__());
|
||||
menu.append(this.createMenuItem(nls.localize({ key: 'miMore', comment: ['&& denotes a mnemonic'] }, "&&More..."), 'workbench.action.openRecent'));
|
||||
menu.append(__separator__());
|
||||
menu.append(new MenuItem(this.likeAction('workbench.action.clearRecentFiles', { label: this.mnemonicLabel(nls.localize({ key: 'miClearRecentOpen', comment: ['&& denotes a mnemonic'] }, "&&Clear Recently Opened")), click: () => this.historyMainService.clearRecentlyOpened() })));
|
||||
|
||||
break;
|
||||
|
||||
case 'Help':
|
||||
let twitterItem: MenuItem;
|
||||
if (product.twitterUrl) {
|
||||
twitterItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miTwitter', comment: ['&& denotes a mnemonic'] }, "&&Join us on Twitter")), click: () => this.openUrl(product.twitterUrl, 'openTwitterUrl') });
|
||||
}
|
||||
|
||||
let featureRequestsItem: MenuItem;
|
||||
if (product.requestFeatureUrl) {
|
||||
featureRequestsItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miUserVoice', comment: ['&& denotes a mnemonic'] }, "&&Search Feature Requests")), click: () => this.openUrl(product.requestFeatureUrl, 'openUserVoiceUrl') });
|
||||
}
|
||||
|
||||
let reportIssuesItem: MenuItem;
|
||||
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");
|
||||
|
||||
reportIssuesItem = new MenuItem({ label: this.mnemonicLabel(label), click: () => this.openUrl(product.reportIssueUrl, 'openReportIssues') });
|
||||
}
|
||||
|
||||
let licenseItem: MenuItem;
|
||||
if (product.privacyStatementUrl) {
|
||||
licenseItem = new MenuItem({
|
||||
label: this.mnemonicLabel(nls.localize({ key: 'miLicense', comment: ['&& denotes a mnemonic'] }, "View &&License")), click: () => {
|
||||
if (language) {
|
||||
const queryArgChar = product.licenseUrl.indexOf('?') > 0 ? '&' : '?';
|
||||
this.openUrl(`${product.licenseUrl}${queryArgChar}lang=${language}`, 'openLicenseUrl');
|
||||
} else {
|
||||
this.openUrl(product.licenseUrl, 'openLicenseUrl');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let privacyStatementItem: MenuItem;
|
||||
if (product.privacyStatementUrl) {
|
||||
privacyStatementItem = new MenuItem({
|
||||
label: this.mnemonicLabel(nls.localize({ key: 'miPrivacyStatement', comment: ['&& denotes a mnemonic'] }, "&&Privacy Statement")), click: () => {
|
||||
if (language) {
|
||||
const queryArgChar = product.licenseUrl.indexOf('?') > 0 ? '&' : '?';
|
||||
this.openUrl(`${product.privacyStatementUrl}${queryArgChar}lang=${language}`, 'openPrivacyStatement');
|
||||
} else {
|
||||
this.openUrl(product.privacyStatementUrl, 'openPrivacyStatement');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (twitterItem) { menu.append(twitterItem); }
|
||||
if (featureRequestsItem) { menu.append(featureRequestsItem); }
|
||||
if (reportIssuesItem) { menu.append(reportIssuesItem); }
|
||||
if ((twitterItem || featureRequestsItem || reportIssuesItem) && (licenseItem || privacyStatementItem)) { menu.append(__separator__()); }
|
||||
if (licenseItem) { menu.append(licenseItem); }
|
||||
if (privacyStatementItem) { menu.append(privacyStatementItem); }
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private setMenu(menu: Electron.Menu, items: Array<MenubarMenuItem>) {
|
||||
items.forEach((item: MenubarMenuItem) => {
|
||||
if (isMenubarMenuItemSeparator(item)) {
|
||||
menu.append(__separator__());
|
||||
} else if (isMenubarMenuItemSubmenu(item)) {
|
||||
const submenu = new Menu();
|
||||
const submenuItem = new MenuItem({ label: this.mnemonicLabel(item.label), submenu: submenu });
|
||||
this.setMenu(submenu, item.submenu.items);
|
||||
menu.append(submenuItem);
|
||||
} else if (isMenubarMenuItemAction(item)) {
|
||||
if (item.id === 'workbench.action.openRecent') {
|
||||
this.insertRecentMenuItems(menu);
|
||||
} else if (item.id === 'workbench.action.showAboutDialog') {
|
||||
this.insertCheckForUpdatesItems(menu);
|
||||
}
|
||||
|
||||
// Store the keybinding
|
||||
if (item.keybinding) {
|
||||
this.keybindings[item.id] = item.keybinding;
|
||||
} else if (this.keybindings[item.id]) {
|
||||
this.keybindings[item.id] = undefined;
|
||||
}
|
||||
|
||||
const menuItem = this.createMenuItem(item.label, item.id, item.enabled, item.checked);
|
||||
menu.append(menuItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private setMenuById(menu: Electron.Menu, menuId: string): void {
|
||||
if (this.menubarMenus && this.menubarMenus[menuId]) {
|
||||
this.setMenu(menu, this.menubarMenus[menuId].items);
|
||||
}
|
||||
}
|
||||
|
||||
private insertCheckForUpdatesItems(menu: Electron.Menu) {
|
||||
const updateItems = this.getUpdateMenuItems();
|
||||
if (updateItems.length) {
|
||||
updateItems.forEach(i => menu.append(i));
|
||||
menu.append(__separator__());
|
||||
}
|
||||
}
|
||||
|
||||
private insertRecentMenuItems(menu: Electron.Menu) {
|
||||
const { workspaces, files } = this.historyMainService.getRecentlyOpened();
|
||||
|
||||
// Workspaces
|
||||
if (workspaces.length > 0) {
|
||||
for (let i = 0; i < Menubar.MAX_MENU_RECENT_ENTRIES && i < workspaces.length; i++) {
|
||||
menu.append(this.createOpenRecentMenuItem(workspaces[i], 'openRecentWorkspace', false));
|
||||
}
|
||||
|
||||
menu.append(__separator__());
|
||||
}
|
||||
|
||||
// Files
|
||||
if (files.length > 0) {
|
||||
for (let i = 0; i < Menubar.MAX_MENU_RECENT_ENTRIES && i < files.length; i++) {
|
||||
menu.append(this.createOpenRecentMenuItem(files[i], 'openRecentFile', true));
|
||||
}
|
||||
|
||||
menu.append(__separator__());
|
||||
}
|
||||
}
|
||||
|
||||
private createOpenRecentMenuItem(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | string, commandId: string, isFile: boolean): Electron.MenuItem {
|
||||
let label: string;
|
||||
let uri: URI;
|
||||
if (isSingleFolderWorkspaceIdentifier(workspace)) {
|
||||
label = unmnemonicLabel(getWorkspaceLabel(workspace, this.environmentService, this.uriDisplayService, { verbose: true }));
|
||||
uri = workspace;
|
||||
} else if (isWorkspaceIdentifier(workspace)) {
|
||||
label = getWorkspaceLabel(workspace, this.environmentService, this.uriDisplayService, { verbose: true });
|
||||
uri = URI.file(workspace.configPath);
|
||||
} else {
|
||||
uri = URI.file(workspace);
|
||||
label = unmnemonicLabel(this.uriDisplayService.getLabel(uri));
|
||||
}
|
||||
|
||||
return new MenuItem(this.likeAction(commandId, {
|
||||
label,
|
||||
click: (menuItem, win, event) => {
|
||||
const openInNewWindow = this.isOptionClick(event);
|
||||
const success = this.windowsMainService.open({
|
||||
context: OpenContext.MENU,
|
||||
cli: this.environmentService.args,
|
||||
urisToOpen: [uri],
|
||||
forceNewWindow: openInNewWindow,
|
||||
forceOpenWorkspaceAsFile: isFile
|
||||
}).length > 0;
|
||||
|
||||
if (!success) {
|
||||
this.historyMainService.removeFromRecentlyOpened([workspace]);
|
||||
}
|
||||
}
|
||||
}, false));
|
||||
}
|
||||
|
||||
private isOptionClick(event: Electron.Event): boolean {
|
||||
return event && ((!isMacintosh && (event.ctrlKey || event.shiftKey)) || (isMacintosh && (event.metaKey || event.altKey)));
|
||||
}
|
||||
|
||||
private setMacWindowMenu(macWindowMenu: Electron.Menu): void {
|
||||
const minimize = new MenuItem({ label: nls.localize('mMinimize', "Minimize"), role: 'minimize', accelerator: 'Command+M', enabled: this.windowsMainService.getWindowCount() > 0 });
|
||||
const zoom = new MenuItem({ label: nls.localize('mZoom', "Zoom"), role: 'zoom', enabled: this.windowsMainService.getWindowCount() > 0 });
|
||||
const bringAllToFront = new MenuItem({ label: nls.localize('mBringToFront', "Bring All to Front"), role: 'front', enabled: this.windowsMainService.getWindowCount() > 0 });
|
||||
const switchWindow = this.createMenuItem(nls.localize({ key: 'miSwitchWindow', comment: ['&& denotes a mnemonic'] }, "Switch &&Window..."), 'workbench.action.switchWindow');
|
||||
|
||||
this.nativeTabMenuItems = [];
|
||||
const nativeTabMenuItems: Electron.MenuItem[] = [];
|
||||
if (this.currentEnableNativeTabs) {
|
||||
const hasMultipleWindows = this.windowsMainService.getWindowCount() > 1;
|
||||
|
||||
this.nativeTabMenuItems.push(this.createMenuItem(nls.localize('mShowPreviousTab', "Show Previous Tab"), 'workbench.action.showPreviousWindowTab', hasMultipleWindows));
|
||||
this.nativeTabMenuItems.push(this.createMenuItem(nls.localize('mShowNextTab', "Show Next Tab"), 'workbench.action.showNextWindowTab', hasMultipleWindows));
|
||||
this.nativeTabMenuItems.push(this.createMenuItem(nls.localize('mMoveTabToNewWindow', "Move Tab to New Window"), 'workbench.action.moveWindowTabToNewWindow', hasMultipleWindows));
|
||||
this.nativeTabMenuItems.push(this.createMenuItem(nls.localize('mMergeAllWindows', "Merge All Windows"), 'workbench.action.mergeAllWindowTabs', hasMultipleWindows));
|
||||
|
||||
nativeTabMenuItems.push(__separator__(), ...this.nativeTabMenuItems);
|
||||
} else {
|
||||
this.nativeTabMenuItems = [];
|
||||
}
|
||||
|
||||
[
|
||||
minimize,
|
||||
zoom,
|
||||
switchWindow,
|
||||
...nativeTabMenuItems,
|
||||
__separator__(),
|
||||
bringAllToFront
|
||||
].forEach(item => macWindowMenu.append(item));
|
||||
}
|
||||
|
||||
private getUpdateMenuItems(): Electron.MenuItem[] {
|
||||
const state = this.updateService.state;
|
||||
|
||||
switch (state.type) {
|
||||
case StateType.Uninitialized:
|
||||
return [];
|
||||
|
||||
case StateType.Idle:
|
||||
return [new MenuItem({
|
||||
label: nls.localize('miCheckForUpdates', "Check for Updates..."), click: () => setTimeout(() => {
|
||||
this.reportMenuActionTelemetry('CheckForUpdate');
|
||||
|
||||
const focusedWindow = this.windowsMainService.getFocusedWindow();
|
||||
const context = focusedWindow ? { windowId: focusedWindow.id } : null;
|
||||
this.updateService.checkForUpdates(context);
|
||||
}, 0)
|
||||
})];
|
||||
|
||||
case StateType.CheckingForUpdates:
|
||||
return [new MenuItem({ label: nls.localize('miCheckingForUpdates', "Checking For Updates..."), enabled: false })];
|
||||
|
||||
case StateType.AvailableForDownload:
|
||||
return [new MenuItem({
|
||||
label: nls.localize('miDownloadUpdate', "Download Available Update"), click: () => {
|
||||
this.updateService.downloadUpdate();
|
||||
}
|
||||
})];
|
||||
|
||||
case StateType.Downloading:
|
||||
return [new MenuItem({ label: nls.localize('miDownloadingUpdate', "Downloading Update..."), enabled: false })];
|
||||
|
||||
case StateType.Downloaded:
|
||||
return [new MenuItem({
|
||||
label: nls.localize('miInstallUpdate', "Install Update..."), click: () => {
|
||||
this.reportMenuActionTelemetry('InstallUpdate');
|
||||
this.updateService.applyUpdate();
|
||||
}
|
||||
})];
|
||||
|
||||
case StateType.Updating:
|
||||
return [new MenuItem({ label: nls.localize('miInstallingUpdate', "Installing Update..."), enabled: false })];
|
||||
|
||||
case StateType.Ready:
|
||||
return [new MenuItem({
|
||||
label: nls.localize('miRestartToUpdate', "Restart to Update..."), click: () => {
|
||||
this.reportMenuActionTelemetry('RestartToUpdate');
|
||||
this.updateService.quitAndInstall();
|
||||
}
|
||||
})];
|
||||
}
|
||||
}
|
||||
|
||||
private createMenuItem(label: string, commandId: string | string[], enabled?: boolean, checked?: boolean): Electron.MenuItem;
|
||||
private createMenuItem(label: string, click: () => void, enabled?: boolean, checked?: boolean): Electron.MenuItem;
|
||||
private createMenuItem(arg1: string, arg2: any, arg3?: boolean, arg4?: boolean): Electron.MenuItem {
|
||||
const label = this.mnemonicLabel(arg1);
|
||||
const click: () => void = (typeof arg2 === 'function') ? arg2 : (menuItem: Electron.MenuItem, win: Electron.BrowserWindow, event: Electron.Event) => {
|
||||
let commandId = arg2;
|
||||
if (Array.isArray(arg2)) {
|
||||
commandId = this.isOptionClick(event) ? arg2[1] : arg2[0]; // support alternative action if we got multiple action Ids and the option key was pressed while invoking
|
||||
}
|
||||
|
||||
this.runActionInRenderer(commandId);
|
||||
};
|
||||
const enabled = typeof arg3 === 'boolean' ? arg3 : this.windowsMainService.getWindowCount() > 0;
|
||||
const checked = typeof arg4 === 'boolean' ? arg4 : false;
|
||||
|
||||
const options: Electron.MenuItemConstructorOptions = {
|
||||
label,
|
||||
click,
|
||||
enabled
|
||||
};
|
||||
|
||||
if (checked) {
|
||||
options['type'] = 'checkbox';
|
||||
options['checked'] = checked;
|
||||
}
|
||||
|
||||
let commandId: string;
|
||||
if (typeof arg2 === 'string') {
|
||||
commandId = arg2;
|
||||
} else if (Array.isArray(arg2)) {
|
||||
commandId = arg2[0];
|
||||
}
|
||||
|
||||
// Add role for special case menu items
|
||||
if (isMacintosh) {
|
||||
if (commandId === 'editor.action.clipboardCutAction') {
|
||||
options['role'] = 'cut';
|
||||
} else if (commandId === 'editor.action.clipboardCopyAction') {
|
||||
options['role'] = 'copy';
|
||||
} else if (commandId === 'editor.action.clipboardPasteAction') {
|
||||
options['role'] = 'paste';
|
||||
}
|
||||
}
|
||||
|
||||
return new MenuItem(this.withKeybinding(commandId, options));
|
||||
}
|
||||
|
||||
private runActionInRenderer(id: string): void {
|
||||
// We make sure to not run actions when the window has no focus, this helps
|
||||
// for https://github.com/Microsoft/vscode/issues/25907 and specifically for
|
||||
// https://github.com/Microsoft/vscode/issues/11928
|
||||
const activeWindow = this.windowsMainService.getFocusedWindow();
|
||||
if (activeWindow) {
|
||||
this.windowsMainService.sendToFocused('vscode:runAction', { id, from: 'menu' } as IRunActionInWindowRequest);
|
||||
}
|
||||
}
|
||||
|
||||
private withKeybinding(commandId: string, options: Electron.MenuItemConstructorOptions): Electron.MenuItemConstructorOptions {
|
||||
const binding = this.keybindings[commandId];
|
||||
|
||||
// Apply binding if there is one
|
||||
if (binding && binding.label) {
|
||||
|
||||
// if the binding is native, we can just apply it
|
||||
if (binding.isNative) {
|
||||
options.accelerator = binding.label;
|
||||
}
|
||||
|
||||
// the keybinding is not native so we cannot show it as part of the accelerator of
|
||||
// the menu item. we fallback to a different strategy so that we always display it
|
||||
else {
|
||||
const bindingIndex = options.label.indexOf('[');
|
||||
if (bindingIndex >= 0) {
|
||||
options.label = `${options.label.substr(0, bindingIndex)} [${binding.label}]`;
|
||||
} else {
|
||||
options.label = `${options.label} [${binding.label}]`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unset bindings if there is none
|
||||
else {
|
||||
options.accelerator = void 0;
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
private likeAction(commandId: string, options: Electron.MenuItemConstructorOptions, setAccelerator = !options.accelerator): Electron.MenuItemConstructorOptions {
|
||||
if (setAccelerator) {
|
||||
options = this.withKeybinding(commandId, options);
|
||||
}
|
||||
|
||||
const originalClick = options.click;
|
||||
options.click = (item, window, event) => {
|
||||
this.reportMenuActionTelemetry(commandId);
|
||||
if (originalClick) {
|
||||
originalClick(item, window, event);
|
||||
}
|
||||
};
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
private openUrl(url: string, id: string): void {
|
||||
shell.openExternal(url);
|
||||
this.reportMenuActionTelemetry(id);
|
||||
}
|
||||
|
||||
private reportMenuActionTelemetry(id: string): void {
|
||||
/* __GDPR__
|
||||
"workbenchActionExecuted" : {
|
||||
"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"from": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('workbenchActionExecuted', { id, from: telemetryFrom });
|
||||
}
|
||||
|
||||
private mnemonicLabel(label: string): string {
|
||||
return baseMnemonicLabel(label, !this.currentEnableMenuBarMnemonics);
|
||||
}
|
||||
}
|
||||
|
||||
function __separator__(): Electron.MenuItem {
|
||||
return new MenuItem({ type: 'separator' });
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import * as nls from 'vs/nls';
|
||||
import { isMacintosh, isLinux, isWindows, language } from 'vs/base/common/platform';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { ipcMain as ipc, app, shell, Menu, MenuItem, BrowserWindow } from 'electron';
|
||||
import { app, shell, Menu, MenuItem, BrowserWindow } from 'electron';
|
||||
import { OpenContext, IRunActionInWindowRequest, IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
import { AutoSaveConfiguration } from 'vs/platform/files/common/files';
|
||||
@@ -18,16 +18,13 @@ import { IUpdateService, StateType } from 'vs/platform/update/common/update';
|
||||
import product from 'vs/platform/node/product';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { mnemonicMenuLabel as baseMnemonicLabel, unmnemonicLabel, getPathLabel } from 'vs/base/common/labels';
|
||||
import { mnemonicMenuLabel as baseMnemonicLabel, unmnemonicLabel } from 'vs/base/common/labels';
|
||||
import { KeybindingsResolver } from 'vs/code/electron-main/keyboard';
|
||||
import { IWindowsMainService, IWindowsCountChangedEvent } from 'vs/platform/windows/electron-main/windows';
|
||||
import { IHistoryMainService } from 'vs/platform/history/common/history';
|
||||
import { IWorkspaceIdentifier, getWorkspaceLabel, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
|
||||
interface IExtensionViewlet {
|
||||
id: string;
|
||||
label: string;
|
||||
}
|
||||
import { IWorkspaceIdentifier, getWorkspaceLabel, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IUriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay';
|
||||
|
||||
interface IMenuItemClickHandler {
|
||||
inDevTools: (contents: Electron.WebContents) => void;
|
||||
@@ -57,8 +54,6 @@ export class CodeMenu {
|
||||
|
||||
private keybindingsResolver: KeybindingsResolver;
|
||||
|
||||
private extensionViewlets: IExtensionViewlet[];
|
||||
|
||||
private closeFolder: Electron.MenuItem;
|
||||
private closeWorkspace: Electron.MenuItem;
|
||||
|
||||
@@ -72,9 +67,9 @@ export class CodeMenu {
|
||||
@IWindowsService private windowsService: IWindowsService,
|
||||
@IEnvironmentService private environmentService: IEnvironmentService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService,
|
||||
@IHistoryMainService private historyMainService: IHistoryMainService
|
||||
@IHistoryMainService private historyMainService: IHistoryMainService,
|
||||
@IUriDisplayService private uriDisplayService: IUriDisplayService
|
||||
) {
|
||||
this.extensionViewlets = [];
|
||||
this.nativeTabMenuItems = [];
|
||||
|
||||
this.menuUpdater = new RunOnceScheduler(() => this.doUpdateMenu(), 0);
|
||||
@@ -99,21 +94,6 @@ export class CodeMenu {
|
||||
this.windowsMainService.onWindowReady(() => this.updateWorkspaceMenuItems());
|
||||
this.windowsMainService.onWindowClose(() => this.updateWorkspaceMenuItems());
|
||||
|
||||
// Listen to extension viewlets
|
||||
ipc.on('vscode:extensionViewlets', (event: any, rawExtensionViewlets: string) => {
|
||||
let extensionViewlets: IExtensionViewlet[] = [];
|
||||
try {
|
||||
extensionViewlets = JSON.parse(rawExtensionViewlets);
|
||||
} catch (error) {
|
||||
// Should not happen
|
||||
}
|
||||
|
||||
if (extensionViewlets.length) {
|
||||
this.extensionViewlets = extensionViewlets;
|
||||
this.updateMenu();
|
||||
}
|
||||
});
|
||||
|
||||
// Update when auto save config changes
|
||||
this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e));
|
||||
|
||||
@@ -217,7 +197,7 @@ export class CodeMenu {
|
||||
private updateWorkspaceMenuItems(): void {
|
||||
const window = this.windowsMainService.getLastActiveWindow();
|
||||
const isInWorkspaceContext = window && !!window.openedWorkspace;
|
||||
const isInFolderContext = window && !!window.openedFolderPath;
|
||||
const isInFolderContext = window && !!window.openedFolderUri;
|
||||
|
||||
this.closeWorkspace.visible = isInWorkspaceContext;
|
||||
this.closeFolder.visible = !isInWorkspaceContext;
|
||||
@@ -410,7 +390,8 @@ export class CodeMenu {
|
||||
const saveAllFiles = this.createMenuItem(nls.localize({ key: 'miSaveAll', comment: ['&& denotes a mnemonic'] }, "Save A&&ll"), 'workbench.action.files.saveAll');
|
||||
|
||||
const autoSaveEnabled = [AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE].some(s => this.currentAutoSaveSetting === s);
|
||||
const autoSave = new MenuItem(this.likeAction('vscode.toggleAutoSave', { label: this.mnemonicLabel(nls.localize('miAutoSave', "Auto Save")), type: 'checkbox', checked: autoSaveEnabled, enabled: this.windowsMainService.getWindowCount() > 0, click: () => this.windowsMainService.sendToFocused('vscode.toggleAutoSave') }, false));
|
||||
|
||||
const autoSave = this.createMenuItem(this.mnemonicLabel(nls.localize('miAutoSave', "Auto Save")), 'workbench.action.toggleAutoSave', this.windowsMainService.getWindowCount() > 0, autoSaveEnabled);
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
const installVsixExtension = this.createMenuItem(nls.localize({ key: 'miinstallVsix', comment: ['&& denotes a mnemonic'] }, "Install Extension from VSIX Package"), 'workbench.extensions.action.installVSIX');
|
||||
@@ -521,13 +502,16 @@ export class CodeMenu {
|
||||
|
||||
private createOpenRecentMenuItem(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | string, commandId: string, isFile: boolean): Electron.MenuItem {
|
||||
let label: string;
|
||||
let path: string;
|
||||
if (isSingleFolderWorkspaceIdentifier(workspace) || typeof workspace === 'string') {
|
||||
label = unmnemonicLabel(getPathLabel(workspace, null, this.environmentService));
|
||||
path = workspace;
|
||||
let uri: URI;
|
||||
if (isSingleFolderWorkspaceIdentifier(workspace)) {
|
||||
label = unmnemonicLabel(getWorkspaceLabel(workspace, this.environmentService, this.uriDisplayService, { verbose: true }));
|
||||
uri = workspace;
|
||||
} else if (isWorkspaceIdentifier(workspace)) {
|
||||
label = getWorkspaceLabel(workspace, this.environmentService, this.uriDisplayService, { verbose: true });
|
||||
uri = URI.file(workspace.configPath);
|
||||
} else {
|
||||
label = getWorkspaceLabel(workspace, this.environmentService, { verbose: true });
|
||||
path = workspace.configPath;
|
||||
uri = URI.file(workspace);
|
||||
label = unmnemonicLabel(this.uriDisplayService.getLabel(uri));
|
||||
}
|
||||
|
||||
return new MenuItem(this.likeAction(commandId, {
|
||||
@@ -537,12 +521,13 @@ export class CodeMenu {
|
||||
const success = this.windowsMainService.open({
|
||||
context: OpenContext.MENU,
|
||||
cli: this.environmentService.args,
|
||||
pathsToOpen: [path], forceNewWindow: openInNewWindow,
|
||||
urisToOpen: [uri],
|
||||
forceNewWindow: openInNewWindow,
|
||||
forceOpenWorkspaceAsFile: isFile
|
||||
}).length > 0;
|
||||
|
||||
if (!success) {
|
||||
this.historyMainService.removeFromRecentlyOpened([isSingleFolderWorkspaceIdentifier(workspace) ? workspace : workspace.configPath]);
|
||||
this.historyMainService.removeFromRecentlyOpened([workspace]);
|
||||
}
|
||||
}
|
||||
}, false));
|
||||
@@ -695,6 +680,9 @@ export class CodeMenu {
|
||||
*/
|
||||
|
||||
private setViewMenu(viewMenu: Electron.Menu): void {
|
||||
const commands = this.createMenuItem(nls.localize({ key: 'miCommandPalette', comment: ['&& denotes a mnemonic'] }, "&&Command Palette..."), 'workbench.action.showCommands');
|
||||
const openView = this.createMenuItem(nls.localize({ key: 'miOpenView', comment: ['&& denotes a mnemonic'] }, "&&Open View..."), 'workbench.action.openView');
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
const servers = this.createMenuItem(nls.localize({ key: 'miViewRegisteredServers', comment: ['&& denotes a mnemonic'] }, "&&Servers"), 'workbench.view.connections');
|
||||
const tasks = this.createMenuItem(nls.localize({ key: 'miViewTasks', comment: ['&& denotes a mnemonic'] }, "&&Tasks"), 'workbench.view.taskHistory');
|
||||
@@ -704,33 +692,23 @@ export class CodeMenu {
|
||||
// {{SQL CARBON EDIT}}
|
||||
// const debug = this.createMenuItem(nls.localize({ key: 'miViewDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug"), 'workbench.view.debug');
|
||||
const extensions = this.createMenuItem(nls.localize({ key: 'miViewExtensions', comment: ['&& denotes a mnemonic'] }, "E&&xtensions"), 'workbench.view.extensions');
|
||||
|
||||
// Panels
|
||||
const output = this.createMenuItem(nls.localize({ key: 'miToggleOutput', comment: ['&& denotes a mnemonic'] }, "&&Output"), 'workbench.action.output.toggleOutput');
|
||||
// {{SQL CARBON EDIT}}
|
||||
// const debugConsole = this.createMenuItem(nls.localize({ key: 'miToggleDebugConsole', comment: ['&& denotes a mnemonic'] }, "De&&bug Console"), 'workbench.debug.action.toggleRepl');
|
||||
const integratedTerminal = this.createMenuItem(nls.localize({ key: 'miToggleIntegratedTerminal', comment: ['&& denotes a mnemonic'] }, "&&Integrated Terminal"), 'workbench.action.terminal.toggleTerminal');
|
||||
const terminal = this.createMenuItem(nls.localize({ key: 'miToggleTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal"), 'workbench.action.terminal.toggleTerminal');
|
||||
const problems = this.createMenuItem(nls.localize({ key: 'miMarker', comment: ['&& denotes a mnemonic'] }, "&&Problems"), 'workbench.actions.view.problems');
|
||||
|
||||
let additionalViewlets: Electron.MenuItem;
|
||||
if (this.extensionViewlets.length) {
|
||||
const additionalViewletsMenu = new Menu();
|
||||
// Appearance
|
||||
|
||||
this.extensionViewlets.forEach(viewlet => {
|
||||
additionalViewletsMenu.append(this.createMenuItem(viewlet.label, viewlet.id));
|
||||
});
|
||||
|
||||
additionalViewlets = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miAdditionalViews', comment: ['&& denotes a mnemonic'] }, "Additional &&Views")), submenu: additionalViewletsMenu, enabled: true });
|
||||
}
|
||||
|
||||
const commands = this.createMenuItem(nls.localize({ key: 'miCommandPalette', comment: ['&& denotes a mnemonic'] }, "&&Command Palette..."), 'workbench.action.showCommands');
|
||||
const openView = this.createMenuItem(nls.localize({ key: 'miOpenView', comment: ['&& denotes a mnemonic'] }, "&&Open View..."), 'workbench.action.openView');
|
||||
const appearanceMenu = new Menu();
|
||||
|
||||
const fullscreen = new MenuItem(this.withKeybinding('workbench.action.toggleFullScreen', { label: this.mnemonicLabel(nls.localize({ key: 'miToggleFullScreen', comment: ['&& denotes a mnemonic'] }, "Toggle &&Full Screen")), click: () => this.windowsMainService.getLastActiveWindow().toggleFullScreen(), enabled: this.windowsMainService.getWindowCount() > 0 }));
|
||||
const toggleZenMode = this.createMenuItem(nls.localize('miToggleZenMode', "Toggle Zen Mode"), 'workbench.action.toggleZenMode');
|
||||
const toggleCenteredLayout = this.createMenuItem(nls.localize('miToggleCenteredLayout', "Toggle Centered Layout"), 'workbench.action.toggleCenteredLayout');
|
||||
const toggleMenuBar = this.createMenuItem(nls.localize({ key: 'miToggleMenuBar', comment: ['&& denotes a mnemonic'] }, "Toggle Menu &&Bar"), 'workbench.action.toggleMenuBar');
|
||||
// {{SQL CARBON EDIT}}
|
||||
//const splitEditor = this.createMenuItem(nls.localize({ key: 'miSplitEditor', comment: ['&& denotes a mnemonic'] }, "Split &&Editor"), 'workbench.action.splitEditor');
|
||||
const toggleEditorLayout = this.createMenuItem(nls.localize({ key: 'miToggleEditorLayout', comment: ['&& denotes a mnemonic'] }, "Toggle Editor Group &&Layout"), 'workbench.action.toggleEditorGroupLayout');
|
||||
|
||||
const toggleSidebar = this.createMenuItem(nls.localize({ key: 'miToggleSidebar', comment: ['&& denotes a mnemonic'] }, "&&Toggle Side Bar"), 'workbench.action.toggleSidebarVisibility');
|
||||
|
||||
let moveSideBarLabel: string;
|
||||
@@ -741,9 +719,7 @@ export class CodeMenu {
|
||||
}
|
||||
|
||||
const moveSidebar = this.createMenuItem(moveSideBarLabel, 'workbench.action.toggleSidebarPosition');
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
// const togglePanel = this.createMenuItem(nls.localize({ key: 'miTogglePanel', comment: ['&& denotes a mnemonic'] }, "Toggle &&Panel"), 'workbench.action.togglePanel');
|
||||
const togglePanel = this.createMenuItem(nls.localize({ key: 'miTogglePanel', comment: ['&& denotes a mnemonic'] }, "Toggle &&Panel"), 'workbench.action.togglePanel');
|
||||
|
||||
let statusBarLabel: string;
|
||||
if (this.currentStatusbarVisible) {
|
||||
@@ -761,20 +737,82 @@ export class CodeMenu {
|
||||
}
|
||||
const toggleActivtyBar = this.createMenuItem(activityBarLabel, 'workbench.action.toggleActivityBarVisibility');
|
||||
|
||||
const zoomIn = this.createMenuItem(nls.localize({ key: 'miZoomIn', comment: ['&& denotes a mnemonic'] }, "&&Zoom In"), 'workbench.action.zoomIn');
|
||||
const zoomOut = this.createMenuItem(nls.localize({ key: 'miZoomOut', comment: ['&& denotes a mnemonic'] }, "Zoom O&&ut"), 'workbench.action.zoomOut');
|
||||
const resetZoom = this.createMenuItem(nls.localize({ key: 'miZoomReset', comment: ['&& denotes a mnemonic'] }, "&&Reset Zoom"), 'workbench.action.zoomReset');
|
||||
// {{SQL CARBON EDIT}}
|
||||
arrays.coalesce([
|
||||
fullscreen,
|
||||
toggleZenMode,
|
||||
toggleCenteredLayout,
|
||||
isWindows || isLinux ? toggleMenuBar : void 0,
|
||||
__separator__(),
|
||||
moveSidebar,
|
||||
toggleSidebar,
|
||||
togglePanel,
|
||||
toggleStatusbar,
|
||||
toggleActivtyBar,
|
||||
__separator__(),
|
||||
zoomIn,
|
||||
zoomOut,
|
||||
resetZoom
|
||||
]).forEach(item => appearanceMenu.append(item));
|
||||
|
||||
const appearance = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miAppearance', comment: ['&& denotes a mnemonic'] }, "&&Appearance")), submenu: appearanceMenu });
|
||||
|
||||
// Editor Layout
|
||||
|
||||
const editorLayoutMenu = new Menu();
|
||||
|
||||
const splitEditorUp = this.createMenuItem(nls.localize({ key: 'miSplitEditorUp', comment: ['&& denotes a mnemonic'] }, "Split &&Up"), 'workbench.action.splitEditorUp');
|
||||
const splitEditorDown = this.createMenuItem(nls.localize({ key: 'miSplitEditorDown', comment: ['&& denotes a mnemonic'] }, "Split &&Down"), 'workbench.action.splitEditorDown');
|
||||
const splitEditorLeft = this.createMenuItem(nls.localize({ key: 'miSplitEditorLeft', comment: ['&& denotes a mnemonic'] }, "Split &&Left"), 'workbench.action.splitEditorLeft');
|
||||
const splitEditorRight = this.createMenuItem(nls.localize({ key: 'miSplitEditorRight', comment: ['&& denotes a mnemonic'] }, "Split &&Right"), 'workbench.action.splitEditorRight');
|
||||
|
||||
const singleColumnEditorLayout = this.createMenuItem(nls.localize({ key: 'miSingleColumnEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Single"), 'workbench.action.editorLayoutSingle');
|
||||
const twoColumnsEditorLayout = this.createMenuItem(nls.localize({ key: 'miTwoColumnsEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Two Columns"), 'workbench.action.editorLayoutTwoColumns');
|
||||
const threeColumnsEditorLayout = this.createMenuItem(nls.localize({ key: 'miThreeColumnsEditorLayout', comment: ['&& denotes a mnemonic'] }, "T&&hree Columns"), 'workbench.action.editorLayoutThreeColumns');
|
||||
const twoRowsEditorLayout = this.createMenuItem(nls.localize({ key: 'miTwoRowsEditorLayout', comment: ['&& denotes a mnemonic'] }, "T&&wo Rows"), 'workbench.action.editorLayoutTwoRows');
|
||||
const threeRowsEditorLayout = this.createMenuItem(nls.localize({ key: 'miThreeRowsEditorLayout', comment: ['&& denotes a mnemonic'] }, "Three &&Rows"), 'workbench.action.editorLayoutThreeRows');
|
||||
const twoByTwoGridEditorLayout = this.createMenuItem(nls.localize({ key: 'miTwoByTwoGridEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Grid (2x2)"), 'workbench.action.editorLayoutTwoByTwoGrid');
|
||||
const twoRowsRightEditorLayout = this.createMenuItem(nls.localize({ key: 'miTwoRowsRightEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two R&&ows Right"), 'workbench.action.editorLayoutTwoRowsRight');
|
||||
const twoColumnsBottomEditorLayout = this.createMenuItem(nls.localize({ key: 'miTwoColumnsBottomEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two &&Columns Bottom"), 'workbench.action.editorLayoutTwoColumnsBottom');
|
||||
|
||||
const toggleEditorLayout = this.createMenuItem(nls.localize({ key: 'miToggleEditorLayout', comment: ['&& denotes a mnemonic'] }, "Flip &&Layout"), 'workbench.action.toggleEditorGroupLayout');
|
||||
|
||||
[
|
||||
splitEditorUp,
|
||||
splitEditorDown,
|
||||
splitEditorLeft,
|
||||
splitEditorRight,
|
||||
__separator__(),
|
||||
singleColumnEditorLayout,
|
||||
twoColumnsEditorLayout,
|
||||
threeColumnsEditorLayout,
|
||||
twoRowsEditorLayout,
|
||||
threeRowsEditorLayout,
|
||||
twoByTwoGridEditorLayout,
|
||||
twoRowsRightEditorLayout,
|
||||
twoColumnsBottomEditorLayout,
|
||||
__separator__(),
|
||||
toggleEditorLayout
|
||||
].forEach(item => editorLayoutMenu.append(item));
|
||||
|
||||
const editorLayout = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miEditorLayout', comment: ['&& denotes a mnemonic'] }, "Editor &&Layout")), submenu: editorLayoutMenu });
|
||||
|
||||
// const toggleWordWrap = this.createMenuItem(nls.localize({ key: 'miToggleWordWrap', comment: ['&& denotes a mnemonic'] }, "Toggle &&Word Wrap"), 'editor.action.toggleWordWrap');
|
||||
// const toggleMinimap = this.createMenuItem(nls.localize({ key: 'miToggleMinimap', comment: ['&& denotes a mnemonic'] }, "Toggle &&Minimap"), 'editor.action.toggleMinimap');
|
||||
// const toggleRenderWhitespace = this.createMenuItem(nls.localize({ key: 'miToggleRenderWhitespace', comment: ['&& denotes a mnemonic'] }, "Toggle &&Render Whitespace"), 'editor.action.toggleRenderWhitespace');
|
||||
// const toggleRenderControlCharacters = this.createMenuItem(nls.localize({ key: 'miToggleRenderControlCharacters', comment: ['&& denotes a mnemonic'] }, "Toggle &&Control Characters"), 'editor.action.toggleRenderControlCharacter');
|
||||
|
||||
const zoomIn = this.createMenuItem(nls.localize({ key: 'miZoomIn', comment: ['&& denotes a mnemonic'] }, "&&Zoom In"), 'workbench.action.zoomIn');
|
||||
const zoomOut = this.createMenuItem(nls.localize({ key: 'miZoomOut', comment: ['&& denotes a mnemonic'] }, "Zoom O&&ut"), 'workbench.action.zoomOut');
|
||||
const resetZoom = this.createMenuItem(nls.localize({ key: 'miZoomReset', comment: ['&& denotes a mnemonic'] }, "&&Reset Zoom"), 'workbench.action.zoomReset');
|
||||
// const toggleBreadcrumbs = this.createMenuItem(nls.localize({ key: 'miToggleBreadcrumbs', comment: ['&& denotes a mnemonic'] }, "Toggle &&Breadcrumbs"), 'breadcrumbs.toggle');
|
||||
|
||||
arrays.coalesce([
|
||||
commands,
|
||||
openView,
|
||||
__separator__(),
|
||||
appearance,
|
||||
editorLayout,
|
||||
__separator__(),
|
||||
servers,
|
||||
tasks,
|
||||
explorer,
|
||||
@@ -783,37 +821,19 @@ export class CodeMenu {
|
||||
// {{SQL CARBON EDIT}}
|
||||
// debug,
|
||||
extensions,
|
||||
additionalViewlets,
|
||||
__separator__(),
|
||||
output,
|
||||
problems,
|
||||
// {{SQL CARBON EDIT}}
|
||||
// debugConsole,
|
||||
integratedTerminal,
|
||||
__separator__(),
|
||||
fullscreen,
|
||||
toggleZenMode,
|
||||
toggleCenteredLayout,
|
||||
isWindows || isLinux ? toggleMenuBar : void 0,
|
||||
__separator__(),
|
||||
terminal,
|
||||
//__separator__(),
|
||||
// {{SQL CARBON EDIT}}
|
||||
//splitEditor,
|
||||
toggleEditorLayout,
|
||||
moveSidebar,
|
||||
toggleSidebar,
|
||||
// togglePanel,
|
||||
toggleStatusbar,
|
||||
toggleActivtyBar,
|
||||
// {{SQL CARBON EDIT}}
|
||||
// __separator__(),
|
||||
// toggleWordWrap,
|
||||
// toggleMinimap,
|
||||
// toggleRenderWhitespace,
|
||||
// toggleRenderControlCharacters,
|
||||
__separator__(),
|
||||
zoomIn,
|
||||
zoomOut,
|
||||
resetZoom
|
||||
// toggleBreadcrumbs
|
||||
]).forEach(item => viewMenu.append(item));
|
||||
}
|
||||
|
||||
@@ -840,19 +860,33 @@ export class CodeMenu {
|
||||
|
||||
const switchGroupMenu = new Menu();
|
||||
|
||||
const focusFirstGroup = this.createMenuItem(nls.localize({ key: 'miFocusFirstGroup', comment: ['&& denotes a mnemonic'] }, "&&First Group"), 'workbench.action.focusFirstEditorGroup');
|
||||
const focusSecondGroup = this.createMenuItem(nls.localize({ key: 'miFocusSecondGroup', comment: ['&& denotes a mnemonic'] }, "&&Second Group"), 'workbench.action.focusSecondEditorGroup');
|
||||
const focusThirdGroup = this.createMenuItem(nls.localize({ key: 'miFocusThirdGroup', comment: ['&& denotes a mnemonic'] }, "&&Third Group"), 'workbench.action.focusThirdEditorGroup');
|
||||
const focusFirstGroup = this.createMenuItem(nls.localize({ key: 'miFocusFirstGroup', comment: ['&& denotes a mnemonic'] }, "Group &&1"), 'workbench.action.focusFirstEditorGroup');
|
||||
const focusSecondGroup = this.createMenuItem(nls.localize({ key: 'miFocusSecondGroup', comment: ['&& denotes a mnemonic'] }, "Group &&2"), 'workbench.action.focusSecondEditorGroup');
|
||||
const focusThirdGroup = this.createMenuItem(nls.localize({ key: 'miFocusThirdGroup', comment: ['&& denotes a mnemonic'] }, "Group &&3"), 'workbench.action.focusThirdEditorGroup');
|
||||
const focusFourthGroup = this.createMenuItem(nls.localize({ key: 'miFocusFourthGroup', comment: ['&& denotes a mnemonic'] }, "Group &&4"), 'workbench.action.focusFourthEditorGroup');
|
||||
const focusFifthGroup = this.createMenuItem(nls.localize({ key: 'miFocusFifthGroup', comment: ['&& denotes a mnemonic'] }, "Group &&5"), 'workbench.action.focusFifthEditorGroup');
|
||||
const nextGroup = this.createMenuItem(nls.localize({ key: 'miNextGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Group"), 'workbench.action.focusNextGroup');
|
||||
const previousGroup = this.createMenuItem(nls.localize({ key: 'miPreviousGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Group"), 'workbench.action.focusPreviousGroup');
|
||||
|
||||
const focusLeftGroup = this.createMenuItem(nls.localize({ key: 'miFocusLeftGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Left"), 'workbench.action.focusLeftGroup');
|
||||
const focusRightGroup = this.createMenuItem(nls.localize({ key: 'miFocusRightGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Right"), 'workbench.action.focusRightGroup');
|
||||
const focusAboveGroup = this.createMenuItem(nls.localize({ key: 'miFocusAboveGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Above"), 'workbench.action.focusAboveGroup');
|
||||
const focusBelowGroup = this.createMenuItem(nls.localize({ key: 'miFocusBelowGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Below"), 'workbench.action.focusBelowGroup');
|
||||
|
||||
[
|
||||
focusFirstGroup,
|
||||
focusSecondGroup,
|
||||
focusThirdGroup,
|
||||
focusFourthGroup,
|
||||
focusFifthGroup,
|
||||
__separator__(),
|
||||
nextGroup,
|
||||
previousGroup
|
||||
previousGroup,
|
||||
__separator__(),
|
||||
focusAboveGroup,
|
||||
focusBelowGroup,
|
||||
focusLeftGroup,
|
||||
focusRightGroup
|
||||
].forEach(item => switchGroupMenu.append(item));
|
||||
|
||||
const switchGroup = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miSwitchGroup', comment: ['&& denotes a mnemonic'] }, "Switch &&Group")), submenu: switchGroupMenu, enabled: true });
|
||||
@@ -901,7 +935,7 @@ export class CodeMenu {
|
||||
const toggleBreakpoint = this.createMenuItem(nls.localize({ key: 'miToggleBreakpoint', comment: ['&& denotes a mnemonic'] }, "Toggle &&Breakpoint"), 'editor.debug.action.toggleBreakpoint');
|
||||
const breakpointsMenu = new Menu();
|
||||
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: 'miInlineBreakpoint', comment: ['&& denotes a mnemonic'] }, "Inline Breakp&&oint"), 'editor.debug.action.toggleInlineBreakpoint'));
|
||||
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 });
|
||||
@@ -969,7 +1003,7 @@ export class CodeMenu {
|
||||
const w = this.windowsMainService.getFocusedWindow();
|
||||
if (w && w.win) {
|
||||
const contents = w.win.webContents;
|
||||
if (w.hasHiddenTitleBarStyle() && !w.win.isFullScreen() && !contents.isDevToolsOpened()) {
|
||||
if (isMacintosh && w.hasHiddenTitleBarStyle() && !w.win.isFullScreen() && !contents.isDevToolsOpened()) {
|
||||
contents.openDevTools({ mode: 'undocked' }); // due to https://github.com/electron/electron/issues/3647
|
||||
} else {
|
||||
contents.toggleDevTools();
|
||||
@@ -1107,7 +1141,10 @@ export class CodeMenu {
|
||||
width: 450,
|
||||
height: 300,
|
||||
show: true,
|
||||
title: nls.localize('accessibilityOptionsWindowTitle', "Accessibility Options")
|
||||
title: nls.localize('accessibilityOptionsWindowTitle', "Accessibility Options"),
|
||||
webPreferences: {
|
||||
disableBlinkFeatures: 'Auxclick'
|
||||
}
|
||||
});
|
||||
|
||||
win.setMenuBarVisibility(false);
|
||||
|
||||
@@ -23,7 +23,7 @@ import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
|
||||
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 { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
|
||||
import { mark, exportEntries } from 'vs/base/common/performance';
|
||||
import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/node/extensionGalleryService';
|
||||
|
||||
@@ -55,8 +55,8 @@ interface ITouchBarSegment extends Electron.SegmentedControlSegment {
|
||||
|
||||
export class CodeWindow implements ICodeWindow {
|
||||
|
||||
public static readonly themeStorageKey = 'theme';
|
||||
public static readonly themeBackgroundStorageKey = 'themeBackground';
|
||||
static readonly themeStorageKey = 'theme';
|
||||
static readonly themeBackgroundStorageKey = 'themeBackground';
|
||||
|
||||
private static readonly DEFAULT_BG_LIGHT = '#FFFFFF';
|
||||
private static readonly DEFAULT_BG_DARK = '#1E1E1E';
|
||||
@@ -166,8 +166,8 @@ export class CodeWindow implements ICodeWindow {
|
||||
}
|
||||
|
||||
let useCustomTitleStyle = false;
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
// if (isMacintosh) {
|
||||
// turn-off custom menus to avoid bug calculating size of SQL editor
|
||||
//
|
||||
// if (isMacintosh && (!windowConfig || !windowConfig.titleBarStyle || windowConfig.titleBarStyle === 'custom')) {
|
||||
@@ -184,29 +184,15 @@ export class CodeWindow implements ICodeWindow {
|
||||
if (useCustomTitleStyle) {
|
||||
options.titleBarStyle = 'hidden';
|
||||
this.hiddenTitleBarStyle = true;
|
||||
if (!isMacintosh) {
|
||||
options.frame = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the browser window.
|
||||
this._win = new BrowserWindow(options);
|
||||
this._id = this._win.id;
|
||||
|
||||
// Bug in Electron (https://github.com/electron/electron/issues/10862). On multi-monitor setups,
|
||||
// it can happen that the position we set to the window is not the correct one on the display.
|
||||
// To workaround, we ask the window for its position and set it again if not matching.
|
||||
// This only applies if the window is not fullscreen or maximized and multiple monitors are used.
|
||||
if (isWindows && !isFullscreenOrMaximized) {
|
||||
try {
|
||||
if (screen.getAllDisplays().length > 1) {
|
||||
const [x, y] = this._win.getPosition();
|
||||
if (x !== this.windowState.x || y !== this.windowState.y) {
|
||||
this._win.setPosition(this.windowState.x, this.windowState.y, false);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
this.logService.warn(`Unexpected error fixing window position on windows with multiple windows: ${err}\n${err.stack}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (useCustomTitleStyle) {
|
||||
this._win.setSheetOffset(22); // offset dialogs by the height of the custom title bar if we have any
|
||||
}
|
||||
@@ -226,35 +212,35 @@ export class CodeWindow implements ICodeWindow {
|
||||
this._lastFocusTime = Date.now(); // since we show directly, we need to set the last focus time too
|
||||
}
|
||||
|
||||
public hasHiddenTitleBarStyle(): boolean {
|
||||
hasHiddenTitleBarStyle(): boolean {
|
||||
return this.hiddenTitleBarStyle;
|
||||
}
|
||||
|
||||
public get isExtensionDevelopmentHost(): boolean {
|
||||
get isExtensionDevelopmentHost(): boolean {
|
||||
return !!this.config.extensionDevelopmentPath;
|
||||
}
|
||||
|
||||
public get isExtensionTestHost(): boolean {
|
||||
get isExtensionTestHost(): boolean {
|
||||
return !!this.config.extensionTestsPath;
|
||||
}
|
||||
|
||||
public get extensionDevelopmentPath(): string {
|
||||
get extensionDevelopmentPath(): string {
|
||||
return this.config.extensionDevelopmentPath;
|
||||
}
|
||||
|
||||
public get config(): IWindowConfiguration {
|
||||
get config(): IWindowConfiguration {
|
||||
return this.currentConfig;
|
||||
}
|
||||
|
||||
public get id(): number {
|
||||
get id(): number {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public get win(): Electron.BrowserWindow {
|
||||
get win(): Electron.BrowserWindow {
|
||||
return this._win;
|
||||
}
|
||||
|
||||
public setRepresentedFilename(filename: string): void {
|
||||
setRepresentedFilename(filename: string): void {
|
||||
if (isMacintosh) {
|
||||
this.win.setRepresentedFilename(filename);
|
||||
} else {
|
||||
@@ -262,7 +248,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
}
|
||||
}
|
||||
|
||||
public getRepresentedFilename(): string {
|
||||
getRepresentedFilename(): string {
|
||||
if (isMacintosh) {
|
||||
return this.win.getRepresentedFilename();
|
||||
}
|
||||
@@ -270,7 +256,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
return this.representedFilename;
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
focus(): void {
|
||||
if (!this._win) {
|
||||
return;
|
||||
}
|
||||
@@ -282,23 +268,23 @@ export class CodeWindow implements ICodeWindow {
|
||||
this._win.focus();
|
||||
}
|
||||
|
||||
public get lastFocusTime(): number {
|
||||
get lastFocusTime(): number {
|
||||
return this._lastFocusTime;
|
||||
}
|
||||
|
||||
public get backupPath(): string {
|
||||
get backupPath(): string {
|
||||
return this.currentConfig ? this.currentConfig.backupPath : void 0;
|
||||
}
|
||||
|
||||
public get openedWorkspace(): IWorkspaceIdentifier {
|
||||
get openedWorkspace(): IWorkspaceIdentifier {
|
||||
return this.currentConfig ? this.currentConfig.workspace : void 0;
|
||||
}
|
||||
|
||||
public get openedFolderPath(): string {
|
||||
return this.currentConfig ? this.currentConfig.folderPath : void 0;
|
||||
get openedFolderUri(): URI {
|
||||
return this.currentConfig ? this.currentConfig.folderUri : void 0;
|
||||
}
|
||||
|
||||
public setReady(): void {
|
||||
setReady(): void {
|
||||
this._readyState = ReadyState.READY;
|
||||
|
||||
// inform all waiting promises that we are ready now
|
||||
@@ -307,7 +293,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
}
|
||||
}
|
||||
|
||||
public ready(): TPromise<ICodeWindow> {
|
||||
ready(): TPromise<ICodeWindow> {
|
||||
return new TPromise<ICodeWindow>((c) => {
|
||||
if (this._readyState === ReadyState.READY) {
|
||||
return c(this);
|
||||
@@ -318,7 +304,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
});
|
||||
}
|
||||
|
||||
public get readyState(): ReadyState {
|
||||
get readyState(): ReadyState {
|
||||
return this._readyState;
|
||||
}
|
||||
|
||||
@@ -371,7 +357,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
}
|
||||
|
||||
// To prevent flashing, we set the window visible after the page has finished to load but before Code is loaded
|
||||
if (!this._win.isVisible()) {
|
||||
if (this._win && !this._win.isVisible()) {
|
||||
if (this.windowState.mode === WindowMode.Maximized) {
|
||||
this._win.maximize();
|
||||
}
|
||||
@@ -397,6 +383,23 @@ export class CodeWindow implements ICodeWindow {
|
||||
this._lastFocusTime = Date.now();
|
||||
});
|
||||
|
||||
// Window (Un)Maximize
|
||||
this._win.on('maximize', (e) => {
|
||||
if (this.currentConfig) {
|
||||
this.currentConfig.maximized = true;
|
||||
}
|
||||
|
||||
app.emit('browser-window-maximize', e, this._win);
|
||||
});
|
||||
|
||||
this._win.on('unmaximize', (e) => {
|
||||
if (this.currentConfig) {
|
||||
this.currentConfig.maximized = false;
|
||||
}
|
||||
|
||||
app.emit('browser-window-unmaximize', e, this._win);
|
||||
});
|
||||
|
||||
// Window Fullscreen
|
||||
this._win.on('enter-full-screen', () => {
|
||||
this.sendWhenReady('vscode:enterFullScreen');
|
||||
@@ -497,7 +500,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
});
|
||||
}
|
||||
|
||||
public load(config: IWindowConfiguration, isReload?: boolean): void {
|
||||
load(config: IWindowConfiguration, isReload?: boolean, disableExtensions?: boolean): void {
|
||||
|
||||
// If this is the first time the window is loaded, we associate the paths
|
||||
// directly with the window because we assume the loading will just work
|
||||
@@ -514,6 +517,13 @@ export class CodeWindow implements ICodeWindow {
|
||||
this._readyState = ReadyState.NAVIGATING;
|
||||
}
|
||||
|
||||
// Add disable-extensions to the config, but do not preserve it on currentConfig or
|
||||
// pendingLoadConfig so that it is applied only on this load
|
||||
const configuration = objects.assign({}, config);
|
||||
if (disableExtensions !== undefined) {
|
||||
configuration['disable-extensions'] = disableExtensions;
|
||||
}
|
||||
|
||||
// Clear Document Edited if needed
|
||||
if (isMacintosh && this._win.isDocumentEdited()) {
|
||||
if (!isReload || !this.backupMainService.isHotExitEnabled()) {
|
||||
@@ -532,7 +542,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
|
||||
// Load URL
|
||||
mark('main:loadWindow');
|
||||
this._win.loadURL(this.getUrl(config));
|
||||
this._win.loadURL(this.getUrl(configuration));
|
||||
|
||||
// Make window visible if it did not open in N seconds because this indicates an error
|
||||
// Only do this when running out of sources and not when running tests
|
||||
@@ -547,7 +557,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
}
|
||||
}
|
||||
|
||||
public reload(configuration?: IWindowConfiguration, cli?: ParsedArgs): void {
|
||||
reload(configuration?: IWindowConfiguration, cli?: ParsedArgs): void {
|
||||
|
||||
// If config is not provided, copy our current one
|
||||
if (!configuration) {
|
||||
@@ -570,14 +580,11 @@ export class CodeWindow implements ICodeWindow {
|
||||
configuration['extensions-dir'] = cli['extensions-dir'];
|
||||
}
|
||||
|
||||
if (cli) {
|
||||
configuration['disable-extensions'] = cli['disable-extensions'];
|
||||
}
|
||||
|
||||
configuration.isInitialStartup = false; // since this is a reload
|
||||
|
||||
// Load config
|
||||
this.load(configuration, true);
|
||||
const disableExtensions = cli ? cli['disable-extensions'] : undefined;
|
||||
this.load(configuration, true, disableExtensions);
|
||||
}
|
||||
|
||||
private getUrl(windowConfiguration: IWindowConfiguration): string {
|
||||
@@ -608,6 +615,10 @@ export class CodeWindow implements ICodeWindow {
|
||||
windowConfiguration.baseTheme = this.getBaseTheme();
|
||||
windowConfiguration.backgroundColor = this.getBackgroundColor();
|
||||
|
||||
// Title style related
|
||||
windowConfiguration.maximized = this._win.isMaximized();
|
||||
windowConfiguration.frameless = this.hasHiddenTitleBarStyle() && !isMacintosh;
|
||||
|
||||
// Perf Counters
|
||||
windowConfiguration.perfEntries = exportEntries();
|
||||
windowConfiguration.perfStartTime = (<any>global).perfStartTime;
|
||||
@@ -650,7 +661,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
return background;
|
||||
}
|
||||
|
||||
public serializeWindowState(): IWindowState {
|
||||
serializeWindowState(): IWindowState {
|
||||
if (!this._win) {
|
||||
return defaultWindowState();
|
||||
}
|
||||
@@ -806,14 +817,14 @@ export class CodeWindow implements ICodeWindow {
|
||||
return null;
|
||||
}
|
||||
|
||||
public getBounds(): Electron.Rectangle {
|
||||
getBounds(): Electron.Rectangle {
|
||||
const pos = this._win.getPosition();
|
||||
const dimension = this._win.getSize();
|
||||
|
||||
return { x: pos[0], y: pos[1], width: dimension[0], height: dimension[1] };
|
||||
}
|
||||
|
||||
public toggleFullScreen(): void {
|
||||
toggleFullScreen(): void {
|
||||
const willBeFullScreen = !this._win.isFullScreen();
|
||||
|
||||
// set fullscreen flag on window
|
||||
@@ -888,7 +899,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
}
|
||||
}
|
||||
|
||||
public onWindowTitleDoubleClick(): void {
|
||||
onWindowTitleDoubleClick(): void {
|
||||
|
||||
// Respect system settings on mac with regards to title click on windows title
|
||||
if (isMacintosh) {
|
||||
@@ -915,25 +926,25 @@ export class CodeWindow implements ICodeWindow {
|
||||
}
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
close(): void {
|
||||
if (this._win) {
|
||||
this._win.close();
|
||||
}
|
||||
}
|
||||
|
||||
public sendWhenReady(channel: string, ...args: any[]): void {
|
||||
sendWhenReady(channel: string, ...args: any[]): void {
|
||||
this.ready().then(() => {
|
||||
this.send(channel, ...args);
|
||||
});
|
||||
}
|
||||
|
||||
public send(channel: string, ...args: any[]): void {
|
||||
send(channel: string, ...args: any[]): void {
|
||||
if (this._win) {
|
||||
this._win.webContents.send(channel, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
public updateTouchBar(groups: ICommandAction[][]): void {
|
||||
updateTouchBar(groups: ISerializableCommandAction[][]): void {
|
||||
if (!isMacintosh) {
|
||||
return; // only supported on macOS
|
||||
}
|
||||
@@ -959,15 +970,10 @@ export class CodeWindow implements ICodeWindow {
|
||||
this.touchBarGroups.push(groupTouchBar);
|
||||
}
|
||||
|
||||
// Ugly workaround for native crash on macOS 10.12.1. We are not
|
||||
// leveraging the API for changing the ESC touch bar item.
|
||||
// See https://github.com/electron/electron/issues/10442
|
||||
(<any>this._win)._setEscapeTouchBarItem = () => { };
|
||||
|
||||
this._win.setTouchBar(new TouchBar({ items: this.touchBarGroups }));
|
||||
}
|
||||
|
||||
private createTouchBarGroup(items: ICommandAction[] = []): Electron.TouchBarSegmentedControl {
|
||||
private createTouchBarGroup(items: ISerializableCommandAction[] = []): Electron.TouchBarSegmentedControl {
|
||||
|
||||
// Group Segments
|
||||
const segments = this.createTouchBarGroupSegments(items);
|
||||
@@ -985,11 +991,11 @@ export class CodeWindow implements ICodeWindow {
|
||||
return control;
|
||||
}
|
||||
|
||||
private createTouchBarGroupSegments(items: ICommandAction[] = []): ITouchBarSegment[] {
|
||||
private createTouchBarGroupSegments(items: ISerializableCommandAction[] = []): ITouchBarSegment[] {
|
||||
const segments: ITouchBarSegment[] = items.map(item => {
|
||||
let icon: Electron.NativeImage;
|
||||
if (item.iconPath) {
|
||||
icon = nativeImage.createFromPath(item.iconPath.dark);
|
||||
if (item.iconLocation && item.iconLocation.dark.scheme === 'file') {
|
||||
icon = nativeImage.createFromPath(URI.revive(item.iconLocation.dark).fsPath);
|
||||
if (icon.isEmpty()) {
|
||||
icon = void 0;
|
||||
}
|
||||
@@ -1005,7 +1011,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
return segments;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
dispose(): void {
|
||||
if (this.showTimeoutHandle) {
|
||||
clearTimeout(this.showTimeoutHandle);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
'use strict';
|
||||
|
||||
import { basename, normalize, join, dirname } from 'path';
|
||||
import * as fs from 'original-fs';
|
||||
import * as fs from 'fs';
|
||||
import { localize } from 'vs/nls';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { assign, mixin, equals } from 'vs/base/common/objects';
|
||||
@@ -20,23 +20,23 @@ import { ILifecycleService, UnloadReason, IWindowUnloadEvent } from 'vs/platform
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, INativeOpenDialogOptions, ReadyState, IPathsToWaitFor, IEnterWorkspaceResult, IMessageBoxResult } from 'vs/platform/windows/common/windows';
|
||||
import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderPath } from 'vs/code/node/windowsFinder';
|
||||
import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri } from 'vs/code/node/windowsFinder';
|
||||
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, 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';
|
||||
import { IWorkspacesMainService, IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, WORKSPACE_FILTER, isSingleFolderWorkspaceIdentifier, IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IWorkspacesMainService, IWorkspaceIdentifier, WORKSPACE_FILTER, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { normalizeNFC } from 'vs/base/common/strings';
|
||||
import { normalizeNFC } from 'vs/base/common/normalization';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { Queue } from 'vs/base/common/async';
|
||||
import { exists } from 'vs/base/node/pfs';
|
||||
import { getComparisonKey, isEqual, hasToIgnoreCase } from 'vs/base/common/resources';
|
||||
|
||||
enum WindowError {
|
||||
UNRESPONSIVE,
|
||||
@@ -49,11 +49,15 @@ interface INewWindowState extends ISingleWindowState {
|
||||
|
||||
interface IWindowState {
|
||||
workspace?: IWorkspaceIdentifier;
|
||||
folderPath?: string;
|
||||
folderUri?: URI;
|
||||
backupPath: string;
|
||||
uiState: ISingleWindowState;
|
||||
}
|
||||
|
||||
interface IBackwardCompatibleWindowState extends IWindowState {
|
||||
folderPath?: string;
|
||||
}
|
||||
|
||||
interface IWindowsState {
|
||||
lastActiveWindow?: IWindowState;
|
||||
lastPluginDevelopmentHostWindow?: IWindowState;
|
||||
@@ -67,7 +71,7 @@ interface IOpenBrowserWindowOptions {
|
||||
cli?: ParsedArgs;
|
||||
|
||||
workspace?: IWorkspaceIdentifier;
|
||||
folderPath?: string;
|
||||
folderUri?: URI;
|
||||
|
||||
initialStartup?: boolean;
|
||||
|
||||
@@ -88,7 +92,7 @@ interface IPathToOpen extends IPath {
|
||||
workspace?: IWorkspaceIdentifier;
|
||||
|
||||
// the folder path for a Code instance to open
|
||||
folderPath?: string;
|
||||
folderUri?: URI;
|
||||
|
||||
// the backup spath for a Code instance to use
|
||||
backupPath?: string;
|
||||
@@ -144,7 +148,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
@IWorkspacesMainService private workspacesMainService: IWorkspacesMainService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService
|
||||
) {
|
||||
this.windowsState = this.stateService.getItem<IWindowsState>(WindowsManager.windowsStateStorageKey) || { openedWindows: [] };
|
||||
this.windowsState = this.getWindowsState();
|
||||
if (!Array.isArray(this.windowsState.openedWindows)) {
|
||||
this.windowsState.openedWindows = [];
|
||||
}
|
||||
@@ -153,7 +157,31 @@ export class WindowsManager implements IWindowsMainService {
|
||||
this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, environmentService, this);
|
||||
}
|
||||
|
||||
public ready(initialUserEnv: IProcessEnvironment): void {
|
||||
private getWindowsState(): IWindowsState {
|
||||
const windowsState = this.stateService.getItem<IWindowsState>(WindowsManager.windowsStateStorageKey) || { openedWindows: [] };
|
||||
if (windowsState.lastActiveWindow) {
|
||||
windowsState.lastActiveWindow = this.revive(windowsState.lastActiveWindow);
|
||||
}
|
||||
if (windowsState.lastPluginDevelopmentHostWindow) {
|
||||
windowsState.lastPluginDevelopmentHostWindow = this.revive(windowsState.lastPluginDevelopmentHostWindow);
|
||||
}
|
||||
if (windowsState.openedWindows) {
|
||||
windowsState.openedWindows = windowsState.openedWindows.map(windowState => this.revive(windowState));
|
||||
}
|
||||
return windowsState;
|
||||
}
|
||||
|
||||
private revive(windowState: IWindowState): IWindowState {
|
||||
if (windowState.folderUri) {
|
||||
windowState.folderUri = URI.revive(windowState.folderUri);
|
||||
}
|
||||
if ((<IBackwardCompatibleWindowState>windowState).folderPath) {
|
||||
windowState.folderUri = URI.file((<IBackwardCompatibleWindowState>windowState).folderPath);
|
||||
}
|
||||
return windowState;
|
||||
}
|
||||
|
||||
ready(initialUserEnv: IProcessEnvironment): void {
|
||||
this.initialUserEnv = initialUserEnv;
|
||||
|
||||
this.registerListeners();
|
||||
@@ -294,10 +322,10 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}
|
||||
|
||||
// Any non extension host window with same workspace or folder
|
||||
else if (!win.isExtensionDevelopmentHost && (!!win.openedWorkspace || !!win.openedFolderPath)) {
|
||||
else if (!win.isExtensionDevelopmentHost && (!!win.openedWorkspace || !!win.openedFolderUri)) {
|
||||
this.windowsState.openedWindows.forEach(o => {
|
||||
const sameWorkspace = win.openedWorkspace && o.workspace && o.workspace.id === win.openedWorkspace.id;
|
||||
const sameFolder = win.openedFolderPath && isEqual(o.folderPath, win.openedFolderPath, !isLinux /* ignorecase */);
|
||||
const sameFolder = win.openedFolderUri && o.folderUri && isEqual(o.folderUri, win.openedFolderUri, hasToIgnoreCase(o.folderUri));
|
||||
|
||||
if (sameWorkspace || sameFolder) {
|
||||
o.uiState = state.uiState;
|
||||
@@ -317,23 +345,24 @@ export class WindowsManager implements IWindowsMainService {
|
||||
private toWindowState(win: ICodeWindow): IWindowState {
|
||||
return {
|
||||
workspace: win.openedWorkspace,
|
||||
folderPath: win.openedFolderPath,
|
||||
folderUri: win.openedFolderUri,
|
||||
backupPath: win.backupPath,
|
||||
uiState: win.serializeWindowState()
|
||||
};
|
||||
}
|
||||
|
||||
public open(openConfig: IOpenConfiguration): ICodeWindow[] {
|
||||
open(openConfig: IOpenConfiguration): ICodeWindow[] {
|
||||
this.logService.trace('windowsManager#open');
|
||||
openConfig = this.validateOpenConfig(openConfig);
|
||||
|
||||
let pathsToOpen = this.getPathsToOpen(openConfig);
|
||||
|
||||
// When run with --add, take the folders that are to be opened as
|
||||
// folders that should be added to the currently active window.
|
||||
let foldersToAdd: IPath[] = [];
|
||||
let foldersToAdd: URI[] = [];
|
||||
if (openConfig.addMode) {
|
||||
foldersToAdd = pathsToOpen.filter(path => !!path.folderPath).map(path => ({ filePath: path.folderPath }));
|
||||
pathsToOpen = pathsToOpen.filter(path => !path.folderPath);
|
||||
foldersToAdd = pathsToOpen.filter(path => !!path.folderUri).map(path => path.folderUri);
|
||||
pathsToOpen = pathsToOpen.filter(path => !path.folderUri);
|
||||
}
|
||||
|
||||
let filesToOpen = pathsToOpen.filter(path => !!path.filePath && !path.createFilePath);
|
||||
@@ -362,12 +391,12 @@ export class WindowsManager implements IWindowsMainService {
|
||||
//
|
||||
// These are windows to open to show either folders or files (including diffing files or creating them)
|
||||
//
|
||||
const foldersToOpen = arrays.distinct(pathsToOpen.filter(win => win.folderPath && !win.filePath).map(win => win.folderPath), folder => isLinux ? folder : folder.toLowerCase()); // prevent duplicates
|
||||
const foldersToOpen = arrays.distinct(pathsToOpen.filter(win => win.folderUri && !win.filePath).map(win => win.folderUri), folder => getComparisonKey(folder)); // prevent duplicates
|
||||
|
||||
//
|
||||
// These are windows to restore because of hot-exit or from previous session (only performed once on startup!)
|
||||
//
|
||||
let foldersToRestore: string[] = [];
|
||||
let foldersToRestore: URI[] = [];
|
||||
let workspacesToRestore: IWorkspaceIdentifier[] = [];
|
||||
let emptyToRestore: string[] = [];
|
||||
if (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath && !openConfig.cli['disable-restore-windows']) {
|
||||
@@ -377,21 +406,22 @@ export class WindowsManager implements IWindowsMainService {
|
||||
workspacesToRestore.push(...this.workspacesMainService.getUntitledWorkspacesSync()); // collect from previous window session
|
||||
|
||||
emptyToRestore = this.backupMainService.getEmptyWindowBackupPaths();
|
||||
emptyToRestore.push(...pathsToOpen.filter(w => !w.workspace && !w.folderPath && w.backupPath).map(w => basename(w.backupPath))); // add empty windows with backupPath
|
||||
emptyToRestore.push(...pathsToOpen.filter(w => !w.workspace && !w.folderUri && w.backupPath).map(w => basename(w.backupPath))); // add empty windows with backupPath
|
||||
emptyToRestore = arrays.distinct(emptyToRestore); // prevent duplicates
|
||||
}
|
||||
|
||||
//
|
||||
// These are empty windows to open
|
||||
//
|
||||
const emptyToOpen = pathsToOpen.filter(win => !win.workspace && !win.folderPath && !win.filePath && !win.backupPath).length;
|
||||
const emptyToOpen = pathsToOpen.filter(win => !win.workspace && !win.folderUri && !win.filePath && !win.backupPath).length;
|
||||
|
||||
// Open based on config
|
||||
const usedWindows = this.doOpen(openConfig, workspacesToOpen, workspacesToRestore, foldersToOpen, foldersToRestore, emptyToRestore, emptyToOpen, filesToOpen, filesToCreate, filesToDiff, filesToWait, foldersToAdd);
|
||||
|
||||
// Make sure to pass focus to the most relevant of the windows if we open multiple
|
||||
if (usedWindows.length > 1) {
|
||||
let focusLastActive = this.windowsState.lastActiveWindow && !openConfig.forceEmpty && !openConfig.cli._.length && (!openConfig.pathsToOpen || !openConfig.pathsToOpen.length);
|
||||
|
||||
let focusLastActive = this.windowsState.lastActiveWindow && !openConfig.forceEmpty && !openConfig.cli._.length && !(openConfig.cli['folder-uri'] || []).length && !(openConfig.urisToOpen || []).length;
|
||||
let focusLastOpened = true;
|
||||
let focusLastWindow = true;
|
||||
|
||||
@@ -410,9 +440,9 @@ export class WindowsManager implements IWindowsMainService {
|
||||
for (let i = usedWindows.length - 1; i >= 0; i--) {
|
||||
const usedWindow = usedWindows[i];
|
||||
if (
|
||||
(usedWindow.openedWorkspace && workspacesToRestore.some(workspace => workspace.id === usedWindow.openedWorkspace.id)) || // skip over restored workspace
|
||||
(usedWindow.openedFolderPath && foldersToRestore.some(folder => folder === usedWindow.openedFolderPath)) || // skip over restored folder
|
||||
(usedWindow.backupPath && emptyToRestore.some(empty => empty === basename(usedWindow.backupPath))) // skip over restored empty window
|
||||
(usedWindow.openedWorkspace && workspacesToRestore.some(workspace => workspace.id === usedWindow.openedWorkspace.id)) || // skip over restored workspace
|
||||
(usedWindow.openedFolderUri && foldersToRestore.some(folder => isEqual(folder, usedWindow.openedFolderUri, hasToIgnoreCase(folder)))) || // skip over restored folder
|
||||
(usedWindow.backupPath && emptyToRestore.some(empty => empty === basename(usedWindow.backupPath))) // skip over restored empty window
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
@@ -436,8 +466,8 @@ export class WindowsManager implements IWindowsMainService {
|
||||
const recentlyOpenedFiles: string[] = [];
|
||||
|
||||
pathsToOpen.forEach(win => {
|
||||
if (win.workspace || win.folderPath) {
|
||||
recentlyOpenedWorkspaces.push(win.workspace || win.folderPath);
|
||||
if (win.workspace || win.folderUri) {
|
||||
recentlyOpenedWorkspaces.push(win.workspace || win.folderUri);
|
||||
} else if (win.filePath) {
|
||||
recentlyOpenedFiles.push(win.filePath);
|
||||
}
|
||||
@@ -472,15 +502,15 @@ export class WindowsManager implements IWindowsMainService {
|
||||
openConfig: IOpenConfiguration,
|
||||
workspacesToOpen: IWorkspaceIdentifier[],
|
||||
workspacesToRestore: IWorkspaceIdentifier[],
|
||||
foldersToOpen: string[],
|
||||
foldersToRestore: string[],
|
||||
foldersToOpen: URI[],
|
||||
foldersToRestore: URI[],
|
||||
emptyToRestore: string[],
|
||||
emptyToOpen: number,
|
||||
filesToOpen: IPath[],
|
||||
filesToCreate: IPath[],
|
||||
filesToDiff: IPath[],
|
||||
filesToWait: IPathsToWaitFor,
|
||||
foldersToAdd: IPath[]
|
||||
foldersToAdd: URI[]
|
||||
) {
|
||||
const usedWindows: ICodeWindow[] = [];
|
||||
|
||||
@@ -510,13 +540,14 @@ export class WindowsManager implements IWindowsMainService {
|
||||
reuseWindow: openConfig.forceReuseWindow,
|
||||
context: openConfig.context,
|
||||
filePath: fileToCheck && fileToCheck.filePath,
|
||||
userHome: this.environmentService.userHome,
|
||||
workspaceResolver: workspace => this.workspacesMainService.resolveWorkspaceSync(workspace.configPath)
|
||||
});
|
||||
|
||||
// Special case: we started with --wait and we got back a folder to open. In this case
|
||||
// we actually prefer to not open the folder but operate purely on the file.
|
||||
if (typeof bestWindowOrFolder === 'string' && filesToWait) {
|
||||
//TODO: #54483 Ben This should not happen
|
||||
console.error(`This should not happen`, bestWindowOrFolder, WindowsManager.WINDOWS);
|
||||
bestWindowOrFolder = !openFilesInNewWindow ? this.getLastActiveWindow() : null;
|
||||
}
|
||||
|
||||
@@ -529,8 +560,8 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}
|
||||
|
||||
// Window is single folder
|
||||
else if (bestWindowOrFolder.openedFolderPath) {
|
||||
foldersToOpen.push(bestWindowOrFolder.openedFolderPath);
|
||||
else if (bestWindowOrFolder.openedFolderUri) {
|
||||
foldersToOpen.push(bestWindowOrFolder.openedFolderUri);
|
||||
}
|
||||
|
||||
// Window is empty
|
||||
@@ -549,7 +580,9 @@ export class WindowsManager implements IWindowsMainService {
|
||||
|
||||
// We found a suitable folder to open: add it to foldersToOpen
|
||||
else if (typeof bestWindowOrFolder === 'string') {
|
||||
foldersToOpen.push(bestWindowOrFolder);
|
||||
//TODO: #54483 Ben This should not happen
|
||||
// foldersToOpen.push(bestWindowOrFolder);
|
||||
console.error(`This should not happen`, bestWindowOrFolder, WindowsManager.WINDOWS);
|
||||
}
|
||||
|
||||
// Finally, if no window or folder is found, just open the files in an empty window
|
||||
@@ -614,7 +647,8 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}
|
||||
|
||||
// Handle folders to open (instructed and to restore)
|
||||
const allFoldersToOpen = arrays.distinct([...foldersToRestore, ...foldersToOpen], folder => isLinux ? folder : folder.toLowerCase()); // prevent duplicates
|
||||
const allFoldersToOpen = arrays.distinct([...foldersToRestore, ...foldersToOpen], folder => getComparisonKey(folder)); // prevent duplicates
|
||||
|
||||
if (allFoldersToOpen.length > 0) {
|
||||
|
||||
// Check for existing instances
|
||||
@@ -636,12 +670,13 @@ export class WindowsManager implements IWindowsMainService {
|
||||
|
||||
// Open remaining ones
|
||||
allFoldersToOpen.forEach(folderToOpen => {
|
||||
if (windowsOnFolderPath.some(win => isEqual(win.openedFolderPath, folderToOpen, !isLinux /* ignorecase */))) {
|
||||
|
||||
if (windowsOnFolderPath.some(win => isEqual(win.openedFolderUri, folderToOpen, hasToIgnoreCase(win.openedFolderUri)))) {
|
||||
return; // ignore folders that are already open
|
||||
}
|
||||
|
||||
// Do open folder
|
||||
usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { folderPath: folderToOpen }, openFolderInNewWindow, filesToOpen, filesToCreate, filesToDiff, filesToWait));
|
||||
usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { folderUri: folderToOpen }, openFolderInNewWindow, filesToOpen, filesToCreate, filesToDiff, filesToWait));
|
||||
|
||||
// Reset these because we handled them
|
||||
filesToOpen = [];
|
||||
@@ -706,7 +741,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
return window;
|
||||
}
|
||||
|
||||
private doAddFoldersToExistingWidow(window: ICodeWindow, foldersToAdd: IPath[]): ICodeWindow {
|
||||
private doAddFoldersToExistingWidow(window: ICodeWindow, foldersToAdd: URI[]): ICodeWindow {
|
||||
window.focus(); // make sure window has focus
|
||||
|
||||
window.ready().then(readyWindow => {
|
||||
@@ -716,18 +751,22 @@ export class WindowsManager implements IWindowsMainService {
|
||||
return window;
|
||||
}
|
||||
|
||||
private doOpenFolderOrWorkspace(openConfig: IOpenConfiguration, folderOrWorkspace: IPathToOpen, openInNewWindow: boolean, filesToOpen: IPath[], filesToCreate: IPath[], filesToDiff: IPath[], filesToWait: IPathsToWaitFor, windowToUse?: ICodeWindow): ICodeWindow {
|
||||
private doOpenFolderOrWorkspace(openConfig: IOpenConfiguration, folderOrWorkspace: IPathToOpen, forceNewWindow: boolean, filesToOpen: IPath[], filesToCreate: IPath[], filesToDiff: IPath[], filesToWait: IPathsToWaitFor, windowToUse?: ICodeWindow): ICodeWindow {
|
||||
if (!forceNewWindow && !windowToUse && typeof openConfig.contextWindowId === 'number') {
|
||||
windowToUse = this.getWindowById(openConfig.contextWindowId); // fix for https://github.com/Microsoft/vscode/issues/49587
|
||||
}
|
||||
|
||||
const browserWindow = this.openInBrowserWindow({
|
||||
userEnv: openConfig.userEnv,
|
||||
cli: openConfig.cli,
|
||||
initialStartup: openConfig.initialStartup,
|
||||
workspace: folderOrWorkspace.workspace,
|
||||
folderPath: folderOrWorkspace.folderPath,
|
||||
folderUri: folderOrWorkspace.folderUri,
|
||||
filesToOpen,
|
||||
filesToCreate,
|
||||
filesToDiff,
|
||||
filesToWait,
|
||||
forceNewWindow: openInNewWindow,
|
||||
forceNewWindow,
|
||||
windowToUse
|
||||
});
|
||||
|
||||
@@ -739,7 +778,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
let isCommandLineOrAPICall = false;
|
||||
|
||||
// Extract paths: from API
|
||||
if (openConfig.pathsToOpen && openConfig.pathsToOpen.length > 0) {
|
||||
if (openConfig.urisToOpen && openConfig.urisToOpen.length > 0) {
|
||||
windowsToOpen = this.doExtractPathsFromAPI(openConfig);
|
||||
isCommandLineOrAPICall = true;
|
||||
}
|
||||
@@ -750,7 +789,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}
|
||||
|
||||
// Extract paths: from CLI
|
||||
else if (openConfig.cli._.length > 0) {
|
||||
else if (openConfig.cli._.length > 0 || (openConfig.cli['folder-uri'] || []).length > 0) {
|
||||
windowsToOpen = this.doExtractPathsFromCLI(openConfig.cli);
|
||||
isCommandLineOrAPICall = true;
|
||||
}
|
||||
@@ -765,13 +804,13 @@ export class WindowsManager implements IWindowsMainService {
|
||||
// If we are in addMode, we should not do this because in that case all
|
||||
// folders should be added to the existing window.
|
||||
if (!openConfig.addMode && isCommandLineOrAPICall) {
|
||||
const foldersToOpen = windowsToOpen.filter(path => !!path.folderPath);
|
||||
const foldersToOpen = windowsToOpen.filter(path => !!path.folderUri);
|
||||
if (foldersToOpen.length > 1) {
|
||||
const workspace = this.workspacesMainService.createWorkspaceSync(foldersToOpen.map(folder => ({ uri: URI.file(folder.folderPath) })));
|
||||
const workspace = this.workspacesMainService.createWorkspaceSync(foldersToOpen.map(folder => ({ uri: folder.folderUri })));
|
||||
|
||||
// Add workspace and remove folders thereby
|
||||
windowsToOpen.push({ workspace });
|
||||
windowsToOpen = windowsToOpen.filter(path => !path.folderPath);
|
||||
windowsToOpen = windowsToOpen.filter(path => !path.folderUri);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -779,8 +818,8 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}
|
||||
|
||||
private doExtractPathsFromAPI(openConfig: IOpenConfiguration): IPath[] {
|
||||
let pathsToOpen = openConfig.pathsToOpen.map(pathToOpen => {
|
||||
const path = this.parsePath(pathToOpen, { gotoLineMode: openConfig.cli && openConfig.cli.goto, forceOpenWorkspaceAsFile: openConfig.forceOpenWorkspaceAsFile });
|
||||
let pathsToOpen = openConfig.urisToOpen.map(pathToOpen => {
|
||||
const path = this.parseUri(pathToOpen, { gotoLineMode: openConfig.cli && openConfig.cli.goto, forceOpenWorkspaceAsFile: openConfig.forceOpenWorkspaceAsFile });
|
||||
|
||||
// Warn if the requested path to open does not exist
|
||||
if (!path) {
|
||||
@@ -789,7 +828,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
type: 'info',
|
||||
buttons: [localize('ok', "OK")],
|
||||
message: localize('pathNotExistTitle', "Path does not exist"),
|
||||
detail: localize('pathNotExistDetail', "The path '{0}' does not seem to exist anymore on disk.", pathToOpen),
|
||||
detail: localize('pathNotExistDetail', "The path '{0}' does not seem to exist anymore on disk.", pathToOpen.scheme === Schemas.file ? pathToOpen.fsPath : pathToOpen.path),
|
||||
noLink: true
|
||||
};
|
||||
|
||||
@@ -806,7 +845,20 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}
|
||||
|
||||
private doExtractPathsFromCLI(cli: ParsedArgs): IPath[] {
|
||||
const pathsToOpen = arrays.coalesce(cli._.map(candidate => this.parsePath(candidate, { ignoreFileNotFound: true, gotoLineMode: cli.goto })));
|
||||
const pathsToOpen = [];
|
||||
|
||||
// folder uris
|
||||
if (cli['folder-uri'] && cli['folder-uri'].length) {
|
||||
const arg = cli['folder-uri'];
|
||||
const folderUris: string[] = typeof arg === 'string' ? [arg] : arg;
|
||||
pathsToOpen.push(...arrays.coalesce(folderUris.map(candidate => this.parseUri(this.parseFolderUriArg(candidate), { ignoreFileNotFound: true, gotoLineMode: cli.goto }))));
|
||||
}
|
||||
|
||||
// folder or file paths
|
||||
if (cli._ && cli._.length) {
|
||||
pathsToOpen.push(...arrays.coalesce(cli._.map(candidate => this.parsePath(candidate, { ignoreFileNotFound: true, gotoLineMode: cli.goto }))));
|
||||
}
|
||||
|
||||
if (pathsToOpen.length > 0) {
|
||||
return pathsToOpen;
|
||||
}
|
||||
@@ -839,9 +891,9 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}
|
||||
|
||||
// folder (if path is valid)
|
||||
else if (lastActiveWindow.folderPath) {
|
||||
const validatedFolder = this.parsePath(lastActiveWindow.folderPath);
|
||||
if (validatedFolder && validatedFolder.folderPath) {
|
||||
else if (lastActiveWindow.folderUri) {
|
||||
const validatedFolder = this.parseUri(lastActiveWindow.folderUri);
|
||||
if (validatedFolder && validatedFolder.folderUri) {
|
||||
return [validatedFolder];
|
||||
}
|
||||
}
|
||||
@@ -867,16 +919,16 @@ export class WindowsManager implements IWindowsMainService {
|
||||
windowsToOpen.push(...workspaceCandidates.map(candidate => this.parsePath(candidate.configPath)).filter(window => window && window.workspace));
|
||||
|
||||
// Folders
|
||||
const folderCandidates = this.windowsState.openedWindows.filter(w => !!w.folderPath).map(w => w.folderPath);
|
||||
if (lastActiveWindow && lastActiveWindow.folderPath) {
|
||||
folderCandidates.push(lastActiveWindow.folderPath);
|
||||
const folderCandidates = this.windowsState.openedWindows.filter(w => !!w.folderUri).map(w => w.folderUri);
|
||||
if (lastActiveWindow && lastActiveWindow.folderUri) {
|
||||
folderCandidates.push(lastActiveWindow.folderUri);
|
||||
}
|
||||
windowsToOpen.push(...folderCandidates.map(candidate => this.parsePath(candidate)).filter(window => window && window.folderPath));
|
||||
windowsToOpen.push(...folderCandidates.map(candidate => this.parseUri(candidate)).filter(window => window && window.folderUri));
|
||||
|
||||
// Windows that were Empty
|
||||
if (restoreWindows === 'all') {
|
||||
const lastOpenedEmpty = this.windowsState.openedWindows.filter(w => !w.workspace && !w.folderPath && w.backupPath).map(w => w.backupPath);
|
||||
const lastActiveEmpty = lastActiveWindow && !lastActiveWindow.workspace && !lastActiveWindow.folderPath && lastActiveWindow.backupPath;
|
||||
const lastOpenedEmpty = this.windowsState.openedWindows.filter(w => !w.workspace && !w.folderUri && w.backupPath).map(w => w.backupPath);
|
||||
const lastActiveEmpty = lastActiveWindow && !lastActiveWindow.workspace && !lastActiveWindow.folderUri && lastActiveWindow.backupPath;
|
||||
if (lastActiveEmpty) {
|
||||
lastOpenedEmpty.push(lastActiveEmpty);
|
||||
}
|
||||
@@ -911,6 +963,28 @@ export class WindowsManager implements IWindowsMainService {
|
||||
return restoreWindows;
|
||||
}
|
||||
|
||||
private parseFolderUriArg(arg: string): URI {
|
||||
// Do not support if user has passed folder path on Windows
|
||||
if (isWindows && /^([a-z])\:(.*)$/i.test(arg)) {
|
||||
return null;
|
||||
}
|
||||
return URI.parse(arg);
|
||||
}
|
||||
|
||||
private parseUri(anyUri: URI, options?: { ignoreFileNotFound?: boolean, gotoLineMode?: boolean, forceOpenWorkspaceAsFile?: boolean; }): IPathToOpen {
|
||||
if (!anyUri || !anyUri.scheme) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (anyUri.scheme === Schemas.file) {
|
||||
return this.parsePath(anyUri.fsPath, options);
|
||||
}
|
||||
|
||||
return {
|
||||
folderUri: anyUri
|
||||
};
|
||||
}
|
||||
|
||||
private parsePath(anyPath: string, options?: { ignoreFileNotFound?: boolean, gotoLineMode?: boolean, forceOpenWorkspaceAsFile?: boolean; }): IPathToOpen {
|
||||
if (!anyPath) {
|
||||
return null;
|
||||
@@ -951,7 +1025,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
// over to us)
|
||||
else if (candidateStat.isDirectory()) {
|
||||
return {
|
||||
folderPath: candidate
|
||||
folderUri: URI.file(candidate)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1007,7 +1081,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
return { openFolderInNewWindow, openFilesInNewWindow };
|
||||
}
|
||||
|
||||
public openExtensionDevelopmentHostWindow(openConfig: IOpenConfiguration): void {
|
||||
openExtensionDevelopmentHostWindow(openConfig: IOpenConfiguration): void {
|
||||
|
||||
// Reload an existing extension development host window on the same path
|
||||
// We currently do not allow more than one extension development window
|
||||
@@ -1021,25 +1095,39 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}
|
||||
|
||||
// Fill in previously opened workspace unless an explicit path is provided and we are not unit testing
|
||||
if (openConfig.cli._.length === 0 && !openConfig.cli.extensionTestsPath) {
|
||||
if (openConfig.cli._.length === 0 && (openConfig.cli['folder-uri'] || []).length === 0 && !openConfig.cli.extensionTestsPath) {
|
||||
const extensionDevelopmentWindowState = this.windowsState.lastPluginDevelopmentHostWindow;
|
||||
const workspaceToOpen = extensionDevelopmentWindowState && (extensionDevelopmentWindowState.workspace || extensionDevelopmentWindowState.folderPath);
|
||||
const workspaceToOpen = extensionDevelopmentWindowState && (extensionDevelopmentWindowState.workspace || extensionDevelopmentWindowState.folderUri);
|
||||
if (workspaceToOpen) {
|
||||
openConfig.cli._ = [isSingleFolderWorkspaceIdentifier(workspaceToOpen) ? workspaceToOpen : workspaceToOpen.configPath];
|
||||
if (isSingleFolderWorkspaceIdentifier(workspaceToOpen)) {
|
||||
if (workspaceToOpen.scheme === Schemas.file) {
|
||||
openConfig.cli._ = [workspaceToOpen.fsPath];
|
||||
} else {
|
||||
openConfig.cli['folder-uri'] = [workspaceToOpen.toString()];
|
||||
}
|
||||
} else {
|
||||
openConfig.cli._ = [workspaceToOpen.configPath];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we are not asked to open a workspace or folder that is already opened
|
||||
if (openConfig.cli._.some(path => !!findWindowOnWorkspaceOrFolderPath(WindowsManager.WINDOWS, path))) {
|
||||
if (openConfig.cli._.some(path => !!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, URI.file(path)))) {
|
||||
openConfig.cli._ = [];
|
||||
}
|
||||
if (openConfig.cli['folder-uri']) {
|
||||
const arg = openConfig.cli['folder-uri'];
|
||||
const folderUris: string[] = typeof arg === 'string' ? [arg] : arg;
|
||||
if (folderUris.some(uri => !!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, this.parseFolderUriArg(uri)))) {
|
||||
openConfig.cli['folder-uri'] = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Open it
|
||||
this.open({ context: openConfig.context, cli: openConfig.cli, forceNewWindow: true, forceEmpty: openConfig.cli._.length === 0, userEnv: openConfig.userEnv });
|
||||
this.open({ context: openConfig.context, cli: openConfig.cli, forceNewWindow: true, forceEmpty: openConfig.cli._.length === 0 && (openConfig.cli['folder-uri'] || []).length === 0, userEnv: openConfig.userEnv });
|
||||
}
|
||||
|
||||
private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow {
|
||||
|
||||
// Build IWindowConfiguration from config and options
|
||||
const configuration: IWindowConfiguration = mixin({}, options.cli); // inherit all properties from CLI
|
||||
configuration.appRoot = this.environmentService.appRoot;
|
||||
@@ -1048,7 +1136,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
configuration.userEnv = assign({}, this.initialUserEnv, options.userEnv || {});
|
||||
configuration.isInitialStartup = options.initialStartup;
|
||||
configuration.workspace = options.workspace;
|
||||
configuration.folderPath = options.folderPath;
|
||||
configuration.folderUri = options.folderUri;
|
||||
configuration.filesToOpen = options.filesToOpen;
|
||||
configuration.filesToCreate = options.filesToCreate;
|
||||
configuration.filesToDiff = options.filesToDiff;
|
||||
@@ -1138,8 +1226,8 @@ export class WindowsManager implements IWindowsMainService {
|
||||
if (!configuration.extensionDevelopmentPath) {
|
||||
if (configuration.workspace) {
|
||||
configuration.backupPath = this.backupMainService.registerWorkspaceBackupSync(configuration.workspace);
|
||||
} else if (configuration.folderPath) {
|
||||
configuration.backupPath = this.backupMainService.registerFolderBackupSync(configuration.folderPath);
|
||||
} else if (configuration.folderUri) {
|
||||
configuration.backupPath = this.backupMainService.registerFolderBackupSync(configuration.folderUri);
|
||||
} else {
|
||||
configuration.backupPath = this.backupMainService.registerEmptyWindowBackupSync(options.emptyWindowBackupFolder);
|
||||
}
|
||||
@@ -1176,8 +1264,8 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}
|
||||
|
||||
// Known Folder - load from stored settings
|
||||
if (configuration.folderPath) {
|
||||
const stateForFolder = this.windowsState.openedWindows.filter(o => isEqual(o.folderPath, configuration.folderPath, !isLinux /* ignorecase */)).map(o => o.uiState);
|
||||
if (configuration.folderUri) {
|
||||
const stateForFolder = this.windowsState.openedWindows.filter(o => o.folderUri && isEqual(o.folderUri, configuration.folderUri, hasToIgnoreCase(o.folderUri))).map(o => o.uiState);
|
||||
if (stateForFolder.length) {
|
||||
return stateForFolder[0];
|
||||
}
|
||||
@@ -1283,7 +1371,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
return state;
|
||||
}
|
||||
|
||||
public reload(win: ICodeWindow, cli?: ParsedArgs): void {
|
||||
reload(win: ICodeWindow, cli?: ParsedArgs): void {
|
||||
|
||||
// Only reload when the window has not vetoed this
|
||||
this.lifecycleService.unload(win, UnloadReason.RELOAD).done(veto => {
|
||||
@@ -1296,18 +1384,22 @@ export class WindowsManager implements IWindowsMainService {
|
||||
});
|
||||
}
|
||||
|
||||
public closeWorkspace(win: ICodeWindow): void {
|
||||
closeWorkspace(win: ICodeWindow): void {
|
||||
this.openInBrowserWindow({
|
||||
cli: this.environmentService.args,
|
||||
windowToUse: win
|
||||
});
|
||||
}
|
||||
|
||||
public saveAndEnterWorkspace(win: ICodeWindow, path: string): TPromise<IEnterWorkspaceResult> {
|
||||
saveAndEnterWorkspace(win: ICodeWindow, path: string): TPromise<IEnterWorkspaceResult> {
|
||||
return this.workspacesManager.saveAndEnterWorkspace(win, path).then(result => this.doEnterWorkspace(win, result));
|
||||
}
|
||||
|
||||
public createAndEnterWorkspace(win: ICodeWindow, folders?: IWorkspaceFolderCreationData[], path?: string): TPromise<IEnterWorkspaceResult> {
|
||||
enterWorkspace(win: ICodeWindow, path: string): TPromise<IEnterWorkspaceResult> {
|
||||
return this.workspacesManager.enterWorkspace(win, path).then(result => this.doEnterWorkspace(win, result));
|
||||
}
|
||||
|
||||
createAndEnterWorkspace(win: ICodeWindow, folders?: IWorkspaceFolderCreationData[], path?: string): TPromise<IEnterWorkspaceResult> {
|
||||
return this.workspacesManager.createAndEnterWorkspace(win, folders, path).then(result => this.doEnterWorkspace(win, result));
|
||||
}
|
||||
|
||||
@@ -1322,7 +1414,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
return result;
|
||||
}
|
||||
|
||||
public pickWorkspaceAndOpen(options: INativeOpenDialogOptions): void {
|
||||
pickWorkspaceAndOpen(options: INativeOpenDialogOptions): void {
|
||||
this.workspacesManager.pickWorkspaceAndOpen(options);
|
||||
}
|
||||
|
||||
@@ -1363,7 +1455,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}));
|
||||
}
|
||||
|
||||
public focusLastActive(cli: ParsedArgs, context: OpenContext): ICodeWindow {
|
||||
focusLastActive(cli: ParsedArgs, context: OpenContext): ICodeWindow {
|
||||
const lastActive = this.getLastActiveWindow();
|
||||
if (lastActive) {
|
||||
lastActive.focus();
|
||||
@@ -1375,15 +1467,15 @@ export class WindowsManager implements IWindowsMainService {
|
||||
return this.open({ context, cli, forceEmpty: true })[0];
|
||||
}
|
||||
|
||||
public getLastActiveWindow(): ICodeWindow {
|
||||
getLastActiveWindow(): ICodeWindow {
|
||||
return getLastActiveWindow(WindowsManager.WINDOWS);
|
||||
}
|
||||
|
||||
public openNewWindow(context: OpenContext): ICodeWindow[] {
|
||||
openNewWindow(context: OpenContext): ICodeWindow[] {
|
||||
return this.open({ context, cli: this.environmentService.args, forceNewWindow: true, forceEmpty: true });
|
||||
}
|
||||
|
||||
public waitForWindowCloseOrLoad(windowId: number): TPromise<void> {
|
||||
waitForWindowCloseOrLoad(windowId: number): TPromise<void> {
|
||||
return new TPromise<void>(c => {
|
||||
function handler(id: number) {
|
||||
if (id === windowId) {
|
||||
@@ -1399,7 +1491,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
});
|
||||
}
|
||||
|
||||
public sendToFocused(channel: string, ...args: any[]): void {
|
||||
sendToFocused(channel: string, ...args: any[]): void {
|
||||
const focusedWindow = this.getFocusedWindow() || this.getLastActiveWindow();
|
||||
|
||||
if (focusedWindow) {
|
||||
@@ -1407,7 +1499,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}
|
||||
}
|
||||
|
||||
public sendToAll(channel: string, payload?: any, windowIdsToIgnore?: number[]): void {
|
||||
sendToAll(channel: string, payload?: any, windowIdsToIgnore?: number[]): void {
|
||||
WindowsManager.WINDOWS.forEach(w => {
|
||||
if (windowIdsToIgnore && windowIdsToIgnore.indexOf(w.id) >= 0) {
|
||||
return; // do not send if we are instructed to ignore it
|
||||
@@ -1417,7 +1509,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
});
|
||||
}
|
||||
|
||||
public getFocusedWindow(): ICodeWindow {
|
||||
getFocusedWindow(): ICodeWindow {
|
||||
const win = BrowserWindow.getFocusedWindow();
|
||||
if (win) {
|
||||
return this.getWindowById(win.id);
|
||||
@@ -1426,7 +1518,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
return null;
|
||||
}
|
||||
|
||||
public getWindowById(windowId: number): ICodeWindow {
|
||||
getWindowById(windowId: number): ICodeWindow {
|
||||
const res = WindowsManager.WINDOWS.filter(w => w.id === windowId);
|
||||
if (res && res.length === 1) {
|
||||
return res[0];
|
||||
@@ -1435,11 +1527,11 @@ export class WindowsManager implements IWindowsMainService {
|
||||
return null;
|
||||
}
|
||||
|
||||
public getWindows(): ICodeWindow[] {
|
||||
getWindows(): ICodeWindow[] {
|
||||
return WindowsManager.WINDOWS;
|
||||
}
|
||||
|
||||
public getWindowCount(): number {
|
||||
getWindowCount(): number {
|
||||
return WindowsManager.WINDOWS.length;
|
||||
}
|
||||
|
||||
@@ -1507,15 +1599,15 @@ export class WindowsManager implements IWindowsMainService {
|
||||
this._onWindowClose.fire(win.id);
|
||||
}
|
||||
|
||||
public pickFileFolderAndOpen(options: INativeOpenDialogOptions): void {
|
||||
pickFileFolderAndOpen(options: INativeOpenDialogOptions): void {
|
||||
this.doPickAndOpen(options, true /* pick folders */, true /* pick files */);
|
||||
}
|
||||
|
||||
public pickFolderAndOpen(options: INativeOpenDialogOptions): void {
|
||||
pickFolderAndOpen(options: INativeOpenDialogOptions): void {
|
||||
this.doPickAndOpen(options, true /* pick folders */, false /* pick files */);
|
||||
}
|
||||
|
||||
public pickFileAndOpen(options: INativeOpenDialogOptions): void {
|
||||
pickFileAndOpen(options: INativeOpenDialogOptions): void {
|
||||
this.doPickAndOpen(options, false /* pick folders */, true /* pick files */);
|
||||
}
|
||||
|
||||
@@ -1553,19 +1645,19 @@ export class WindowsManager implements IWindowsMainService {
|
||||
this.dialogs.pickAndOpen(internalOptions);
|
||||
}
|
||||
|
||||
public showMessageBox(options: Electron.MessageBoxOptions, win?: ICodeWindow): TPromise<IMessageBoxResult> {
|
||||
showMessageBox(options: Electron.MessageBoxOptions, win?: ICodeWindow): TPromise<IMessageBoxResult> {
|
||||
return this.dialogs.showMessageBox(options, win);
|
||||
}
|
||||
|
||||
public showSaveDialog(options: Electron.SaveDialogOptions, win?: ICodeWindow): TPromise<string> {
|
||||
showSaveDialog(options: Electron.SaveDialogOptions, win?: ICodeWindow): TPromise<string> {
|
||||
return this.dialogs.showSaveDialog(options, win);
|
||||
}
|
||||
|
||||
public showOpenDialog(options: Electron.OpenDialogOptions, win?: ICodeWindow): TPromise<string[]> {
|
||||
showOpenDialog(options: Electron.OpenDialogOptions, win?: ICodeWindow): TPromise<string[]> {
|
||||
return this.dialogs.showOpenDialog(options, win);
|
||||
}
|
||||
|
||||
public quit(): void {
|
||||
quit(): void {
|
||||
|
||||
// If the user selected to exit from an extension development host window, do not quit, but just
|
||||
// close the window unless this is the last window that is opened.
|
||||
@@ -1605,8 +1697,8 @@ class Dialogs {
|
||||
this.noWindowDialogQueue = new Queue<any>();
|
||||
}
|
||||
|
||||
public pickAndOpen(options: INativeOpenDialogOptions): void {
|
||||
this.getFileOrFolderPaths(options).then(paths => {
|
||||
pickAndOpen(options: INativeOpenDialogOptions): void {
|
||||
this.getFileOrFolderUris(options).then(paths => {
|
||||
const numberOfPaths = paths ? paths.length : 0;
|
||||
|
||||
// Telemetry
|
||||
@@ -1624,7 +1716,7 @@ class Dialogs {
|
||||
this.windowsMainService.open({
|
||||
context: OpenContext.DIALOG,
|
||||
cli: this.environmentService.args,
|
||||
pathsToOpen: paths,
|
||||
urisToOpen: paths,
|
||||
forceNewWindow: options.forceNewWindow,
|
||||
forceOpenWorkspaceAsFile: options.dialogOptions && !equals(options.dialogOptions.filters, WORKSPACE_FILTER)
|
||||
});
|
||||
@@ -1632,7 +1724,7 @@ class Dialogs {
|
||||
});
|
||||
}
|
||||
|
||||
private getFileOrFolderPaths(options: IInternalNativeOpenDialogOptions): TPromise<string[]> {
|
||||
private getFileOrFolderUris(options: IInternalNativeOpenDialogOptions): TPromise<URI[]> {
|
||||
|
||||
// Ensure dialog options
|
||||
if (!options.dialogOptions) {
|
||||
@@ -1670,7 +1762,7 @@ class Dialogs {
|
||||
// Remember path in storage for next time
|
||||
this.stateService.setItem(Dialogs.workingDirPickerStorageKey, dirname(paths[0]));
|
||||
|
||||
return paths;
|
||||
return paths.map(path => URI.file(path));
|
||||
}
|
||||
|
||||
return void 0;
|
||||
@@ -1691,7 +1783,7 @@ class Dialogs {
|
||||
return windowDialogQueue;
|
||||
}
|
||||
|
||||
public showMessageBox(options: Electron.MessageBoxOptions, window?: ICodeWindow): TPromise<IMessageBoxResult> {
|
||||
showMessageBox(options: Electron.MessageBoxOptions, window?: ICodeWindow): TPromise<IMessageBoxResult> {
|
||||
return this.getDialogQueue(window).queue(() => {
|
||||
return new TPromise((c, e) => {
|
||||
dialog.showMessageBox(window ? window.win : void 0, options, (response: number, checkboxChecked: boolean) => {
|
||||
@@ -1701,7 +1793,7 @@ class Dialogs {
|
||||
});
|
||||
}
|
||||
|
||||
public showSaveDialog(options: Electron.SaveDialogOptions, window?: ICodeWindow): TPromise<string> {
|
||||
showSaveDialog(options: Electron.SaveDialogOptions, window?: ICodeWindow): TPromise<string> {
|
||||
|
||||
function normalizePath(path: string): string {
|
||||
if (path && isMacintosh) {
|
||||
@@ -1720,7 +1812,7 @@ class Dialogs {
|
||||
});
|
||||
}
|
||||
|
||||
public showOpenDialog(options: Electron.OpenDialogOptions, window?: ICodeWindow): TPromise<string[]> {
|
||||
showOpenDialog(options: Electron.OpenDialogOptions, window?: ICodeWindow): TPromise<string[]> {
|
||||
|
||||
function normalizePaths(paths: string[]): string[] {
|
||||
if (paths && paths.length > 0 && isMacintosh) {
|
||||
@@ -1764,7 +1856,7 @@ class WorkspacesManager {
|
||||
) {
|
||||
}
|
||||
|
||||
public saveAndEnterWorkspace(window: ICodeWindow, path: string): TPromise<IEnterWorkspaceResult> {
|
||||
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
|
||||
}
|
||||
@@ -1772,7 +1864,24 @@ class WorkspacesManager {
|
||||
return this.doSaveAndOpenWorkspace(window, window.openedWorkspace, path);
|
||||
}
|
||||
|
||||
public createAndEnterWorkspace(window: ICodeWindow, folders?: IWorkspaceFolderCreationData[], path?: string): TPromise<IEnterWorkspaceResult> {
|
||||
enterWorkspace(window: ICodeWindow, 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
|
||||
}
|
||||
|
||||
return this.isValidTargetWorkspacePath(window, path).then(isValid => {
|
||||
if (!isValid) {
|
||||
return TPromise.as<IEnterWorkspaceResult>(null); // return early if the workspace is not valid
|
||||
}
|
||||
|
||||
return this.workspacesMainService.resolveWorkspace(path).then(workspace => {
|
||||
return this.doOpenWorkspace(window, workspace);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1823,25 +1932,27 @@ class WorkspacesManager {
|
||||
savePromise = TPromise.as(workspace);
|
||||
}
|
||||
|
||||
return savePromise.then(workspace => {
|
||||
window.focus();
|
||||
|
||||
// Register window for backups and migrate current backups over
|
||||
let backupPath: string;
|
||||
if (!window.config.extensionDevelopmentPath) {
|
||||
backupPath = this.backupMainService.registerWorkspaceBackupSync(workspace, window.config.backupPath);
|
||||
}
|
||||
|
||||
// Update window configuration properly based on transition to workspace
|
||||
window.config.folderPath = void 0;
|
||||
window.config.workspace = workspace;
|
||||
window.config.backupPath = backupPath;
|
||||
|
||||
return { workspace, backupPath };
|
||||
});
|
||||
return savePromise.then(workspace => this.doOpenWorkspace(window, workspace));
|
||||
}
|
||||
|
||||
public pickWorkspaceAndOpen(options: INativeOpenDialogOptions): void {
|
||||
private doOpenWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): IEnterWorkspaceResult {
|
||||
window.focus();
|
||||
|
||||
// Register window for backups and migrate current backups over
|
||||
let backupPath: string;
|
||||
if (!window.config.extensionDevelopmentPath) {
|
||||
backupPath = this.backupMainService.registerWorkspaceBackupSync(workspace, window.config.backupPath);
|
||||
}
|
||||
|
||||
// Update window configuration properly based on transition to workspace
|
||||
window.config.folderUri = void 0;
|
||||
window.config.workspace = workspace;
|
||||
window.config.backupPath = backupPath;
|
||||
|
||||
return { workspace, backupPath };
|
||||
}
|
||||
|
||||
pickWorkspaceAndOpen(options: INativeOpenDialogOptions): void {
|
||||
const window = this.windowsMainService.getWindowById(options.windowId) || this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow();
|
||||
|
||||
this.windowsMainService.pickFileAndOpen({
|
||||
@@ -1859,7 +1970,7 @@ class WorkspacesManager {
|
||||
});
|
||||
}
|
||||
|
||||
public promptToSaveUntitledWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): TPromise<boolean> {
|
||||
promptToSaveUntitledWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): TPromise<boolean> {
|
||||
enum ConfirmResult {
|
||||
SAVE,
|
||||
DONT_SAVE,
|
||||
@@ -1927,7 +2038,7 @@ class WorkspacesManager {
|
||||
private getUntitledWorkspaceSaveDialogDefaultPath(workspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier): string {
|
||||
if (workspace) {
|
||||
if (isSingleFolderWorkspaceIdentifier(workspace)) {
|
||||
return dirname(workspace);
|
||||
return workspace.scheme === Schemas.file ? dirname(workspace.fsPath) : void 0;
|
||||
}
|
||||
|
||||
const resolvedWorkspace = this.workspacesMainService.resolveWorkspaceSync(workspace.configPath);
|
||||
|
||||
@@ -31,7 +31,7 @@ interface IMainCli {
|
||||
main: (argv: ParsedArgs) => TPromise<void>;
|
||||
}
|
||||
|
||||
export async function main(argv: string[]): TPromise<any> {
|
||||
export async function main(argv: string[]): Promise<any> {
|
||||
let args: ParsedArgs;
|
||||
|
||||
try {
|
||||
@@ -318,7 +318,7 @@ export async function main(argv: string[]): TPromise<any> {
|
||||
env
|
||||
};
|
||||
|
||||
if (typeof args['upload-logs'] !== undefined) {
|
||||
if (typeof args['upload-logs'] !== 'undefined') {
|
||||
options['stdio'] = ['pipe', 'pipe', 'pipe'];
|
||||
} else if (!verbose) {
|
||||
options['stdio'] = 'ignore';
|
||||
|
||||
@@ -7,6 +7,7 @@ import { localize } from 'vs/nls';
|
||||
import product from 'vs/platform/node/product';
|
||||
import pkg from 'vs/platform/node/package';
|
||||
import * as path from 'path';
|
||||
import * as semver from 'semver';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { sequence } from 'vs/base/common/async';
|
||||
@@ -38,6 +39,8 @@ import { ILogService, getLogLevel } from 'vs/platform/log/common/log';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { CommandLineDialogService } from 'vs/platform/dialogs/node/dialogService';
|
||||
import { areSameExtensions, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
|
||||
const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id);
|
||||
const notInstalled = (id: string) => localize('notInstalled', "Extension '{0}' is not installed.", id);
|
||||
@@ -58,26 +61,28 @@ class Main {
|
||||
constructor(
|
||||
@IEnvironmentService private environmentService: IEnvironmentService,
|
||||
@IExtensionManagementService private extensionManagementService: IExtensionManagementService,
|
||||
@IExtensionGalleryService private extensionGalleryService: IExtensionGalleryService
|
||||
@IExtensionGalleryService private extensionGalleryService: IExtensionGalleryService,
|
||||
@IDialogService private dialogService: IDialogService
|
||||
) { }
|
||||
|
||||
run(argv: ParsedArgs): TPromise<any> {
|
||||
// TODO@joao - make this contributable
|
||||
|
||||
let returnPromise: TPromise<any>;
|
||||
if (argv['install-source']) {
|
||||
return this.setInstallSource(argv['install-source']);
|
||||
returnPromise = this.setInstallSource(argv['install-source']);
|
||||
} else if (argv['list-extensions']) {
|
||||
return this.listExtensions(argv['show-versions']);
|
||||
returnPromise = this.listExtensions(argv['show-versions']);
|
||||
} else if (argv['install-extension']) {
|
||||
const arg = argv['install-extension'];
|
||||
const args: string[] = typeof arg === 'string' ? [arg] : arg;
|
||||
return this.installExtension(args);
|
||||
returnPromise = this.installExtension(args);
|
||||
} else if (argv['uninstall-extension']) {
|
||||
const arg = argv['uninstall-extension'];
|
||||
const ids: string[] = typeof arg === 'string' ? [arg] : arg;
|
||||
return this.uninstallExtension(ids);
|
||||
returnPromise = this.uninstallExtension(ids);
|
||||
}
|
||||
return undefined;
|
||||
return returnPromise || TPromise.as(null);
|
||||
}
|
||||
|
||||
private setInstallSource(installSource: string): TPromise<any> {
|
||||
@@ -111,15 +116,8 @@ class Main {
|
||||
const galleryTasks: Task[] = extensions
|
||||
.filter(e => !/\.vsix$/i.test(e))
|
||||
.map(id => () => {
|
||||
return this.extensionManagementService.getInstalled(LocalExtensionType.User).then(installed => {
|
||||
const isInstalled = installed.some(e => getId(e.manifest) === id);
|
||||
|
||||
if (isInstalled) {
|
||||
console.log(localize('alreadyInstalled', "Extension '{0}' is already installed.", id));
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
return this.extensionGalleryService.query({ names: [id], source: 'cli' })
|
||||
return this.extensionManagementService.getInstalled(LocalExtensionType.User)
|
||||
.then(installed => this.extensionGalleryService.query({ names: [id], source: 'cli' })
|
||||
.then<IPager<IGalleryExtension>>(null, err => {
|
||||
if (err.responseText) {
|
||||
try {
|
||||
@@ -129,7 +127,6 @@ class Main {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
return TPromise.wrapError(err);
|
||||
})
|
||||
.then(result => {
|
||||
@@ -139,29 +136,52 @@ class Main {
|
||||
return TPromise.wrapError(new Error(`${notFound(id)}\n${useId}`));
|
||||
}
|
||||
|
||||
console.log(localize('foundExtension', "Found '{0}' in the marketplace.", id));
|
||||
console.log(localize('installing', "Installing..."));
|
||||
const [installedExtension] = installed.filter(e => areSameExtensions({ id: getGalleryExtensionIdFromLocal(e) }, { id }));
|
||||
if (installedExtension) {
|
||||
const outdated = semver.gt(extension.version, installedExtension.manifest.version);
|
||||
if (outdated) {
|
||||
const updateMessage = localize('updateMessage', "Extension '{0}' v{1} is already installed, but a newer version {2} is available in the marketplace. Would you like to update?", id, installedExtension.manifest.version, extension.version);
|
||||
return this.dialogService.show(Severity.Info, updateMessage, [localize('yes', "Yes"), localize('no', "No")])
|
||||
.then(option => {
|
||||
if (option === 0) {
|
||||
return this.installFromGallery(id, extension);
|
||||
}
|
||||
console.log(localize('cancelInstall', "Cancelled installing Extension '{0}'.", id));
|
||||
return TPromise.as(null);
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.log(localize('alreadyInstalled', "Extension '{0}' is already installed.", id));
|
||||
return TPromise.as(null);
|
||||
}
|
||||
} else {
|
||||
console.log(localize('foundExtension', "Found '{0}' in the marketplace.", id));
|
||||
return this.installFromGallery(id, extension);
|
||||
}
|
||||
|
||||
}));
|
||||
});
|
||||
|
||||
return sequence([...vsixTasks, ...galleryTasks]);
|
||||
}
|
||||
|
||||
private installFromGallery(id: string, extension: IGalleryExtension): TPromise<void> {
|
||||
console.log(localize('installing', "Installing..."));
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private uninstallExtension(extensions: string[]): TPromise<any> {
|
||||
async function getExtensionId(extensionDescription: string): TPromise<string> {
|
||||
async function getExtensionId(extensionDescription: string): Promise<string> {
|
||||
if (!/\.vsix$/i.test(extensionDescription)) {
|
||||
return extensionDescription;
|
||||
}
|
||||
@@ -174,7 +194,7 @@ class Main {
|
||||
return sequence(extensions.map(extension => () => {
|
||||
return getExtensionId(extension).then(id => {
|
||||
return this.extensionManagementService.getInstalled(LocalExtensionType.User).then(installed => {
|
||||
const [extension] = installed.filter(e => getId(e.manifest) === id);
|
||||
const [extension] = installed.filter(e => areSameExtensions({ id: getGalleryExtensionIdFromLocal(e) }, { id }));
|
||||
|
||||
if (!extension) {
|
||||
return TPromise.wrapError(new Error(`${notInstalled(id)}\n${useId}`));
|
||||
@@ -221,17 +241,13 @@ export function main(argv: ParsedArgs): TPromise<void> {
|
||||
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
|
||||
services.set(IDialogService, new SyncDescriptor(CommandLineDialogService));
|
||||
|
||||
const appenders: AppInsightsAppender[] = [];
|
||||
if (isBuilt && !extensionDevelopmentPath && !envService.args['disable-telemetry'] && product.enableTelemetry) {
|
||||
const appenders: AppInsightsAppender[] = [];
|
||||
|
||||
if (product.aiConfig && product.aiConfig.asimovKey) {
|
||||
appenders.push(new AppInsightsAppender(eventPrefix, null, product.aiConfig.asimovKey));
|
||||
appenders.push(new AppInsightsAppender(eventPrefix, null, product.aiConfig.asimovKey, logService));
|
||||
}
|
||||
|
||||
// It is important to dispose the AI adapter properly because
|
||||
// only then they flush remaining data.
|
||||
process.once('exit', () => appenders.forEach(a => a.dispose()));
|
||||
|
||||
const config: ITelemetryServiceConfig = {
|
||||
appender: combinedAppender(...appenders),
|
||||
commonProperties: resolveCommonProperties(product.commit, pkg.version, stateService.getItem('telemetry.machineId'), installSourcePath),
|
||||
@@ -246,7 +262,10 @@ export function main(argv: ParsedArgs): TPromise<void> {
|
||||
const instantiationService2 = instantiationService.createChild(services);
|
||||
const main = instantiationService2.createInstance(Main);
|
||||
|
||||
return main.run(argv);
|
||||
return main.run(argv).then(() => {
|
||||
// Dispose the AI adapter so that remaining data gets flushed.
|
||||
return combinedAppender(...appenders).dispose();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,12 +8,14 @@
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as paths from 'vs/base/common/paths';
|
||||
import { OpenContext } from 'vs/platform/windows/common/windows';
|
||||
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IResolvedWorkspace } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IWorkspaceIdentifier, IResolvedWorkspace, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { hasToIgnoreCase, isEqual } from 'vs/base/common/resources';
|
||||
|
||||
export interface ISimpleWindow {
|
||||
openedWorkspace?: IWorkspaceIdentifier;
|
||||
openedFolderPath?: string;
|
||||
openedFolderUri?: URI;
|
||||
openedFilePath?: string;
|
||||
extensionDevelopmentPath?: string;
|
||||
lastFocusTime: number;
|
||||
@@ -30,7 +32,7 @@ export interface IBestWindowOrFolderOptions<W extends ISimpleWindow> {
|
||||
workspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace;
|
||||
}
|
||||
|
||||
export function findBestWindowOrFolderForFile<W extends ISimpleWindow>({ windows, newWindow, reuseWindow, context, filePath, userHome, codeSettingsFolder, workspaceResolver }: IBestWindowOrFolderOptions<W>): W | string {
|
||||
export function findBestWindowOrFolderForFile<W extends ISimpleWindow>({ windows, newWindow, reuseWindow, context, filePath, workspaceResolver }: IBestWindowOrFolderOptions<W>): W {
|
||||
if (!newWindow && filePath && (context === OpenContext.DESKTOP || context === OpenContext.CLI || context === OpenContext.DOCK)) {
|
||||
const windowOnFilePath = findWindowOnFilePath(windows, filePath, workspaceResolver);
|
||||
if (windowOnFilePath) {
|
||||
@@ -54,9 +56,9 @@ function findWindowOnFilePath<W extends ISimpleWindow>(windows: W[], filePath: s
|
||||
}
|
||||
|
||||
// Then go with single folder windows that are parent of the provided file path
|
||||
const singleFolderWindowsOnFilePath = windows.filter(window => typeof window.openedFolderPath === 'string' && paths.isEqualOrParent(filePath, window.openedFolderPath, !platform.isLinux /* ignorecase */));
|
||||
const singleFolderWindowsOnFilePath = windows.filter(window => window.openedFolderUri && window.openedFolderUri.scheme === Schemas.file && paths.isEqualOrParent(filePath, window.openedFolderUri.fsPath, !platform.isLinux /* ignorecase */));
|
||||
if (singleFolderWindowsOnFilePath.length) {
|
||||
return singleFolderWindowsOnFilePath.sort((a, b) => -(a.openedFolderPath.length - b.openedFolderPath.length))[0];
|
||||
return singleFolderWindowsOnFilePath.sort((a, b) => -(a.openedFolderUri.path.length - b.openedFolderUri.path.length))[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -73,7 +75,7 @@ export function findWindowOnWorkspace<W extends ISimpleWindow>(windows: W[], wor
|
||||
|
||||
// match on folder
|
||||
if (isSingleFolderWorkspaceIdentifier(workspace)) {
|
||||
if (typeof window.openedFolderPath === 'string' && (paths.isEqual(window.openedFolderPath, workspace, !platform.isLinux /* ignorecase */))) {
|
||||
if (window.openedFolderUri && isEqual(window.openedFolderUri, workspace, hasToIgnoreCase(window.openedFolderUri))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -101,16 +103,16 @@ export function findWindowOnExtensionDevelopmentPath<W extends ISimpleWindow>(wi
|
||||
})[0];
|
||||
}
|
||||
|
||||
export function findWindowOnWorkspaceOrFolderPath<W extends ISimpleWindow>(windows: W[], path: string): W {
|
||||
export function findWindowOnWorkspaceOrFolderUri<W extends ISimpleWindow>(windows: W[], uri: URI): W {
|
||||
return windows.filter(window => {
|
||||
|
||||
// check for workspace config path
|
||||
if (window.openedWorkspace && paths.isEqual(window.openedWorkspace.configPath, path, !platform.isLinux /* ignorecase */)) {
|
||||
if (window.openedWorkspace && isEqual(URI.file(window.openedWorkspace.configPath), uri, !platform.isLinux /* ignorecase */)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// check for folder path
|
||||
if (window.openedFolderPath && paths.isEqual(window.openedFolderPath, path, !platform.isLinux /* ignorecase */)) {
|
||||
if (window.openedFolderUri && isEqual(window.openedFolderUri, uri, hasToIgnoreCase(uri))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { findBestWindowOrFolderForFile, ISimpleWindow, IBestWindowOrFolderOption
|
||||
import { OpenContext } from 'vs/platform/windows/common/windows';
|
||||
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { toWorkspaceFolders } from 'vs/platform/workspace/common/workspace';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
const fixturesFolder = require.toUrl('./fixtures');
|
||||
|
||||
@@ -30,10 +31,10 @@ function options(custom?: Partial<IBestWindowOrFolderOptions<ISimpleWindow>>): I
|
||||
};
|
||||
}
|
||||
|
||||
const vscodeFolderWindow = { lastFocusTime: 1, openedFolderPath: path.join(fixturesFolder, 'vscode_folder') };
|
||||
const lastActiveWindow = { lastFocusTime: 3, openedFolderPath: null };
|
||||
const noVscodeFolderWindow = { lastFocusTime: 2, openedFolderPath: path.join(fixturesFolder, 'no_vscode_folder') };
|
||||
const windows = [
|
||||
const vscodeFolderWindow: ISimpleWindow = { lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'vscode_folder')) };
|
||||
const lastActiveWindow: ISimpleWindow = { lastFocusTime: 3, openedFolderUri: null };
|
||||
const noVscodeFolderWindow: ISimpleWindow = { lastFocusTime: 2, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder')) };
|
||||
const windows: ISimpleWindow[] = [
|
||||
vscodeFolderWindow,
|
||||
lastActiveWindow,
|
||||
noVscodeFolderWindow,
|
||||
@@ -103,7 +104,7 @@ suite('WindowsFinder', () => {
|
||||
windows,
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt')
|
||||
})), vscodeFolderWindow);
|
||||
const window = { lastFocusTime: 1, openedFolderPath: path.join(fixturesFolder, 'vscode_folder', 'nested_folder') };
|
||||
const window: ISimpleWindow = { lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'nested_folder')) };
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
windows: [window],
|
||||
filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_folder', 'subfolder', 'file.txt')
|
||||
@@ -111,8 +112,8 @@ suite('WindowsFinder', () => {
|
||||
});
|
||||
|
||||
test('More specific existing window wins', () => {
|
||||
const window = { lastFocusTime: 2, openedFolderPath: path.join(fixturesFolder, 'no_vscode_folder') };
|
||||
const nestedFolderWindow = { lastFocusTime: 1, openedFolderPath: path.join(fixturesFolder, 'no_vscode_folder', 'nested_folder') };
|
||||
const window: ISimpleWindow = { lastFocusTime: 2, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder')) };
|
||||
const nestedFolderWindow: ISimpleWindow = { lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'nested_folder')) };
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
windows: [window, nestedFolderWindow],
|
||||
filePath: path.join(fixturesFolder, 'no_vscode_folder', 'nested_folder', 'subfolder', 'file.txt')
|
||||
@@ -120,7 +121,7 @@ suite('WindowsFinder', () => {
|
||||
});
|
||||
|
||||
test('Workspace folder wins', () => {
|
||||
const window = { lastFocusTime: 1, openedWorkspace: testWorkspace };
|
||||
const window: ISimpleWindow = { lastFocusTime: 1, openedWorkspace: testWorkspace };
|
||||
assert.equal(findBestWindowOrFolderForFile(options({
|
||||
windows: [window],
|
||||
filePath: path.join(fixturesFolder, 'vscode_workspace_2_folder', 'nested_vscode_folder', 'subfolder', 'file.txt')
|
||||
|
||||
Reference in New Issue
Block a user