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:
Karl Burtram
2018-09-04 14:55:00 -07:00
committed by GitHub
parent 3763278366
commit 81329fa7fa
2638 changed files with 118456 additions and 64012 deletions

View File

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

View File

@@ -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';
}
}

View File

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

View File

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

View File

@@ -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%;
}

View File

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

View File

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

View File

@@ -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) => {

View File

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

View File

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

View File

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

View File

@@ -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 {

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@@ -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';

View File

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

View File

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

View File

@@ -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')