From b66ce71972e56a3abcb73d15e8fe846e7291f4df Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Sun, 15 Sep 2019 22:17:17 -0700 Subject: [PATCH 01/36] Add Codicon --- .../codicon/codicon-animations.css | 14 + .../ui/codiconLabel/codicon/codicon.css | 354 ++++++++++++++++++ .../ui/codiconLabel/codicon/codicon.ttf | Bin 0 -> 52456 bytes .../ui/codiconLabel/codiconLabel.mock.ts | 24 ++ .../browser/ui/codiconLabel/codiconLabel.ts | 33 ++ .../ui/highlightedlabel/highlightedLabel.ts | 8 +- 6 files changed, 429 insertions(+), 4 deletions(-) create mode 100644 src/vs/base/browser/ui/codiconLabel/codicon/codicon-animations.css create mode 100644 src/vs/base/browser/ui/codiconLabel/codicon/codicon.css create mode 100644 src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf create mode 100644 src/vs/base/browser/ui/codiconLabel/codiconLabel.mock.ts create mode 100644 src/vs/base/browser/ui/codiconLabel/codiconLabel.ts diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon-animations.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon-animations.css new file mode 100644 index 00000000000..86c223674ec --- /dev/null +++ b/src/vs/base/browser/ui/codiconLabel/codicon/codicon-animations.css @@ -0,0 +1,14 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +@keyframes codicon-spin { + 100% { + transform:rotate(360deg); + } +} + +.codicon-animation-spin { + animation: octicon-spin 1.5s linear infinite; +} diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css new file mode 100644 index 00000000000..04309db0ca4 --- /dev/null +++ b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css @@ -0,0 +1,354 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +@font-face { + font-family: "codicon"; + src: url("./codicon.ttf?e042d2dda15ef7b36b910e3edf539f26") format("truetype"), +url("./codicon.svg?e042d2dda15ef7b36b910e3edf539f26#codicon") format("svg"); +} + +.codicon { + font: normal normal normal 16px/1 codicon; + display: inline-block; + text-decoration: none; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + + +.codicon-add:before { content: "\ea60" } +.codicon-plus:before { content: "\ea60" } +.codicon-gist-new:before { content: "\ea60" } +.codicon-repo-create:before { content: "\ea60" } +.codicon-lightbulb:before { content: "\ea61" } +.codicon-light-bulb:before { content: "\ea61" } +.codicon-repo:before { content: "\ea62" } +.codicon-repo-delete:before { content: "\ea62" } +.codicon-gist-fork:before { content: "\ea63" } +.codicon-repo-forked:before { content: "\ea63" } +.codicon-git-pull-request:before { content: "\ea64" } +.codicon-git-pull-request-abandoned:before { content: "\ea64" } +.codicon-record-keys:before { content: "\ea65" } +.codicon-keyboard:before { content: "\ea65" } +.codicon-tag:before { content: "\ea66" } +.codicon-tag-add:before { content: "\ea66" } +.codicon-tag-remove:before { content: "\ea66" } +.codicon-person:before { content: "\ea67" } +.codicon-person-add:before { content: "\ea67" } +.codicon-person-follow:before { content: "\ea67" } +.codicon-person-outline:before { content: "\ea67" } +.codicon-person-filled:before { content: "\ea67" } +.codicon-git-branch:before { content: "\ea68" } +.codicon-git-branch-create:before { content: "\ea68" } +.codicon-git-branch-delete:before { content: "\ea68" } +.codicon-source-control:before { content: "\ea68" } +.codicon-mirror:before { content: "\ea69" } +.codicon-mirror-public:before { content: "\ea69" } +.codicon-star:before { content: "\ea6a" } +.codicon-star-add:before { content: "\ea6a" } +.codicon-star-delete:before { content: "\ea6a" } +.codicon-comment:before { content: "\ea6b" } +.codicon-comment-add:before { content: "\ea6b" } +.codicon-alert:before { content: "\ea6c" } +.codicon-warning:before { content: "\ea6c" } +.codicon-search:before { content: "\ea6d" } +.codicon-search-save:before { content: "\ea6d" } +.codicon-log-out:before { content: "\ea6e" } +.codicon-sign-out:before { content: "\ea6e" } +.codicon-log-in:before { content: "\ea6f" } +.codicon-sign-in:before { content: "\ea6f" } +.codicon-eye:before { content: "\ea70" } +.codicon-eye-unwatch:before { content: "\ea70" } +.codicon-eye-watch:before { content: "\ea70" } +.codicon-circle-filled:before { content: "\ea71" } +.codicon-primitive-dot:before { content: "\ea71" } +.codicon-stop:before { content: "\ea72" } +.codicon-primitive-square:before { content: "\ea72" } +.codicon-edit:before { content: "\ea73" } +.codicon-pencil:before { content: "\ea73" } +.codicon-info:before { content: "\ea74" } +.codicon-issue-opened:before { content: "\ea74" } +.codicon-gist-private:before { content: "\ea75" } +.codicon-git-fork-private:before { content: "\ea75" } +.codicon-lock:before { content: "\ea75" } +.codicon-mirror-private:before { content: "\ea75" } +.codicon-close:before { content: "\ea76" } +.codicon-remove-close:before { content: "\ea76" } +.codicon-x:before { content: "\ea76" } +.codicon-repo-sync:before { content: "\ea77" } +.codicon-sync:before { content: "\ea77" } +.codicon-clone:before { content: "\ea78" } +.codicon-desktop-download:before { content: "\ea78" } +.codicon-beaker:before { content: "\ea79" } +.codicon-microscope:before { content: "\ea79" } +.codicon-vm:before { content: "\ea7a" } +.codicon-device-desktop:before { content: "\ea7a" } +.codicon-file:before { content: "\ea7b" } +.codicon-file-text:before { content: "\ea7b" } +.codicon-more:before { content: "\ea7c" } +.codicon-kebab-horizontal:before { content: "\ea7c" } +.codicon-mail-reply:before { content: "\ea7d" } +.codicon-reply:before { content: "\ea7d" } +.codicon-organization:before { content: "\ea7e" } +.codicon-organization-filled:before { content: "\ea7e" } +.codicon-organization-outline:before { content: "\ea7e" } +.codicon-new-file:before { content: "\ea7f" } +.codicon-file-add:before { content: "\ea7f" } +.codicon-new-folder:before { content: "\ea80" } +.codicon-file-directory-create:before { content: "\ea80" } +.codicon-Vector:before { content: "\f101" } +.codicon-activate-breakpoints:before { content: "\f102" } +.codicon-archive:before { content: "\f103" } +.codicon-array:before { content: "\f104" } +.codicon-arrow-both:before { content: "\f105" } +.codicon-arrow-down:before { content: "\f106" } +.codicon-arrow-left:before { content: "\f107" } +.codicon-arrow-right:before { content: "\f108" } +.codicon-arrow-small-down:before { content: "\f109" } +.codicon-arrow-small-left:before { content: "\f10a" } +.codicon-arrow-small-right:before { content: "\f10b" } +.codicon-arrow-small-up:before { content: "\f10c" } +.codicon-arrow-up:before { content: "\f10d" } +.codicon-bell:before { content: "\f10e" } +.codicon-bold:before { content: "\f10f" } +.codicon-book:before { content: "\f110" } +.codicon-bookmark:before { content: "\f111" } +.codicon-boolean:before { content: "\f112" } +.codicon-breakpoint-conditional-unverified:before { content: "\f113" } +.codicon-breakpoint-conditional:before { content: "\f114" } +.codicon-breakpoint-data-unverified:before { content: "\f115" } +.codicon-breakpoint-data:before { content: "\f116" } +.codicon-breakpoint-log-unverified:before { content: "\f117" } +.codicon-breakpoint-log:before { content: "\f118" } +.codicon-briefcase:before { content: "\f119" } +.codicon-broadcast:before { content: "\f11a" } +.codicon-browser:before { content: "\f11b" } +.codicon-bug:before { content: "\f11c" } +.codicon-calendar:before { content: "\f11d" } +.codicon-case-sensitive:before { content: "\f11e" } +.codicon-check:before { content: "\f11f" } +.codicon-checklist:before { content: "\f120" } +.codicon-chevron-down:before { content: "\f121" } +.codicon-chevron-left:before { content: "\f122" } +.codicon-chevron-right:before { content: "\f123" } +.codicon-chevron-up:before { content: "\f124" } +.codicon-circle-outline:before { content: "\f125" } +.codicon-circle-slash:before { content: "\f126" } +.codicon-circuit-board:before { content: "\f127" } +.codicon-class:before { content: "\f128" } +.codicon-clear-all:before { content: "\f129" } +.codicon-clippy:before { content: "\f12a" } +.codicon-close-all:before { content: "\f12b" } +.codicon-cloud-download:before { content: "\f12c" } +.codicon-cloud-upload:before { content: "\f12d" } +.codicon-code:before { content: "\f12e" } +.codicon-collapse-all:before { content: "\f12f" } +.codicon-color-mode:before { content: "\f130" } +.codicon-color:before { content: "\f131" } +.codicon-comment-discussion:before { content: "\f132" } +.codicon-compare-changes:before { content: "\f133" } +.codicon-console:before { content: "\f134" } +.codicon-constant:before { content: "\f135" } +.codicon-continue:before { content: "\f136" } +.codicon-credit-card:before { content: "\f137" } +.codicon-current-and-breakpoint:before { content: "\f138" } +.codicon-current:before { content: "\f139" } +.codicon-dash:before { content: "\f13a" } +.codicon-dashboard:before { content: "\f13b" } +.codicon-database:before { content: "\f13c" } +.codicon-debug:before { content: "\f13d" } +.codicon-device-camera-video:before { content: "\f13e" } +.codicon-device-camera:before { content: "\f13f" } +.codicon-device-mobile:before { content: "\f140" } +.codicon-diff-added:before { content: "\f141" } +.codicon-diff-ignored:before { content: "\f142" } +.codicon-diff-modified:before { content: "\f143" } +.codicon-diff-removed:before { content: "\f144" } +.codicon-diff-renamed:before { content: "\f145" } +.codicon-diff:before { content: "\f146" } +.codicon-discard:before { content: "\f147" } +.codicon-disconnect-:before { content: "\f148" } +.codicon-editor-layout:before { content: "\f149" } +.codicon-ellipsis:before { content: "\f14a" } +.codicon-empty-window:before { content: "\f14b" } +.codicon-enumerator-member:before { content: "\f14c" } +.codicon-enumerator:before { content: "\f14d" } +.codicon-error:before { content: "\f14e" } +.codicon-event:before { content: "\f14f" } +.codicon-exclude:before { content: "\f150" } +.codicon-extensions:before { content: "\f151" } +.codicon-eye-closed:before { content: "\f152" } +.codicon-field:before { content: "\f153" } +.codicon-file-binary:before { content: "\f154" } +.codicon-file-code:before { content: "\f155" } +.codicon-file-media:before { content: "\f156" } +.codicon-file-pdf:before { content: "\f157" } +.codicon-file-submodule:before { content: "\f158" } +.codicon-file-symlink-directory:before { content: "\f159" } +.codicon-file-symlink-file:before { content: "\f15a" } +.codicon-file-zip:before { content: "\f15b" } +.codicon-files:before { content: "\f15c" } +.codicon-filter:before { content: "\f15d" } +.codicon-flame:before { content: "\f15e" } +.codicon-fold-down:before { content: "\f15f" } +.codicon-fold-up:before { content: "\f160" } +.codicon-fold:before { content: "\f161" } +.codicon-folder-active:before { content: "\f162" } +.codicon-folder-opened:before { content: "\f163" } +.codicon-folder:before { content: "\f164" } +.codicon-gift:before { content: "\f165" } +.codicon-gist-secret:before { content: "\f166" } +.codicon-gist:before { content: "\f167" } +.codicon-git-commit:before { content: "\f168" } +.codicon-git-compare:before { content: "\f169" } +.codicon-git-merge:before { content: "\f16a" } +.codicon-github-action:before { content: "\f16b" } +.codicon-github-alt:before { content: "\f16c" } +.codicon-github:before { content: "\f16d" } +.codicon-globe:before { content: "\f16e" } +.codicon-go-to-file:before { content: "\f16f" } +.codicon-grabber:before { content: "\f170" } +.codicon-graph:before { content: "\f171" } +.codicon-gripper:before { content: "\f172" } +.codicon-heart:before { content: "\f173" } +.codicon-history:before { content: "\f174" } +.codicon-home:before { content: "\f175" } +.codicon-horizontal-rule:before { content: "\f176" } +.codicon-hubot:before { content: "\f177" } +.codicon-inbox:before { content: "\f178" } +.codicon-interface:before { content: "\f179" } +.codicon-issue-closed:before { content: "\f17a" } +.codicon-issue-reopened:before { content: "\f17b" } +.codicon-issues:before { content: "\f17c" } +.codicon-italic:before { content: "\f17d" } +.codicon-jersey:before { content: "\f17e" } +.codicon-json:before { content: "\f17f" } +.codicon-kebab-vertical:before { content: "\f180" } +.codicon-key:before { content: "\f181" } +.codicon-keyword:before { content: "\f182" } +.codicon-law:before { content: "\f183" } +.codicon-lightbulb-autofix:before { content: "\f184" } +.codicon-link-external:before { content: "\f185" } +.codicon-link:before { content: "\f186" } +.codicon-list-ordered:before { content: "\f187" } +.codicon-list-unordered:before { content: "\f188" } +.codicon-live-share:before { content: "\f189" } +.codicon-loading:before { content: "\f18a" } +.codicon-location:before { content: "\f18b" } +.codicon-mail-read:before { content: "\f18c" } +.codicon-mail:before { content: "\f18d" } +.codicon-markdown:before { content: "\f18e" } +.codicon-megaphone:before { content: "\f18f" } +.codicon-mention:before { content: "\f190" } +.codicon-method:before { content: "\f191" } +.codicon-milestone:before { content: "\f192" } +.codicon-misc:before { content: "\f193" } +.codicon-mortar-board:before { content: "\f194" } +.codicon-move:before { content: "\f195" } +.codicon-multiple-windows:before { content: "\f196" } +.codicon-mute:before { content: "\f197" } +.codicon-namespace:before { content: "\f198" } +.codicon-no-newline:before { content: "\f199" } +.codicon-note:before { content: "\f19a" } +.codicon-numeric:before { content: "\f19b" } +.codicon-octoface:before { content: "\f19c" } +.codicon-open-preview:before { content: "\f19d" } +.codicon-operator:before { content: "\f19e" } +.codicon-package:before { content: "\f19f" } +.codicon-paintcan:before { content: "\f1a0" } +.codicon-parameter:before { content: "\f1a1" } +.codicon-pause:before { content: "\f1a2" } +.codicon-pin:before { content: "\f1a3" } +.codicon-play:before { content: "\f1a4" } +.codicon-plug:before { content: "\f1a5" } +.codicon-preserve-case:before { content: "\f1a6" } +.codicon-preview:before { content: "\f1a7" } +.codicon-project:before { content: "\f1a8" } +.codicon-property:before { content: "\f1a9" } +.codicon-pulse:before { content: "\f1aa" } +.codicon-question:before { content: "\f1ab" } +.codicon-quote:before { content: "\f1ac" } +.codicon-radio-tower:before { content: "\f1ad" } +.codicon-reactions:before { content: "\f1ae" } +.codicon-references:before { content: "\f1af" } +.codicon-refresh:before { content: "\f1b0" } +.codicon-regex:before { content: "\f1b1" } +.codicon-remote:before { content: "\f1b2" } +.codicon-remove:before { content: "\f1b3" } +.codicon-replace-all:before { content: "\f1b4" } +.codicon-replace:before { content: "\f1b5" } +.codicon-repo-clone:before { content: "\f1b6" } +.codicon-repo-force-push:before { content: "\f1b7" } +.codicon-repo-pull:before { content: "\f1b8" } +.codicon-repo-push:before { content: "\f1b9" } +.codicon-report:before { content: "\f1ba" } +.codicon-request-changes:before { content: "\f1bb" } +.codicon-restart:before { content: "\f1bc" } +.codicon-rocket:before { content: "\f1bd" } +.codicon-root-folder-opened:before { content: "\f1be" } +.codicon-root-folder:before { content: "\f1bf" } +.codicon-rss:before { content: "\f1c0" } +.codicon-ruby:before { content: "\f1c1" } +.codicon-ruler:before { content: "\f1c2" } +.codicon-save-all:before { content: "\f1c3" } +.codicon-save-as:before { content: "\f1c4" } +.codicon-save:before { content: "\f1c5" } +.codicon-screen-full:before { content: "\f1c6" } +.codicon-screen-normal:before { content: "\f1c7" } +.codicon-search-stop:before { content: "\f1c8" } +.codicon-selection:before { content: "\f1c9" } +.codicon-server:before { content: "\f1ca" } +.codicon-settings:before { content: "\f1cb" } +.codicon-shield:before { content: "\f1cc" } +.codicon-smiley:before { content: "\f1cd" } +.codicon-snippet:before { content: "\f1ce" } +.codicon-sort-precedence:before { content: "\f1cf" } +.codicon-split-horizontal:before { content: "\f1d0" } +.codicon-split-vertical:before { content: "\f1d1" } +.codicon-squirrel:before { content: "\f1d2" } +.codicon-star-empty:before { content: "\f1d3" } +.codicon-star-full:before { content: "\f1d4" } +.codicon-star-half:before { content: "\f1d5" } +.codicon-start:before { content: "\f1d6" } +.codicon-step-into:before { content: "\f1d7" } +.codicon-step-out:before { content: "\f1d8" } +.codicon-step-over:before { content: "\f1d9" } +.codicon-string:before { content: "\f1da" } +.codicon-structure:before { content: "\f1db" } +.codicon-tasklist:before { content: "\f1dc" } +.codicon-telescope:before { content: "\f1dd" } +.codicon-text-size:before { content: "\f1de" } +.codicon-three-bars:before { content: "\f1df" } +.codicon-thumbsdown:before { content: "\f1e0" } +.codicon-thumbsup:before { content: "\f1e1" } +.codicon-tools:before { content: "\f1e2" } +.codicon-trash:before { content: "\f1e3" } +.codicon-triangle-down:before { content: "\f1e4" } +.codicon-triangle-left:before { content: "\f1e5" } +.codicon-triangle-right:before { content: "\f1e6" } +.codicon-triangle-up:before { content: "\f1e7" } +.codicon-twitter:before { content: "\f1e8" } +.codicon-unfold:before { content: "\f1e9" } +.codicon-unlock:before { content: "\f1ea" } +.codicon-unmute:before { content: "\f1eb" } +.codicon-unverified:before { content: "\f1ec" } +.codicon-variable:before { content: "\f1ed" } +.codicon-verified:before { content: "\f1ee" } +.codicon-versions:before { content: "\f1ef" } +.codicon-vm-active:before { content: "\f1f0" } +.codicon-vm-outline:before { content: "\f1f1" } +.codicon-vm-running:before { content: "\f1f2" } +.codicon-watch:before { content: "\f1f3" } +.codicon-whitespace:before { content: "\f1f4" } +.codicon-whole-word:before { content: "\f1f5" } +.codicon-window:before { content: "\f1f6" } +.codicon-word-wrap:before { content: "\f1f7" } +.codicon-zoom-in:before { content: "\f1f8" } +.codicon-zoom-out:before { content: "\f1f9" } diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf new file mode 100644 index 0000000000000000000000000000000000000000..688b9ffd0bd65ca22309fb809f1e91d3685faf52 GIT binary patch literal 52456 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 literal 0 HcmV?d00001 diff --git a/src/vs/base/browser/ui/codiconLabel/codiconLabel.mock.ts b/src/vs/base/browser/ui/codiconLabel/codiconLabel.mock.ts new file mode 100644 index 00000000000..37862376c2c --- /dev/null +++ b/src/vs/base/browser/ui/codiconLabel/codiconLabel.mock.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { escape } from 'vs/base/common/strings'; + +export function renderCodicons(text: string): string { + return escape(text); +} + +export class CodiconLabel { + + private _container: HTMLElement; + + constructor(container: HTMLElement) { + this._container = container; + } + + set text(text: string) { + this._container.innerHTML = renderCodicons(text || ''); + } + +} diff --git a/src/vs/base/browser/ui/codiconLabel/codiconLabel.ts b/src/vs/base/browser/ui/codiconLabel/codiconLabel.ts new file mode 100644 index 00000000000..42d38948e88 --- /dev/null +++ b/src/vs/base/browser/ui/codiconLabel/codiconLabel.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./codicon/codicon'; +import 'vs/css!./codicon/codicon-animations'; +import { escape } from 'vs/base/common/strings'; + +function expand(text: string): string { + return text.replace(/\$\(((.+?)(~(.*?))?)\)/g, (_match, _g1, name, _g3, animation) => { + return ``; + }); +} + +export function renderCodicons(label: string): string { + return expand(escape(label)); +} + +export class CodiconLabel { + + constructor( + private readonly _container: HTMLElement + ) { } + + set text(text: string) { + this._container.innerHTML = renderCodicons(text || ''); + } + + set title(title: string) { + this._container.title = title; + } +} diff --git a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts index 63c805b4237..e7dcf431b0d 100644 --- a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts +++ b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as objects from 'vs/base/common/objects'; -import { renderOcticons } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; +import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel'; import { escape } from 'vs/base/common/strings'; export interface IHighlight { @@ -65,13 +65,13 @@ export class HighlightedLabel { if (pos < highlight.start) { htmlContent += ''; const substring = this.text.substring(pos, highlight.start); - htmlContent += this.supportOcticons ? renderOcticons(substring) : escape(substring); + htmlContent += this.supportOcticons ? renderCodicons(substring) : escape(substring); htmlContent += ''; pos = highlight.end; } htmlContent += ''; const substring = this.text.substring(highlight.start, highlight.end); - htmlContent += this.supportOcticons ? renderOcticons(substring) : escape(substring); + htmlContent += this.supportOcticons ? renderCodicons(substring) : escape(substring); htmlContent += ''; pos = highlight.end; } @@ -79,7 +79,7 @@ export class HighlightedLabel { if (pos < this.text.length) { htmlContent += ''; const substring = this.text.substring(pos); - htmlContent += this.supportOcticons ? renderOcticons(substring) : escape(substring); + htmlContent += this.supportOcticons ? renderCodicons(substring) : escape(substring); htmlContent += ''; } From 79e4024d11d7d4abb4332c89dfd0c7164e4b3525 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Mon, 16 Sep 2019 13:50:27 -0700 Subject: [PATCH 02/36] Update panel icons to use icon font --- src/vs/base/browser/ui/actionbar/actionbar.ts | 4 +- .../ui/codiconLabel/codicon/codicon.css | 3 +- .../parts/tree/browser/collapse-all-dark.svg | 4 -- .../parts/tree/browser/collapse-all-hc.svg | 4 -- .../parts/tree/browser/collapse-all-light.svg | 4 -- src/vs/base/parts/tree/browser/tree.css | 12 ----- .../parts/panel/media/chevron-down-dark.svg | 3 -- .../parts/panel/media/chevron-down-hc.svg | 3 -- .../parts/panel/media/chevron-down-light.svg | 3 -- .../parts/panel/media/chevron-up-dark.svg | 3 -- .../parts/panel/media/chevron-up-hc.svg | 3 -- .../parts/panel/media/chevron-up-light.svg | 3 -- .../browser/parts/panel/media/close-dark.svg | 4 -- .../browser/parts/panel/media/close-hc.svg | 4 -- .../browser/parts/panel/media/close-light.svg | 4 -- .../browser/parts/panel/media/panelpart.css | 14 ----- .../browser/parts/panel/panelActions.ts | 6 +-- .../debug/browser/media/clear-dark.svg | 7 --- .../contrib/debug/browser/media/clear-hc.svg | 7 --- .../debug/browser/media/clear-light.svg | 7 --- .../contrib/debug/browser/media/repl.css | 13 ----- .../workbench/contrib/debug/browser/repl.ts | 2 +- .../contrib/markers/browser/markersPanel.ts | 2 +- .../markers/browser/markersPanelActions.ts | 2 +- .../browser/media/exclude-settings-dark.svg | 3 -- .../browser/media/exclude-settings-hc.svg | 3 -- .../browser/media/exclude-settings-light.svg | 3 -- .../contrib/markers/browser/media/markers.css | 13 ----- .../output/browser/media/clear-dark.svg | 7 --- .../contrib/output/browser/media/clear-hc.svg | 7 --- .../output/browser/media/clear-light.svg | 7 --- .../output/browser/media/locked-dark.svg | 3 -- .../output/browser/media/locked-hc.svg | 3 -- .../output/browser/media/locked-light.svg | 3 -- .../output/browser/media/open-file-dark.svg | 3 -- .../output/browser/media/open-file-hc.svg | 3 -- .../output/browser/media/open-file-light.svg | 3 -- .../contrib/output/browser/media/output.css | 52 ------------------- .../output/browser/media/unlocked-dark.svg | 3 -- .../output/browser/media/unlocked-hc.svg | 3 -- .../output/browser/media/unlocked-light.svg | 3 -- .../contrib/output/browser/outputActions.ts | 10 ++-- .../terminal/browser/media/kill-dark.svg | 3 -- .../terminal/browser/media/kill-hc.svg | 3 -- .../terminal/browser/media/kill-light.svg | 3 -- .../terminal/browser/media/new-dark.svg | 3 -- .../contrib/terminal/browser/media/new-hc.svg | 3 -- .../terminal/browser/media/new-light.svg | 3 -- .../media/split-editor-horizontal-dark.svg | 3 -- .../media/split-editor-horizontal-hc.svg | 3 -- .../media/split-editor-horizontal-light.svg | 3 -- .../media/split-editor-vertical-dark.svg | 3 -- .../media/split-editor-vertical-hc.svg | 3 -- .../media/split-editor-vertical-light.svg | 3 -- .../terminal/browser/media/terminal.css | 18 ------- .../terminal/browser/terminalActions.ts | 6 +-- 56 files changed, 18 insertions(+), 295 deletions(-) delete mode 100644 src/vs/base/parts/tree/browser/collapse-all-dark.svg delete mode 100644 src/vs/base/parts/tree/browser/collapse-all-hc.svg delete mode 100644 src/vs/base/parts/tree/browser/collapse-all-light.svg delete mode 100644 src/vs/workbench/browser/parts/panel/media/chevron-down-dark.svg delete mode 100644 src/vs/workbench/browser/parts/panel/media/chevron-down-hc.svg delete mode 100644 src/vs/workbench/browser/parts/panel/media/chevron-down-light.svg delete mode 100644 src/vs/workbench/browser/parts/panel/media/chevron-up-dark.svg delete mode 100644 src/vs/workbench/browser/parts/panel/media/chevron-up-hc.svg delete mode 100644 src/vs/workbench/browser/parts/panel/media/chevron-up-light.svg delete mode 100644 src/vs/workbench/browser/parts/panel/media/close-dark.svg delete mode 100644 src/vs/workbench/browser/parts/panel/media/close-hc.svg delete mode 100644 src/vs/workbench/browser/parts/panel/media/close-light.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/clear-dark.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/clear-hc.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/clear-light.svg delete mode 100644 src/vs/workbench/contrib/markers/browser/media/exclude-settings-dark.svg delete mode 100644 src/vs/workbench/contrib/markers/browser/media/exclude-settings-hc.svg delete mode 100644 src/vs/workbench/contrib/markers/browser/media/exclude-settings-light.svg delete mode 100644 src/vs/workbench/contrib/output/browser/media/clear-dark.svg delete mode 100644 src/vs/workbench/contrib/output/browser/media/clear-hc.svg delete mode 100644 src/vs/workbench/contrib/output/browser/media/clear-light.svg delete mode 100644 src/vs/workbench/contrib/output/browser/media/locked-dark.svg delete mode 100644 src/vs/workbench/contrib/output/browser/media/locked-hc.svg delete mode 100644 src/vs/workbench/contrib/output/browser/media/locked-light.svg delete mode 100644 src/vs/workbench/contrib/output/browser/media/open-file-dark.svg delete mode 100644 src/vs/workbench/contrib/output/browser/media/open-file-hc.svg delete mode 100644 src/vs/workbench/contrib/output/browser/media/open-file-light.svg delete mode 100644 src/vs/workbench/contrib/output/browser/media/output.css delete mode 100644 src/vs/workbench/contrib/output/browser/media/unlocked-dark.svg delete mode 100644 src/vs/workbench/contrib/output/browser/media/unlocked-hc.svg delete mode 100644 src/vs/workbench/contrib/output/browser/media/unlocked-light.svg delete mode 100644 src/vs/workbench/contrib/terminal/browser/media/kill-dark.svg delete mode 100644 src/vs/workbench/contrib/terminal/browser/media/kill-hc.svg delete mode 100644 src/vs/workbench/contrib/terminal/browser/media/kill-light.svg delete mode 100644 src/vs/workbench/contrib/terminal/browser/media/new-dark.svg delete mode 100644 src/vs/workbench/contrib/terminal/browser/media/new-hc.svg delete mode 100644 src/vs/workbench/contrib/terminal/browser/media/new-light.svg delete mode 100644 src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-dark.svg delete mode 100644 src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-hc.svg delete mode 100644 src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-light.svg delete mode 100644 src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-dark.svg delete mode 100644 src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-hc.svg delete mode 100644 src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-light.svg diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 538f7fa0dbe..38ec0a60a51 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -311,14 +311,14 @@ export class ActionViewItem extends BaseActionViewItem { if (this.options.icon) { this.cssClass = this.getAction().class; - DOM.addClass(this.label, 'icon'); + DOM.addClass(this.label, 'codicon'); if (this.cssClass) { DOM.addClasses(this.label, this.cssClass); } this.updateEnabled(); } else { - DOM.removeClass(this.label, 'icon'); + DOM.removeClass(this.label, 'codicon'); } } diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css index 04309db0ca4..689f29ba4c6 100644 --- a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css +++ b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css @@ -9,11 +9,12 @@ url("./codicon.svg?e042d2dda15ef7b36b910e3edf539f26#codicon") format("svg"); } -.codicon { +.codicon[class*='codicon-'] { font: normal normal normal 16px/1 codicon; display: inline-block; text-decoration: none; text-rendering: auto; + text-align: center; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; -webkit-user-select: none; diff --git a/src/vs/base/parts/tree/browser/collapse-all-dark.svg b/src/vs/base/parts/tree/browser/collapse-all-dark.svg deleted file mode 100644 index 4862c55dbeb..00000000000 --- a/src/vs/base/parts/tree/browser/collapse-all-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/base/parts/tree/browser/collapse-all-hc.svg b/src/vs/base/parts/tree/browser/collapse-all-hc.svg deleted file mode 100644 index 05f920b29b6..00000000000 --- a/src/vs/base/parts/tree/browser/collapse-all-hc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/base/parts/tree/browser/collapse-all-light.svg b/src/vs/base/parts/tree/browser/collapse-all-light.svg deleted file mode 100644 index 6359b42e623..00000000000 --- a/src/vs/base/parts/tree/browser/collapse-all-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/base/parts/tree/browser/tree.css b/src/vs/base/parts/tree/browser/tree.css index 2a3cb47ddbb..6eb5f9b7aff 100644 --- a/src/vs/base/parts/tree/browser/tree.css +++ b/src/vs/base/parts/tree/browser/tree.css @@ -110,15 +110,3 @@ .hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children.loading > .content:before { background-image: url('loading-hc.svg'); } - -.monaco-tree-action.collapse-all { - background: url('collapse-all-light.svg') center center no-repeat; -} - -.vs-dark .monaco-tree-action.collapse-all { - background: url('collapse-all-dark.svg') center center no-repeat; -} - -.hc-black .monaco-tree-action.collapse-all { - background: url('collapse-all-hc.svg') center center no-repeat; -} diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-down-dark.svg b/src/vs/workbench/browser/parts/panel/media/chevron-down-dark.svg deleted file mode 100644 index a1df6a8d44a..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-down-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-down-hc.svg b/src/vs/workbench/browser/parts/panel/media/chevron-down-hc.svg deleted file mode 100644 index 4f2ec146927..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-down-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-down-light.svg b/src/vs/workbench/browser/parts/panel/media/chevron-down-light.svg deleted file mode 100644 index e60e357f573..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-down-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-up-dark.svg b/src/vs/workbench/browser/parts/panel/media/chevron-up-dark.svg deleted file mode 100644 index 5b9da8932e1..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-up-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-up-hc.svg b/src/vs/workbench/browser/parts/panel/media/chevron-up-hc.svg deleted file mode 100644 index 13bad537364..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-up-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-up-light.svg b/src/vs/workbench/browser/parts/panel/media/chevron-up-light.svg deleted file mode 100644 index 5498cda5bc4..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-up-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/close-dark.svg b/src/vs/workbench/browser/parts/panel/media/close-dark.svg deleted file mode 100644 index e0475f7b85a..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/close-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/parts/panel/media/close-hc.svg b/src/vs/workbench/browser/parts/panel/media/close-hc.svg deleted file mode 100644 index 64618b61760..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/close-hc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/parts/panel/media/close-light.svg b/src/vs/workbench/browser/parts/panel/media/close-light.svg deleted file mode 100644 index 3bd44674699..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/close-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index 9dc9bb2000f..0f4232c4e44 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -124,20 +124,6 @@ margin-right: 10px; } -/* Close */ -.monaco-workbench .hide-panel-action { - background: url('close-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .hide-panel-action { - background: url('close-dark.svg') center center no-repeat; -} - -.hc-black .monaco-workbench .hide-panel-action { - background: url('close-hc.svg') center center no-repeat; -} - - /* Up */ .monaco-workbench .maximize-panel-action { background-image: url('chevron-up-light.svg'); diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 904f8a364ba..c8419eed7e7 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -28,7 +28,7 @@ export class ClosePanelAction extends Action { name: string, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { - super(id, name, 'hide-panel-action'); + super(id, name, 'codicon-close'); } run(): Promise { @@ -141,11 +141,11 @@ export class ToggleMaximizedPanelAction extends Action { @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IEditorGroupsService editorGroupsService: IEditorGroupsService ) { - super(id, label, layoutService.isPanelMaximized() ? 'minimize-panel-action' : 'maximize-panel-action'); + super(id, label, layoutService.isPanelMaximized() ? 'codicon-chevron-down' : 'codicon-chevron-up'); this.toDispose.add(editorGroupsService.onDidLayout(() => { const maximized = this.layoutService.isPanelMaximized(); - this.class = maximized ? 'minimize-panel-action' : 'maximize-panel-action'; + this.class = maximized ? 'codicon-chevron-down' : 'codicon-chevron-up'; this.label = maximized ? ToggleMaximizedPanelAction.RESTORE_LABEL : ToggleMaximizedPanelAction.MAXIMIZE_LABEL; })); } diff --git a/src/vs/workbench/contrib/debug/browser/media/clear-dark.svg b/src/vs/workbench/contrib/debug/browser/media/clear-dark.svg deleted file mode 100644 index 04d64ab41ca..00000000000 --- a/src/vs/workbench/contrib/debug/browser/media/clear-dark.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/clear-hc.svg b/src/vs/workbench/contrib/debug/browser/media/clear-hc.svg deleted file mode 100644 index 44a41edd3b3..00000000000 --- a/src/vs/workbench/contrib/debug/browser/media/clear-hc.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/clear-light.svg b/src/vs/workbench/contrib/debug/browser/media/clear-light.svg deleted file mode 100644 index f6a51c856f0..00000000000 --- a/src/vs/workbench/contrib/debug/browser/media/clear-light.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/repl.css b/src/vs/workbench/contrib/debug/browser/media/repl.css index 9d92c972d48..e7befa20952 100644 --- a/src/vs/workbench/contrib/debug/browser/media/repl.css +++ b/src/vs/workbench/contrib/debug/browser/media/repl.css @@ -88,19 +88,6 @@ font-size: 9px; } -/* Actions */ -.debug-action.clear-repl { - background: url('clear-light.svg') center center no-repeat; -} - -.vs-dark .debug-action.clear-repl { - background: url('clear-dark.svg') center center no-repeat; -} - -.hc-black .debug-action.clear-repl { - background: url('clear-hc.svg') center center no-repeat; -} - /* Output coloring and styling */ .repl .repl-tree .output.expression > .ignore { font-style: italic; diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index ba438198881..ec21ca71f2c 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -1008,7 +1008,7 @@ export class ClearReplAction extends Action { constructor(id: string, label: string, @IPanelService private readonly panelService: IPanelService ) { - super(id, label, 'debug-action clear-repl'); + super(id, label, 'debug-action codicon-clear-all'); } run(): Promise { diff --git a/src/vs/workbench/contrib/markers/browser/markersPanel.ts b/src/vs/workbench/contrib/markers/browser/markersPanel.ts index bbc4392c7f7..01ef11a2cd4 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanel.ts @@ -117,7 +117,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this.rangeHighlightDecorations = this._register(this.instantiationService.createInstance(RangeHighlightDecorations)); // actions - this.collapseAllAction = this._register(new Action('vs.tree.collapse', localize('collapseAll', "Collapse All"), 'monaco-tree-action collapse-all', true, async () => this.collapseAll())); + this.collapseAllAction = this._register(new Action('vs.tree.collapse', localize('collapseAll', "Collapse All"), 'monaco-tree-action codicon-collapse-all', true, async () => this.collapseAll())); this.filterAction = this._register(this.instantiationService.createInstance(MarkersFilterAction, { filterText: this.panelState['filter'] || '', filterHistory: this.panelState['filterHistory'] || [], useFilesExclude: !!this.panelState['useFilesExclude'] })); } diff --git a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts b/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts index c45dee82e5d..1bc91c2d7cf 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts @@ -213,7 +213,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { private createFilesExcludeCheckbox(container: HTMLElement): void { const filesExcludeFilter = this._register(new Checkbox({ - actionClassName: 'markers-panel-filter-filesExclude', + actionClassName: 'codicon codicon-exclude', title: this.action.useFilesExclude ? Messages.MARKERS_PANEL_ACTION_TOOLTIP_DO_NOT_USE_FILES_EXCLUDE : Messages.MARKERS_PANEL_ACTION_TOOLTIP_USE_FILES_EXCLUDE, isChecked: this.action.useFilesExclude })); diff --git a/src/vs/workbench/contrib/markers/browser/media/exclude-settings-dark.svg b/src/vs/workbench/contrib/markers/browser/media/exclude-settings-dark.svg deleted file mode 100644 index 0b1694dc2f1..00000000000 --- a/src/vs/workbench/contrib/markers/browser/media/exclude-settings-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/markers/browser/media/exclude-settings-hc.svg b/src/vs/workbench/contrib/markers/browser/media/exclude-settings-hc.svg deleted file mode 100644 index ba88235419a..00000000000 --- a/src/vs/workbench/contrib/markers/browser/media/exclude-settings-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/markers/browser/media/exclude-settings-light.svg b/src/vs/workbench/contrib/markers/browser/media/exclude-settings-light.svg deleted file mode 100644 index 114ec3f0fec..00000000000 --- a/src/vs/workbench/contrib/markers/browser/media/exclude-settings-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/markers/browser/media/markers.css b/src/vs/workbench/contrib/markers/browser/media/markers.css index 42321b8dba4..c8e96443126 100644 --- a/src/vs/workbench/contrib/markers/browser/media/markers.css +++ b/src/vs/workbench/contrib/markers/browser/media/markers.css @@ -56,19 +56,6 @@ display: none; } -.vs .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-filesExclude { - background: url('exclude-settings-light.svg') center center no-repeat; -} - -.vs-dark .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-filesExclude, -.hc-black .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-filesExclude { - background: url('exclude-settings-dark.svg') center center no-repeat; -} - -.hc-black .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-filesExclude { - background: url('exclude-settings-hc.svg') center center no-repeat; -} - .markers-panel .markers-panel-container { height: 100%; } diff --git a/src/vs/workbench/contrib/output/browser/media/clear-dark.svg b/src/vs/workbench/contrib/output/browser/media/clear-dark.svg deleted file mode 100644 index 04d64ab41ca..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/clear-dark.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/output/browser/media/clear-hc.svg b/src/vs/workbench/contrib/output/browser/media/clear-hc.svg deleted file mode 100644 index 44a41edd3b3..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/clear-hc.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/output/browser/media/clear-light.svg b/src/vs/workbench/contrib/output/browser/media/clear-light.svg deleted file mode 100644 index f6a51c856f0..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/clear-light.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/output/browser/media/locked-dark.svg b/src/vs/workbench/contrib/output/browser/media/locked-dark.svg deleted file mode 100644 index ebdc1c0078a..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/locked-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/media/locked-hc.svg b/src/vs/workbench/contrib/output/browser/media/locked-hc.svg deleted file mode 100644 index 350dc417710..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/locked-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/media/locked-light.svg b/src/vs/workbench/contrib/output/browser/media/locked-light.svg deleted file mode 100644 index 03b03513688..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/locked-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/media/open-file-dark.svg b/src/vs/workbench/contrib/output/browser/media/open-file-dark.svg deleted file mode 100644 index ed302ae1398..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/open-file-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/media/open-file-hc.svg b/src/vs/workbench/contrib/output/browser/media/open-file-hc.svg deleted file mode 100644 index bba63494e6f..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/open-file-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/media/open-file-light.svg b/src/vs/workbench/contrib/output/browser/media/open-file-light.svg deleted file mode 100644 index 392a840c5ef..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/open-file-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/media/output.css b/src/vs/workbench/contrib/output/browser/media/output.css deleted file mode 100644 index 0f19102cc38..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/output.css +++ /dev/null @@ -1,52 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-workbench .output-action.clear-output { - background: url('clear-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .output-action.clear-output { - background: url('clear-dark.svg') center center no-repeat; -} - -.hc-black .monaco-workbench .output-action.clear-output { - background: url('clear-hc.svg') center center no-repeat; -} - -.monaco-workbench .output-action.output-scroll-lock { - background: url('locked-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .output-action.output-scroll-lock { - background: url('locked-dark.svg') center center no-repeat; -} - -.hc-black .monaco-workbench .output-action.output-scroll-lock { - background: url('locked-hc.svg') center center no-repeat; -} - -.monaco-workbench .output-action.output-scroll-unlock { - background: url('unlocked-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .output-action.output-scroll-unlock { - background: url('unlocked-dark.svg') center center no-repeat; -} - -.hc-black .monaco-workbench .output-action.output-scroll-unlock { - background: url('unlocked-hc.svg') center center no-repeat; -} - -.monaco-workbench .output-action.open-log-file { - background: url('open-file-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .output-action.open-log-file { - background: url('open-file-dark.svg') center center no-repeat; -} - -.hc-black .monaco-workbench .output-action.open-log-file { - background: url('open-file-hc.svg') center center no-repeat; -} diff --git a/src/vs/workbench/contrib/output/browser/media/unlocked-dark.svg b/src/vs/workbench/contrib/output/browser/media/unlocked-dark.svg deleted file mode 100644 index 3b7947f9752..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/unlocked-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/media/unlocked-hc.svg b/src/vs/workbench/contrib/output/browser/media/unlocked-hc.svg deleted file mode 100644 index 100cfc1385a..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/unlocked-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/media/unlocked-light.svg b/src/vs/workbench/contrib/output/browser/media/unlocked-light.svg deleted file mode 100644 index 0c3cb4b93b0..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/unlocked-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/outputActions.ts b/src/vs/workbench/contrib/output/browser/outputActions.ts index 561891fc62e..3760d018bf5 100644 --- a/src/vs/workbench/contrib/output/browser/outputActions.ts +++ b/src/vs/workbench/contrib/output/browser/outputActions.ts @@ -45,7 +45,7 @@ export class ClearOutputAction extends Action { id: string, label: string, @IOutputService private readonly outputService: IOutputService ) { - super(id, label, 'output-action clear-output'); + super(id, label, 'output-action codicon-clear-all'); } public run(): Promise { @@ -67,7 +67,7 @@ export class ToggleOrSetOutputScrollLockAction extends Action { public static readonly LABEL = nls.localize({ key: 'toggleOutputScrollLock', comment: ['Turn on / off automatic output scrolling'] }, "Toggle Output Scroll Lock"); constructor(id: string, label: string, @IOutputService private readonly outputService: IOutputService) { - super(id, label, 'output-action output-scroll-unlock'); + super(id, label, 'output-action codicon-unlock'); this._register(this.outputService.onActiveOutputChannel(channel => { const activeChannel = this.outputService.getActiveChannel(); if (activeChannel) { @@ -94,10 +94,10 @@ export class ToggleOrSetOutputScrollLockAction extends Action { private setClassAndLabel(locked: boolean) { if (locked) { - this.class = 'output-action output-scroll-lock'; + this.class = 'output-action codicon-lock'; this.label = nls.localize('outputScrollOn', "Turn Auto Scrolling On"); } else { - this.class = 'output-action output-scroll-unlock'; + this.class = 'output-action codicon-unlock'; this.label = nls.localize('outputScrollOff', "Turn Auto Scrolling Off"); } } @@ -185,7 +185,7 @@ export class OpenLogOutputFile extends Action { @IEditorService private readonly editorService: IEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { - super(OpenLogOutputFile.ID, OpenLogOutputFile.LABEL, 'output-action open-log-file'); + super(OpenLogOutputFile.ID, OpenLogOutputFile.LABEL, 'output-action codicon-go-to-file'); this._register(this.outputService.onActiveOutputChannel(this.update, this)); this.update(); } diff --git a/src/vs/workbench/contrib/terminal/browser/media/kill-dark.svg b/src/vs/workbench/contrib/terminal/browser/media/kill-dark.svg deleted file mode 100644 index 9831c96a166..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/kill-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/kill-hc.svg b/src/vs/workbench/contrib/terminal/browser/media/kill-hc.svg deleted file mode 100644 index 656f3bd7a42..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/kill-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/kill-light.svg b/src/vs/workbench/contrib/terminal/browser/media/kill-light.svg deleted file mode 100644 index d5ac851860b..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/kill-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/new-dark.svg b/src/vs/workbench/contrib/terminal/browser/media/new-dark.svg deleted file mode 100644 index 4d9389336b9..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/new-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/new-hc.svg b/src/vs/workbench/contrib/terminal/browser/media/new-hc.svg deleted file mode 100644 index fb50c6c2849..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/new-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/new-light.svg b/src/vs/workbench/contrib/terminal/browser/media/new-light.svg deleted file mode 100644 index 01a9de7d5ab..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/new-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-dark.svg b/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-dark.svg deleted file mode 100644 index 8c22a7c5bfe..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-hc.svg b/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-hc.svg deleted file mode 100644 index 82c19d0c8fc..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-light.svg b/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-light.svg deleted file mode 100644 index 2d53ab6d3c2..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-dark.svg b/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-dark.svg deleted file mode 100644 index 419c21be4f6..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-hc.svg b/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-hc.svg deleted file mode 100644 index 7565fd3c168..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-light.svg b/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-light.svg deleted file mode 100644 index 7e95763b463..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index be6edf706dc..d50dd4b9253 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -144,24 +144,6 @@ opacity: 0 !important; } -/* Light theme */ -.monaco-workbench .terminal-action.kill { background: url('kill-light.svg') center center no-repeat; } -.monaco-workbench .terminal-action.new { background: url('new-light.svg') center center no-repeat; } -.monaco-workbench .terminal-action.split { background: url('split-editor-horizontal-light.svg') center center no-repeat; } -.monaco-workbench .panel.right .terminal-action.split { background: url('split-editor-vertical-light.svg') center center no-repeat; } - -/* Dark theme */ -.vs-dark .monaco-workbench .terminal-action.kill, .hc-black .monaco-workbench .terminal-action.kill { background: url('kill-dark.svg') center center no-repeat; } -.vs-dark .monaco-workbench .terminal-action.new, .hc-black .monaco-workbench .terminal-action.new { background: url('new-dark.svg') center center no-repeat; } -.vs-dark .monaco-workbench .terminal-action.split, .hc-black .monaco-workbench .terminal-action.split { background: url('split-editor-horizontal-dark.svg') center center no-repeat; } -.vs-dark .monaco-workbench .panel.right .terminal-action.split, .hc-black .monaco-workbench .panel.right .terminal-action.split { background: url('split-editor-vertical-dark.svg') center center no-repeat; } - -/* HC theme */ -.hc-black .monaco-workbench .terminal-action.kill, .hc-black .monaco-workbench .terminal-action.kill { background: url('kill-hc.svg') center center no-repeat; } -.hc-black .monaco-workbench .terminal-action.new, .hc-black .monaco-workbench .terminal-action.new { background: url('new-hc.svg') center center no-repeat; } -.hc-black .monaco-workbench .terminal-action.split, .hc-black .monaco-workbench .terminal-action.split { background: url('split-editor-horizontal-hc.svg') center center no-repeat; } -.hc-black .monaco-workbench .panel.right .terminal-action.split, .hc-black .monaco-workbench .panel.right .terminal-action.split { background: url('split-editor-vertical-hc.svg') center center no-repeat; } - .vs-dark .monaco-workbench.mac .panel.integrated-terminal .terminal-outer-container:not(.alt-active) .terminal:not(.enable-mouse-events), .hc-black .monaco-workbench.mac .panel.integrated-terminal .terminal-outer-container:not(.alt-active) .terminal:not(.enable-mouse-events) { cursor: -webkit-image-set(url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAL0lEQVQoz2NgCD3x//9/BhBYBWdhgFVAiVW4JBFKGIa4AqD0//9D3pt4I4tAdAMAHTQ/j5Zom30AAAAASUVORK5CYII=') 1x, url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAz0lEQVRIx2NgYGBY/R8I/vx5eelX3n82IJ9FxGf6tksvf/8FiTMQAcAGQMDvSwu09abffY8QYSAScNk45G198eX//yev73/4///701eh//kZSARckrNBRvz//+8+6ZohwCzjGNjdgQxkAg7B9WADeBjIBqtJCbhRA0YNoIkBSNmaPEMoNmA0FkYNoFKhapJ6FGyAH3nauaSmPfwI0v/3OukVi0CIZ+F25KrtYcx/CTIy0e+rC7R1Z4KMICVTQQ14feVXIbR695u14+Ir4gwAAD49E54wc1kWAAAAAElFTkSuQmCC') 2x) 5 8, text; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 6357326ade2..5c6c751e720 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -105,7 +105,7 @@ export class KillTerminalAction extends Action { id: string, label: string, @ITerminalService private readonly terminalService: ITerminalService ) { - super(id, label, 'terminal-action kill'); + super(id, label, 'terminal-action codicon-trash'); } public run(event?: any): Promise { @@ -336,7 +336,7 @@ export class CreateNewTerminalAction extends Action { @ICommandService private readonly commandService: ICommandService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService ) { - super(id, label, 'terminal-action new'); + super(id, label, 'terminal-action codicon-add'); } public run(event?: any): Promise { @@ -412,7 +412,7 @@ export class SplitTerminalAction extends Action { @ICommandService private readonly commandService: ICommandService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService ) { - super(id, label, 'terminal-action split'); + super(id, label, 'terminal-action codicon-split-horizontal'); } public run(event?: any): Promise { From 801585eda1207d422773a235c6af8cd7a7354de6 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Mon, 16 Sep 2019 14:33:24 -0700 Subject: [PATCH 03/36] Add icon default color style --- src/vs/platform/theme/common/colorRegistry.ts | 2 ++ src/vs/workbench/browser/style.ts | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index f5fc75c9324..0c6eef628ac 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -187,6 +187,8 @@ export const activeContrastBorder = registerColor('contrastActiveBorder', { ligh export const selectionBackground = registerColor('selection.background', { light: null, dark: null, hc: null }, nls.localize('selectionBackground', "The background color of text selections in the workbench (e.g. for input fields or text areas). Note that this does not apply to selections within the editor.")); +export const iconDefault = registerColor('icon.default', { light: '#424242', dark: '#C5C5C5', hc: '#FFFFFF' }, nls.localize('iconDefault', "The default color for icons in the workbench.")); + // ------ text colors export const textSeparatorForeground = registerColor('textSeparator.foreground', { light: '#0000002e', dark: '#ffffff2e', hc: Color.black }, nls.localize('textSeparatorForeground', "Color for text separators.")); diff --git a/src/vs/workbench/browser/style.ts b/src/vs/workbench/browser/style.ts index dbb318045f5..5ab87d454dc 100644 --- a/src/vs/workbench/browser/style.ts +++ b/src/vs/workbench/browser/style.ts @@ -6,11 +6,17 @@ import 'vs/css!./media/style'; import { registerThemingParticipant, ITheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; -import { foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; +import { iconDefault, foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; import { WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { + // Icon defaults + const iconDefaultColor = theme.getColor(iconDefault); + if (iconDefaultColor) { + collector.addRule(`.monaco-workbench { color: ${iconDefaultColor}; }`); + } + // Foreground const windowForeground = theme.getColor(foreground); if (windowForeground) { @@ -136,4 +142,5 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { } `); } + }); From 5764546fa65c5a377890160e2ce88f61004683b6 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Mon, 16 Sep 2019 19:28:11 -0700 Subject: [PATCH 04/36] Remove output style --- src/vs/workbench/contrib/output/browser/outputPanel.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/output/browser/outputPanel.ts b/src/vs/workbench/contrib/output/browser/outputPanel.ts index f1c0b211d32..9f58f043851 100644 --- a/src/vs/workbench/contrib/output/browser/outputPanel.ts +++ b/src/vs/workbench/contrib/output/browser/outputPanel.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/output'; import * as nls from 'vs/nls'; import { Action, IAction } from 'vs/base/common/actions'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; From 9b9b142852f8d41253518a8f0bc28a5d5b2e5a12 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Mon, 16 Sep 2019 19:30:11 -0700 Subject: [PATCH 05/36] Scope icon color token --- src/vs/workbench/browser/style.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/style.ts b/src/vs/workbench/browser/style.ts index 5ab87d454dc..b7c6fc80afa 100644 --- a/src/vs/workbench/browser/style.ts +++ b/src/vs/workbench/browser/style.ts @@ -14,7 +14,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { // Icon defaults const iconDefaultColor = theme.getColor(iconDefault); if (iconDefaultColor) { - collector.addRule(`.monaco-workbench { color: ${iconDefaultColor}; }`); + collector.addRule(`.monaco-workbench .codicon { color: ${iconDefaultColor}; }`); } // Foreground From 663b07c8e9857ebb98b2eec9c995e6b8193d3500 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Mon, 16 Sep 2019 19:38:39 -0700 Subject: [PATCH 06/36] Update icon ref to codicon --- src/vs/base/browser/ui/actionbar/actionbar.css | 4 ++-- src/vs/base/browser/ui/inputbox/inputBox.css | 2 +- .../contrib/extensions/browser/media/extensionActions.css | 2 +- src/vs/workbench/contrib/search/browser/media/searchview.css | 2 +- test/automation/src/keybindings.ts | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/base/browser/ui/actionbar/actionbar.css b/src/vs/base/browser/ui/actionbar/actionbar.css index 25795ddb631..08b4920ae47 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.css +++ b/src/vs/base/browser/ui/actionbar/actionbar.css @@ -40,7 +40,7 @@ transform: scale(1.272019649, 1.272019649); /* 1.272019649 = √φ */ } -.monaco-action-bar .action-item .icon { +.monaco-action-bar .action-item .codicon { display: inline-block; } @@ -95,4 +95,4 @@ display: flex; align-items: center; justify-content: center; -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/inputbox/inputBox.css b/src/vs/base/browser/ui/inputbox/inputBox.css index ae4cd429a07..939d0cb5f01 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.css +++ b/src/vs/base/browser/ui/inputbox/inputBox.css @@ -123,7 +123,7 @@ margin-left: 2px; } -.monaco-inputbox .monaco-action-bar .action-item .icon { +.monaco-inputbox .monaco-action-bar .action-item .codicon { background-repeat: no-repeat; width: 16px; height: 16px; diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css index 79d7e01a69c..ed1ba80ba46 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css @@ -78,7 +78,7 @@ margin: 0.15em; } -.monaco-action-bar .action-item .action-label.system-disable.icon { +.monaco-action-bar .action-item .action-label.system-disable.codicon { opacity: 1; height: 18px; width: 10px; diff --git a/src/vs/workbench/contrib/search/browser/media/searchview.css b/src/vs/workbench/contrib/search/browser/media/searchview.css index f530bce968a..decc6173962 100644 --- a/src/vs/workbench/contrib/search/browser/media/searchview.css +++ b/src/vs/workbench/contrib/search/browser/media/searchview.css @@ -86,7 +86,7 @@ height: 25px; } -.search-view .search-widget .replace-container .monaco-action-bar .action-item .icon { +.search-view .search-widget .replace-container .monaco-action-bar .action-item .codicon { background-repeat: no-repeat; width: 20px; height: 25px; diff --git a/test/automation/src/keybindings.ts b/test/automation/src/keybindings.ts index 9544b5992db..70be12b2378 100644 --- a/test/automation/src/keybindings.ts +++ b/test/automation/src/keybindings.ts @@ -24,7 +24,7 @@ export class KeybindingsEditor { await this.code.waitAndClick('.keybindings-list-container .monaco-list-row.keybinding-item'); await this.code.waitForElement('.keybindings-list-container .monaco-list-row.keybinding-item.focused.selected'); - await this.code.waitAndClick('.keybindings-list-container .monaco-list-row.keybinding-item .action-item .icon.add'); + await this.code.waitAndClick('.keybindings-list-container .monaco-list-row.keybinding-item .action-item .codicon.add'); await this.code.waitForActiveElement('.defineKeybindingWidget .monaco-inputbox input'); await this.code.dispatchKeybinding(keybinding); From a2a9ec0eb6a971f27518f052131fcd7f366d9b91 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Tue, 17 Sep 2019 08:00:04 -0700 Subject: [PATCH 07/36] Rename iconDefault => iconForeground --- src/vs/platform/theme/common/colorRegistry.ts | 2 +- .../browser/parts/notifications/notificationsActions.ts | 4 ++-- src/vs/workbench/browser/style.ts | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 0c6eef628ac..b6ec6f9e6f9 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -187,7 +187,7 @@ export const activeContrastBorder = registerColor('contrastActiveBorder', { ligh export const selectionBackground = registerColor('selection.background', { light: null, dark: null, hc: null }, nls.localize('selectionBackground', "The background color of text selections in the workbench (e.g. for input fields or text areas). Note that this does not apply to selections within the editor.")); -export const iconDefault = registerColor('icon.default', { light: '#424242', dark: '#C5C5C5', hc: '#FFFFFF' }, nls.localize('iconDefault', "The default color for icons in the workbench.")); +export const iconForeground = registerColor('icon.foreground', { light: '#424242', dark: '#C5C5C5', hc: '#FFFFFF' }, nls.localize('iconForeground', "The default color for icons in the workbench.")); // ------ text colors diff --git a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts index 16f7f408a3e..712cb046a3f 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts @@ -43,7 +43,7 @@ export class ClearAllNotificationsAction extends Action { label: string, @ICommandService private readonly commandService: ICommandService ) { - super(id, label, 'clear-all-notifications-action'); + super(id, label, 'codicon-clear-all'); } run(notification: INotificationViewItem): Promise { @@ -63,7 +63,7 @@ export class HideNotificationsCenterAction extends Action { label: string, @ICommandService private readonly commandService: ICommandService ) { - super(id, label, 'hide-all-notifications-action'); + super(id, label, 'codicon-chevron-down'); } run(notification: INotificationViewItem): Promise { diff --git a/src/vs/workbench/browser/style.ts b/src/vs/workbench/browser/style.ts index b7c6fc80afa..deaaf4953ed 100644 --- a/src/vs/workbench/browser/style.ts +++ b/src/vs/workbench/browser/style.ts @@ -6,15 +6,15 @@ import 'vs/css!./media/style'; import { registerThemingParticipant, ITheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; -import { iconDefault, foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; +import { iconForeground, foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; import { WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { // Icon defaults - const iconDefaultColor = theme.getColor(iconDefault); - if (iconDefaultColor) { - collector.addRule(`.monaco-workbench .codicon { color: ${iconDefaultColor}; }`); + const iconForegroundColor = theme.getColor(iconForeground); + if (iconForegroundColor) { + collector.addRule(`.monaco-workbench .codicon { color: ${iconForegroundColor}; }`); } // Foreground From 64b9f9b00af98b02b8ef459ba601e2c595f36095 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Tue, 17 Sep 2019 08:01:39 -0700 Subject: [PATCH 08/36] Revert notification icon change --- .../browser/parts/notifications/notificationsActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts index 712cb046a3f..16f7f408a3e 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts @@ -43,7 +43,7 @@ export class ClearAllNotificationsAction extends Action { label: string, @ICommandService private readonly commandService: ICommandService ) { - super(id, label, 'codicon-clear-all'); + super(id, label, 'clear-all-notifications-action'); } run(notification: INotificationViewItem): Promise { @@ -63,7 +63,7 @@ export class HideNotificationsCenterAction extends Action { label: string, @ICommandService private readonly commandService: ICommandService ) { - super(id, label, 'codicon-chevron-down'); + super(id, label, 'hide-all-notifications-action'); } run(notification: INotificationViewItem): Promise { From e83d9d3acf99de46d74a2ec3c1e17c3ccaa87506 Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 17 Sep 2019 17:14:08 +0200 Subject: [PATCH 09/36] breakpoints: render candidates as decorations --- .../browser/breakpointEditorContribution.ts | 66 ++++++++++++++++--- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 8b613a2058a..4515fd4dfdd 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -28,6 +28,7 @@ import { getBreakpointMessageAndClassName } from 'vs/workbench/contrib/debug/bro import { generateUuid } from 'vs/base/common/uuid'; import { memoize } from 'vs/base/common/decorators'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { distinct } from 'vs/base/common/arrays'; const $ = dom.$; @@ -63,6 +64,28 @@ function createBreakpointDecorations(model: ITextModel, breakpoints: ReadonlyArr return result; } +async function createCandidateDecorations(model: ITextModel, lineNumbers: number[], debugService: IDebugService): Promise<{ range: Range; options: IModelDecorationOptions; }[]> { + const result: { range: Range; options: IModelDecorationOptions; }[] = []; + const session = debugService.getViewModel().focusedSession; + if (session && session.capabilities.supportsBreakpointLocationsRequest) { + lineNumbers.forEach(async lineNumber => { + const positions = await session.breakpointsLocations(model.uri, lineNumber); + positions.forEach(p => { + result.push({ + range: new Range(p.lineNumber, p.column, p.lineNumber, p.column + 1), + options: { + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + beforeContentClassName: `debug-breakpoint-placeholder` + } + }); + }); + }); + } + + return result; +} + + function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoint, debugService: IDebugService): IModelDecorationOptions { const { className, message } = getBreakpointMessageAndClassName(debugService, breakpoint); let glyphMarginHoverMessage: MarkdownString | undefined; @@ -93,6 +116,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { private ignoreDecorationsChangedEvent = false; private ignoreFirstBreakpointsChangeEvent = false; private breakpointDecorations: IBreakpointDecoration[] = []; + private candidateDecoraions: { decorationId: string, inlineWidget: InlineBreakpointWidget }[] = []; constructor( private readonly editor: ICodeEditor, @@ -193,16 +217,16 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { this.ensureBreakpointHintDecoration(-1); })); - this.toDispose.push(this.editor.onDidChangeModel(() => { + this.toDispose.push(this.editor.onDidChangeModel(async () => { this.closeBreakpointWidget(); - this.setDecorations(); + await this.setDecorations(); })); - this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => { + this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(async () => { if (this.ignoreFirstBreakpointsChangeEvent) { this.ignoreFirstBreakpointsChangeEvent = false; return; } - this.setDecorations(); + await this.setDecorations(); })); this.toDispose.push(this.editor.onDidChangeModelDecorations(() => this.onModelDecorationsChanged())); } @@ -311,7 +335,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { this.breakpointHintDecoration = this.editor.deltaDecorations(this.breakpointHintDecoration, newDecoration); } - private setDecorations(): void { + private async setDecorations(): Promise { if (!this.editor.hasModel()) { return; } @@ -319,11 +343,13 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { const activeCodeEditor = this.editor; const model = activeCodeEditor.getModel(); const breakpoints = this.debugService.getModel().getBreakpoints({ uri: model.uri }); - const desiredDecorations = createBreakpointDecorations(model, breakpoints, this.debugService); + const desiredBreakpointDecorations = createBreakpointDecorations(model, breakpoints, this.debugService); try { this.ignoreDecorationsChangedEvent = true; - const decorationIds = activeCodeEditor.deltaDecorations(this.breakpointDecorations.map(bpd => bpd.decorationId), desiredDecorations); + + // Set breakpoint decorations + const decorationIds = activeCodeEditor.deltaDecorations(this.breakpointDecorations.map(bpd => bpd.decorationId), desiredBreakpointDecorations); this.breakpointDecorations.forEach(bpd => { if (bpd.inlineWidget) { bpd.inlineWidget.dispose(); @@ -333,19 +359,41 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { let inlineWidget: InlineBreakpointWidget | undefined = undefined; const breakpoint = breakpoints[index]; if (breakpoint.column) { - inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, desiredDecorations[index].options.glyphMarginClassName, breakpoint, this.debugService, this.contextMenuService, () => this.getContextMenuActions([breakpoint], activeCodeEditor.getModel().uri, breakpoint.lineNumber, breakpoint.column)); + inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, desiredBreakpointDecorations[index].options.glyphMarginClassName, breakpoint, this.debugService, this.contextMenuService, () => this.getContextMenuActions([breakpoint], activeCodeEditor.getModel().uri, breakpoint.lineNumber, breakpoint.column)); } return { decorationId, breakpointId: breakpoint.getId(), - range: desiredDecorations[index].range, + range: desiredBreakpointDecorations[index].range, inlineWidget }; }); + } finally { this.ignoreDecorationsChangedEvent = false; } + + // Set breakpoint candidate decorations + const lineNumbers = distinct(this.breakpointDecorations.map(bpd => bpd.range.startLineNumber)); + let desiredCandidateDecorations = await createCandidateDecorations(this.editor.getModel(), lineNumbers, this.debugService); + desiredCandidateDecorations = desiredCandidateDecorations.filter(dbd => { + const breakpointDecorationAlreadyAtCandidateLocation = this.breakpointDecorations.filter(bd => bd.range.equalsRange(dbd.range)).length >= 0; + return !breakpointDecorationAlreadyAtCandidateLocation; + }); + const candidateDecorationids = this.editor.deltaDecorations(this.candidateDecoraions.map(c => c.decorationId), desiredCandidateDecorations); + this.candidateDecoraions.forEach(candidate => { + candidate.inlineWidget.dispose(); + }); + this.candidateDecoraions = candidateDecorationids.map((decorationId, index) => { + const candidate = desiredCandidateDecorations[index]; + const inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, 'debug-breakpoint-disabled', undefined, this.debugService, this.contextMenuService, () => this.getContextMenuActions([], activeCodeEditor.getModel().uri, candidate.range.startLineNumber, candidate.range.startColumn)); + + return { + decorationId, + inlineWidget + }; + }); } private async onModelDecorationsChanged(): Promise { From 6fe6269c41bf555073cc2a0024709f6e76eb6dca Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 17 Sep 2019 17:27:15 +0200 Subject: [PATCH 10/36] Keep better track of custom executions for reruns and depends Fixes #80964 --- src/vs/workbench/api/common/extHostTask.ts | 36 ++++++++++++++-------- src/vs/workbench/api/node/extHostTask.ts | 4 +-- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTask.ts b/src/vs/workbench/api/common/extHostTask.ts index a2119063d3d..7667465f6bd 100644 --- a/src/vs/workbench/api/common/extHostTask.ts +++ b/src/vs/workbench/api/common/extHostTask.ts @@ -374,8 +374,9 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { protected _handlers: Map; protected _taskExecutions: Map; protected _providedCustomExecutions2: Map; + private _notProvidedCustomExecutions: Set; // Used for custom executions tasks that are created and run through executeTask. protected _activeCustomExecutions2: Map; - + private _lastStartedTask: string | undefined; protected readonly _onDidExecuteTask: Emitter = new Emitter(); protected readonly _onDidTerminateTask: Emitter = new Emitter(); @@ -399,6 +400,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { this._handlers = new Map(); this._taskExecutions = new Map(); this._providedCustomExecutions2 = new Map(); + this._notProvidedCustomExecutions = new Set(); this._activeCustomExecutions2 = new Map(); } @@ -462,6 +464,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { this._activeCustomExecutions2.set(execution.id, execution2); this._terminalService.attachPtyToTerminal(terminalId, await execution2.callback()); } + this._lastStartedTask = execution.id; this._onDidExecuteTask.fire({ execution: await this.getTaskExecution(execution) @@ -571,7 +574,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { } if (CustomExecution2DTO.is(resolvedTaskDTO.execution)) { - await this.addCustomExecution2(resolvedTaskDTO, resolvedTask); + await this.addCustomExecution2(resolvedTaskDTO, resolvedTask, true); } return await this.resolveTaskInternal(resolvedTaskDTO); @@ -585,8 +588,11 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { return this._handleCounter++; } - protected async addCustomExecution2(taskDTO: tasks.TaskDTO, task: vscode.Task2): Promise { + protected async addCustomExecution2(taskDTO: tasks.TaskDTO, task: vscode.Task2, isProvided: boolean): Promise { const taskId = await this._proxy.$createTaskId(taskDTO); + if (!isProvided && !this._providedCustomExecutions2.has(taskId)) { + this._notProvidedCustomExecutions.add(taskId); + } this._providedCustomExecutions2.set(taskId, (task).execution2); } @@ -618,16 +624,22 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { this._activeCustomExecutions2.delete(execution.id); } - const lastCustomExecution = this._providedCustomExecutions2.get(execution.id); // Technically we don't really need to do this, however, if an extension // is executing a task through "executeTask" over and over again - // with different properties in the task definition, then this list + // with different properties in the task definition, then the map of executions // could grow indefinitely, something we don't want. - this._providedCustomExecutions2.clear(); - // We do still need to hang on to the last custom execution so that the - // Rerun Task command doesn't choke when it tries to rerun a custom execution - if (lastCustomExecution) { - this._providedCustomExecutions2.set(execution.id, lastCustomExecution); + if (this._notProvidedCustomExecutions.has(execution.id) && (this._lastStartedTask !== execution.id)) { + this._providedCustomExecutions2.delete(execution.id); + this._notProvidedCustomExecutions.delete(execution.id); + } + let iterator = this._notProvidedCustomExecutions.values(); + let iteratorResult = iterator.next(); + while (!iteratorResult.done) { + if (!this._activeCustomExecutions2.has(iteratorResult.value) && (this._lastStartedTask !== iteratorResult.value)) { + this._providedCustomExecutions2.delete(iteratorResult.value); + this._notProvidedCustomExecutions.delete(iteratorResult.value); + } + iteratorResult = iterator.next(); } } @@ -663,7 +675,7 @@ export class WorkerExtHostTask extends ExtHostTaskBase { // in the provided custom execution map that is cleaned up after the // task is executed. if (CustomExecution2DTO.is(dto.execution)) { - await this.addCustomExecution2(dto, task); + await this.addCustomExecution2(dto, task, false); } else { throw new Error('Not implemented'); } @@ -685,7 +697,7 @@ export class WorkerExtHostTask extends ExtHostTaskBase { // The ID is calculated on the main thread task side, so, let's call into it here. // We need the task id's pre-computed for custom task executions because when OnDidStartTask // is invoked, we have to be able to map it back to our data. - taskIdPromises.push(this.addCustomExecution2(taskDTO, task)); + taskIdPromises.push(this.addCustomExecution2(taskDTO, task, true)); } else { console.warn('Only custom execution tasks supported.'); } diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index f1379251ab1..71d8b818b72 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -57,7 +57,7 @@ export class ExtHostTask extends ExtHostTaskBase { // in the provided custom execution map that is cleaned up after the // task is executed. if (CustomExecution2DTO.is(dto.execution)) { - await this.addCustomExecution2(dto, task); + await this.addCustomExecution2(dto, task, false); } return this._proxy.$executeTask(dto).then(value => this.getTaskExecution(value, task)); @@ -80,7 +80,7 @@ export class ExtHostTask extends ExtHostTaskBase { // The ID is calculated on the main thread task side, so, let's call into it here. // We need the task id's pre-computed for custom task executions because when OnDidStartTask // is invoked, we have to be able to map it back to our data. - taskIdPromises.push(this.addCustomExecution2(taskDTO, task)); + taskIdPromises.push(this.addCustomExecution2(taskDTO, task, true)); } } } From 7ecb5821cdc3035a54280265dee4ccc8ef1cdfd4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 17 Sep 2019 17:47:02 +0200 Subject: [PATCH 11/36] debt - remove IWindowService#log in favor of a logger that can log to the main side --- .../issue/issueReporterMain.ts | 6 +-- .../sharedProcess/sharedProcessMain.ts | 8 +-- src/vs/code/electron-main/app.ts | 8 +-- src/vs/platform/log/common/log.ts | 51 ++++++++++++++++++- src/vs/platform/log/common/logIpc.ts | 31 +++++++++-- src/vs/platform/windows/common/windows.ts | 1 - src/vs/platform/windows/common/windowsIpc.ts | 1 - .../electron-browser/windowsService.ts | 4 -- .../windows/electron-main/windowsService.ts | 18 ------- .../api/browser/mainThreadConsole.ts | 9 ++-- .../workbench/browser/web.simpleservices.ts | 4 -- .../remote/common/remote.contribution.ts | 8 +-- .../electron-browser/remote.contribution.ts | 4 +- .../electron-browser/desktop.main.ts | 27 +++++++--- .../extensions/common/remoteConsoleUtil.ts | 32 ++++++++++++ .../electron-browser/extensionHost.ts | 8 +-- .../workbench/test/workbenchTestServices.ts | 4 -- 17 files changed, 155 insertions(+), 69 deletions(-) create mode 100644 src/vs/workbench/services/extensions/common/remoteConsoleUtil.ts diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index a0f146faa19..6d0693d7657 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -32,7 +32,7 @@ import { EnvironmentService } from 'vs/platform/environment/node/environmentServ import { IssueReporterModel, IssueReporterData as IssueReporterModelData } from 'vs/code/electron-browser/issue/issueReporterModel'; import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssueReporterData, IssueReporterFeatures, IssueReporterExtensionData } from 'vs/platform/issue/node/issue'; import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage'; -import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; +import { LoggerChannelClient, 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 { normalizeGitHubUrl } from 'vs/code/electron-browser/issue/issueReporterUtil'; @@ -300,8 +300,8 @@ export class IssueReporter extends Disposable { this.environmentService = new EnvironmentService(configuration, configuration.execPath); const logService = new SpdLogService(`issuereporter${configuration.windowId}`, this.environmentService.logsPath, getLogLevel(this.environmentService)); - const logLevelClient = new LogLevelSetterChannelClient(mainProcessService.getChannel('loglevel')); - this.logService = new FollowerLogService(logLevelClient, logService); + const loggerClient = new LoggerChannelClient(mainProcessService.getChannel('logger')); + this.logService = new FollowerLogService(loggerClient, logService); const sharedProcess = (serviceCollection.get(IWindowsService)).whenSharedProcessReady() .then(() => connectNet(this.environmentService.sharedIPCHandle, `window:${configuration.windowId}`)); diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 56f7ed460f3..6343cddc3a8 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -30,7 +30,7 @@ import { IWindowsService } from 'vs/platform/windows/common/windows'; import { WindowsService } from 'vs/platform/windows/electron-browser/windowsService'; import { ipcRenderer } from 'electron'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; -import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; +import { LoggerChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; import { LocalizationsChannel } from 'vs/platform/localizations/node/localizationsIpc'; @@ -96,8 +96,8 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat const environmentService = new EnvironmentService(initData.args, process.execPath); const mainRouter = new StaticRouter(ctx => ctx === 'main'); - const logLevelClient = new LogLevelSetterChannelClient(server.getChannel('loglevel', mainRouter)); - const logService = new FollowerLogService(logLevelClient, new SpdLogService('sharedprocess', environmentService.logsPath, initData.logLevel)); + const loggerClient = new LoggerChannelClient(server.getChannel('logger', mainRouter)); + const logService = new FollowerLogService(loggerClient, new SpdLogService('sharedprocess', environmentService.logsPath, initData.logLevel)); disposables.add(logService); logService.info('main', JSON.stringify(configuration)); @@ -135,7 +135,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat const services = new ServiceCollection(); const environmentService = accessor.get(IEnvironmentService); const { appRoot, extensionsPath, extensionDevelopmentLocationURI: extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService; - const telemetryLogService = new FollowerLogService(logLevelClient, new SpdLogService('telemetry', environmentService.logsPath, initData.logLevel)); + const telemetryLogService = new FollowerLogService(loggerClient, new SpdLogService('telemetry', environmentService.logsPath, initData.logLevel)); telemetryLogService.info('The below are logs for every telemetry event sent from VS Code once the log level is set to trace.'); telemetryLogService.info('==========================================================='); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 4e2f8842a9e..f0eeddedf86 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -47,7 +47,7 @@ import { DarwinUpdateService } from 'vs/platform/update/electron-main/updateServ import { IIssueService } from 'vs/platform/issue/node/issue'; import { IssueChannel } from 'vs/platform/issue/electron-main/issueIpc'; import { IssueMainService } from 'vs/platform/issue/electron-main/issueMainService'; -import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc'; +import { LoggerChannel } from 'vs/platform/log/common/logIpc'; import { setUnexpectedErrorHandler, onUnexpectedError } 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'; @@ -567,9 +567,9 @@ export class CodeApplication extends Disposable { const storageChannel = this._register(new GlobalStorageDatabaseChannel(this.logService, storageMainService)); electronIpcServer.registerChannel('storage', storageChannel); - const logLevelChannel = new LogLevelSetterChannel(accessor.get(ILogService)); - electronIpcServer.registerChannel('loglevel', logLevelChannel); - sharedProcessClient.then(client => client.registerChannel('loglevel', logLevelChannel)); + const loggerChannel = new LoggerChannel(accessor.get(ILogService)); + electronIpcServer.registerChannel('logger', loggerChannel); + sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel)); // ExtensionHost Debug broadcast service electronIpcServer.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ExtensionHostDebugBroadcastChannel()); diff --git a/src/vs/platform/log/common/log.ts b/src/vs/platform/log/common/log.ts index 5e6b7300c49..0009b304e53 100644 --- a/src/vs/platform/log/common/log.ts +++ b/src/vs/platform/log/common/log.ts @@ -8,6 +8,7 @@ import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { isWindows } from 'vs/base/common/platform'; import { Event, Emitter } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { LoggerChannelClient } from 'vs/platform/log/common/logIpc'; export const ILogService = createServiceDecorator('logService'); @@ -183,6 +184,54 @@ export class ConsoleLogService extends AbstractLogService implements ILogService dispose(): void { } } +export class ConsoleLogInMainService extends AbstractLogService implements ILogService { + + _serviceBrand: undefined; + + constructor(private readonly client: LoggerChannelClient, logLevel: LogLevel = DEFAULT_LOG_LEVEL) { + super(); + this.setLevel(logLevel); + } + + trace(message: string, ...args: any[]): void { + if (this.getLevel() <= LogLevel.Trace) { + this.client.consoleLog('trace', [message, ...args]); + } + } + + debug(message: string, ...args: any[]): void { + if (this.getLevel() <= LogLevel.Debug) { + this.client.consoleLog('debug', [message, ...args]); + } + } + + info(message: string, ...args: any[]): void { + if (this.getLevel() <= LogLevel.Info) { + this.client.consoleLog('info', [message, ...args]); + } + } + + warn(message: string | Error, ...args: any[]): void { + if (this.getLevel() <= LogLevel.Warning) { + this.client.consoleLog('warn', [message, ...args]); + } + } + + error(message: string, ...args: any[]): void { + if (this.getLevel() <= LogLevel.Error) { + this.client.consoleLog('error', [message, ...args]); + } + } + + critical(message: string, ...args: any[]): void { + if (this.getLevel() <= LogLevel.Critical) { + this.client.consoleLog('critical', [message, ...args]); + } + } + + dispose(): void { } +} + export class MultiplexLogService extends AbstractLogService implements ILogService { _serviceBrand: undefined; @@ -326,4 +375,4 @@ export function getLogLevel(environmentService: IEnvironmentService): LogLevel { } } return DEFAULT_LOG_LEVEL; -} \ No newline at end of file +} diff --git a/src/vs/platform/log/common/logIpc.ts b/src/vs/platform/log/common/logIpc.ts index 5f631b8b9d1..260922cedcb 100644 --- a/src/vs/platform/log/common/logIpc.ts +++ b/src/vs/platform/log/common/logIpc.ts @@ -7,7 +7,7 @@ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { LogLevel, ILogService, DelegatedLogService } from 'vs/platform/log/common/log'; import { Event } from 'vs/base/common/event'; -export class LogLevelSetterChannel implements IServerChannel { +export class LoggerChannel implements IServerChannel { onDidChangeLogLevel: Event; @@ -26,13 +26,32 @@ export class LogLevelSetterChannel implements IServerChannel { call(_: unknown, command: string, arg?: any): Promise { switch (command) { case 'setLevel': this.service.setLevel(arg); return Promise.resolve(); + case 'consoleLog': this.consoleLog(arg[0], arg[1]); return Promise.resolve(); } throw new Error(`Call not found: ${command}`); } + + private consoleLog(severity: string, args: string[]): void { + let consoleFn = console.log; + + switch (severity) { + case 'error': + consoleFn = console.error; + break; + case 'warn': + consoleFn = console.warn; + break; + case 'info': + consoleFn = console.info; + break; + } + + consoleFn.call(console, ...args); + } } -export class LogLevelSetterChannelClient { +export class LoggerChannelClient { constructor(private channel: IChannel) { } @@ -43,12 +62,16 @@ export class LogLevelSetterChannelClient { setLevel(level: LogLevel): void { this.channel.call('setLevel', level); } + + consoleLog(severity: string, args: string[]): void { + this.channel.call('consoleLog', [severity, args]); + } } export class FollowerLogService extends DelegatedLogService implements ILogService { _serviceBrand: undefined; - constructor(private master: LogLevelSetterChannelClient, logService: ILogService) { + constructor(private master: LoggerChannelClient, logService: ILogService) { super(logService); this._register(master.onDidChangeLogLevel(level => logService.setLevel(level))); } @@ -56,4 +79,4 @@ export class FollowerLogService extends DelegatedLogService implements ILogServi setLevel(level: LogLevel): void { this.master.setLevel(level); } -} \ No newline at end of file +} diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 7f697c0d445..97f4c9b7687 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -156,7 +156,6 @@ export interface IWindowsService { openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise; getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]>; getWindowCount(): Promise; - log(severity: string, args: string[]): Promise; showItemInFolder(path: URI): Promise; getActiveWindowId(): Promise; diff --git a/src/vs/platform/windows/common/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts index 5e3c0944d94..091f49e137f 100644 --- a/src/vs/platform/windows/common/windowsIpc.ts +++ b/src/vs/platform/windows/common/windowsIpc.ts @@ -109,7 +109,6 @@ export class WindowsChannel implements IServerChannel { case 'whenSharedProcessReady': return this.service.whenSharedProcessReady(); case 'toggleSharedProcess': return this.service.toggleSharedProcess(); case 'quit': return this.service.quit(); - case 'log': return this.service.log(arg[0], arg[1]); case 'showItemInFolder': return this.service.showItemInFolder(URI.revive(arg)); case 'getActiveWindowId': return this.service.getActiveWindowId(); case 'openExternal': return this.service.openExternal(arg); diff --git a/src/vs/platform/windows/electron-browser/windowsService.ts b/src/vs/platform/windows/electron-browser/windowsService.ts index d6089d90776..283259ac8d6 100644 --- a/src/vs/platform/windows/electron-browser/windowsService.ts +++ b/src/vs/platform/windows/electron-browser/windowsService.ts @@ -226,10 +226,6 @@ export class WindowsService implements IWindowsService { return this.channel.call('getWindowCount'); } - log(severity: string, args: string[]): Promise { - return this.channel.call('log', [severity, args]); - } - showItemInFolder(path: URI): Promise { return this.channel.call('showItemInFolder', path); } diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index c6ee7cfc593..740891b52f9 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -336,24 +336,6 @@ export class WindowsService extends Disposable implements IWindowsService, IURLH return this.windowsMainService.getWindows().length; } - async log(severity: string, args: string[]): Promise { - let consoleFn = console.log; - - switch (severity) { - case 'error': - consoleFn = console.error; - break; - case 'warn': - consoleFn = console.warn; - break; - case 'info': - consoleFn = console.info; - break; - } - - consoleFn.call(console, ...args); - } - async showItemInFolder(resource: URI): Promise { this.logService.trace('windowsService#showItemInFolder'); diff --git a/src/vs/workbench/api/browser/mainThreadConsole.ts b/src/vs/workbench/api/browser/mainThreadConsole.ts index 1d7a7723404..4a244875f42 100644 --- a/src/vs/workbench/api/browser/mainThreadConsole.ts +++ b/src/vs/workbench/api/browser/mainThreadConsole.ts @@ -6,9 +6,10 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { MainContext, MainThreadConsoleShape, IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IRemoteConsoleLog, log, parse } from 'vs/base/common/console'; +import { IRemoteConsoleLog, log } from 'vs/base/common/console'; +import { logRemoteEntry } from 'vs/workbench/services/extensions/common/remoteConsoleUtil'; import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { ILogService } from 'vs/platform/log/common/log'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; @extHostNamedCustomer(MainContext.MainThreadConsole) @@ -20,7 +21,7 @@ export class MainThreadConsole implements MainThreadConsoleShape { constructor( extHostContext: IExtHostContext, @IEnvironmentService private readonly _environmentService: IEnvironmentService, - @IWindowsService private readonly _windowsService: IWindowsService, + @ILogService private readonly _logService: ILogService, @IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService, ) { const devOpts = parseExtensionDevOptions(this._environmentService); @@ -40,7 +41,7 @@ export class MainThreadConsole implements MainThreadConsoleShape { // Log on main side if running tests from cli if (this._isExtensionDevTestFromCli) { - this._windowsService.log(entry.severity, parse(entry).args); + logRemoteEntry(this._logService, entry); } // Broadcast to other windows if we are in development mode diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts index 86e496ceedd..87a1fee80e8 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -508,10 +508,6 @@ export class SimpleWindowsService implements IWindowsService { return Promise.resolve(this.windowCount); } - log(_severity: string, _args: string[]): Promise { - return Promise.resolve(); - } - showItemInFolder(_path: URI): Promise { return Promise.resolve(); } diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index 8ec38b3795e..1f4297c039f 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -11,7 +11,7 @@ import { OperatingSystem, isWeb } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; import { IRemoteAgentService, RemoteExtensionLogFileName } from 'vs/workbench/services/remote/common/remoteAgentService'; import { ILogService } from 'vs/platform/log/common/log'; -import { LogLevelSetterChannelClient } from 'vs/platform/log/common/logIpc'; +import { LoggerChannelClient } from 'vs/platform/log/common/logIpc'; import { IOutputChannelRegistry, Extensions as OutputExt, } from 'vs/workbench/contrib/output/common/output'; import { localize } from 'vs/nls'; import { joinPath } from 'vs/base/common/resources'; @@ -79,9 +79,9 @@ class RemoteChannelsContribution extends Disposable implements IWorkbenchContrib super(); const connection = remoteAgentService.getConnection(); if (connection) { - const logLevelClient = new LogLevelSetterChannelClient(connection.getChannel('loglevel')); - logLevelClient.setLevel(logService.getLevel()); - this._register(logService.onDidChangeLogLevel(level => logLevelClient.setLevel(level))); + const loggerClient = new LoggerChannelClient(connection.getChannel('logger')); + loggerClient.setLevel(logService.getLevel()); + this._register(logService.onDidChangeLogLevel(level => loggerClient.setLevel(level))); } } } diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index 37cae45ace2..e45a5faa59e 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -26,7 +26,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { DialogChannel } from 'vs/platform/dialogs/electron-browser/dialogIpc'; import { DownloadServiceChannel } from 'vs/platform/download/common/downloadIpc'; -import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc'; +import { LoggerChannel } from 'vs/platform/log/common/logIpc'; import { ipcRenderer as ipc } from 'electron'; import { IDiagnosticInfoOptions, IRemoteDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -229,7 +229,7 @@ class RemoteChannelsContribution implements IWorkbenchContribution { if (connection) { connection.registerChannel('dialog', new DialogChannel(dialogService)); connection.registerChannel('download', new DownloadServiceChannel(downloadService)); - connection.registerChannel('loglevel', new LogLevelSetterChannel(logService)); + connection.registerChannel('logger', new LoggerChannel(logService)); } } } diff --git a/src/vs/workbench/electron-browser/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts index ebf89cda089..076d03a5b1e 100644 --- a/src/vs/workbench/electron-browser/desktop.main.ts +++ b/src/vs/workbench/electron-browser/desktop.main.ts @@ -23,9 +23,9 @@ import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { webFrame } from 'electron'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { ConsoleLogService, MultiplexLogService, ILogService } from 'vs/platform/log/common/log'; +import { ConsoleLogService, MultiplexLogService, ILogService, ConsoleLogInMainService } from 'vs/platform/log/common/log'; import { StorageService } from 'vs/platform/storage/node/storageService'; -import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; +import { LoggerChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; import { Schemas } from 'vs/base/common/network'; import { sanitizeFilePath } from 'vs/base/common/extpath'; import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc'; @@ -345,12 +345,25 @@ class CodeRendererMain extends Disposable { } private createLogService(mainProcessService: IMainProcessService, environmentService: IWorkbenchEnvironmentService): ILogService { - const spdlogService = new SpdLogService(`renderer${this.environmentService.configuration.windowId}`, environmentService.logsPath, this.environmentService.configuration.logLevel); - const consoleLogService = new ConsoleLogService(this.environmentService.configuration.logLevel); - const logService = new MultiplexLogService([consoleLogService, spdlogService]); - const logLevelClient = new LogLevelSetterChannelClient(mainProcessService.getChannel('loglevel')); + const loggerClient = new LoggerChannelClient(mainProcessService.getChannel('logger')); - return new FollowerLogService(logLevelClient, logService); + // Extension development test CLI: forward everything to main side + const loggers: ILogService[] = []; + if (environmentService.isExtensionDevelopment && !!environmentService.extensionTestsLocationURI) { + loggers.push( + new ConsoleLogInMainService(loggerClient, this.environmentService.configuration.logLevel) + ); + } + + // Normal logger: spdylog and console + else { + loggers.push( + new ConsoleLogService(this.environmentService.configuration.logLevel), + new SpdLogService(`renderer${this.environmentService.configuration.windowId}`, environmentService.logsPath, this.environmentService.configuration.logLevel) + ); + } + + return new FollowerLogService(loggerClient, new MultiplexLogService(loggers)); } } diff --git a/src/vs/workbench/services/extensions/common/remoteConsoleUtil.ts b/src/vs/workbench/services/extensions/common/remoteConsoleUtil.ts new file mode 100644 index 00000000000..58ca081f88f --- /dev/null +++ b/src/vs/workbench/services/extensions/common/remoteConsoleUtil.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IRemoteConsoleLog, parse } from 'vs/base/common/console'; +import { ILogService } from 'vs/platform/log/common/log'; + +export function logRemoteEntry(logService: ILogService, entry: IRemoteConsoleLog): void { + const args = parse(entry).args; + const firstArg = args.shift(); + if (typeof firstArg !== 'string') { + return; + } + + if (!entry.severity) { + entry.severity = 'info'; + } + + switch (entry.severity) { + case 'log': + case 'info': + logService.info(firstArg, ...args); + break; + case 'warn': + logService.warn(firstArg, ...args); + break; + case 'error': + logService.error(firstArg, ...args); + break; + } +} diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index 5d5e96479a2..4ceaca310a8 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -14,7 +14,8 @@ import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import * as objects from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; -import { IRemoteConsoleLog, log, parse } from 'vs/base/common/console'; +import { IRemoteConsoleLog, log } from 'vs/base/common/console'; +import { logRemoteEntry } from 'vs/workbench/services/extensions/common/remoteConsoleUtil'; import { findFreePort, randomPort } from 'vs/base/node/ports'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net'; @@ -26,7 +27,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import product from 'vs/platform/product/common/product'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; +import { IWindowService } from 'vs/platform/windows/common/windows'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IInitData, UIKind } from 'vs/workbench/api/common/extHost.protocol'; import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; @@ -67,7 +68,6 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { private readonly _extensionHostLogsLocation: URI, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @INotificationService private readonly _notificationService: INotificationService, - @IWindowsService private readonly _windowsService: IWindowsService, @IWindowService private readonly _windowService: IWindowService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @@ -435,7 +435,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { // Log on main side if running tests from cli if (this._isExtensionDevTestFromCli) { - this._windowsService.log(entry.severity, parse(entry).args); + logRemoteEntry(this._logService, entry); } // Broadcast to other windows if we are in development mode diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 6c4add507aa..ad9ae1c822d 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -1494,10 +1494,6 @@ export class TestWindowsService implements IWindowsService { return Promise.resolve(this.windowCount); } - log(_severity: string, _args: string[]): Promise { - return Promise.resolve(); - } - showItemInFolder(_path: URI): Promise { return Promise.resolve(); } From 99a7194d4e77699bb13cb04c003234786c8cb5c2 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Tue, 17 Sep 2019 09:02:29 -0700 Subject: [PATCH 12/36] Remove svg reference --- src/vs/base/browser/ui/codiconLabel/codicon/codicon.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css index 689f29ba4c6..417afa0de5d 100644 --- a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css +++ b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css @@ -5,8 +5,7 @@ @font-face { font-family: "codicon"; - src: url("./codicon.ttf?e042d2dda15ef7b36b910e3edf539f26") format("truetype"), -url("./codicon.svg?e042d2dda15ef7b36b910e3edf539f26#codicon") format("svg"); + src: url("./codicon.ttf?e042d2dda15ef7b36b910e3edf539f26") format("truetype"); } .codicon[class*='codicon-'] { From 74bcfa3aa3e9c1800b6d40391781276a8c15ca70 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 17 Sep 2019 18:04:49 +0200 Subject: [PATCH 13/36] :up: distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 673662926a8..1fc6e8141ae 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.39.0", - "distro": "e892717dc1ae3cea3824697bc2c28d64f8476f93", + "distro": "e1ddf4bc0450761d824c22ed1b7d24888f51bac9", "author": { "name": "Microsoft Corporation" }, From 0fa53a6e66fb6d0c372dde3d1a6fcfab475c20dc Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Tue, 17 Sep 2019 09:05:42 -0700 Subject: [PATCH 14/36] Update refs to include codicon --- build/gulpfile.editor.js | 2 ++ build/gulpfile.hygiene.js | 1 + build/gulpfile.vscode.js | 1 + 3 files changed, 4 insertions(+) diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 7bbf246a8dc..44f38953adb 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -45,6 +45,7 @@ var editorResources = [ '!out-build/vs/base/browser/ui/splitview/**/*', '!out-build/vs/base/browser/ui/toolbar/**/*', '!out-build/vs/base/browser/ui/octiconLabel/**/*', + '!out-build/vs/base/browser/ui/codiconLabel/**/*', '!out-build/vs/workbench/**', '!**/test/**' ]; @@ -91,6 +92,7 @@ const extractEditorSrcTask = task.define('extract-editor-src', () => { ], redirects: { 'vs/base/browser/ui/octiconLabel/octiconLabel': 'vs/base/browser/ui/octiconLabel/octiconLabel.mock', + 'vs/base/browser/ui/codiconLabel/codiconLabel': 'vs/base/browser/ui/codiconLabel/codiconLabel.mock', }, shakeLevel: 2, // 0-Files, 1-InnerFile, 2-ClassMembers importIgnorePattern: /(^vs\/css!)|(promise-polyfill\/polyfill)/, diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js index 5c60d5db221..30c98f95d06 100644 --- a/build/gulpfile.hygiene.js +++ b/build/gulpfile.hygiene.js @@ -72,6 +72,7 @@ const indentationFilter = [ // except multiple specific folders '!**/octicons/**', + '!**/codicon/**', '!**/fixtures/**', '!**/lib/**', '!extensions/**/out/**', diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 3e9e2276bfe..9a63fcec936 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -68,6 +68,7 @@ const vscodeResources = [ 'out-build/vs/base/node/languagePacks.js', 'out-build/vs/base/node/{stdForkStart.js,terminateProcess.sh,cpuUsage.sh,ps.sh}', 'out-build/vs/base/browser/ui/octiconLabel/octicons/**', + 'out-build/vs/base/browser/ui/codiconLabel/codicon/**', 'out-build/vs/workbench/browser/media/*-theme.css', 'out-build/vs/workbench/contrib/debug/**/*.json', 'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt', From 945d678d361270dd3ebfc68ad5efffa245ed52ea Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 17 Sep 2019 18:06:50 +0200 Subject: [PATCH 15/36] uv_pipe_open error when starting code-insiders from WSL microsoft/vscode-remote-release#1420 --- resources/win32/bin/code.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/resources/win32/bin/code.sh b/resources/win32/bin/code.sh index f925851fb95..43aaa25a96e 100644 --- a/resources/win32/bin/code.sh +++ b/resources/win32/bin/code.sh @@ -27,15 +27,15 @@ if grep -qi Microsoft /proc/version; then WSL_EXT_ID="ms-vscode-remote.remote-wsl" if [ $WSL_BUILD -ge 41955 -a $WSL_BUILD -lt 41959 ]; then - # WSL2 in workaround for https://github.com/microsoft/WSL/issues/4337 + # WSL2 workaround for https://github.com/microsoft/WSL/issues/4337 CWD="$(pwd)" cd "$VSCODE_PATH" - cmd.exe /C ".\\bin\\$APP_NAME.cmd --locate-extension $WSL_EXT_ID >remote-wsl-loc.txt" - WSL_EXT_WLOC="$(cat ./remote-wsl-loc.txt)" - rm remote-wsl-loc.txt + cmd.exe /C ".\\bin\\$APP_NAME.cmd --locate-extension $WSL_EXT_ID >%TEMP%\\remote-wsl-loc.txt" + WSL_EXT_WLOC=$(cmd.exe /C type %TEMP%\\remote-wsl-loc.txt) cd "$CWD" else - WSL_EXT_WLOC=$(ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" --locate-extension $WSL_EXT_ID) + ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" --locate-extension $WSL_EXT_ID >/tmp/remote-wsl-loc.txt + WSL_EXT_WLOC=$(cat /tmp/remote-wsl-loc.txt) fi if [ -n "$WSL_EXT_WLOC" ]; then # replace \r\n with \n in WSL_EXT_WLOC From 66a25a0ec1a7c3e599b246eb9538bc3b84839967 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 17 Sep 2019 18:13:47 +0200 Subject: [PATCH 16/36] electron-main renames :lipstick: --- src/vs/code/electron-main/app.ts | 24 ++++++++--------- src/vs/code/electron-main/main.ts | 16 ++++++------ src/vs/code/electron-main/sharedProcess.ts | 4 +-- src/vs/code/electron-main/windows.ts | 26 +++++++++---------- .../platform/driver/electron-main/driver.ts | 14 +++++----- .../electron-main/historyMainService.ts | 5 ++-- .../issue/electron-main/issueMainService.ts | 14 +++++----- .../platform/menubar/electron-main/menubar.ts | 4 +-- .../electron-main/abstractUpdateService.ts | 4 +-- .../electron-main/updateService.darwin.ts | 4 +-- .../electron-main/updateService.linux.ts | 4 +-- .../electron-main/updateService.snap.ts | 10 +++---- .../electron-main/updateService.win32.ts | 4 +-- .../url/electron-main/electronUrlListener.ts | 6 ++--- .../windows/electron-main/windowsService.ts | 16 ++++++------ 15 files changed, 78 insertions(+), 77 deletions(-) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index f0eeddedf86..72b1fdc43fb 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -92,7 +92,7 @@ export class CodeApplication extends Disposable { @IInstantiationService private readonly instantiationService: IInstantiationService, @ILogService private readonly logService: ILogService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @ILifecycleMainService private readonly lifecycleService: ILifecycleMainService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IConfigurationService private readonly configurationService: IConfigurationService, @IStateService private readonly stateService: IStateService ) { @@ -109,7 +109,7 @@ export class CodeApplication extends Disposable { process.on('unhandledRejection', (reason: unknown) => onUnexpectedError(reason)); // Dispose on shutdown - this.lifecycleService.onWillShutdown(() => this.dispose()); + this.lifecycleMainService.onWillShutdown(() => this.dispose()); // Contextmenu via IPC support registerContextMenuListener(); @@ -255,7 +255,7 @@ export class CodeApplication extends Disposable { this.logService.trace('IPC#vscode:exit', code); this.dispose(); - this.lifecycleService.kill(code); + this.lifecycleMainService.kill(code); }); ipc.on('vscode:fetchShellEnv', async (event: Electron.IpcMainEvent) => { @@ -282,7 +282,7 @@ export class CodeApplication extends Disposable { // Some listeners after window opened (async () => { - await this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen); + await this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen); // After waking up from sleep (after window opened) powerMonitor.on('resume', () => { @@ -361,7 +361,7 @@ export class CodeApplication extends Disposable { // Spawn shared process after the first window has opened and 3s have passed const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv); const sharedProcessClient = sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main')); - this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen).then(() => { + this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => { this._register(new RunOnceScheduler(async () => { const userEnv = await getShellEnvironment(this.logService, this.environmentService); @@ -461,7 +461,7 @@ export class CodeApplication extends Disposable { const storageMainService = new StorageMainService(this.logService, this.environmentService); services.set(IStorageMainService, storageMainService); - this.lifecycleService.onWillShutdown(e => e.join(storageMainService.close())); + this.lifecycleMainService.onWillShutdown(e => e.join(storageMainService.close())); const backupMainService = new BackupMainService(this.environmentService, this.configurationService, this.logService); services.set(IBackupMainService, backupMainService); @@ -529,8 +529,8 @@ export class CodeApplication extends Disposable { private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise>): ICodeWindow[] { // Register more Main IPC services - const launchService = accessor.get(ILaunchMainService); - const launchChannel = new LaunchChannel(launchService); + const launchMainService = accessor.get(ILaunchMainService); + const launchChannel = new LaunchChannel(launchMainService); this.mainIpcServer.registerChannel('launch', launchChannel); // Register more Electron IPC services @@ -546,8 +546,8 @@ export class CodeApplication extends Disposable { const electronChannel = new ElectronChannel(electronService); electronIpcServer.registerChannel('electron', electronChannel); - const workspacesService = accessor.get(IWorkspacesMainService); - const workspacesChannel = new WorkspacesChannel(workspacesService); + const workspacesMainService = accessor.get(IWorkspacesMainService); + const workspacesChannel = new WorkspacesChannel(workspacesMainService); electronIpcServer.registerChannel('workspaces', workspacesChannel); const windowsService = accessor.get(IWindowsService); @@ -575,7 +575,7 @@ export class CodeApplication extends Disposable { electronIpcServer.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ExtensionHostDebugBroadcastChannel()); // Signal phase: ready (services set) - this.lifecycleService.phase = LifecycleMainPhase.Ready; + this.lifecycleMainService.phase = LifecycleMainPhase.Ready; // Propagate to clients const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); @@ -685,7 +685,7 @@ export class CodeApplication extends Disposable { private afterWindowOpen(): void { // Signal phase: after window open - this.lifecycleService.phase = LifecycleMainPhase.AfterWindowOpen; + this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen; // Remote Authorities this.handleRemoteAuthorities(); diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index f2d5cf7b88d..534cfddbe4c 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -116,13 +116,13 @@ class CodeMain { await instantiationService.invokeFunction(async accessor => { const environmentService = accessor.get(IEnvironmentService); const logService = accessor.get(ILogService); - const lifecycleService = accessor.get(ILifecycleMainService); + const lifecycleMainService = accessor.get(ILifecycleMainService); const configurationService = accessor.get(IConfigurationService); - const mainIpcServer = await this.doStartup(logService, environmentService, lifecycleService, instantiationService, true); + const mainIpcServer = await this.doStartup(logService, environmentService, lifecycleMainService, instantiationService, true); bufferLogService.logger = new SpdLogService('main', environmentService.logsPath, bufferLogService.getLevel()); - once(lifecycleService.onWillShutdown)(() => (configurationService as ConfigurationService).dispose()); + once(lifecycleMainService.onWillShutdown)(() => (configurationService as ConfigurationService).dispose()); return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup(); }); @@ -189,7 +189,7 @@ class CodeMain { return instanceEnvironment; } - private async doStartup(logService: ILogService, environmentService: IEnvironmentService, lifecycleService: ILifecycleMainService, instantiationService: IInstantiationService, retry: boolean): Promise { + private async doStartup(logService: ILogService, environmentService: IEnvironmentService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, retry: boolean): Promise { // Try to setup a server for running. If that succeeds it means // we are the first instance to startup. Otherwise it is likely @@ -197,7 +197,7 @@ class CodeMain { let server: Server; try { server = await serve(environmentService.mainIPCHandle); - once(lifecycleService.onWillShutdown)(() => server.dispose()); + once(lifecycleMainService.onWillShutdown)(() => server.dispose()); } catch (error) { // Handle unexpected errors (the only expected error is EADDRINUSE that @@ -245,7 +245,7 @@ class CodeMain { throw error; } - return this.doStartup(logService, environmentService, lifecycleService, instantiationService, false); + return this.doStartup(logService, environmentService, lifecycleMainService, instantiationService, false); } // Tests from CLI require to be the only instance currently @@ -374,7 +374,7 @@ class CodeMain { private quit(accessor: ServicesAccessor, reason?: ExpectedError | Error): void { const logService = accessor.get(ILogService); - const lifecycleService = accessor.get(ILifecycleMainService); + const lifecycleMainService = accessor.get(ILifecycleMainService); let exitCode = 0; @@ -394,7 +394,7 @@ class CodeMain { } } - lifecycleService.kill(exitCode); + lifecycleMainService.kill(exitCode); } } diff --git a/src/vs/code/electron-main/sharedProcess.ts b/src/vs/code/electron-main/sharedProcess.ts index 29ef3a38ddc..b59ecadb78f 100644 --- a/src/vs/code/electron-main/sharedProcess.ts +++ b/src/vs/code/electron-main/sharedProcess.ts @@ -24,7 +24,7 @@ export class SharedProcess implements ISharedProcess { private readonly machineId: string, private userEnv: NodeJS.ProcessEnv, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @ILifecycleMainService private readonly lifecycleService: ILifecycleMainService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @ILogService private readonly logService: ILogService, @IThemeMainService private readonly themeMainService: IThemeMainService ) { } @@ -68,7 +68,7 @@ export class SharedProcess implements ISharedProcess { const disposables = new DisposableStore(); - this.lifecycleService.onWillShutdown(() => { + this.lifecycleMainService.onWillShutdown(() => { disposables.dispose(); // Shut the shared process down when we are quitting diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index d5ba7ef2ae2..3bb019cd961 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -188,7 +188,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { @ILogService private readonly logService: ILogService, @IStateService private readonly stateService: IStateService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @ILifecycleMainService private readonly lifecycleService: ILifecycleMainService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IBackupMainService private readonly backupMainService: IBackupMainService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IConfigurationService private readonly configurationService: IConfigurationService, @@ -207,8 +207,8 @@ export class WindowsManager extends Disposable implements IWindowsMainService { this.dialogs = new Dialogs(stateService, this); this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, this); - this.lifecycleService.when(LifecycleMainPhase.Ready).then(() => this.registerListeners()); - this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.installWindowsMutex()); + this.lifecycleMainService.when(LifecycleMainPhase.Ready).then(() => this.registerListeners()); + this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.installWindowsMutex()); } private installWindowsMutex(): void { @@ -217,7 +217,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { try { const WindowsMutex = (require.__$__nodeRequire('windows-mutex') as typeof import('windows-mutex')).Mutex; const mutex = new WindowsMutex(win32MutexName); - once(this.lifecycleService.onWillShutdown)(() => mutex.release()); + once(this.lifecycleMainService.onWillShutdown)(() => mutex.release()); } catch (e) { this.logService.error(e); } @@ -251,8 +251,8 @@ export class WindowsManager extends Disposable implements IWindowsMainService { } // Handle various lifecycle events around windows - this.lifecycleService.onBeforeWindowClose(window => this.onBeforeWindowClose(window)); - this.lifecycleService.onBeforeShutdown(() => this.onBeforeShutdown()); + this.lifecycleMainService.onBeforeWindowClose(window => this.onBeforeWindowClose(window)); + this.lifecycleMainService.onBeforeShutdown(() => this.onBeforeShutdown()); this.onWindowsCountChanged(e => { if (e.newCount - e.oldCount > 0) { // clear last closed window state when a new window opens. this helps on macOS where @@ -340,7 +340,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { // See note on #onBeforeShutdown() for details how these events are flowing private onBeforeWindowClose(win: ICodeWindow): void { - if (this.lifecycleService.quitRequested) { + if (this.lifecycleMainService.quitRequested) { return; // during quit, many windows close in parallel so let it be handled in the before-quit handler } @@ -985,7 +985,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { private getRestoreWindowsSetting(): RestoreWindowsSetting { let restoreWindows: RestoreWindowsSetting; - if (this.lifecycleService.wasRestarted) { + if (this.lifecycleMainService.wasRestarted) { restoreWindows = 'all'; // always reopen all windows when an update was applied } else { const windowConfig = this.configurationService.getValue('window'); @@ -1328,7 +1328,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { // Window state is from a previous session: only allow fullscreen when we got updated or user wants to restore else { - allowFullscreen = this.lifecycleService.wasRestarted || (windowConfig && windowConfig.restoreFullscreen); + allowFullscreen = this.lifecycleMainService.wasRestarted || (windowConfig && windowConfig.restoreFullscreen); } if (state.mode === WindowMode.Fullscreen && !allowFullscreen) { @@ -1364,7 +1364,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { window.win.on('closed', () => this.onWindowClosed(window!)); // Lifecycle - (this.lifecycleService as LifecycleMainService).registerWindow(window); + (this.lifecycleMainService as LifecycleMainService).registerWindow(window); } // Existing window @@ -1387,7 +1387,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { // first and only load the new configuration if that was // not vetoed if (window.isReady) { - this.lifecycleService.unload(window, UnloadReason.LOAD).then(veto => { + this.lifecycleMainService.unload(window, UnloadReason.LOAD).then(veto => { if (!veto) { this.doOpenInBrowserWindow(window!, configuration, options); } @@ -1554,7 +1554,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { async reload(win: ICodeWindow, cli?: ParsedArgs): Promise { // Only reload when the window has not vetoed this - const veto = await this.lifecycleService.unload(win, UnloadReason.RELOAD); + const veto = await this.lifecycleMainService.unload(win, UnloadReason.RELOAD); if (!veto) { win.reload(undefined, cli); } @@ -1866,7 +1866,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { // Otherwise: normal quit else { setTimeout(() => { - this.lifecycleService.quit(); + this.lifecycleMainService.quit(); }, 10 /* delay to unwind callback stack (IPC) */); } } diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index 49404125de3..8966f02836c 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -34,7 +34,7 @@ export class Driver implements IDriver, IWindowDriverRegistry { constructor( private windowServer: IPCServer, private options: IDriverOptions, - @IWindowsMainService private readonly windowsService: IWindowsMainService + @IWindowsMainService private readonly windowsMainService: IWindowsMainService ) { } async registerWindowDriver(windowId: number): Promise { @@ -49,7 +49,7 @@ export class Driver implements IDriver, IWindowDriverRegistry { } async getWindowIds(): Promise { - return this.windowsService.getWindows() + return this.windowsMainService.getWindows() .map(w => w.id) .filter(id => this.registeredWindowIds.has(id) && !this.reloadingWindowIds.has(id)); } @@ -57,7 +57,7 @@ export class Driver implements IDriver, IWindowDriverRegistry { async capturePage(windowId: number): Promise { await this.whenUnfrozen(windowId); - const window = this.windowsService.getWindowById(windowId); + const window = this.windowsMainService.getWindowById(windowId); if (!window) { throw new Error('Invalid window'); } @@ -69,16 +69,16 @@ export class Driver implements IDriver, IWindowDriverRegistry { async reloadWindow(windowId: number): Promise { await this.whenUnfrozen(windowId); - const window = this.windowsService.getWindowById(windowId); + const window = this.windowsMainService.getWindowById(windowId); if (!window) { throw new Error('Invalid window'); } this.reloadingWindowIds.add(windowId); - this.windowsService.reload(window); + this.windowsMainService.reload(window); } async exitApplication(): Promise { - return this.windowsService.quit(); + return this.windowsMainService.quit(); } async dispatchKeybinding(windowId: number, keybinding: string): Promise { @@ -96,7 +96,7 @@ export class Driver implements IDriver, IWindowDriverRegistry { throw new Error('ScanCodeBindings not supported'); } - const window = this.windowsService.getWindowById(windowId); + const window = this.windowsMainService.getWindowById(windowId); if (!window) { throw new Error('Invalid window'); } diff --git a/src/vs/platform/history/electron-main/historyMainService.ts b/src/vs/platform/history/electron-main/historyMainService.ts index b67c268b997..a76d56fcc21 100644 --- a/src/vs/platform/history/electron-main/historyMainService.ts +++ b/src/vs/platform/history/electron-main/historyMainService.ts @@ -29,6 +29,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' export const IHistoryMainService = createDecorator('historyMainService'); export interface IHistoryMainService { + _serviceBrand: undefined; onRecentlyOpenedChange: CommonEvent; @@ -68,11 +69,11 @@ export class HistoryMainService implements IHistoryMainService { @ILogService private readonly logService: ILogService, @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @ILifecycleMainService lifecycleService: ILifecycleMainService + @ILifecycleMainService lifecycleMainService: ILifecycleMainService ) { this.macOSRecentDocumentsUpdater = new ThrottledDelayer(800); - lifecycleService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.handleWindowsJumpList()); + lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.handleWindowsJumpList()); } private handleWindowsJumpList(): void { diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index 2278e311a04..55d332040c5 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -31,7 +31,7 @@ export class IssueMainService implements IIssueService { private machineId: string, private userEnv: IProcessEnvironment, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @ILaunchMainService private readonly launchService: ILaunchMainService, + @ILaunchMainService private readonly launchMainService: ILaunchMainService, @ILogService private readonly logService: ILogService, @IDiagnosticsService private readonly diagnosticsService: IDiagnosticsService, @IWindowsService private readonly windowsService: IWindowsService @@ -41,7 +41,7 @@ export class IssueMainService implements IIssueService { private registerListeners(): void { ipcMain.on('vscode:issueSystemInfoRequest', async (event: Electron.IpcMainEvent) => { - Promise.all([this.launchService.getMainProcessInfo(), this.launchService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]) + Promise.all([this.launchMainService.getMainProcessInfo(), this.launchMainService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]) .then(result => { const [info, remoteData] = result; this.diagnosticsService.getSystemInfo(info, remoteData).then(msg => { @@ -54,9 +54,9 @@ export class IssueMainService implements IIssueService { const processes = []; try { - const mainPid = await this.launchService.getMainProcessId(); + const mainPid = await this.launchMainService.getMainProcessId(); processes.push({ name: localize('local', "Local"), rootProcess: await listProcesses(mainPid) }); - (await this.launchService.getRemoteDiagnostics({ includeProcesses: true })) + (await this.launchMainService.getRemoteDiagnostics({ includeProcesses: true })) .forEach(data => { if (isRemoteDiagnosticError(data)) { processes.push({ @@ -157,7 +157,7 @@ export class IssueMainService implements IIssueService { }); ipcMain.on('windowsInfoRequest', (event: Electron.IpcMainEvent) => { - this.launchService.getMainProcessInfo().then(info => { + this.launchMainService.getMainProcessInfo().then(info => { event.sender.send('vscode:windowsInfoResponse', info.windows); }); }); @@ -268,7 +268,7 @@ export class IssueMainService implements IIssueService { } public async getSystemStatus(): Promise { - return Promise.all([this.launchService.getMainProcessInfo(), this.launchService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]) + return Promise.all([this.launchMainService.getMainProcessInfo(), this.launchMainService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]) .then(result => { const [info, remoteData] = result; return this.diagnosticsService.getDiagnostics(info, remoteData); @@ -345,7 +345,7 @@ export class IssueMainService implements IIssueService { private getPerformanceInfo(): Promise { return new Promise(async (resolve, reject) => { - Promise.all([this.launchService.getMainProcessInfo(), this.launchService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true })]) + Promise.all([this.launchMainService.getMainProcessInfo(), this.launchMainService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true })]) .then(result => { const [info, remoteData] = result; this.diagnosticsService.getPerformanceInfo(info, remoteData) diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index 25139ec68e5..70caae25b47 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -68,7 +68,7 @@ export class Menubar { @ITelemetryService private readonly telemetryService: ITelemetryService, @IHistoryMainService private readonly historyMainService: IHistoryMainService, @IStateService private readonly stateService: IStateService, - @ILifecycleMainService private readonly lifecycleService: ILifecycleMainService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @ILogService private readonly logService: ILogService ) { this.menuUpdater = new RunOnceScheduler(() => this.doUpdateMenu(), 0); @@ -160,7 +160,7 @@ export class Menubar { private registerListeners(): void { // Keep flag when app quits - this.lifecycleService.onWillShutdown(() => this.willShutdown = true); + this.lifecycleMainService.onWillShutdown(() => this.willShutdown = true); // // Listen to some events from window service to update menu this.windowsMainService.onWindowsCountChanged(e => this.onWindowsCountChanged(e)); diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index 8847db647a7..276c468a51a 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -44,7 +44,7 @@ export abstract class AbstractUpdateService implements IUpdateService { } constructor( - @ILifecycleMainService private readonly lifecycleService: ILifecycleMainService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IConfigurationService protected configurationService: IConfigurationService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IRequestService protected requestService: IRequestService, @@ -152,7 +152,7 @@ export abstract class AbstractUpdateService implements IUpdateService { this.logService.trace('update#quitAndInstall(): before lifecycle quit()'); - this.lifecycleService.quit(true /* from update */).then(vetod => { + this.lifecycleMainService.quit(true /* from update */).then(vetod => { this.logService.trace(`update#quitAndInstall(): after lifecycle quit() with veto: ${vetod}`); if (vetod) { return; diff --git a/src/vs/platform/update/electron-main/updateService.darwin.ts b/src/vs/platform/update/electron-main/updateService.darwin.ts index cf016a44280..cd73a5cd95a 100644 --- a/src/vs/platform/update/electron-main/updateService.darwin.ts +++ b/src/vs/platform/update/electron-main/updateService.darwin.ts @@ -28,14 +28,14 @@ export class DarwinUpdateService extends AbstractUpdateService { @memoize private get onRawUpdateDownloaded(): Event { return Event.fromNodeEventEmitter(electron.autoUpdater, 'update-downloaded', (_, releaseNotes, version, date) => ({ releaseNotes, version, productVersion: version, date })); } constructor( - @ILifecycleMainService lifecycleService: ILifecycleMainService, + @ILifecycleMainService lifecycleMainService: ILifecycleMainService, @IConfigurationService configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IEnvironmentService environmentService: IEnvironmentService, @IRequestService requestService: IRequestService, @ILogService logService: ILogService ) { - super(lifecycleService, configurationService, environmentService, requestService, logService); + super(lifecycleMainService, configurationService, environmentService, requestService, logService); this.onRawError(this.onError, this, this.disposables); this.onRawUpdateAvailable(this.onUpdateAvailable, this, this.disposables); this.onRawUpdateDownloaded(this.onUpdateDownloaded, this, this.disposables); diff --git a/src/vs/platform/update/electron-main/updateService.linux.ts b/src/vs/platform/update/electron-main/updateService.linux.ts index 7adb7e00bf8..8b59c871d09 100644 --- a/src/vs/platform/update/electron-main/updateService.linux.ts +++ b/src/vs/platform/update/electron-main/updateService.linux.ts @@ -20,14 +20,14 @@ export class LinuxUpdateService extends AbstractUpdateService { _serviceBrand: undefined; constructor( - @ILifecycleMainService lifecycleService: ILifecycleMainService, + @ILifecycleMainService lifecycleMainService: ILifecycleMainService, @IConfigurationService configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IEnvironmentService environmentService: IEnvironmentService, @IRequestService requestService: IRequestService, @ILogService logService: ILogService ) { - super(lifecycleService, configurationService, environmentService, requestService, logService); + super(lifecycleMainService, configurationService, environmentService, requestService, logService); } protected buildUpdateFeedUrl(quality: string): string { diff --git a/src/vs/platform/update/electron-main/updateService.snap.ts b/src/vs/platform/update/electron-main/updateService.snap.ts index 36db202ff33..75d7c1e187e 100644 --- a/src/vs/platform/update/electron-main/updateService.snap.ts +++ b/src/vs/platform/update/electron-main/updateService.snap.ts @@ -35,7 +35,7 @@ abstract class AbstractUpdateService2 implements IUpdateService { } constructor( - @ILifecycleMainService private readonly lifecycleService: ILifecycleMainService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IEnvironmentService environmentService: IEnvironmentService, @ILogService protected logService: ILogService, ) { @@ -106,7 +106,7 @@ abstract class AbstractUpdateService2 implements IUpdateService { this.logService.trace('update#quitAndInstall(): before lifecycle quit()'); - this.lifecycleService.quit(true /* from update */).then(vetod => { + this.lifecycleMainService.quit(true /* from update */).then(vetod => { this.logService.trace(`update#quitAndInstall(): after lifecycle quit() with veto: ${vetod}`); if (vetod) { return; @@ -139,12 +139,12 @@ export class SnapUpdateService extends AbstractUpdateService2 { constructor( private snap: string, private snapRevision: string, - @ILifecycleMainService lifecycleService: ILifecycleMainService, + @ILifecycleMainService lifecycleMainService: ILifecycleMainService, @IEnvironmentService environmentService: IEnvironmentService, @ILogService logService: ILogService, @ITelemetryService private readonly telemetryService: ITelemetryService ) { - super(lifecycleService, environmentService, logService); + super(lifecycleMainService, environmentService, logService); const watcher = watch(path.dirname(this.snap)); const onChange = Event.fromNodeEventEmitter(watcher, 'change', (_, fileName: string) => fileName); @@ -152,7 +152,7 @@ export class SnapUpdateService extends AbstractUpdateService2 { const onDebouncedCurrentChange = Event.debounce(onCurrentChange, (_, e) => e, 2000); const listener = onDebouncedCurrentChange(this.checkForUpdates, this); - lifecycleService.onWillShutdown(() => { + lifecycleMainService.onWillShutdown(() => { listener.dispose(); watcher.close(); }); diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index 7395de466da..c053fda3904 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -60,7 +60,7 @@ export class Win32UpdateService extends AbstractUpdateService { } constructor( - @ILifecycleMainService lifecycleService: ILifecycleMainService, + @ILifecycleMainService lifecycleMainService: ILifecycleMainService, @IConfigurationService configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IEnvironmentService environmentService: IEnvironmentService, @@ -68,7 +68,7 @@ export class Win32UpdateService extends AbstractUpdateService { @ILogService logService: ILogService, @IFileService private readonly fileService: IFileService ) { - super(lifecycleService, configurationService, environmentService, requestService, logService); + super(lifecycleMainService, configurationService, environmentService, requestService, logService); if (getUpdateType() === UpdateType.Setup) { /* __GDPR__ diff --git a/src/vs/platform/url/electron-main/electronUrlListener.ts b/src/vs/platform/url/electron-main/electronUrlListener.ts index 6edd504f66c..f8db65d4e1e 100644 --- a/src/vs/platform/url/electron-main/electronUrlListener.ts +++ b/src/vs/platform/url/electron-main/electronUrlListener.ts @@ -28,7 +28,7 @@ export class ElectronURLListener { constructor( initial: string | string[], @IURLService private readonly urlService: IURLService, - @IWindowsMainService windowsService: IWindowsMainService + @IWindowsMainService windowsMainService: IWindowsMainService ) { const globalBuffer = ((global).getOpenUrls() || []) as string[]; const rawBuffer = [ @@ -58,14 +58,14 @@ export class ElectronURLListener { const onOpenUrl = Event.filter(Event.map(onOpenElectronUrl, uriFromRawUrl), uri => !!uri); onOpenUrl(this.urlService.open, this.urlService, this.disposables); - const isWindowReady = windowsService.getWindows() + const isWindowReady = windowsMainService.getWindows() .filter(w => w.isReady) .length > 0; if (isWindowReady) { flush(); } else { - Event.once(windowsService.onWindowReady)(flush); + Event.once(windowsMainService.onWindowReady)(flush); } } diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index 740891b52f9..4d219fc827b 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -38,15 +38,15 @@ export class WindowsService extends Disposable implements IWindowsService, IURLH Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-focus', (_, w: Electron.BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)) ); - readonly onRecentlyOpenedChange: Event = this.historyService.onRecentlyOpenedChange; + readonly onRecentlyOpenedChange: Event = this.historyMainService.onRecentlyOpenedChange; constructor( private sharedProcess: ISharedProcess, @IWindowsMainService private readonly windowsMainService: IWindowsMainService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IURLService urlService: IURLService, - @ILifecycleMainService private readonly lifecycleService: ILifecycleMainService, - @IHistoryMainService private readonly historyService: IHistoryMainService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, + @IHistoryMainService private readonly historyMainService: IHistoryMainService, @ILogService private readonly logService: ILogService ) { super(); @@ -157,25 +157,25 @@ export class WindowsService extends Disposable implements IWindowsService, IURLH async addRecentlyOpened(recents: IRecent[]): Promise { this.logService.trace('windowsService#addRecentlyOpened'); - this.historyService.addRecentlyOpened(recents); + this.historyMainService.addRecentlyOpened(recents); } async removeFromRecentlyOpened(paths: URI[]): Promise { this.logService.trace('windowsService#removeFromRecentlyOpened'); - this.historyService.removeFromRecentlyOpened(paths); + this.historyMainService.removeFromRecentlyOpened(paths); } async clearRecentlyOpened(): Promise { this.logService.trace('windowsService#clearRecentlyOpened'); - this.historyService.clearRecentlyOpened(); + this.historyMainService.clearRecentlyOpened(); } async getRecentlyOpened(windowId: number): Promise { this.logService.trace('windowsService#getRecentlyOpened', windowId); - return this.withWindow(windowId, codeWindow => this.historyService.getRecentlyOpened(codeWindow.config.workspace, codeWindow.config.folderUri, codeWindow.config.filesToOpenOrCreate), () => this.historyService.getRecentlyOpened())!; + return this.withWindow(windowId, codeWindow => this.historyMainService.getRecentlyOpened(codeWindow.config.workspace, codeWindow.config.folderUri, codeWindow.config.filesToOpenOrCreate), () => this.historyMainService.getRecentlyOpened())!; } async newWindowTab(): Promise { @@ -370,7 +370,7 @@ export class WindowsService extends Disposable implements IWindowsService, IURLH async relaunch(options: { addArgs?: string[], removeArgs?: string[] }): Promise { this.logService.trace('windowsService#relaunch'); - this.lifecycleService.relaunch(options); + this.lifecycleMainService.relaunch(options); } async whenSharedProcessReady(): Promise { From a62c6ee148f724e64b280359040c6c274bec150d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 17 Sep 2019 18:34:57 +0200 Subject: [PATCH 17/36] debt - add openExtensionDevelopmentHostWindow into IExtensionHostDebugService --- .../debug/common/extensionHostDebug.ts | 16 +++--- .../debug/common/extensionHostDebugIpc.ts | 10 +++- .../workbench/browser/web.simpleservices.ts | 43 --------------- .../contrib/debug/browser/debugSession.ts | 7 +-- .../browser/extensionHostDebugService.ts | 52 ++++++++++++++++++- .../contrib/debug/browser/rawDebugSession.ts | 6 +-- .../extensionHostDebugService.ts | 13 ++++- 7 files changed, 87 insertions(+), 60 deletions(-) diff --git a/src/vs/platform/debug/common/extensionHostDebug.ts b/src/vs/platform/debug/common/extensionHostDebug.ts index 3386e62b25d..3ccb12051b7 100644 --- a/src/vs/platform/debug/common/extensionHostDebug.ts +++ b/src/vs/platform/debug/common/extensionHostDebug.ts @@ -6,6 +6,8 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; import { IRemoteConsoleLog } from 'vs/base/common/console'; +import { ParsedArgs } from 'vs/platform/environment/common/environment'; +import { IProcessEnvironment } from 'vs/base/common/platform'; export const IExtensionHostDebugService = createDecorator('extensionHostDebugService'); @@ -37,17 +39,19 @@ export interface IExtensionHostDebugService { _serviceBrand: undefined; reload(sessionId: string): void; - onReload: Event; + readonly onReload: Event; close(sessionId: string): void; - onClose: Event; + readonly onClose: Event; attachSession(sessionId: string, port: number, subId?: string): void; - onAttachSession: Event; + readonly onAttachSession: Event; logToSession(sessionId: string, log: IRemoteConsoleLog): void; - onLogToSession: Event; + readonly onLogToSession: Event; terminateSession(sessionId: string, subId?: string): void; - onTerminateSession: Event; -} \ No newline at end of file + readonly onTerminateSession: Event; + + openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise; +} diff --git a/src/vs/platform/debug/common/extensionHostDebugIpc.ts b/src/vs/platform/debug/common/extensionHostDebugIpc.ts index 3511be94950..555d90bfde0 100644 --- a/src/vs/platform/debug/common/extensionHostDebugIpc.ts +++ b/src/vs/platform/debug/common/extensionHostDebugIpc.ts @@ -8,6 +8,8 @@ import { IReloadSessionEvent, ICloseSessionEvent, IAttachSessionEvent, ILogToSes import { Event, Emitter } from 'vs/base/common/event'; import { IRemoteConsoleLog } from 'vs/base/common/console'; import { Disposable } from 'vs/base/common/lifecycle'; +import { ParsedArgs } from 'vs/platform/environment/common/environment'; +import { IProcessEnvironment } from 'vs/base/common/platform'; export class ExtensionHostDebugBroadcastChannel implements IServerChannel { @@ -99,4 +101,10 @@ export class ExtensionHostDebugChannelClient extends Disposable implements IExte get onTerminateSession(): Event { return this.channel.listen('terminate'); } -} \ No newline at end of file + + openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { + // TODO@Isidor + //return this.channel.call('openExtensionDevelopmentHostWindow', [args, env]); + return Promise.resolve(); + } +} diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts index 87a1fee80e8..fdc94ca35b0 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -454,49 +454,6 @@ export class SimpleWindowsService implements IWindowsService { } openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { - - // we pass the "ParsedArgs" as query parameters of the URL - - let newAddress = `${document.location.origin}/?`; - let gotFolder = false; - - const addQueryParameter = (key: string, value: string) => { - const lastChar = newAddress.charAt(newAddress.length - 1); - if (lastChar !== '?' && lastChar !== '&') { - newAddress += '&'; - } - newAddress += `${key}=${encodeURIComponent(value)}`; - }; - - const f = args['folder-uri']; - if (f) { - const u = URI.parse(f[0]); - gotFolder = true; - addQueryParameter('folder', u.path); - } - if (!gotFolder) { - // request empty window - addQueryParameter('ew', 'true'); - } - - const ep = args['extensionDevelopmentPath']; - if (ep) { - let u = ep[0]; - addQueryParameter('edp', u); - } - - const di = args['debugId']; - if (di) { - addQueryParameter('di', di); - } - - const ibe = args['inspect-brk-extensions']; - if (ibe) { - addQueryParameter('ibe', ibe); - } - - window.open(newAddress); - return Promise.resolve(); } diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 92c18c46873..0f53246d206 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -22,7 +22,8 @@ import { IWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspac import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; import { generateUuid } from 'vs/base/common/uuid'; -import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; +import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { normalizeDriveLetter } from 'vs/base/common/labels'; import { Range } from 'vs/editor/common/core/range'; @@ -74,7 +75,7 @@ export class DebugSession implements IDebugSession { @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @INotificationService private readonly notificationService: INotificationService, @IProductService private readonly productService: IProductService, - @IWindowsService private readonly windowsService: IWindowsService, + @IExtensionHostDebugService private readonly extensionHostDebugService: IExtensionHostDebugService, @IOpenerService private readonly openerService: IOpenerService ) { this.id = generateUuid(); @@ -186,7 +187,7 @@ export class DebugSession implements IDebugSession { return dbgr.createDebugAdapter(this).then(debugAdapter => { - this.raw = new RawDebugSession(debugAdapter, dbgr, this.telemetryService, customTelemetryService, this.windowsService, this.openerService); + this.raw = new RawDebugSession(debugAdapter, dbgr, this.telemetryService, customTelemetryService, this.extensionHostDebugService, this.openerService); return this.raw.start().then(() => { diff --git a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts index 49b8815ca93..af19c1bbe2e 100644 --- a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts @@ -5,7 +5,7 @@ import { ExtensionHostDebugChannelClient, ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { IDebugHelperService } from 'vs/workbench/contrib/debug/common/debug'; @@ -13,8 +13,10 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; +import { IProcessEnvironment } from 'vs/base/common/platform'; +import { URI } from 'vs/base/common/uri'; -class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient { +class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient implements IExtensionHostDebugService { constructor( @IRemoteAgentService remoteAgentService: IRemoteAgentService, @@ -45,6 +47,52 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient { } })); } + + openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { + // we pass the "ParsedArgs" as query parameters of the URL + + let newAddress = `${document.location.origin}/?`; + let gotFolder = false; + + const addQueryParameter = (key: string, value: string) => { + const lastChar = newAddress.charAt(newAddress.length - 1); + if (lastChar !== '?' && lastChar !== '&') { + newAddress += '&'; + } + newAddress += `${key}=${encodeURIComponent(value)}`; + }; + + const f = args['folder-uri']; + if (f) { + const u = URI.parse(f[0]); + gotFolder = true; + addQueryParameter('folder', u.path); + } + if (!gotFolder) { + // request empty window + addQueryParameter('ew', 'true'); + } + + const ep = args['extensionDevelopmentPath']; + if (ep) { + let u = ep[0]; + addQueryParameter('edp', u); + } + + const di = args['debugId']; + if (di) { + addQueryParameter('di', di); + } + + const ibe = args['inspect-brk-extensions']; + if (ibe) { + addQueryParameter('ibe', ibe); + } + + window.open(newAddress); + + return Promise.resolve(); + } } registerSingleton(IExtensionHostDebugService, BrowserExtensionHostDebugService); diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index 53a10ed04c5..45ffb5776a8 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -13,7 +13,7 @@ import { formatPII, isUri } from 'vs/workbench/contrib/debug/common/debugUtils'; import { IDebugAdapter, IConfig, AdapterEndEvent, IDebugger } from 'vs/workbench/contrib/debug/common/debug'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { URI } from 'vs/base/common/uri'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { env as processEnv } from 'vs/base/common/process'; @@ -79,7 +79,7 @@ export class RawDebugSession implements IDisposable { dbgr: IDebugger, private readonly telemetryService: ITelemetryService, public readonly customTelemetryService: ITelemetryService | undefined, - private readonly windowsService: IWindowsService, + private readonly extensionHostDebugService: IExtensionHostDebugService, private readonly openerService: IOpenerService ) { @@ -632,7 +632,7 @@ export class RawDebugSession implements IDisposable { Object.keys(env).filter(k => env[k] === null).forEach(key => delete env[key]); } - return this.windowsService.openExtensionDevelopmentHostWindow(args, env); + return this.extensionHostDebugService.openExtensionDevelopmentHostWindow(args, env); } private send(command: string, args: any, token?: CancellationToken, timeout?: number): Promise { diff --git a/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts index e292a9ecd31..dad63fd4fb0 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts @@ -7,13 +7,22 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { ExtensionHostDebugChannelClient, ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; +import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { IProcessEnvironment } from 'vs/base/common/platform'; +import { ParsedArgs } from 'vs/platform/environment/common/environment'; export class ExtensionHostDebugService extends ExtensionHostDebugChannelClient { constructor( - @IMainProcessService readonly windowService: IMainProcessService, + @IMainProcessService readonly mainProcessService: IMainProcessService, + @IWindowsService private readonly windowsService: IWindowsService ) { - super(windowService.getChannel(ExtensionHostDebugBroadcastChannel.ChannelName)); + super(mainProcessService.getChannel(ExtensionHostDebugBroadcastChannel.ChannelName)); + } + + openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { + // TODO@Isidor use debug IPC channel + return this.windowsService.openExtensionDevelopmentHostWindow(args, env); } } From 86baf839044cd6a886716eb0924bee6d23592f50 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Tue, 17 Sep 2019 10:52:21 -0700 Subject: [PATCH 18/36] Update distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1fc6e8141ae..a03e9dd1f40 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.39.0", - "distro": "e1ddf4bc0450761d824c22ed1b7d24888f51bac9", + "distro": "8ec29a9878d2ca77f73efc02f382f177d200fbb9", "author": { "name": "Microsoft Corporation" }, From 5fc4fe2266dbb57a83fed24d1f9ff2552ee41202 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 17 Sep 2019 15:25:27 -0700 Subject: [PATCH 19/36] Add resolveExternalUri embedder contribution point --- .../editor/browser/services/openerService.ts | 14 ++++++++-- src/vs/platform/opener/common/opener.ts | 18 ++++++++++--- .../contrib/url/common/externalUriResolver.ts | 26 +++++++++++++++++++ .../contrib/url/common/url.contribution.ts | 7 ++++- src/vs/workbench/workbench.web.api.ts | 5 ++++ 5 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 src/vs/workbench/contrib/url/common/externalUriResolver.ts diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index c42f82fdb26..8a122c18fc5 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -13,7 +13,7 @@ import { equalsIgnoreCase } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; -import { IOpener, IOpenerService, IValidator } from 'vs/platform/opener/common/opener'; +import { IOpener, IOpenerService, IValidator, IExternalUriResolver } from 'vs/platform/opener/common/opener'; export class OpenerService extends Disposable implements IOpenerService { @@ -21,6 +21,7 @@ export class OpenerService extends Disposable implements IOpenerService { private readonly _openers = new LinkedList(); private readonly _validators = new LinkedList(); + private readonly _resolvers = new LinkedList(); constructor( @ICodeEditorService private readonly _editorService: ICodeEditorService, @@ -39,6 +40,11 @@ export class OpenerService extends Disposable implements IOpenerService { return { dispose: remove }; } + registerExternalUriResolver(resolver: IExternalUriResolver): IDisposable { + const remove = this._resolvers.push(resolver); + return { dispose: remove }; + } + async open(resource: URI, options?: { openToSide?: boolean, openExternal?: boolean }): Promise { // no scheme ?!? if (!resource.scheme) { @@ -118,7 +124,11 @@ export class OpenerService extends Disposable implements IOpenerService { } } - private _doOpenExternal(resource: URI): Promise { + private async _doOpenExternal(resource: URI): Promise { + for (const resolver of this._resolvers.toArray()) { + resource = await resolver.resolveExternalUri(resource); + } + dom.windowOpenNoOpener(encodeURI(resource.toString(true))); return Promise.resolve(true); diff --git a/src/vs/platform/opener/common/opener.ts b/src/vs/platform/opener/common/opener.ts index 8563f3fa2af..0c9a243a952 100644 --- a/src/vs/platform/opener/common/opener.ts +++ b/src/vs/platform/opener/common/opener.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; export const IOpenerService = createDecorator('openerService'); @@ -18,6 +18,10 @@ export interface IValidator { shouldOpen(resource: URI): Promise; } +export interface IExternalUriResolver { + resolveExternalUri(resource: URI): Promise; +} + export interface IOpenerService { _serviceBrand: undefined; @@ -29,10 +33,15 @@ export interface IOpenerService { /** * Register a participant that can validate if the URI resource be opened. - * validators are run before openers. + * Validators are run before openers. */ registerValidator(validator: IValidator): IDisposable; + /** + * Register a participant that can resolve an external URI resource to be opened. + */ + registerExternalUriResolver(resolver: IExternalUriResolver): IDisposable; + /** * Opens a resource, like a webaddress, a document uri, or executes command. * @@ -45,7 +54,8 @@ export interface IOpenerService { export const NullOpenerService: IOpenerService = Object.freeze({ _serviceBrand: undefined, - registerOpener() { return { dispose() { } }; }, - registerValidator() { return { dispose() { } }; }, + registerOpener() { return Disposable.None; }, + registerValidator() { return Disposable.None; }, + registerExternalUriResolver() { return Disposable.None; }, open() { return Promise.resolve(false); }, }); diff --git a/src/vs/workbench/contrib/url/common/externalUriResolver.ts b/src/vs/workbench/contrib/url/common/externalUriResolver.ts new file mode 100644 index 00000000000..454d76644b6 --- /dev/null +++ b/src/vs/workbench/contrib/url/common/externalUriResolver.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; + +export class ExternalUriResolverContribution extends Disposable implements IWorkbenchContribution { + constructor( + @IOpenerService _openerService: IOpenerService, + @IWorkbenchEnvironmentService _workbenchEnvironmentService: IWorkbenchEnvironmentService, + ) { + super(); + + if (_workbenchEnvironmentService.options && _workbenchEnvironmentService.options.resolveExternalUri) { + this._register(_openerService.registerExternalUriResolver({ + resolveExternalUri: async (resource) => { + return _workbenchEnvironmentService.options!.resolveExternalUri!(resource); + } + })); + } + } +} diff --git a/src/vs/workbench/contrib/url/common/url.contribution.ts b/src/vs/workbench/contrib/url/common/url.contribution.ts index f51bd6d3f03..e299262de05 100644 --- a/src/vs/workbench/contrib/url/common/url.contribution.ts +++ b/src/vs/workbench/contrib/url/common/url.contribution.ts @@ -14,9 +14,10 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IURLService } from 'vs/platform/url/common/url'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { ExternalUriResolverContribution } from 'vs/workbench/contrib/url/common/externalUriResolver'; import { configureTrustedDomainSettingsCommand } from 'vs/workbench/contrib/url/common/trustedDomains'; -import { OpenerValidatorContributions } from 'vs/workbench/contrib/url/common/trustedDomainsValidator'; import { TrustedDomainsFileSystemProvider } from 'vs/workbench/contrib/url/common/trustedDomainsFileSystemProvider'; +import { OpenerValidatorContributions } from 'vs/workbench/contrib/url/common/trustedDomainsValidator'; export class OpenUrlAction extends Action { static readonly ID = 'workbench.action.url.openUrl'; @@ -65,3 +66,7 @@ Registry.as(WorkbenchExtensions.Workbench).regi TrustedDomainsFileSystemProvider, LifecyclePhase.Ready ); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution( + ExternalUriResolverContribution, + LifecyclePhase.Ready +); diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index cd4351d6016..38bb092333d 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -90,6 +90,11 @@ interface IWorkbenchConstructionOptions { * Experimental: Support for update reporting. */ updateProvider?: IUpdateProvider; + + /** + * Experimental: Resolves an external uri before it is opened. + */ + readonly resolveExternalUri?: (uri: URI) => Promise; } /** From db358b5b02231ca794bdadd1bd1c45942664e514 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 17 Sep 2019 15:31:44 -0700 Subject: [PATCH 20/36] Fixing a mismatch in default config for webviews --- .../environment/browser/environmentService.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index a68e9cef955..346c6c6c81b 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -181,16 +181,17 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment get webviewExternalEndpoint(): string { // TODO: get fallback from product.json - return this.options.webviewEndpoint || 'https://{{uuid}}.vscode-webview-test.com'; - } - - get webviewResourceRoot(): string { - return `${this.webviewExternalEndpoint}/{{commit}}/vscode-resource{{resource}}` + return (this.options.webviewEndpoint || 'https://{{uuid}}.vscode-webview-test.com/{{commit}}') .replace('{{commit}}', product.commit || '211fa02efe8c041fd7baa8ec3dce199d5185aa44'); } + get webviewResourceRoot(): string { + return `${this.webviewExternalEndpoint}/vscode-resource{{resource}}`; + } + get webviewCspSource(): string { - return this.options.webviewEndpoint || `https://*.vscode-webview-test.com`; + return this.webviewExternalEndpoint + .replace('{{uuid}}', '*'); } } From 88ff4a7bdd9ad85ac8a44fafecdd9a103a6f8f28 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 17 Sep 2019 15:35:38 -0700 Subject: [PATCH 21/36] Fix #80525 --- src/vs/workbench/contrib/search/browser/searchWidget.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 4a97413453c..254346cc052 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -364,11 +364,12 @@ export class SearchWidget extends Widget { } })); - let controls = document.createElement('div'); + const controls = document.createElement('div'); controls.className = 'controls'; controls.style.display = 'block'; controls.appendChild(this._preserveCase.domNode); replaceBox.appendChild(controls); + this.replaceInput.paddingRight = this._preserveCase.width(); this._register(attachInputBoxStyler(this.replaceInput, this.themeService)); this.onkeydown(this.replaceInput.inputElement, (keyboardEvent) => this.onReplaceInputKeyDown(keyboardEvent)); From cc481ab31b5827add5f99a770f2c860d0575df69 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Tue, 17 Sep 2019 23:23:11 -0700 Subject: [PATCH 22/36] Remove unused icon references --- .../parts/panel/media/chevron-left-dark.svg | 3 -- .../parts/panel/media/chevron-left-hc.svg | 3 -- .../parts/panel/media/chevron-left-light.svg | 3 -- .../parts/panel/media/chevron-right-dark.svg | 3 -- .../parts/panel/media/chevron-right-hc.svg | 3 -- .../parts/panel/media/chevron-right-light.svg | 3 -- .../browser/parts/panel/media/panelpart.css | 52 ------------------- 7 files changed, 70 deletions(-) delete mode 100644 src/vs/workbench/browser/parts/panel/media/chevron-left-dark.svg delete mode 100644 src/vs/workbench/browser/parts/panel/media/chevron-left-hc.svg delete mode 100644 src/vs/workbench/browser/parts/panel/media/chevron-left-light.svg delete mode 100644 src/vs/workbench/browser/parts/panel/media/chevron-right-dark.svg delete mode 100644 src/vs/workbench/browser/parts/panel/media/chevron-right-hc.svg delete mode 100644 src/vs/workbench/browser/parts/panel/media/chevron-right-light.svg diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-left-dark.svg b/src/vs/workbench/browser/parts/panel/media/chevron-left-dark.svg deleted file mode 100644 index dbc37784a70..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-left-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-left-hc.svg b/src/vs/workbench/browser/parts/panel/media/chevron-left-hc.svg deleted file mode 100644 index 3767c200513..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-left-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-left-light.svg b/src/vs/workbench/browser/parts/panel/media/chevron-left-light.svg deleted file mode 100644 index 2bd4c96832f..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-left-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-right-dark.svg b/src/vs/workbench/browser/parts/panel/media/chevron-right-dark.svg deleted file mode 100644 index f518fc1632a..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-right-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-right-hc.svg b/src/vs/workbench/browser/parts/panel/media/chevron-right-hc.svg deleted file mode 100644 index 40ba72b7086..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-right-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-right-light.svg b/src/vs/workbench/browser/parts/panel/media/chevron-right-light.svg deleted file mode 100644 index 0d746558a4f..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-right-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index 0f4232c4e44..fcb9fc8edfa 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -123,55 +123,3 @@ min-width: 110px; margin-right: 10px; } - -/* Up */ -.monaco-workbench .maximize-panel-action { - background-image: url('chevron-up-light.svg'); -} - -.vs-dark .monaco-workbench .maximize-panel-action { - background-image: url('chevron-up-dark.svg'); -} - -.hc-black .monaco-workbench .maximize-panel-action { - background-image: url('chevron-up-hc.svg'); -} - -/* Down */ -.monaco-workbench .minimize-panel-action { - background-image: url('chevron-down-light.svg'); -} - -.vs-dark .monaco-workbench .minimize-panel-action { - background-image: url('chevron-down-dark.svg'); -} - -.hc-black .monaco-workbench .minimize-panel-action { - background-image: url('chevron-down-hc.svg'); -} - -/* Left */ -.monaco-workbench .panel.right .maximize-panel-action { - background-image: url('chevron-left-light.svg'); -} - -.vs-dark .monaco-workbench .panel.right .maximize-panel-action { - background-image: url('chevron-left-dark.svg'); -} - -.hc-black .monaco-workbench .panel.right .maximize-panel-action { - background-image: url('chevron-left-hc.svg'); -} - -/* Right */ -.monaco-workbench .panel.right .minimize-panel-action { - background-image: url('chevron-right-light.svg'); -} - -.vs-dark .monaco-workbench .panel.right .minimize-panel-action { - background-image: url('chevron-right-dark.svg'); -} - -.hc-black .monaco-workbench .panel.right .minimize-panel-action { - background-image: url('chevron-right-hc.svg'); -} From 8940dc827dd5fa4674a97c0649a001aeaa68b49c Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 18 Sep 2019 10:58:33 +0200 Subject: [PATCH 23/36] fix file dialogs --- .../contrib/files/browser/fileActions.contribution.ts | 8 ++++++++ .../remote/electron-browser/remote.contribution.ts | 10 +--------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index ff484b1f19c..539455d8825 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -49,6 +49,14 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleAutoSaveAction, const workspacesCategory = nls.localize('workspaces', "Workspaces"); registry.registerWorkbenchAction(new SyncActionDescriptor(OpenWorkspaceAction, OpenWorkspaceAction.ID, OpenWorkspaceAction.LABEL), 'Workspaces: Open Workspace...', workspacesCategory); +const fileCategory = nls.localize('file', "File"); +if (isMacintosh) { + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open...', fileCategory); +} else { + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileAction, OpenFileAction.ID, OpenFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open File...', fileCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O) }), 'File: Open Folder...', fileCategory); +} + // Commands CommandsRegistry.registerCommand('_files.windowOpen', openWindowCommand); CommandsRegistry.registerCommand('_files.newWindow', newWindowCommand); diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index e45a5faa59e..3ba4103fd34 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -12,7 +12,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { isMacintosh } from 'vs/base/common/platform'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { MenuId, IMenuService, MenuItemAction, IMenu, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { MenuId, IMenuService, MenuItemAction, IMenu, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchContributionsExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar'; @@ -33,8 +33,6 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; -import { OpenFileFolderAction, OpenFileAction, OpenFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { RemoteConnectionState, Deprecated_RemoteAuthorityContext, RemoteFileDialogContext } from 'vs/workbench/browser/contextkeys'; @@ -377,11 +375,7 @@ Registry.as(ConfigurationExtensions.Configuration) } }); -const registry = Registry.as(ActionExtensions.WorkbenchActions); -const fileCategory = nls.localize('file', "File"); - if (isMacintosh) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open...', fileCategory); KeybindingsRegistry.registerCommandAndKeybindingRule({ id: OpenLocalFileFolderCommand.ID, weight: KeybindingWeight.WorkbenchContrib, @@ -391,8 +385,6 @@ if (isMacintosh) { handler: OpenLocalFileFolderCommand.handler() }); } else { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileAction, OpenFileAction.ID, OpenFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open File...', fileCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O) }), 'File: Open Folder...', fileCategory); KeybindingsRegistry.registerCommandAndKeybindingRule({ id: OpenLocalFileCommand.ID, weight: KeybindingWeight.WorkbenchContrib, From 23d176011797b78ba9a1843fcf5623ae067a348d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Sep 2019 11:32:09 +0200 Subject: [PATCH 24/36] debt - first cut extract showIteminFolder --- .../electron-main/electronMainService.ts | 6 +- src/vs/platform/electron/node/electron.ts | 3 + src/vs/platform/windows/common/windows.ts | 1 - src/vs/platform/windows/common/windowsIpc.ts | 1 - .../electron-browser/windowsService.ts | 4 - .../windows/electron-main/windowsService.ts | 8 -- .../browser/parts/titlebar/titlebarPart.ts | 5 +- .../workbench/browser/web.simpleservices.ts | 4 - .../browser/extensions.contribution.ts | 11 +-- .../extensions/browser/extensionsActions.ts | 38 +-------- .../extensions.contribution.ts | 19 ++++- .../electron-browser/extensionsActions.ts | 47 +++++++++++ .../files/browser/fileActions.contribution.ts | 29 +------ .../contrib/files/browser/fileCommands.ts | 43 +--------- .../fileActions.contribution.ts | 83 +++++++++++++++++++ .../files/electron-browser/fileCommands.ts | 31 +++++++ .../contrib/logs/common/logs.contribution.ts | 6 +- .../contrib/logs/common/logsActions.ts | 20 ----- .../electron-browser/logs.contribution.ts | 14 ++++ .../logs/electron-browser/logsActions.ts | 28 +++++++ .../electron-browser/startupProfiler.ts | 6 +- src/vs/workbench/electron-browser/window.ts | 6 +- .../workbench/test/workbenchTestServices.ts | 4 - src/vs/workbench/workbench.desktop.main.ts | 6 ++ 24 files changed, 254 insertions(+), 169 deletions(-) create mode 100644 src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts create mode 100644 src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts create mode 100644 src/vs/workbench/contrib/files/electron-browser/fileCommands.ts create mode 100644 src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts create mode 100644 src/vs/workbench/contrib/logs/electron-browser/logsActions.ts diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts index a8c92d2414b..bcdee9d4a02 100644 --- a/src/vs/platform/electron/electron-main/electronMainService.ts +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -5,7 +5,7 @@ import { IElectronService } from 'vs/platform/electron/node/electron'; import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; -import { MessageBoxOptions, MessageBoxReturnValue } from 'electron'; +import { MessageBoxOptions, MessageBoxReturnValue, shell } from 'electron'; import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; @@ -30,6 +30,10 @@ export class ElectronMainService implements IElectronService { checkboxChecked: !!result.checkboxChecked }; } + + async showItemInFolder(path: string): Promise { + shell.showItemInFolder(path); + } } export class ElectronChannel implements IServerChannel { diff --git a/src/vs/platform/electron/node/electron.ts b/src/vs/platform/electron/node/electron.ts index 0f6c3a83091..5411269d98e 100644 --- a/src/vs/platform/electron/node/electron.ts +++ b/src/vs/platform/electron/node/electron.ts @@ -14,4 +14,7 @@ export interface IElectronService { // Dialogs showMessageBox(options: MessageBoxOptions): Promise; + + // OS + showItemInFolder(path: string): Promise; } diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 97f4c9b7687..a34ebe2978c 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -156,7 +156,6 @@ export interface IWindowsService { openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise; getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]>; getWindowCount(): Promise; - showItemInFolder(path: URI): Promise; getActiveWindowId(): Promise; // This needs to be handled from browser process to prevent diff --git a/src/vs/platform/windows/common/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts index 091f49e137f..6534f2f56d3 100644 --- a/src/vs/platform/windows/common/windowsIpc.ts +++ b/src/vs/platform/windows/common/windowsIpc.ts @@ -109,7 +109,6 @@ export class WindowsChannel implements IServerChannel { case 'whenSharedProcessReady': return this.service.whenSharedProcessReady(); case 'toggleSharedProcess': return this.service.toggleSharedProcess(); case 'quit': return this.service.quit(); - case 'showItemInFolder': return this.service.showItemInFolder(URI.revive(arg)); case 'getActiveWindowId': return this.service.getActiveWindowId(); case 'openExternal': return this.service.openExternal(arg); case 'startCrashReporter': return this.service.startCrashReporter(arg); diff --git a/src/vs/platform/windows/electron-browser/windowsService.ts b/src/vs/platform/windows/electron-browser/windowsService.ts index 283259ac8d6..4e983130529 100644 --- a/src/vs/platform/windows/electron-browser/windowsService.ts +++ b/src/vs/platform/windows/electron-browser/windowsService.ts @@ -226,10 +226,6 @@ export class WindowsService implements IWindowsService { return this.channel.call('getWindowCount'); } - showItemInFolder(path: URI): Promise { - return this.channel.call('showItemInFolder', path); - } - getActiveWindowId(): Promise { return this.channel.call('getActiveWindowId'); } diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index 4d219fc827b..6cdde7c3da8 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -336,14 +336,6 @@ export class WindowsService extends Disposable implements IWindowsService, IURLH return this.windowsMainService.getWindows().length; } - async showItemInFolder(resource: URI): Promise { - this.logService.trace('windowsService#showItemInFolder'); - - if (resource.scheme === Schemas.file) { - shell.showItemInFolder(resource.fsPath); - } - } - async getActiveWindowId(): Promise { return this._activeWindowId; } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index a934e456352..f222733af9d 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -376,7 +376,6 @@ export class TitlebarPart extends Part implements ITitleService { if (!isMacintosh && !isWeb) { this.windowControls = append(this.element, $('div.window-controls-container')); - // Minimize const minimizeIconContainer = append(this.windowControls, $('div.window-icon-bg')); const minimizeIcon = append(minimizeIconContainer, $('div.window-icon')); @@ -612,7 +611,9 @@ class ShowItemInFolderAction extends Action { } run(): Promise { - return this.windowsService.showItemInFolder(URI.file(this.path)); + if (this.path && this.windowsService) { } + return Promise.resolve(); + // return this.windowsService.showItemInFolder(URI.file(this.path)); } } diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts index fdc94ca35b0..a0fe7aa1915 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -465,10 +465,6 @@ export class SimpleWindowsService implements IWindowsService { return Promise.resolve(this.windowCount); } - showItemInFolder(_path: URI): Promise { - return Promise.resolve(); - } - newWindowTab(): Promise { return Promise.resolve(); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index b7bed063f07..db05deb3e72 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -20,7 +20,7 @@ import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/brow import { OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction, ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowDisabledExtensionsAction, ShowBuiltInExtensionsAction, UpdateAllAction, - EnableAllAction, EnableAllWorkpsaceAction, DisableAllAction, DisableAllWorkpsaceAction, CheckForUpdatesAction, ShowLanguageExtensionsAction, ShowAzureExtensionsAction, EnableAutoUpdateAction, DisableAutoUpdateAction, ConfigureRecommendedExtensionsCommandsContributor, OpenExtensionsFolderAction, InstallVSIXAction, ReinstallAction, InstallSpecificVersionOfExtensionAction + EnableAllAction, EnableAllWorkpsaceAction, DisableAllAction, DisableAllWorkpsaceAction, CheckForUpdatesAction, ShowLanguageExtensionsAction, ShowAzureExtensionsAction, EnableAutoUpdateAction, DisableAutoUpdateAction, ConfigureRecommendedExtensionsCommandsContributor, InstallVSIXAction, ReinstallAction, InstallSpecificVersionOfExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } from 'vs/workbench/browser/viewlet'; @@ -43,7 +43,6 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { ExtensionDependencyChecker } from 'vs/workbench/contrib/extensions/browser/extensionsDependencyChecker'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { RemoteExtensionsInstaller } from 'vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller'; import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/browser/extensionTipsService'; @@ -344,7 +343,6 @@ const workbenchRegistry = Registry.as(Workbench class ExtensionsContributions implements IWorkbenchContribution { constructor( - @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService, @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService ) { @@ -362,14 +360,7 @@ class ExtensionsContributions implements IWorkbenchContribution { ) ); } - - if (workbenchEnvironmentService.extensionsPath) { - const openExtensionsFolderActionDescriptor = new SyncActionDescriptor(OpenExtensionsFolderAction, OpenExtensionsFolderAction.ID, OpenExtensionsFolderAction.LABEL); - actionRegistry.registerWorkbenchAction(openExtensionsFolderActionDescriptor, 'Extensions: Open Extensions Folder', ExtensionsLabel); - } - } - } workbenchRegistry.registerWorkbenchContribution(ExtensionsContributions, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index b9356f1b35e..a18bcf9b4ba 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -25,7 +25,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; import { IFileService, IFileContent } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; +import { IWindowService } from 'vs/platform/windows/common/windows'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; @@ -44,7 +44,6 @@ import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/w import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; @@ -2777,41 +2776,6 @@ export class EnableAllWorkpsaceAction extends Action { } } -export class OpenExtensionsFolderAction extends Action { - - static readonly ID = 'workbench.extensions.action.openExtensionsFolder'; - static LABEL = localize('openExtensionsFolder', "Open Extensions Folder"); - - constructor( - id: string, - label: string, - @IWindowsService private readonly windowsService: IWindowsService, - @IFileService private readonly fileService: IFileService, - @IEnvironmentService private readonly environmentService: IEnvironmentService - ) { - super(id, label, undefined, true); - } - - run(): Promise { - if (this.environmentService.extensionsPath) { - - const extensionsHome = URI.file(this.environmentService.extensionsPath); - - return Promise.resolve(this.fileService.resolve(extensionsHome)).then(file => { - let itemToShow: URI; - if (file.children && file.children.length > 0) { - itemToShow = file.children[0].resource; - } else { - itemToShow = extensionsHome; - } - - return this.windowsService.showItemInFolder(itemToShow); - }); - } - return Promise.resolve(); - } -} - export class InstallVSIXAction extends Action { static readonly ID = 'workbench.extensions.action.installVSIX'; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts index d47550c32a9..19e5153bb53 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts @@ -8,7 +8,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -22,6 +22,9 @@ import { URI } from 'vs/base/common/uri'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionsAutoProfiler } from 'vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler'; import { registerAndGetAmdImageURL } from 'vs/base/common/amd'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { OpenExtensionsFolderAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions'; +import { ExtensionsLabel } from 'vs/platform/extensionManagement/common/extensionManagement'; // Singletons registerSingleton(IExtensionHostProfileService, ExtensionHostProfileService, true); @@ -57,6 +60,20 @@ const actionRegistry = Registry.as(WorkbenchActionExte actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowRuntimeExtensionsAction, ShowRuntimeExtensionsAction.ID, ShowRuntimeExtensionsAction.LABEL), 'Show Running Extensions', localize('developer', "Developer")); +class ExtensionsContributions implements IWorkbenchContribution { + + constructor( + @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService + ) { + if (workbenchEnvironmentService.extensionsPath) { + const openExtensionsFolderActionDescriptor = new SyncActionDescriptor(OpenExtensionsFolderAction, OpenExtensionsFolderAction.ID, OpenExtensionsFolderAction.LABEL); + actionRegistry.registerWorkbenchAction(openExtensionsFolderActionDescriptor, 'Extensions: Open Extensions Folder', ExtensionsLabel); + } + } +} + +workbenchRegistry.registerWorkbenchContribution(ExtensionsContributions, LifecyclePhase.Starting); + // Register Commands CommandsRegistry.registerCommand(DebugExtensionHostAction.ID, (accessor: ServicesAccessor) => { diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts new file mode 100644 index 00000000000..b4185a6614f --- /dev/null +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { Action } from 'vs/base/common/actions'; +import { IFileService } from 'vs/platform/files/common/files'; +import { URI } from 'vs/base/common/uri'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { Schemas } from 'vs/base/common/network'; + +export class OpenExtensionsFolderAction extends Action { + + static readonly ID = 'workbench.extensions.action.openExtensionsFolder'; + static LABEL = localize('openExtensionsFolder', "Open Extensions Folder"); + + constructor( + id: string, + label: string, + @IElectronService private readonly electronService: IElectronService, + @IFileService private readonly fileService: IFileService, + @IEnvironmentService private readonly environmentService: IEnvironmentService + ) { + super(id, label, undefined, true); + } + + async run(): Promise { + if (this.environmentService.extensionsPath) { + const extensionsHome = URI.file(this.environmentService.extensionsPath); + const file = await this.fileService.resolve(extensionsHome); + + let itemToShow: URI; + if (file.children && file.children.length > 0) { + itemToShow = file.children[0].resource; + } else { + itemToShow = extensionsHome; + } + + if (itemToShow.scheme === Schemas.file) { + return this.electronService.showItemInFolder(itemToShow.fsPath); + } + } + } +} + diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index 539455d8825..47c9912e899 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -10,11 +10,11 @@ import { revertLocalChangesCommand, acceptLocalChangesCommand, CONFLICT_RESOLUTI import { SyncActionDescriptor, MenuId, MenuRegistry, ILocalizedString } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; -import { openWindowCommand, REVEAL_IN_OS_COMMAND_ID, COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_TO_SIDE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_LABEL, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, OpenEditorsGroupContext, COMPARE_WITH_SAVED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, REVEAL_IN_OS_LABEL, DirtyEditorContext, COMPARE_SELECTED_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, REMOVE_ROOT_FOLDER_LABEL, SAVE_FILES_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_LABEL, newWindowCommand } from 'vs/workbench/contrib/files/browser/fileCommands'; +import { openWindowCommand, COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_TO_SIDE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_LABEL, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, OpenEditorsGroupContext, COMPARE_WITH_SAVED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, DirtyEditorContext, COMPARE_SELECTED_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, REMOVE_ROOT_FOLDER_LABEL, SAVE_FILES_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_LABEL, newWindowCommand } from 'vs/workbench/contrib/files/browser/fileCommands'; import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { isWindows, isMacintosh } from 'vs/base/common/platform'; +import { isMacintosh } from 'vs/base/common/platform'; import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext, ExplorerResourceNotReadonlyContext, ExplorerResourceCut, IExplorerService, ExplorerResourceMoveableToTrash, ExplorerViewletVisibleContext } from 'vs/workbench/contrib/files/common/files'; import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL } from 'vs/workbench/browser/actions/workspaceCommands'; import { CLOSE_SAVED_EDITORS_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; @@ -170,11 +170,9 @@ const copyRelativePathCommand = { // Editor Title Context Menu appendEditorTitleContextMenuItem(COPY_PATH_COMMAND_ID, copyPathCommand.title, ResourceContextKey.IsFileSystemResource, '1_cutcopypaste'); appendEditorTitleContextMenuItem(COPY_RELATIVE_PATH_COMMAND_ID, copyRelativePathCommand.title, ResourceContextKey.IsFileSystemResource, '1_cutcopypaste'); -appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL, ResourceContextKey.Scheme.isEqualTo(Schemas.file)); -appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL, ContextKeyExpr.and(IsWebContext.toNegated(), ResourceContextKey.Scheme.isEqualTo(Schemas.userData))); appendEditorTitleContextMenuItem(REVEAL_IN_EXPLORER_COMMAND_ID, nls.localize('revealInSideBar', "Reveal in Side Bar"), ResourceContextKey.IsFileSystemResource); -function appendEditorTitleContextMenuItem(id: string, title: string, when: ContextKeyExpr | undefined, group?: string): void { +export function appendEditorTitleContextMenuItem(id: string, title: string, when: ContextKeyExpr | undefined, group?: string): void { // Menu MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { @@ -210,7 +208,7 @@ function appendSaveConflictEditorTitleAction(id: string, title: string, iconLoca // Menu registration - command palette -function appendToCommandPalette(id: string, title: ILocalizedString, category: ILocalizedString, when?: ContextKeyExpr): void { +export function appendToCommandPalette(id: string, title: ILocalizedString, category: ILocalizedString, when?: ContextKeyExpr): void { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id, @@ -230,7 +228,6 @@ appendToCommandPalette(SAVE_ALL_IN_GROUP_COMMAND_ID, { value: nls.localize('save appendToCommandPalette(SAVE_FILES_COMMAND_ID, { value: nls.localize('saveFiles', "Save All Files"), original: 'Save All Files' }, category); appendToCommandPalette(REVERT_FILE_COMMAND_ID, { value: nls.localize('revert', "Revert File"), original: 'Revert File' }, category); appendToCommandPalette(COMPARE_WITH_SAVED_COMMAND_ID, { value: nls.localize('compareActiveWithSaved', "Compare Active File with Saved"), original: 'Compare Active File with Saved' }, category); -appendToCommandPalette(REVEAL_IN_OS_COMMAND_ID, { value: REVEAL_IN_OS_LABEL, original: isWindows ? 'Reveal in Explorer' : isMacintosh ? 'Reveal in Finder' : 'Open Containing Folder' }, category); appendToCommandPalette(SAVE_FILE_AS_COMMAND_ID, { value: SAVE_FILE_AS_LABEL, original: 'Save As...' }, category); appendToCommandPalette(CLOSE_EDITOR_COMMAND_ID, { value: nls.localize('closeEditor', "Close Editor"), original: 'Close Editor' }, { value: nls.localize('view', "View"), original: 'View' }); appendToCommandPalette(NEW_FILE_COMMAND_ID, { value: NEW_FILE_LABEL, original: 'New File' }, category, WorkspaceFolderCountContext.notEqualsTo('0')); @@ -250,17 +247,6 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { when: ResourceContextKey.IsFileSystemResource }); -const revealInOsCommand = { - id: REVEAL_IN_OS_COMMAND_ID, - title: isWindows ? nls.localize('revealInWindows', "Reveal in Explorer") : isMacintosh ? nls.localize('revealInMac', "Reveal in Finder") : nls.localize('openContainer', "Open Containing Folder") -}; -MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { - group: 'navigation', - order: 20, - command: revealInOsCommand, - when: ResourceContextKey.IsFileSystemResource -}); - MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { group: '1_cutcopypaste', order: 10, @@ -428,13 +414,6 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { when: ContextKeyExpr.and(ExplorerFolderContext.toNegated(), ResourceContextKey.HasResource) }); -MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { - group: 'navigation', - order: 20, - command: revealInOsCommand, - when: ResourceContextKey.Scheme.isEqualTo(Schemas.file) -}); - MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { group: '3_compare', order: 20, diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index 2ee93fec213..98363ee5a05 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -25,9 +25,8 @@ import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; -import { isWindows, isMacintosh } from 'vs/base/common/platform'; +import { isWindows } from 'vs/base/common/platform'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { sequence } from 'vs/base/common/async'; import { getResourceForCommand, getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { getMultiSelectedEditorContexts } from 'vs/workbench/browser/parts/editor/editorCommands'; @@ -46,8 +45,6 @@ import { withUndefinedAsNull } from 'vs/base/common/types'; // Commands -export const REVEAL_IN_OS_COMMAND_ID = 'revealFileInOS'; -export const REVEAL_IN_OS_LABEL = isWindows ? nls.localize('revealInWindows', "Reveal in Explorer") : isMacintosh ? nls.localize('revealInMac', "Reveal in Finder") : nls.localize('openContainer', "Open Containing Folder"); export const REVEAL_IN_EXPLORER_COMMAND_ID = 'revealInExplorer'; export const REVERT_FILE_COMMAND_ID = 'workbench.action.files.revert'; export const OPEN_TO_SIDE_COMMAND_ID = 'explorer.openToSide'; @@ -406,44 +403,6 @@ CommandsRegistry.registerCommand({ } }); -function revealResourcesInOS(resources: URI[], windowsService: IWindowsService, notificationService: INotificationService, workspaceContextService: IWorkspaceContextService): void { - if (resources.length) { - sequence(resources.map(r => () => windowsService.showItemInFolder(r.scheme === Schemas.userData ? r.with({ scheme: Schemas.file }) : r))); - } else if (workspaceContextService.getWorkspace().folders.length) { - windowsService.showItemInFolder(workspaceContextService.getWorkspace().folders[0].uri); - } else { - notificationService.info(nls.localize('openFileToReveal', "Open a file first to reveal")); - } -} - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: REVEAL_IN_OS_COMMAND_ID, - weight: KeybindingWeight.WorkbenchContrib, - when: EditorContextKeys.focus.toNegated(), - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_R, - win: { - primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_R - }, - handler: (accessor: ServicesAccessor, resource: URI | object) => { - const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService)); - revealResourcesInOS(resources, accessor.get(IWindowsService), accessor.get(INotificationService), accessor.get(IWorkspaceContextService)); - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - weight: KeybindingWeight.WorkbenchContrib, - when: undefined, - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_R), - id: 'workbench.action.files.revealActiveFileInWindows', - handler: (accessor: ServicesAccessor) => { - const editorService = accessor.get(IEditorService); - const activeInput = editorService.activeEditor; - const resource = activeInput ? activeInput.getResource() : null; - const resources = resource ? [resource] : []; - revealResourcesInOS(resources, accessor.get(IWindowsService), accessor.get(INotificationService), accessor.get(IWorkspaceContextService)); - } -}); - async function resourcesToClipboard(resources: URI[], relative: boolean, clipboardService: IClipboardService, notificationService: INotificationService, labelService: ILabelService): Promise { if (resources.length) { const lineDelimiter = isWindows ? '\r\n' : '\n'; diff --git a/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts new file mode 100644 index 00000000000..20be6820e3f --- /dev/null +++ b/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts @@ -0,0 +1,83 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { URI } from 'vs/base/common/uri'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { isWindows, isMacintosh } from 'vs/base/common/platform'; +import { Schemas } from 'vs/base/common/network'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; +import { IListService } from 'vs/platform/list/browser/listService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { revealResourcesInOS } from 'vs/workbench/contrib/files/electron-browser/fileCommands'; +import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { ResourceContextKey } from 'vs/workbench/common/resources'; +import { appendToCommandPalette, appendEditorTitleContextMenuItem } from 'vs/workbench/contrib/files/browser/fileActions.contribution'; + +const REVEAL_IN_OS_COMMAND_ID = 'revealFileInOS'; +const REVEAL_IN_OS_LABEL = isWindows ? nls.localize('revealInWindows', "Reveal in Explorer") : isMacintosh ? nls.localize('revealInMac', "Reveal in Finder") : nls.localize('openContainer', "Open Containing Folder"); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: REVEAL_IN_OS_COMMAND_ID, + weight: KeybindingWeight.WorkbenchContrib, + when: EditorContextKeys.focus.toNegated(), + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_R, + win: { + primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_R + }, + handler: (accessor: ServicesAccessor, resource: URI | object) => { + const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService)); + revealResourcesInOS(resources, accessor.get(IElectronService), accessor.get(INotificationService), accessor.get(IWorkspaceContextService)); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_R), + id: 'workbench.action.files.revealActiveFileInWindows', + handler: (accessor: ServicesAccessor) => { + const editorService = accessor.get(IEditorService); + const activeInput = editorService.activeEditor; + const resource = activeInput ? activeInput.getResource() : null; + const resources = resource ? [resource] : []; + revealResourcesInOS(resources, accessor.get(IElectronService), accessor.get(INotificationService), accessor.get(IWorkspaceContextService)); + } +}); + +appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL, ResourceContextKey.Scheme.isEqualTo(Schemas.file)); + +// Menu registration - open editors + +const revealInOsCommand = { + id: REVEAL_IN_OS_COMMAND_ID, + title: isWindows ? nls.localize('revealInWindows', "Reveal in Explorer") : isMacintosh ? nls.localize('revealInMac', "Reveal in Finder") : nls.localize('openContainer', "Open Containing Folder") +}; +MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { + group: 'navigation', + order: 20, + command: revealInOsCommand, + when: ResourceContextKey.IsFileSystemResource +}); + +// Menu registration - explorer + +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + group: 'navigation', + order: 20, + command: revealInOsCommand, + when: ResourceContextKey.Scheme.isEqualTo(Schemas.file) +}); + +// Command Palette + +const category = { value: nls.localize('filesCategory', "File"), original: 'File' }; +appendToCommandPalette(REVEAL_IN_OS_COMMAND_ID, { value: REVEAL_IN_OS_LABEL, original: isWindows ? 'Reveal in Explorer' : isMacintosh ? 'Reveal in Finder' : 'Open Containing Folder' }, category); diff --git a/src/vs/workbench/contrib/files/electron-browser/fileCommands.ts b/src/vs/workbench/contrib/files/electron-browser/fileCommands.ts new file mode 100644 index 00000000000..0a6e710462c --- /dev/null +++ b/src/vs/workbench/contrib/files/electron-browser/fileCommands.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { URI } from 'vs/base/common/uri'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { sequence } from 'vs/base/common/async'; +import { Schemas } from 'vs/base/common/network'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IElectronService } from 'vs/platform/electron/node/electron'; + +// Commands + +export function revealResourcesInOS(resources: URI[], electronService: IElectronService, notificationService: INotificationService, workspaceContextService: IWorkspaceContextService): void { + if (resources.length) { + sequence(resources.map(r => async () => { + if (r.scheme === Schemas.file) { + electronService.showItemInFolder(r.fsPath); + } + })); + } else if (workspaceContextService.getWorkspace().folders.length) { + const uri = workspaceContextService.getWorkspace().folders[0].uri; + if (uri.scheme === Schemas.file) { + electronService.showItemInFolder(uri.fsPath); + } + } else { + notificationService.info(nls.localize('openFileToReveal', "Open a file first to reveal")); + } +} diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index 3de9142da6b..ab29875c3c3 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -8,7 +8,7 @@ import { join } from 'vs/base/common/path'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { SetLogLevelAction, OpenLogsFolderAction, OpenWindowSessionLogFileAction } from 'vs/workbench/contrib/logs/common/logsActions'; +import { SetLogLevelAction, OpenWindowSessionLogFileAction } from 'vs/workbench/contrib/logs/common/logsActions'; import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -64,10 +64,6 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { }; registerTelemetryChannel(this.logService.getLevel()); this.logService.onDidChangeLogLevel(registerTelemetryChannel); - - const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); - const devCategory = nls.localize('developer', "Developer"); - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenLogsFolderAction, OpenLogsFolderAction.ID, OpenLogsFolderAction.LABEL), 'Developer: Open Logs Folder', devCategory); } private async registerLogChannel(id: string, label: string, file: URI): Promise { diff --git a/src/vs/workbench/contrib/logs/common/logsActions.ts b/src/vs/workbench/contrib/logs/common/logsActions.ts index 00a100d4c64..34e9eed4475 100644 --- a/src/vs/workbench/contrib/logs/common/logsActions.ts +++ b/src/vs/workbench/contrib/logs/common/logsActions.ts @@ -5,9 +5,6 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; -import { join } from 'vs/base/common/path'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; import { ILogService, LogLevel, DEFAULT_LOG_LEVEL } from 'vs/platform/log/common/log'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { URI } from 'vs/base/common/uri'; @@ -16,23 +13,6 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { dirname, basename, isEqual } from 'vs/base/common/resources'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -export class OpenLogsFolderAction extends Action { - - static ID = 'workbench.action.openLogsFolder'; - static LABEL = nls.localize('openLogsFolder', "Open Logs Folder"); - - constructor(id: string, label: string, - @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IWindowsService private readonly windowsService: IWindowsService, - ) { - super(id, label); - } - - run(): Promise { - return this.windowsService.showItemInFolder(URI.file(join(this.environmentService.logsPath, 'main.log'))); - } -} - export class SetLogLevelAction extends Action { static ID = 'workbench.action.setLogLevel'; diff --git a/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts b/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts new file mode 100644 index 00000000000..f6ceef58f71 --- /dev/null +++ b/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts @@ -0,0 +1,14 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { OpenLogsFolderAction } from 'vs/workbench/contrib/logs/electron-browser/logsActions'; + +const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); +const devCategory = nls.localize('developer', "Developer"); +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenLogsFolderAction, OpenLogsFolderAction.ID, OpenLogsFolderAction.LABEL), 'Developer: Open Logs Folder', devCategory); diff --git a/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts b/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts new file mode 100644 index 00000000000..547558b7deb --- /dev/null +++ b/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { Action } from 'vs/base/common/actions'; +import { join } from 'vs/base/common/path'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { URI } from 'vs/base/common/uri'; +import { IElectronService } from 'vs/platform/electron/node/electron'; + +export class OpenLogsFolderAction extends Action { + + static ID = 'workbench.action.openLogsFolder'; + static LABEL = nls.localize('openLogsFolder', "Open Logs Folder"); + + constructor(id: string, label: string, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IElectronService private readonly electronService: IElectronService, + ) { + super(id, label); + } + + run(): Promise { + return this.electronService.showItemInFolder(URI.file(join(this.environmentService.logsPath, 'main.log')).fsPath); + } +} diff --git a/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts index daa2adf2ccd..0509b63e0d3 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts @@ -18,6 +18,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { URI } from 'vs/base/common/uri'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IElectronService } from 'vs/platform/electron/node/electron'; export class StartupProfiler implements IWorkbenchContribution { @@ -29,7 +30,8 @@ export class StartupProfiler implements IWorkbenchContribution { @IClipboardService private readonly _clipboardService: IClipboardService, @ILifecycleService lifecycleService: ILifecycleService, @IExtensionService extensionService: IExtensionService, - @IOpenerService private readonly _openerService: IOpenerService + @IOpenerService private readonly _openerService: IOpenerService, + @IElectronService private readonly _electronService: IElectronService ) { // wait for everything to be ready Promise.all([ @@ -81,7 +83,7 @@ export class StartupProfiler implements IWorkbenchContribution { }).then(res => { if (res.confirmed) { Promise.all([ - this._windowsService.showItemInFolder(URI.file(join(dir, files[0]))), + this._electronService.showItemInFolder(URI.file(join(dir, files[0])).fsPath), this._createPerfIssue(files) ]).then(() => { // keep window stable until restart is selected diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index d650e0a1594..80eadd8163a 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -55,6 +55,7 @@ import { IMenubarService, IMenubarData, IMenubarMenu, IMenubarKeybinding, IMenub import { withNullAsUndefined } from 'vs/base/common/types'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { Schemas } from 'vs/base/common/network'; +import { IElectronService } from 'vs/platform/electron/node/electron'; const TextInputActions: IAction[] = [ new Action('undo', nls.localize('undo', "Undo"), undefined, true, () => Promise.resolve(document.execCommand('undo'))), @@ -102,7 +103,8 @@ export class ElectronWindow extends Disposable { @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @ITextFileService private readonly textFileService: ITextFileService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IOpenerService private readonly openerService: IOpenerService + @IOpenerService private readonly openerService: IOpenerService, + @IElectronService private readonly electronService: IElectronService ) { super(); @@ -371,7 +373,7 @@ export class ElectronWindow extends Disposable { const success = await $this.windowsService.openExternal(encodeURI(resource.toString(true))); if (!success && resource.scheme === Schemas.file) { // if opening failed, and this is a file, we can still try to reveal it - await $this.windowsService.showItemInFolder(resource); + await $this.electronService.showItemInFolder(resource.fsPath); } return true; diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index ad9ae1c822d..73a3517bd60 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -1494,10 +1494,6 @@ export class TestWindowsService implements IWindowsService { return Promise.resolve(this.windowCount); } - showItemInFolder(_path: URI): Promise { - return Promise.resolve(); - } - newWindowTab(): Promise { return Promise.resolve(); } diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index f22592cb8fe..7739c33362a 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -90,6 +90,9 @@ registerSingleton(IElectronService, ElectronService, true); // Localizations import 'vs/workbench/contrib/localizations/browser/localizations.contribution'; +// Logs +import 'vs/workbench/contrib/logs/electron-browser/logs.contribution'; + // Stats import 'vs/workbench/contrib/stats/electron-browser/workspaceStatsService'; import 'vs/workbench/contrib/stats/electron-browser/stats.contribution'; @@ -97,6 +100,9 @@ import 'vs/workbench/contrib/stats/electron-browser/stats.contribution'; // Rapid Render Splash import 'vs/workbench/contrib/splash/electron-browser/partsSplash.contribution'; +// Explorer +import 'vs/workbench/contrib/files/electron-browser/fileActions.contribution'; + // Debug import 'vs/workbench/contrib/debug/node/debugHelperService'; import 'vs/workbench/contrib/debug/electron-browser/extensionHostDebugService'; From 6d7917cd3c4ecc17f249583daf9c2f6c23e6d6e2 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 18 Sep 2019 12:14:32 +0200 Subject: [PATCH 25/36] Add some C++ scopes to dark+ and light+ themes Part of #80783 --- .../test/colorize-results/test-23630_cpp.json | 32 ++++----- .../test/colorize-results/test-23850_cpp.json | 32 ++++----- .../test/colorize-results/test-78769_cpp.json | 28 ++++---- .../cpp/test/colorize-results/test_cc.json | 26 +++---- .../cpp/test/colorize-results/test_cpp.json | 28 ++++---- .../theme-defaults/themes/dark_plus.json | 67 +++++++++++++++++++ .../theme-defaults/themes/light_plus.json | 50 +++++++++++++- 7 files changed, 189 insertions(+), 74 deletions(-) diff --git a/extensions/cpp/test/colorize-results/test-23630_cpp.json b/extensions/cpp/test/colorize-results/test-23630_cpp.json index 08d81e6afff..26da4333e92 100644 --- a/extensions/cpp/test/colorize-results/test-23630_cpp.json +++ b/extensions/cpp/test/colorize-results/test-23630_cpp.json @@ -3,8 +3,8 @@ "c": "#", "t": "source.cpp keyword.control.directive.conditional.ifndef.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -14,8 +14,8 @@ "c": "ifndef", "t": "source.cpp keyword.control.directive.conditional.ifndef.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -36,8 +36,8 @@ "c": "_UCRT", "t": "source.cpp meta.preprocessor.conditional.cpp entity.name.function.preprocessor.cpp", "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", + "dark_plus": "source.cpp entity.name.function.preprocessor: #C586C0", + "light_plus": "source.cpp entity.name.function.preprocessor: #AF00DB", "dark_vs": "meta.preprocessor: #569CD6", "light_vs": "meta.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA" @@ -58,8 +58,8 @@ "c": "#", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -69,8 +69,8 @@ "c": "define", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -91,8 +91,8 @@ "c": "_UCRT", "t": "source.cpp meta.preprocessor.macro.cpp entity.name.function.preprocessor.cpp", "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", + "dark_plus": "source.cpp entity.name.function.preprocessor: #C586C0", + "light_plus": "source.cpp entity.name.function.preprocessor: #AF00DB", "dark_vs": "meta.preprocessor: #569CD6", "light_vs": "meta.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA" @@ -102,8 +102,8 @@ "c": "#", "t": "source.cpp keyword.control.directive.endif.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -113,8 +113,8 @@ "c": "endif", "t": "source.cpp keyword.control.directive.endif.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" diff --git a/extensions/cpp/test/colorize-results/test-23850_cpp.json b/extensions/cpp/test/colorize-results/test-23850_cpp.json index 4ba6b59dc9b..9a0466d8c29 100644 --- a/extensions/cpp/test/colorize-results/test-23850_cpp.json +++ b/extensions/cpp/test/colorize-results/test-23850_cpp.json @@ -3,8 +3,8 @@ "c": "#", "t": "source.cpp keyword.control.directive.conditional.ifndef.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -14,8 +14,8 @@ "c": "ifndef", "t": "source.cpp keyword.control.directive.conditional.ifndef.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -36,8 +36,8 @@ "c": "_UCRT", "t": "source.cpp meta.preprocessor.conditional.cpp entity.name.function.preprocessor.cpp", "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", + "dark_plus": "source.cpp entity.name.function.preprocessor: #C586C0", + "light_plus": "source.cpp entity.name.function.preprocessor: #AF00DB", "dark_vs": "meta.preprocessor: #569CD6", "light_vs": "meta.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA" @@ -47,8 +47,8 @@ "c": "#", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -58,8 +58,8 @@ "c": "define", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -80,8 +80,8 @@ "c": "_UCRT", "t": "source.cpp meta.preprocessor.macro.cpp entity.name.function.preprocessor.cpp", "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", + "dark_plus": "source.cpp entity.name.function.preprocessor: #C586C0", + "light_plus": "source.cpp entity.name.function.preprocessor: #AF00DB", "dark_vs": "meta.preprocessor: #569CD6", "light_vs": "meta.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA" @@ -91,8 +91,8 @@ "c": "#", "t": "source.cpp keyword.control.directive.endif.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -102,8 +102,8 @@ "c": "endif", "t": "source.cpp keyword.control.directive.endif.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" diff --git a/extensions/cpp/test/colorize-results/test-78769_cpp.json b/extensions/cpp/test/colorize-results/test-78769_cpp.json index e714cf5e26b..da4cbdd99b2 100644 --- a/extensions/cpp/test/colorize-results/test-78769_cpp.json +++ b/extensions/cpp/test/colorize-results/test-78769_cpp.json @@ -3,8 +3,8 @@ "c": "#", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -14,8 +14,8 @@ "c": "define", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -36,8 +36,8 @@ "c": "DOCTEST_IMPLEMENT_FIXTURE", "t": "source.cpp meta.preprocessor.macro.cpp entity.name.function.preprocessor.cpp", "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", + "dark_plus": "source.cpp entity.name.function.preprocessor: #C586C0", + "light_plus": "source.cpp entity.name.function.preprocessor: #AF00DB", "dark_vs": "meta.preprocessor: #569CD6", "light_vs": "meta.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA" @@ -58,8 +58,8 @@ "c": "der", "t": "source.cpp meta.preprocessor.macro.cpp variable.parameter.preprocessor.cpp", "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", + "dark_plus": "source.cpp variable.parameter: #7F7F7F", + "light_plus": "source.cpp variable.parameter: #808080", "dark_vs": "meta.preprocessor: #569CD6", "light_vs": "meta.preprocessor: #0000FF", "hc_black": "variable: #9CDCFE" @@ -91,8 +91,8 @@ "c": "base", "t": "source.cpp meta.preprocessor.macro.cpp variable.parameter.preprocessor.cpp", "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", + "dark_plus": "source.cpp variable.parameter: #7F7F7F", + "light_plus": "source.cpp variable.parameter: #808080", "dark_vs": "meta.preprocessor: #569CD6", "light_vs": "meta.preprocessor: #0000FF", "hc_black": "variable: #9CDCFE" @@ -124,8 +124,8 @@ "c": "func", "t": "source.cpp meta.preprocessor.macro.cpp variable.parameter.preprocessor.cpp", "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", + "dark_plus": "source.cpp variable.parameter: #7F7F7F", + "light_plus": "source.cpp variable.parameter: #808080", "dark_vs": "meta.preprocessor: #569CD6", "light_vs": "meta.preprocessor: #0000FF", "hc_black": "variable: #9CDCFE" @@ -157,8 +157,8 @@ "c": "decorators", "t": "source.cpp meta.preprocessor.macro.cpp variable.parameter.preprocessor.cpp", "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", + "dark_plus": "source.cpp variable.parameter: #7F7F7F", + "light_plus": "source.cpp variable.parameter: #808080", "dark_vs": "meta.preprocessor: #569CD6", "light_vs": "meta.preprocessor: #0000FF", "hc_black": "variable: #9CDCFE" diff --git a/extensions/cpp/test/colorize-results/test_cc.json b/extensions/cpp/test/colorize-results/test_cc.json index 902ef645164..6c3e21a12a3 100644 --- a/extensions/cpp/test/colorize-results/test_cc.json +++ b/extensions/cpp/test/colorize-results/test_cc.json @@ -3,8 +3,8 @@ "c": "#", "t": "source.cpp keyword.control.directive.conditional.if.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -14,8 +14,8 @@ "c": "if", "t": "source.cpp keyword.control.directive.conditional.if.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -36,8 +36,8 @@ "c": "B4G_DEBUG_CHECK", "t": "source.cpp meta.preprocessor.conditional.cpp entity.name.function.preprocessor.cpp", "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", + "dark_plus": "source.cpp entity.name.function.preprocessor: #C586C0", + "light_plus": "source.cpp entity.name.function.preprocessor: #AF00DB", "dark_vs": "meta.preprocessor: #569CD6", "light_vs": "meta.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA" @@ -652,8 +652,8 @@ "c": "#", "t": "source.cpp keyword.control.directive.endif.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -663,8 +663,8 @@ "c": "endif", "t": "source.cpp keyword.control.directive.endif.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -751,8 +751,8 @@ "c": "obj", "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp variable.parameter.cpp", "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", + "dark_plus": "source.cpp variable.parameter: #7F7F7F", + "light_plus": "source.cpp variable.parameter: #808080", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", "hc_black": "variable: #9CDCFE" @@ -1037,7 +1037,7 @@ "c": "x", "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp variable.other.property.cpp", "r": { - "dark_plus": "variable: #9CDCFE", + "dark_plus": "source.cpp variable.other.property: #DADADA", "light_plus": "variable: #001080", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", diff --git a/extensions/cpp/test/colorize-results/test_cpp.json b/extensions/cpp/test/colorize-results/test_cpp.json index f84d916afa3..9a6fe977b57 100644 --- a/extensions/cpp/test/colorize-results/test_cpp.json +++ b/extensions/cpp/test/colorize-results/test_cpp.json @@ -25,8 +25,8 @@ "c": "#", "t": "source.cpp meta.preprocessor.include.cpp keyword.control.directive.include.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -36,8 +36,8 @@ "c": "include", "t": "source.cpp meta.preprocessor.include.cpp keyword.control.directive.include.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -157,8 +157,8 @@ "c": "#", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -168,8 +168,8 @@ "c": "define", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -190,8 +190,8 @@ "c": "EXTERN_C", "t": "source.cpp meta.preprocessor.macro.cpp entity.name.function.preprocessor.cpp", "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", + "dark_plus": "source.cpp entity.name.function.preprocessor: #C586C0", + "light_plus": "source.cpp entity.name.function.preprocessor: #AF00DB", "dark_vs": "meta.preprocessor: #569CD6", "light_vs": "meta.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA" @@ -817,8 +817,8 @@ "c": "x", "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp variable.parameter.cpp", "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", + "dark_plus": "source.cpp variable.parameter: #7F7F7F", + "light_plus": "source.cpp variable.parameter: #808080", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", "hc_black": "variable: #9CDCFE" @@ -872,8 +872,8 @@ "c": "y", "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp variable.parameter.cpp", "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", + "dark_plus": "source.cpp variable.parameter: #7F7F7F", + "light_plus": "source.cpp variable.parameter: #808080", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", "hc_black": "variable: #9CDCFE" diff --git a/extensions/theme-defaults/themes/dark_plus.json b/extensions/theme-defaults/themes/dark_plus.json index f12d3d7633e..3efcc124ff0 100644 --- a/extensions/theme-defaults/themes/dark_plus.json +++ b/extensions/theme-defaults/themes/dark_plus.json @@ -170,6 +170,73 @@ "settings": { "foreground": "#d7ba7d" } + }, + // Scopes that are potentially C++ only follow + { + "scope": "source.cpp entity.name", + "settings": { + "foreground": "#C8C8C8" + } + }, + { + "scope": "source.cpp keyword.control.directive", + "settings": { + "foreground": "#9B9B9B" + } + }, + { + "scope": "source.cpp entity.name.function.operator", + "settings": { + "foreground": "#B4B4B4" + } + }, + { + "scope": "source.cpp entity.name.function.preprocessor", + "settings": { + "foreground": "#C586C0" + } + }, + { + "scope": "source.cpp entity.name.label", + "settings": { + "foreground": "#C8C8C8" + } + }, + { + "scope": "source.cpp entity.name.operator.custom-literal", + "settings": { + "foreground": "#DADADA" + } + }, + { + "scope": "source.cpp entity.name.operator.custom-literal.number", + "settings": { + "foreground": "#B5CEA8" + } + }, + { + "scope": "source.cpp entity.name.operator.custom-literal.string", + "settings": { + "foreground": "#ce9178" + } + }, + { + "scope": "source.cpp variable.other.enummember", + "settings": { + "foreground": "#B8D7A3" + } + }, + { + "scope": "source.cpp variable.other.property", + "settings": { + "foreground": "#DADADA" + } + }, + { + "scope": "source.cpp variable.parameter", + "settings": { + "foreground": "#7F7F7F" + } } ] } diff --git a/extensions/theme-defaults/themes/light_plus.json b/extensions/theme-defaults/themes/light_plus.json index 3d30c5d17fb..e6a4c922357 100644 --- a/extensions/theme-defaults/themes/light_plus.json +++ b/extensions/theme-defaults/themes/light_plus.json @@ -170,7 +170,55 @@ "settings": { "foreground": "#ff0000" } + }, + // Scopes that are potentially C++ only follow + { + "scope": "source.cpp entity.name", + "settings": { + "foreground": "#000000" + } + }, + { + "scope": "source.cpp keyword.control.directive", + "settings": { + "foreground": "#808080" + } + }, + { + "scope": "source.cpp entity.name.function.operator", + "settings": { + "foreground": "#008080" + } + }, + { + "scope": "source.cpp entity.name.function.preprocessor", + "settings": { + "foreground": "#AF00DB" + } + }, + { + "scope": "source.cpp entity.name.label", + "settings": { + "foreground": "#000000" + } + }, + { + "scope": "source.cpp entity.name.operator.custom-literal.string", + "settings": { + "foreground": "#0451a5" + } + }, + { + "scope": "source.cpp variable.other.enummember", + "settings": { + "foreground": "#2F4F4F" + } + }, + { + "scope": "source.cpp variable.parameter", + "settings": { + "foreground": "#808080" + } } - ] } From d9ec15cda43b96eff8946bbab0c7890cfb3291f5 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Sep 2019 12:16:09 +0200 Subject: [PATCH 26/36] debt - introduce simple IPC helpers for basic communication forwarding using proxys --- src/vs/base/parts/ipc/common/ipc.ts | 27 ++++++++++++++++- src/vs/code/electron-main/app.ts | 9 +++--- .../electron-browser/electronService.ts | 18 ++--------- .../electron-main/electronMainService.ts | 28 ----------------- .../electron-browser/mainProcessService.ts | 16 ++++++++++ .../issue/electron-browser/issueService.ts | 23 +++----------- .../platform/issue/electron-main/issueIpc.ts | 30 ------------------- 7 files changed, 53 insertions(+), 98 deletions(-) delete mode 100644 src/vs/platform/issue/electron-main/issueIpc.ts diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index e0205092202..cb81bef96bc 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -32,7 +32,6 @@ export interface IServerChannel { listen(ctx: TContext, event: string, arg?: any): Event; } - export const enum RequestType { Promise = 100, PromiseCancel = 101, @@ -833,3 +832,29 @@ export class StaticRouter implements IClientRouter return await this.route(hub); } } + +export class SimpleServiceProxyChannel implements IServerChannel { + + private service: { [key: string]: unknown }; + + constructor(service: unknown) { + this.service = service as { [key: string]: unknown }; + } + + listen(_: unknown, event: string): Event { + throw new Error(`Events are currently unsupported by SimpleServiceProxyChannel: ${event}`); + } + + call(_: unknown, command: string, arg?: any): Promise { + const target = this.service[command]; + if (typeof target === 'function') { + if (Array.isArray(arg)) { + return target.apply(this.service, arg); + } + + return target.call(this.service, arg); + } + + throw new Error(`Call Not Found: ${command}`); + } +} diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 72b1fdc43fb..8f18f8d7915 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -32,7 +32,7 @@ import { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc'; import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; -import { getDelayedChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc'; +import { getDelayedChannel, StaticRouter, SimpleServiceProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import product from 'vs/platform/product/common/product'; import { ProxyAuthHandler } from 'vs/code/electron-main/auth'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -45,7 +45,6 @@ import { Win32UpdateService } from 'vs/platform/update/electron-main/updateServi import { LinuxUpdateService } from 'vs/platform/update/electron-main/updateService.linux'; import { DarwinUpdateService } from 'vs/platform/update/electron-main/updateService.darwin'; import { IIssueService } from 'vs/platform/issue/node/issue'; -import { IssueChannel } from 'vs/platform/issue/electron-main/issueIpc'; import { IssueMainService } from 'vs/platform/issue/electron-main/issueMainService'; import { LoggerChannel } from 'vs/platform/log/common/logIpc'; import { setUnexpectedErrorHandler, onUnexpectedError } from 'vs/base/common/errors'; @@ -77,7 +76,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; import { IElectronService } from 'vs/platform/electron/node/electron'; -import { ElectronMainService, ElectronChannel } from 'vs/platform/electron/electron-main/electronMainService'; +import { ElectronMainService } from 'vs/platform/electron/electron-main/electronMainService'; export class CodeApplication extends Disposable { @@ -539,11 +538,11 @@ export class CodeApplication extends Disposable { electronIpcServer.registerChannel('update', updateChannel); const issueService = accessor.get(IIssueService); - const issueChannel = new IssueChannel(issueService); + const issueChannel = new SimpleServiceProxyChannel(issueService); electronIpcServer.registerChannel('issue', issueChannel); const electronService = accessor.get(IElectronService); - const electronChannel = new ElectronChannel(electronService); + const electronChannel = new SimpleServiceProxyChannel(electronService); electronIpcServer.registerChannel('electron', electronChannel); const workspacesMainService = accessor.get(IWorkspacesMainService); diff --git a/src/vs/platform/electron/electron-browser/electronService.ts b/src/vs/platform/electron/electron-browser/electronService.ts index fa2b5f09b18..3633f642e94 100644 --- a/src/vs/platform/electron/electron-browser/electronService.ts +++ b/src/vs/platform/electron/electron-browser/electronService.ts @@ -3,26 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { IMainProcessService, createSimpleMainChannelProxy } from 'vs/platform/ipc/electron-browser/mainProcessService'; export class ElectronService { _serviceBrand: undefined; constructor(@IMainProcessService mainProcessService: IMainProcessService) { - const channel = mainProcessService.getChannel('electron'); - - // Proxy: forward any property access to the channel - return new Proxy({}, { - get(_target, propKey, _receiver) { - if (typeof propKey === 'string') { - return function (...args: any[]) { - return channel.call(propKey, ...args); - }; - } - - throw new Error(`Not Implemented in ElectronService: ${String(propKey)}`); - } - }) as ElectronService; + return createSimpleMainChannelProxy('electron', mainProcessService); } } diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts index bcdee9d4a02..1846a5cb086 100644 --- a/src/vs/platform/electron/electron-main/electronMainService.ts +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -6,8 +6,6 @@ import { IElectronService } from 'vs/platform/electron/node/electron'; import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; import { MessageBoxOptions, MessageBoxReturnValue, shell } from 'electron'; -import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { Event } from 'vs/base/common/event'; export class ElectronMainService implements IElectronService { @@ -35,29 +33,3 @@ export class ElectronMainService implements IElectronService { shell.showItemInFolder(path); } } - -export class ElectronChannel implements IServerChannel { - - private service: { [key: string]: unknown }; - - constructor(service: IElectronService) { - this.service = service as unknown as { [key: string]: unknown }; - } - - listen(_: unknown, event: string): Event { - throw new Error(`Event not found: ${event}`); - } - - call(_: unknown, command: string, arg?: any): Promise { - const target = this.service[command]; - if (typeof target === 'function') { - if (Array.isArray(arg)) { - return target.apply(this.service, arg); - } - - return target.call(this.service, arg); - } - - throw new Error(`Call Not Found in ElectronService: ${command}`); - } -} diff --git a/src/vs/platform/ipc/electron-browser/mainProcessService.ts b/src/vs/platform/ipc/electron-browser/mainProcessService.ts index c72b1c703d3..ca24035c1e2 100644 --- a/src/vs/platform/ipc/electron-browser/mainProcessService.ts +++ b/src/vs/platform/ipc/electron-browser/mainProcessService.ts @@ -41,3 +41,19 @@ export class MainProcessService extends Disposable implements IMainProcessServic this.mainProcessConnection.registerChannel(channelName, channel); } } + +export function createSimpleMainChannelProxy(channelName: string, mainProcessService: IMainProcessService): T { + const channel = mainProcessService.getChannel(channelName); + + return new Proxy({}, { + get(_target, propKey, _receiver) { + if (typeof propKey === 'string') { + return function (...args: any[]) { + return channel.call(propKey, ...args); + }; + } + + throw new Error(`Unable to provide main channel proxy implementation for: ${String(propKey)} in ${channelName}`); + } + }) as T; +} diff --git a/src/vs/platform/issue/electron-browser/issueService.ts b/src/vs/platform/issue/electron-browser/issueService.ts index 56068fab19e..f7e67b7a3ac 100644 --- a/src/vs/platform/issue/electron-browser/issueService.ts +++ b/src/vs/platform/issue/electron-browser/issueService.ts @@ -3,29 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IIssueService, IssueReporterData, ProcessExplorerData } from 'vs/platform/issue/node/issue'; -import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; +import { IIssueService } from 'vs/platform/issue/node/issue'; +import { IMainProcessService, createSimpleMainChannelProxy } from 'vs/platform/ipc/electron-browser/mainProcessService'; -export class IssueService implements IIssueService { +export class IssueService { _serviceBrand: undefined; - private channel: IChannel; - constructor(@IMainProcessService mainProcessService: IMainProcessService) { - this.channel = mainProcessService.getChannel('issue'); - } - - openReporter(data: IssueReporterData): Promise { - return this.channel.call('openIssueReporter', data); - } - - openProcessExplorer(data: ProcessExplorerData): Promise { - return this.channel.call('openProcessExplorer', data); - } - - getSystemStatus(): Promise { - return this.channel.call('getSystemStatus'); + return createSimpleMainChannelProxy('issue', mainProcessService); } } diff --git a/src/vs/platform/issue/electron-main/issueIpc.ts b/src/vs/platform/issue/electron-main/issueIpc.ts deleted file mode 100644 index 271bcf5ceee..00000000000 --- a/src/vs/platform/issue/electron-main/issueIpc.ts +++ /dev/null @@ -1,30 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { Event } from 'vs/base/common/event'; -import { IIssueService } from 'vs/platform/issue/node/issue'; - -export class IssueChannel implements IServerChannel { - - constructor(private service: IIssueService) { } - - listen(_: unknown, event: string): Event { - throw new Error(`Event not found: ${event}`); - } - - call(_: unknown, command: string, arg?: any): Promise { - switch (command) { - case 'openIssueReporter': - return this.service.openReporter(arg); - case 'openProcessExplorer': - return this.service.openProcessExplorer(arg); - case 'getSystemStatus': - return this.service.getSystemStatus(); - } - - throw new Error(`Call not found: ${command}`); - } -} \ No newline at end of file From 7f182c40a991d22e64be500663522b3e0d9ea71f Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 18 Sep 2019 12:34:19 +0200 Subject: [PATCH 27/36] debug brekpoints: polish inline breakpoint decorations --- .../browser/breakpointEditorContribution.ts | 20 +++++++++---------- .../contrib/debug/browser/debugSession.ts | 2 +- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 4515fd4dfdd..d905e63ad14 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -68,7 +68,7 @@ async function createCandidateDecorations(model: ITextModel, lineNumbers: number const result: { range: Range; options: IModelDecorationOptions; }[] = []; const session = debugService.getViewModel().focusedSession; if (session && session.capabilities.supportsBreakpointLocationsRequest) { - lineNumbers.forEach(async lineNumber => { + await Promise.all(lineNumbers.map(async lineNumber => { const positions = await session.breakpointsLocations(model.uri, lineNumber); positions.forEach(p => { result.push({ @@ -79,7 +79,7 @@ async function createCandidateDecorations(model: ITextModel, lineNumbers: number } }); }); - }); + })); } return result; @@ -114,7 +114,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { private breakpointWidgetVisible: IContextKey; private toDispose: IDisposable[] = []; private ignoreDecorationsChangedEvent = false; - private ignoreFirstBreakpointsChangeEvent = false; + private ignoreBreakpointsChangeEvent = false; private breakpointDecorations: IBreakpointDecoration[] = []; private candidateDecoraions: { decorationId: string, inlineWidget: InlineBreakpointWidget }[] = []; @@ -222,11 +222,9 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { await this.setDecorations(); })); this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(async () => { - if (this.ignoreFirstBreakpointsChangeEvent) { - this.ignoreFirstBreakpointsChangeEvent = false; - return; + if (!this.ignoreBreakpointsChangeEvent) { + await this.setDecorations(); } - await this.setDecorations(); })); this.toDispose.push(this.editor.onDidChangeModelDecorations(() => this.onModelDecorationsChanged())); } @@ -298,7 +296,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { nls.localize('addLogPoint', "Add Logpoint..."), undefined, true, - () => Promise.resolve(this.showBreakpointWidget(lineNumber, BreakpointWidgetContext.LOG_MESSAGE)) + () => Promise.resolve(this.showBreakpointWidget(lineNumber, column, BreakpointWidgetContext.LOG_MESSAGE)) )); } @@ -378,7 +376,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { const lineNumbers = distinct(this.breakpointDecorations.map(bpd => bpd.range.startLineNumber)); let desiredCandidateDecorations = await createCandidateDecorations(this.editor.getModel(), lineNumbers, this.debugService); desiredCandidateDecorations = desiredCandidateDecorations.filter(dbd => { - const breakpointDecorationAlreadyAtCandidateLocation = this.breakpointDecorations.filter(bd => bd.range.equalsRange(dbd.range)).length >= 0; + const breakpointDecorationAlreadyAtCandidateLocation = this.breakpointDecorations.filter(bd => bd.range.equalsRange(dbd.range)).length > 0; return !breakpointDecorationAlreadyAtCandidateLocation; }); const candidateDecorationids = this.editor.deltaDecorations(this.candidateDecoraions.map(c => c.decorationId), desiredCandidateDecorations); @@ -436,10 +434,10 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { } try { - this.ignoreFirstBreakpointsChangeEvent = true; + this.ignoreBreakpointsChangeEvent = true; await this.debugService.updateBreakpoints(model.uri, data, true); } finally { - this.ignoreFirstBreakpointsChangeEvent = false; + this.ignoreBreakpointsChangeEvent = false; } } diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 0f53246d206..928e9d322ed 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -376,7 +376,7 @@ export class DebugSession implements IDebugSession { const response = await this.raw.breakpointLocations({ source, line: lineNumber }); const positions = response.body.breakpoints.map(bp => ({ lineNumber: bp.line, column: bp.column || 1 })); - return distinct(positions, p => p.toString()); + return distinct(positions, p => `${p.lineNumber}:${p.column}`); } return Promise.reject(new Error('no debug adapter')); } From a0e65a532d668d0cb96cf8e4815431ddc42e0c6f Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 18 Sep 2019 12:57:34 +0200 Subject: [PATCH 28/36] breakpoints: update decorations with a minor timeout to react once to multiple nearby events --- .../contrib/debug/browser/breakpointEditorContribution.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index d905e63ad14..29030b4537b 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -29,6 +29,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import { memoize } from 'vs/base/common/decorators'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { distinct } from 'vs/base/common/arrays'; +import { RunOnceScheduler } from 'vs/base/common/async'; const $ = dom.$; @@ -117,6 +118,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { private ignoreBreakpointsChangeEvent = false; private breakpointDecorations: IBreakpointDecoration[] = []; private candidateDecoraions: { decorationId: string, inlineWidget: InlineBreakpointWidget }[] = []; + private setDecorationsScheduler: RunOnceScheduler; constructor( private readonly editor: ICodeEditor, @@ -128,6 +130,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { ) { this.breakpointWidgetVisible = CONTEXT_BREAKPOINT_WIDGET_VISIBLE.bindTo(contextKeyService); this.registerListeners(); + this.setDecorationsScheduler = new RunOnceScheduler(() => this.setDecorations(), 30); } getId(): string { @@ -222,8 +225,8 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { await this.setDecorations(); })); this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(async () => { - if (!this.ignoreBreakpointsChangeEvent) { - await this.setDecorations(); + if (!this.ignoreBreakpointsChangeEvent && !this.setDecorationsScheduler.isScheduled()) { + this.setDecorationsScheduler.schedule(); } })); this.toDispose.push(this.editor.onDidChangeModelDecorations(() => this.onModelDecorationsChanged())); From c3d611c7ad601dbf095cc8e7ad8f9a0ec43c879d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Sep 2019 13:05:38 +0200 Subject: [PATCH 29/36] debt - introduce and use simpleIpcProxy --- src/vs/base/parts/ipc/common/ipc.ts | 26 ---------- src/vs/code/electron-main/app.ts | 3 +- .../electron-browser/electronService.ts | 5 +- .../electron-browser/mainProcessService.ts | 16 ------- src/vs/platform/ipc/node/simpleIpcProxy.ts | 48 +++++++++++++++++++ .../issue/electron-browser/issueService.ts | 5 +- 6 files changed, 56 insertions(+), 47 deletions(-) create mode 100644 src/vs/platform/ipc/node/simpleIpcProxy.ts diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index cb81bef96bc..392cfeaeae7 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -832,29 +832,3 @@ export class StaticRouter implements IClientRouter return await this.route(hub); } } - -export class SimpleServiceProxyChannel implements IServerChannel { - - private service: { [key: string]: unknown }; - - constructor(service: unknown) { - this.service = service as { [key: string]: unknown }; - } - - listen(_: unknown, event: string): Event { - throw new Error(`Events are currently unsupported by SimpleServiceProxyChannel: ${event}`); - } - - call(_: unknown, command: string, arg?: any): Promise { - const target = this.service[command]; - if (typeof target === 'function') { - if (Array.isArray(arg)) { - return target.apply(this.service, arg); - } - - return target.call(this.service, arg); - } - - throw new Error(`Call Not Found: ${command}`); - } -} diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 8f18f8d7915..e7eac8d5f3d 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -32,7 +32,8 @@ import { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc'; import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; -import { getDelayedChannel, StaticRouter, SimpleServiceProxyChannel } from 'vs/base/parts/ipc/common/ipc'; +import { getDelayedChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc'; +import { SimpleServiceProxyChannel } from 'vs/platform/ipc/node/simpleIpcProxy'; import product from 'vs/platform/product/common/product'; import { ProxyAuthHandler } from 'vs/code/electron-main/auth'; import { Disposable } from 'vs/base/common/lifecycle'; diff --git a/src/vs/platform/electron/electron-browser/electronService.ts b/src/vs/platform/electron/electron-browser/electronService.ts index 3633f642e94..0a88c90efc5 100644 --- a/src/vs/platform/electron/electron-browser/electronService.ts +++ b/src/vs/platform/electron/electron-browser/electronService.ts @@ -4,13 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { IElectronService } from 'vs/platform/electron/node/electron'; -import { IMainProcessService, createSimpleMainChannelProxy } from 'vs/platform/ipc/electron-browser/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; +import { createSimpleChannelProxy } from 'vs/platform/ipc/node/simpleIpcProxy'; export class ElectronService { _serviceBrand: undefined; constructor(@IMainProcessService mainProcessService: IMainProcessService) { - return createSimpleMainChannelProxy('electron', mainProcessService); + return createSimpleChannelProxy(mainProcessService.getChannel('electron')); } } diff --git a/src/vs/platform/ipc/electron-browser/mainProcessService.ts b/src/vs/platform/ipc/electron-browser/mainProcessService.ts index ca24035c1e2..c72b1c703d3 100644 --- a/src/vs/platform/ipc/electron-browser/mainProcessService.ts +++ b/src/vs/platform/ipc/electron-browser/mainProcessService.ts @@ -41,19 +41,3 @@ export class MainProcessService extends Disposable implements IMainProcessServic this.mainProcessConnection.registerChannel(channelName, channel); } } - -export function createSimpleMainChannelProxy(channelName: string, mainProcessService: IMainProcessService): T { - const channel = mainProcessService.getChannel(channelName); - - return new Proxy({}, { - get(_target, propKey, _receiver) { - if (typeof propKey === 'string') { - return function (...args: any[]) { - return channel.call(propKey, ...args); - }; - } - - throw new Error(`Unable to provide main channel proxy implementation for: ${String(propKey)} in ${channelName}`); - } - }) as T; -} diff --git a/src/vs/platform/ipc/node/simpleIpcProxy.ts b/src/vs/platform/ipc/node/simpleIpcProxy.ts new file mode 100644 index 00000000000..43e4cdbc31f --- /dev/null +++ b/src/vs/platform/ipc/node/simpleIpcProxy.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; + +// +// Use both `SimpleServiceProxyChannel` and `createSimpleChannelProxy` +// for a very basic process <=> process communication over methods. +// + +export class SimpleServiceProxyChannel implements IServerChannel { + + private service: { [key: string]: unknown }; + + constructor(service: unknown) { + this.service = service as { [key: string]: unknown }; + } + + listen(_: unknown, event: string): Event { + throw new Error(`Events are currently unsupported by SimpleServiceProxyChannel: ${event}`); + } + + call(_: unknown, command: string, args: any[]): Promise { + const target = this.service[command]; + if (typeof target === 'function') { + return target.apply(this.service, args); + } + + throw new Error(`Method not found: ${command}`); + } +} + +export function createSimpleChannelProxy(channel: IChannel): T { + return new Proxy({}, { + get(_target, propKey, _receiver) { + if (typeof propKey === 'string') { + return function (...args: any[]) { + return channel.call(propKey, args); + }; + } + + throw new Error(`Unable to provide main channel proxy implementation for: ${String(propKey)}`); + } + }) as T; +} diff --git a/src/vs/platform/issue/electron-browser/issueService.ts b/src/vs/platform/issue/electron-browser/issueService.ts index f7e67b7a3ac..72502019070 100644 --- a/src/vs/platform/issue/electron-browser/issueService.ts +++ b/src/vs/platform/issue/electron-browser/issueService.ts @@ -4,13 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { IIssueService } from 'vs/platform/issue/node/issue'; -import { IMainProcessService, createSimpleMainChannelProxy } from 'vs/platform/ipc/electron-browser/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; +import { createSimpleChannelProxy } from 'vs/platform/ipc/node/simpleIpcProxy'; export class IssueService { _serviceBrand: undefined; constructor(@IMainProcessService mainProcessService: IMainProcessService) { - return createSimpleMainChannelProxy('issue', mainProcessService); + return createSimpleChannelProxy(mainProcessService.getChannel('issue')); } } From afde7913ebfba84852df795bfae33f6609843a97 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Sep 2019 13:55:03 +0200 Subject: [PATCH 30/36] build - set no-sandbox everywhere (#81096) --- build/azure-pipelines/linux/product-build-linux.yml | 2 +- resources/linux/code-url-handler.desktop | 2 +- resources/linux/code.desktop | 4 ++-- scripts/test-integration.sh | 4 ++-- scripts/test.sh | 2 +- src/vs/code/node/cli.ts | 6 +++++- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 0d0b2608757..0d01ba8a608 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -100,7 +100,7 @@ steps: - script: | set -e - DISPLAY=:10 ./scripts/test.sh --build --tfs --no-sandbox "Unit Tests" + DISPLAY=:10 ./scripts/test.sh --build --tfs "Unit Tests" displayName: Run unit tests condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) diff --git a/resources/linux/code-url-handler.desktop b/resources/linux/code-url-handler.desktop index 7106e0e0969..b85525fbd04 100644 --- a/resources/linux/code-url-handler.desktop +++ b/resources/linux/code-url-handler.desktop @@ -2,7 +2,7 @@ Name=@@NAME_LONG@@ - URL Handler Comment=Code Editing. Redefined. GenericName=Text Editor -Exec=@@EXEC@@ --open-url %U +Exec=@@EXEC@@ --no-sandbox --open-url %U Icon=@@ICON@@ Type=Application NoDisplay=true diff --git a/resources/linux/code.desktop b/resources/linux/code.desktop index 1273bb2db7c..b975e1094a2 100644 --- a/resources/linux/code.desktop +++ b/resources/linux/code.desktop @@ -2,7 +2,7 @@ Name=@@NAME_LONG@@ Comment=Code Editing. Redefined. GenericName=Text Editor -Exec=@@EXEC@@ --unity-launch %F +Exec=@@EXEC@@ --no-sandbox --unity-launch %F Icon=@@ICON@@ Type=Application StartupNotify=false @@ -14,5 +14,5 @@ Keywords=vscode; [Desktop Action new-empty-window] Name=New Empty Window -Exec=@@EXEC@@ --new-window %F +Exec=@@EXEC@@ --no-sandbox --new-window %F Icon=@@ICON@@ diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index 143e2ff4eb4..2c3a571ffc7 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -8,7 +8,7 @@ if [[ "$OSTYPE" == "darwin"* ]]; then else ROOT=$(dirname $(dirname $(readlink -f $0))) VSCODEUSERDATADIR=`mktemp -d 2>/dev/null` - LINUX_NO_SANDBOX="--no-sandbox" # workaround Electron 6 issue on Linux when running tests in container + LINUX_NO_SANDBOX="--no-sandbox" # Electron 6 introduces a chrome-sandbox that requires root to run. This can fail. Disable sandbox via --no-sandbox. fi cd $ROOT @@ -34,7 +34,7 @@ else fi # Integration tests in AMD -./scripts/test.sh $LINUX_NO_SANDBOX --runGlob **/*.integrationTest.js "$@" +./scripts/test.sh --runGlob **/*.integrationTest.js "$@" # Tests in the extension host "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-api-tests/testWorkspace --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR diff --git a/scripts/test.sh b/scripts/test.sh index e1ed5aa65c7..630af4e53e5 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -34,5 +34,5 @@ else cd $ROOT ; \ ELECTRON_ENABLE_LOGGING=1 \ "$CODE" \ - test/electron/index.js "$@" + test/electron/index.js --no-sandbox "$@" # Electron 6 introduces a chrome-sandbox that requires root to run. This can fail. Disable sandbox via --no-sandbox. fi diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index 84997b65040..6dc1c356bac 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -15,7 +15,7 @@ import { whenDeleted, writeFileSync } from 'vs/base/node/pfs'; import { findFreePort, randomPort } from 'vs/base/node/ports'; import { resolveTerminalEncoding } from 'vs/base/node/encoding'; import * as iconv from 'iconv-lite'; -import { isWindows } from 'vs/base/common/platform'; +import { isWindows, isLinux } from 'vs/base/common/platform'; import { ProfilingSession, Target } from 'v8-inspect-profiler'; import { isString } from 'vs/base/common/types'; @@ -360,6 +360,10 @@ export async function main(argv: string[]): Promise { options['stdio'] = 'ignore'; } + if (isLinux) { + addArg(argv, '--no-sandbox'); // Electron 6 introduces a chrome-sandbox that requires root to run. This can fail. Disable sandbox via --no-sandbox + } + const child = spawn(process.execPath, argv.slice(2), options); if (args.wait && waitMarkerFilePath) { From fb7991a3fd5392e3f3abb5d2f5fd7e5a89ab4445 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Sep 2019 15:03:24 +0200 Subject: [PATCH 31/36] debt - bring back macOS custom title menu --- src/vs/platform/actions/common/actions.ts | 1 + .../browser/parts/titlebar/titlebarPart.ts | 82 +++++-------------- src/vs/workbench/electron-browser/window.ts | 50 ++++++++++- .../services/title/common/titleService.ts | 3 +- 4 files changed, 73 insertions(+), 63 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 7a409b98a5e..c7bcd9b066c 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -93,6 +93,7 @@ export const enum MenuId { SearchContext, StatusBarWindowIndicatorMenu, TouchBarContext, + TitleBarContext, ViewItemContext, ViewTitle, CommentThreadTitle, diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index f222733af9d..394f522bd67 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -4,18 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/titlebarpart'; -import { dirname, posix } from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import { Part } from 'vs/workbench/browser/part'; import { ITitleService, ITitleProperties } from 'vs/workbench/services/title/common/titleService'; import { getZoomFactor } from 'vs/base/browser/browser'; -import { IWindowService, IWindowsService, MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows'; +import { IWindowService, MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { IAction, Action } from 'vs/base/common/actions'; +import { IAction } from 'vs/base/common/actions'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; import * as nls from 'vs/nls'; import { EditorInput, toResource, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -29,7 +28,7 @@ import { trim } from 'vs/base/common/strings'; import { EventType, EventHelper, Dimension, isAncestor, hide, show, removeClass, addClass, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { template, getBaseLabel } from 'vs/base/common/labels'; +import { template } from 'vs/base/common/labels'; import { ILabelService } from 'vs/platform/label/common/label'; import { Event, Emitter } from 'vs/base/common/event'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -37,6 +36,9 @@ import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/bro import { RunOnceScheduler } from 'vs/base/common/async'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Schemas } from 'vs/base/common/network'; +import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; export class TitlebarPart extends Part implements ITitleService { @@ -71,7 +73,6 @@ export class TitlebarPart extends Part implements ITitleService { private lastLayoutDimensions: Dimension; private pendingTitle: string; - private representedFileName: string; private isInactive: boolean; @@ -80,11 +81,12 @@ export class TitlebarPart extends Part implements ITitleService { private titleUpdater: RunOnceScheduler = this._register(new RunOnceScheduler(() => this.doUpdateTitle(), 0)); + private contextMenu: IMenu; + constructor( @IContextMenuService private readonly contextMenuService: IContextMenuService, @IWindowService private readonly windowService: IWindowService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IWindowsService private readonly windowsService: IWindowsService, @IEditorService private readonly editorService: IEditorService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @@ -92,10 +94,14 @@ export class TitlebarPart extends Part implements ITitleService { @IThemeService themeService: IThemeService, @ILabelService private readonly labelService: ILabelService, @IStorageService storageService: IStorageService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @IMenuService menuService: IMenuService, + @IContextKeyService contextKeyService: IContextKeyService ) { super(Parts.TITLEBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); + this.contextMenu = this._register(menuService.createMenu(MenuId.TitleBarContext, contextKeyService)); + this.registerListeners(); } @@ -181,9 +187,6 @@ export class TitlebarPart extends Part implements ITitleService { // Apply to window this.windowService.setRepresentedFilename(path); - - // Keep for context menu - this.representedFileName = path; } private doUpdateTitle(): void { @@ -502,44 +505,16 @@ export class TitlebarPart extends Part implements ITitleService { const event = new StandardMouseEvent(e); const anchor = { x: event.posx, y: event.posy }; - // Show menu - const actions = this.getContextMenuActions(); - if (actions.length) { - this.contextMenuService.showContextMenu({ - getAnchor: () => anchor, - getActions: () => actions, - onHide: () => actions.forEach(a => a.dispose()) - }); - } - } - - private getContextMenuActions(): IAction[] { + // Fill in contributed actions const actions: IAction[] = []; + const actionsDisposable = createAndFillInContextMenuActions(this.contextMenu, undefined, actions, this.contextMenuService); - if (this.representedFileName) { - const segments = this.representedFileName.split(posix.sep); - for (let i = segments.length; i > 0; i--) { - const isFile = (i === segments.length); - - let pathOffset = i; - if (!isFile) { - pathOffset++; // for segments which are not the file name we want to open the folder - } - - const path = segments.slice(0, pathOffset).join(posix.sep); - - let label: string; - if (!isFile) { - label = getBaseLabel(dirname(path)); - } else { - label = getBaseLabel(path); - } - - actions.push(new ShowItemInFolderAction(path, label || posix.sep, this.windowsService)); - } - } - - return actions; + // Show it + this.contextMenuService.showContextMenu({ + getAnchor: () => anchor, + getActions: () => actions, + onHide: () => dispose(actionsDisposable) + }); } private adjustTitleMarginToCenter(): void { @@ -604,19 +579,6 @@ export class TitlebarPart extends Part implements ITitleService { } } -class ShowItemInFolderAction extends Action { - - constructor(private path: string, label: string, private windowsService: IWindowsService) { - super('showItemInFolder.action.id', label); - } - - run(): Promise { - if (this.path && this.windowsService) { } - return Promise.resolve(); - // return this.windowsService.showItemInFolder(URI.file(this.path)); - } -} - registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const titlebarActiveFg = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND); if (titlebarActiveFg) { diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 80eadd8163a..31cb64f2d5b 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -19,12 +19,12 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { ITitleService } from 'vs/workbench/services/title/common/titleService'; import { IWorkbenchThemeService, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService'; import * as browser from 'vs/base/browser/browser'; -import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IResourceInput } from 'vs/platform/editor/common/editor'; import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; import { ipcRenderer as ipc, webFrame, crashReporter, Event } from 'electron'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; -import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -56,6 +56,8 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { Schemas } from 'vs/base/common/network'; import { IElectronService } from 'vs/platform/electron/node/electron'; +import { posix, dirname } from 'vs/base/common/path'; +import { getBaseLabel } from 'vs/base/common/labels'; const TextInputActions: IAction[] = [ new Action('undo', nls.localize('undo', "Undo"), undefined, true, () => Promise.resolve(document.execCommand('undo'))), @@ -74,6 +76,8 @@ export class ElectronWindow extends Disposable { private readonly touchBarDisposables = this._register(new DisposableStore()); private lastInstalledTouchedBar: ICommandAction[][] | undefined; + private customTitleContextMenuDisposable = this._register(new DisposableStore()); + private previousConfiguredZoomLevel: number | undefined; private addFoldersScheduler: RunOnceScheduler; @@ -245,6 +249,11 @@ export class ElectronWindow extends Disposable { this._register(this.trackClosedWaitFiles(waitMarkerFile, resourcesToWaitFor)); } + + // macOS custom title menu + if (isMacintosh) { + this._register(this.editorService.onDidActiveEditorChange(() => this.provideCustomTitleContextMenu())); + } } private onDidVisibleEditorsChange(): void { @@ -308,6 +317,43 @@ export class ElectronWindow extends Disposable { } } + private provideCustomTitleContextMenu(): void { + + // Clear old menu + this.customTitleContextMenuDisposable.clear(); + + // Provide new menu if a file is opened and we are on a custom title + const fileResource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.file }); + if (!fileResource || getTitleBarStyle(this.configurationService, this.environmentService) !== 'custom') { + return; + } + + // Split up filepath into segments + const filePath = fileResource.fsPath; + const segments = filePath.split(posix.sep); + for (let i = segments.length; i > 0; i--) { + const isFile = (i === segments.length); + + let pathOffset = i; + if (!isFile) { + pathOffset++; // for segments which are not the file name we want to open the folder + } + + const path = segments.slice(0, pathOffset).join(posix.sep); + + let label: string; + if (!isFile) { + label = getBaseLabel(dirname(path)); + } else { + label = getBaseLabel(path); + } + + const commandId = `workbench.action.revealPathInFinder${i}`; + this.customTitleContextMenuDisposable.add(CommandsRegistry.registerCommand(commandId, () => this.electronService.showItemInFolder(path))); + this.customTitleContextMenuDisposable.add(MenuRegistry.appendMenuItem(MenuId.TitleBarContext, { command: { id: commandId, title: label || posix.sep }, order: -i })); + } + } + private create(): void { // Native menu controller diff --git a/src/vs/workbench/services/title/common/titleService.ts b/src/vs/workbench/services/title/common/titleService.ts index b3476f94deb..a827857a797 100644 --- a/src/vs/workbench/services/title/common/titleService.ts +++ b/src/vs/workbench/services/title/common/titleService.ts @@ -14,6 +14,7 @@ export interface ITitleProperties { } export interface ITitleService { + _serviceBrand: undefined; /** @@ -25,4 +26,4 @@ export interface ITitleService { * Update some environmental title properties. */ updateProperties(properties: ITitleProperties): void; -} \ No newline at end of file +} From 6789fa221879a840690f1cc66feaed6b47b444ed Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Wed, 18 Sep 2019 06:08:07 -0700 Subject: [PATCH 32/36] Make .icon inline-block --- src/vs/base/browser/ui/actionbar/actionbar.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/base/browser/ui/actionbar/actionbar.css b/src/vs/base/browser/ui/actionbar/actionbar.css index 08b4920ae47..f2eff2c7f66 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.css +++ b/src/vs/base/browser/ui/actionbar/actionbar.css @@ -40,6 +40,7 @@ transform: scale(1.272019649, 1.272019649); /* 1.272019649 = √φ */ } +.monaco-action-bar .action-item .icon, .monaco-action-bar .action-item .codicon { display: inline-block; } From bcaf8121316794c17e38c07682899c3dfbfadb6d Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 18 Sep 2019 16:27:01 +0200 Subject: [PATCH 33/36] breakpoints: make the candidate breakpoints look more clickable --- .../contrib/debug/browser/media/debug.contribution.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css index 69be074454c..08d90c5579a 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -21,6 +21,10 @@ background: url('breakpoint-disabled.svg') center center no-repeat; } +.monaco-editor .inline-breakpoint-widget.debug-breakpoint-disabled:hover { + background: url('breakpoint-hint.svg') center center no-repeat; +} + .debug-breakpoint-unverified, .monaco-editor .inline-breakpoint-widget.debug-breakpoint-unverified { background: url('breakpoint-unverified.svg') center center no-repeat; @@ -57,6 +61,7 @@ width: 1.3em; height: 1.3em; margin-left: 0.61em; + cursor: pointer; } .debug-function-breakpoint { From 6a54c8176fd60745ad7f2ba9dc89af4631752bae Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Wed, 18 Sep 2019 07:51:50 -0700 Subject: [PATCH 34/36] Update action label classes --- src/vs/base/browser/ui/splitview/panelview.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/splitview/panelview.css b/src/vs/base/browser/ui/splitview/panelview.css index d4def58461f..194ef683029 100644 --- a/src/vs/base/browser/ui/splitview/panelview.css +++ b/src/vs/base/browser/ui/splitview/panelview.css @@ -68,7 +68,8 @@ } /* TODO: actions should be part of the panel, but they aren't yet */ -.monaco-panel-view .panel > .panel-header > .actions .action-label.icon { +.monaco-panel-view .panel > .panel-header > .actions .action-label.icon, +.monaco-panel-view .panel > .panel-header > .actions .action-label.codicon { width: 28px; height: 22px; background-size: 16px; From 9c85ca3757812664246e009403320ea54bbdd89c Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 18 Sep 2019 18:00:52 +0200 Subject: [PATCH 35/36] breakpoint inline candidate polish --- .../browser/breakpointEditorContribution.ts | 87 ++++++++++--------- .../browser/media/debug.contribution.css | 4 + 2 files changed, 51 insertions(+), 40 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 29030b4537b..fda9ec6e6ea 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -35,7 +35,7 @@ const $ = dom.$; interface IBreakpointDecoration { decorationId: string; - breakpointId: string; + breakpoint: IBreakpoint; range: Range; inlineWidget?: InlineBreakpointWidget; } @@ -65,28 +65,6 @@ function createBreakpointDecorations(model: ITextModel, breakpoints: ReadonlyArr return result; } -async function createCandidateDecorations(model: ITextModel, lineNumbers: number[], debugService: IDebugService): Promise<{ range: Range; options: IModelDecorationOptions; }[]> { - const result: { range: Range; options: IModelDecorationOptions; }[] = []; - const session = debugService.getViewModel().focusedSession; - if (session && session.capabilities.supportsBreakpointLocationsRequest) { - await Promise.all(lineNumbers.map(async lineNumber => { - const positions = await session.breakpointsLocations(model.uri, lineNumber); - positions.forEach(p => { - result.push({ - range: new Range(p.lineNumber, p.column, p.lineNumber, p.column + 1), - options: { - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - beforeContentClassName: `debug-breakpoint-placeholder` - } - }); - }); - })); - } - - return result; -} - - function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoint, debugService: IDebugService): IModelDecorationOptions { const { className, message } = getBreakpointMessageAndClassName(debugService, breakpoint); let glyphMarginHoverMessage: MarkdownString | undefined; @@ -108,6 +86,39 @@ function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoi }; } +async function createCandidateDecorations(model: ITextModel, breakpointDecorations: IBreakpointDecoration[], debugService: IDebugService): Promise<{ range: Range; options: IModelDecorationOptions; breakpoint: IBreakpoint | undefined }[]> { + const lineNumbers = distinct(breakpointDecorations.map(bpd => bpd.range.startLineNumber)); + const result: { range: Range; options: IModelDecorationOptions; breakpoint: IBreakpoint | undefined }[] = []; + const session = debugService.getViewModel().focusedSession; + if (session && session.capabilities.supportsBreakpointLocationsRequest) { + await Promise.all(lineNumbers.map(async lineNumber => { + const positions = await session.breakpointsLocations(model.uri, lineNumber); + if (positions.length > 1) { + // Do not render candidates if there is only one, since it is already covered by the line breakpoint + positions.forEach(p => { + const range = new Range(p.lineNumber, p.column, p.lineNumber, p.column + 1); + const breakpointAtPosition = breakpointDecorations.filter(bpd => bpd.range.equalsRange(range)).pop(); + if (breakpointAtPosition && breakpointAtPosition.inlineWidget) { + // Space already occupied, do not render candidate. + return; + } + result.push({ + range, + options: { + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + beforeContentClassName: `debug-breakpoint-placeholder` + }, + breakpoint: breakpointAtPosition ? breakpointAtPosition.breakpoint : undefined + }); + }); + } + })); + } + + return result; +} + + class BreakpointEditorContribution implements IBreakpointEditorContribution { private breakpointHintDecoration: string[] = []; @@ -117,7 +128,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { private ignoreDecorationsChangedEvent = false; private ignoreBreakpointsChangeEvent = false; private breakpointDecorations: IBreakpointDecoration[] = []; - private candidateDecoraions: { decorationId: string, inlineWidget: InlineBreakpointWidget }[] = []; + private candidateDecorations: { decorationId: string, inlineWidget: InlineBreakpointWidget }[] = []; private setDecorationsScheduler: RunOnceScheduler; constructor( @@ -365,7 +376,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { return { decorationId, - breakpointId: breakpoint.getId(), + breakpoint, range: desiredBreakpointDecorations[index].range, inlineWidget }; @@ -376,19 +387,15 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { } // Set breakpoint candidate decorations - const lineNumbers = distinct(this.breakpointDecorations.map(bpd => bpd.range.startLineNumber)); - let desiredCandidateDecorations = await createCandidateDecorations(this.editor.getModel(), lineNumbers, this.debugService); - desiredCandidateDecorations = desiredCandidateDecorations.filter(dbd => { - const breakpointDecorationAlreadyAtCandidateLocation = this.breakpointDecorations.filter(bd => bd.range.equalsRange(dbd.range)).length > 0; - return !breakpointDecorationAlreadyAtCandidateLocation; - }); - const candidateDecorationids = this.editor.deltaDecorations(this.candidateDecoraions.map(c => c.decorationId), desiredCandidateDecorations); - this.candidateDecoraions.forEach(candidate => { + const desiredCandidateDecorations = await createCandidateDecorations(this.editor.getModel(), this.breakpointDecorations, this.debugService); + const candidateDecorationIds = this.editor.deltaDecorations(this.candidateDecorations.map(c => c.decorationId), desiredCandidateDecorations); + this.candidateDecorations.forEach(candidate => { candidate.inlineWidget.dispose(); }); - this.candidateDecoraions = candidateDecorationids.map((decorationId, index) => { + this.candidateDecorations = candidateDecorationIds.map((decorationId, index) => { const candidate = desiredCandidateDecorations[index]; - const inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, 'debug-breakpoint-disabled', undefined, this.debugService, this.contextMenuService, () => this.getContextMenuActions([], activeCodeEditor.getModel().uri, candidate.range.startLineNumber, candidate.range.startColumn)); + const cssClass = candidate.breakpoint ? undefined : 'debug-breakpoint-disabled'; + const inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, cssClass, candidate.breakpoint, this.debugService, this.contextMenuService, () => this.getContextMenuActions([], activeCodeEditor.getModel().uri, candidate.range.startLineNumber, candidate.range.startColumn)); return { decorationId, @@ -419,18 +426,16 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { } const data = new Map(); - const breakpoints = this.debugService.getModel().getBreakpoints(); for (let i = 0, len = this.breakpointDecorations.length; i < len; i++) { const breakpointDecoration = this.breakpointDecorations[i]; const decorationRange = model.getDecorationRange(breakpointDecoration.decorationId); // check if the line got deleted. if (decorationRange) { - const breakpoint = breakpoints.filter(bp => bp.getId() === breakpointDecoration.breakpointId).pop(); // since we know it is collapsed, it cannot grow to multiple lines - if (breakpoint) { - data.set(breakpoint.getId(), { + if (breakpointDecoration.breakpoint) { + data.set(breakpointDecoration.breakpoint.getId(), { lineNumber: decorationRange.startLineNumber, - column: breakpoint.column ? decorationRange.startColumn : undefined, + column: breakpointDecoration.breakpoint.column ? decorationRange.startColumn : undefined, }); } } @@ -544,6 +549,8 @@ class InlineBreakpointWidget implements IContentWidget, IDisposable { if (!this.range) { return null; } + // Workaround: since the content widget can not be placed before the first column we need to force the left position + dom.toggleClass(this.domNode, 'line-start', this.range.startColumn === 1); return { position: { lineNumber: this.range.startLineNumber, column: this.range.startColumn - 1 }, diff --git a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css index 08d90c5579a..4576abcbe9a 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -25,6 +25,10 @@ background: url('breakpoint-hint.svg') center center no-repeat; } +.monaco-editor .inline-breakpoint-widget.line-start { + left: -0.45em !important; +} + .debug-breakpoint-unverified, .monaco-editor .inline-breakpoint-widget.debug-breakpoint-unverified { background: url('breakpoint-unverified.svg') center center no-repeat; From b5a0ab0c94057eca2df3969306d94eac619f8eb4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Sep 2019 19:01:59 +0200 Subject: [PATCH 36/36] debt - introduce IHostService and distinguish between web and desktop --- .../electron-main/electronMainService.ts | 12 + src/vs/platform/electron/node/electron.ts | 3 + src/vs/platform/windows/common/windows.ts | 1 - src/vs/platform/windows/common/windowsIpc.ts | 1 - .../electron-browser/windowsService.ts | 4 - .../workbench/browser/web.simpleservices.ts | 6 - .../customEditor/browser/customEditorInput.ts | 2 +- .../electron-browser/startupTimings.ts | 4 +- .../gettingStarted/browser/telemetryOptOut.ts | 4 +- .../host/browser/browserHostService.ts | 20 + .../workbench/services/host/browser/host.ts | 22 + .../electron-browser/desktopHostService.ts | 23 + .../browser/browserTextFileService.ts | 62 + .../textfile/browser/textFileService.ts | 1085 ++++++++++++++++- .../textfile/common/textFileService.ts | 1069 ---------------- .../nativeTextFileService.ts} | 6 +- .../textfile/test/textFileService.io.test.ts | 6 +- .../textfile/test/textFileService.test.ts | 8 +- .../timer/electron-browser/timerService.ts | 6 +- .../browser/workspaceEditingService.ts | 6 +- .../workbench/test/workbenchTestServices.ts | 23 +- src/vs/workbench/workbench.common.main.ts | 1 + src/vs/workbench/workbench.desktop.main.ts | 3 +- src/vs/workbench/workbench.web.main.ts | 2 +- 24 files changed, 1230 insertions(+), 1149 deletions(-) create mode 100644 src/vs/workbench/services/host/browser/browserHostService.ts create mode 100644 src/vs/workbench/services/host/browser/host.ts create mode 100644 src/vs/workbench/services/host/electron-browser/desktopHostService.ts create mode 100644 src/vs/workbench/services/textfile/browser/browserTextFileService.ts delete mode 100644 src/vs/workbench/services/textfile/common/textFileService.ts rename src/vs/workbench/services/textfile/{node/textFileService.ts => electron-browser/nativeTextFileService.ts} (98%) diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts index 1846a5cb086..d51d4b65d3e 100644 --- a/src/vs/platform/electron/electron-main/electronMainService.ts +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -16,10 +16,20 @@ export class ElectronMainService implements IElectronService { ) { } + //#region Window + private get window(): ICodeWindow | undefined { return this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow(); } + async windowCount(): Promise { + return this.windowsMainService.getWindowCount(); + } + + //#endregion + + //#region Other + async showMessageBox(options: MessageBoxOptions): Promise { const result = await this.windowsMainService.showMessageBox(options, this.window); @@ -32,4 +42,6 @@ export class ElectronMainService implements IElectronService { async showItemInFolder(path: string): Promise { shell.showItemInFolder(path); } + + //#endregion } diff --git a/src/vs/platform/electron/node/electron.ts b/src/vs/platform/electron/node/electron.ts index 5411269d98e..8257bf0eee2 100644 --- a/src/vs/platform/electron/node/electron.ts +++ b/src/vs/platform/electron/node/electron.ts @@ -12,6 +12,9 @@ export interface IElectronService { _serviceBrand: undefined; + // Window + windowCount(): Promise; + // Dialogs showMessageBox(options: MessageBoxOptions): Promise; diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index a34ebe2978c..8d713a46ec6 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -155,7 +155,6 @@ export interface IWindowsService { openNewWindow(options?: INewWindowOptions): Promise; openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise; getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]>; - getWindowCount(): Promise; getActiveWindowId(): Promise; // This needs to be handled from browser process to prevent diff --git a/src/vs/platform/windows/common/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts index 6534f2f56d3..a17937f4455 100644 --- a/src/vs/platform/windows/common/windowsIpc.ts +++ b/src/vs/platform/windows/common/windowsIpc.ts @@ -104,7 +104,6 @@ export class WindowsChannel implements IServerChannel { case 'openNewWindow': return this.service.openNewWindow(arg); case 'openExtensionDevelopmentHostWindow': return this.service.openExtensionDevelopmentHostWindow(arg[0], arg[1]); case 'getWindows': return this.service.getWindows(); - case 'getWindowCount': return this.service.getWindowCount(); case 'relaunch': return this.service.relaunch(arg[0]); case 'whenSharedProcessReady': return this.service.whenSharedProcessReady(); case 'toggleSharedProcess': return this.service.toggleSharedProcess(); diff --git a/src/vs/platform/windows/electron-browser/windowsService.ts b/src/vs/platform/windows/electron-browser/windowsService.ts index 4e983130529..92808158543 100644 --- a/src/vs/platform/windows/electron-browser/windowsService.ts +++ b/src/vs/platform/windows/electron-browser/windowsService.ts @@ -222,10 +222,6 @@ export class WindowsService implements IWindowsService { return result; } - getWindowCount(): Promise { - return this.channel.call('getWindowCount'); - } - getActiveWindowId(): Promise { return this.channel.call('getActiveWindowId'); } diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts index a0fe7aa1915..53fb58d10a2 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -318,8 +318,6 @@ registerSingleton(IWindowService, SimpleWindowService); export class SimpleWindowsService implements IWindowsService { _serviceBrand: undefined; - windowCount = 1; - readonly onWindowOpen: Event = Event.None; readonly onWindowFocus: Event = Event.None; readonly onWindowBlur: Event = Event.None; @@ -461,10 +459,6 @@ export class SimpleWindowsService implements IWindowsService { return Promise.resolve([]); } - getWindowCount(): Promise { - return Promise.resolve(this.windowCount); - } - newWindowTab(): Promise { return Promise.resolve(); } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 776eb29464f..2a5c59429a3 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -17,7 +17,7 @@ import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webvi import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { IWebviewEditorService } from 'vs/workbench/contrib/webview/browser/webviewEditorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { promptSave } from 'vs/workbench/services/textfile/common/textFileService'; +import { promptSave } from 'vs/workbench/services/textfile/browser/textFileService'; export class CustomFileEditorInput extends WebviewInput { diff --git a/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts b/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts index c4d1c7cd4c1..e0c7f7f6bd2 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts @@ -21,6 +21,7 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { didUseCachedData, ITimerService } from 'vs/workbench/services/timer/electron-browser/timerService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { getEntries } from 'vs/base/common/performance'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export class StartupTimings implements IWorkbenchContribution { @@ -34,6 +35,7 @@ export class StartupTimings implements IWorkbenchContribution { @ILifecycleService private readonly _lifecycleService: ILifecycleService, @IUpdateService private readonly _updateService: IUpdateService, @IEnvironmentService private readonly _envService: IEnvironmentService, + @IHostService private readonly _hostService: IHostService ) { // this._report().catch(onUnexpectedError); @@ -91,7 +93,7 @@ export class StartupTimings implements IWorkbenchContribution { if (this._lifecycleService.startupKind !== StartupKind.NewWindow) { return false; } - if (await this._windowsService.getWindowCount() !== 1) { + if (await this._hostService.windowCount !== 1) { return false; } const activeViewlet = this._viewletService.getActiveViewlet(); diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/telemetryOptOut.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/telemetryOptOut.ts index 9752407f3f8..c942edef9c9 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/telemetryOptOut.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/telemetryOptOut.ts @@ -18,6 +18,7 @@ import { language, locale } from 'vs/base/common/platform'; import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IProductService } from 'vs/platform/product/common/productService'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export class TelemetryOptOut implements IWorkbenchContribution { @@ -30,6 +31,7 @@ export class TelemetryOptOut implements IWorkbenchContribution { @INotificationService private readonly notificationService: INotificationService, @IWindowService windowService: IWindowService, @IWindowsService windowsService: IWindowsService, + @IHostService hostService: IHostService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IExperimentService private readonly experimentService: IExperimentService, @IConfigurationService private readonly configurationService: IConfigurationService, @@ -42,7 +44,7 @@ export class TelemetryOptOut implements IWorkbenchContribution { const experimentId = 'telemetryOptOut'; Promise.all([ windowService.isFocused(), - windowsService.getWindowCount(), + hostService.windowCount, experimentService.getExperimentById(experimentId) ]).then(([focused, count, experimentState]) => { if (!focused && count > 1) { diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts new file mode 100644 index 00000000000..a452c20287c --- /dev/null +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +export class BrowserHostService implements IHostService { + + _serviceBrand: undefined; + + //#region Window + + readonly windowCount = Promise.resolve(1); + + //#endregion +} + +registerSingleton(IHostService, BrowserHostService, true); diff --git a/src/vs/workbench/services/host/browser/host.ts b/src/vs/workbench/services/host/browser/host.ts new file mode 100644 index 00000000000..735b21869e9 --- /dev/null +++ b/src/vs/workbench/services/host/browser/host.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IHostService = createDecorator('hostService'); + +export interface IHostService { + + _serviceBrand: undefined; + + //#region Window + + /** + * The number of windows that belong to the current client session. + */ + readonly windowCount: Promise; + + //#endregion +} diff --git a/src/vs/workbench/services/host/electron-browser/desktopHostService.ts b/src/vs/workbench/services/host/electron-browser/desktopHostService.ts new file mode 100644 index 00000000000..833310b965c --- /dev/null +++ b/src/vs/workbench/services/host/electron-browser/desktopHostService.ts @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +export class DesktopHostService implements IHostService { + + _serviceBrand: undefined; + + constructor(@IElectronService private readonly electronService: IElectronService) { } + + //#region Window + + get windowCount() { return this.electronService.windowCount(); } + + //#endregion +} + +registerSingleton(IHostService, DesktopHostService, true); diff --git a/src/vs/workbench/services/textfile/browser/browserTextFileService.ts b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts new file mode 100644 index 00000000000..4cdfa523db0 --- /dev/null +++ b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AbstractTextFileService } from 'vs/workbench/services/textfile/browser/textFileService'; +import { ITextFileService, IResourceEncodings, IResourceEncoding, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; + +export class BrowserTextFileService extends AbstractTextFileService { + + readonly encoding: IResourceEncodings = { + getPreferredWriteEncoding(): IResourceEncoding { + return { encoding: 'utf8', hasBOM: false }; + } + }; + + protected onBeforeShutdown(reason: ShutdownReason): boolean { + // Web: we cannot perform long running in the shutdown phase + // As such we need to check sync if there are any dirty files + // that have not been backed up yet and then prevent the shutdown + // if that is the case. + return this.doBeforeShutdownSync(); + } + + private doBeforeShutdownSync(): boolean { + if (this.models.getAll().some(model => model.hasState(ModelState.PENDING_SAVE) || model.hasState(ModelState.PENDING_AUTO_SAVE))) { + return true; // files are pending to be saved: veto + } + + const dirtyResources = this.getDirty(); + if (!dirtyResources.length) { + return false; // no dirty: no veto + } + + if (!this.isHotExitEnabled) { + return true; // dirty without backup: veto + } + + for (const dirtyResource of dirtyResources) { + let hasBackup = false; + + if (this.fileService.canHandleResource(dirtyResource)) { + const model = this.models.get(dirtyResource); + hasBackup = !!(model && model.hasBackup()); + } else if (dirtyResource.scheme === Schemas.untitled) { + hasBackup = this.untitledEditorService.hasBackup(dirtyResource); + } + + if (!hasBackup) { + console.warn('Unload prevented: pending backups'); + return true; // dirty without backup: veto + } + } + + return false; // dirty with backups: no veto + } +} + +registerSingleton(ITextFileService, BrowserTextFileService); diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index ed7f5273046..0925a9f3504 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -3,60 +3,1067 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { TextFileService } from 'vs/workbench/services/textfile/common/textFileService'; -import { ITextFileService, IResourceEncodings, IResourceEncoding, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; +import * as nls from 'vs/nls'; +import { URI } from 'vs/base/common/uri'; +import * as errors from 'vs/base/common/errors'; +import * as objects from 'vs/base/common/objects'; +import { Event, Emitter } from 'vs/base/common/event'; +import * as platform from 'vs/base/common/platform'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, AutoSaveContext, IWillMoveEvent, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; +import { ConfirmResult, IRevertOptions } from 'vs/workbench/common/editor'; +import { ILifecycleService, ShutdownReason, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IFileService, IFilesConfiguration, FileOperationError, FileOperationResult, AutoSaveConfiguration, HotExitConfiguration, IFileStatWithMetadata, ICreateFileOptions } from 'vs/platform/files/common/files'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel'; +import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ResourceMap } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { createTextBufferFactoryFromSnapshot, createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { isEqualOrParent, isEqual, joinPath, dirname, extname, basename, toLocalResource } from 'vs/base/common/resources'; +import { getConfirmMessage, IDialogService, IFileDialogService, ISaveDialogOptions, IConfirmation } from 'vs/platform/dialogs/common/dialogs'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { coalesce } from 'vs/base/common/arrays'; +import { trim } from 'vs/base/common/strings'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { ITextSnapshot } from 'vs/editor/common/model'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; -export class BrowserTextFileService extends TextFileService { +/** + * The workbench file service implementation implements the raw file service spec and adds additional methods on top. + */ +export abstract class AbstractTextFileService extends Disposable implements ITextFileService { - readonly encoding: IResourceEncodings = { - getPreferredWriteEncoding(): IResourceEncoding { - return { encoding: 'utf8', hasBOM: false }; - } - }; + _serviceBrand: undefined; - protected onBeforeShutdown(reason: ShutdownReason): boolean { - // Web: we cannot perform long running in the shutdown phase - // As such we need to check sync if there are any dirty files - // that have not been backed up yet and then prevent the shutdown - // if that is the case. - return this.doBeforeShutdownSync(); + private readonly _onAutoSaveConfigurationChange: Emitter = this._register(new Emitter()); + readonly onAutoSaveConfigurationChange: Event = this._onAutoSaveConfigurationChange.event; + + private readonly _onFilesAssociationChange: Emitter = this._register(new Emitter()); + readonly onFilesAssociationChange: Event = this._onFilesAssociationChange.event; + + private readonly _onWillMove = this._register(new Emitter()); + readonly onWillMove: Event = this._onWillMove.event; + + private _models: TextFileEditorModelManager; + get models(): ITextFileEditorModelManager { return this._models; } + + abstract get encoding(): IResourceEncodings; + + private currentFilesAssociationConfig: { [key: string]: string; }; + private configuredAutoSaveDelay?: number; + private configuredAutoSaveOnFocusChange: boolean | undefined; + private configuredAutoSaveOnWindowChange: boolean | undefined; + private configuredHotExit: string | undefined; + private autoSaveContext: IContextKey; + + constructor( + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IFileService protected readonly fileService: IFileService, + @IUntitledEditorService protected readonly untitledEditorService: IUntitledEditorService, + @ILifecycleService private readonly lifecycleService: ILifecycleService, + @IInstantiationService protected instantiationService: IInstantiationService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IModeService private readonly modeService: IModeService, + @IModelService private readonly modelService: IModelService, + @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, + @INotificationService private readonly notificationService: INotificationService, + @IBackupFileService private readonly backupFileService: IBackupFileService, + @IHostService private readonly hostService: IHostService, + @IHistoryService private readonly historyService: IHistoryService, + @IContextKeyService contextKeyService: IContextKeyService, + @IDialogService private readonly dialogService: IDialogService, + @IFileDialogService private readonly fileDialogService: IFileDialogService, + @IEditorService private readonly editorService: IEditorService, + @ITextResourceConfigurationService protected readonly textResourceConfigurationService: ITextResourceConfigurationService + ) { + super(); + + this._models = this._register(instantiationService.createInstance(TextFileEditorModelManager)); + this.autoSaveContext = AutoSaveContext.bindTo(contextKeyService); + + const configuration = configurationService.getValue(); + this.currentFilesAssociationConfig = configuration && configuration.files && configuration.files.associations; + + this.onFilesConfigurationChange(configuration); + + this.registerListeners(); } - private doBeforeShutdownSync(): boolean { - if (this.models.getAll().some(model => model.hasState(ModelState.PENDING_SAVE) || model.hasState(ModelState.PENDING_AUTO_SAVE))) { - return true; // files are pending to be saved: veto - } + //#region event handling - const dirtyResources = this.getDirty(); - if (!dirtyResources.length) { - return false; // no dirty: no veto - } + private registerListeners(): void { - if (!this.isHotExitEnabled) { - return true; // dirty without backup: veto - } + // Lifecycle + this.lifecycleService.onBeforeShutdown(event => event.veto(this.onBeforeShutdown(event.reason))); + this.lifecycleService.onShutdown(this.dispose, this); - for (const dirtyResource of dirtyResources) { - let hasBackup = false; + // Files configuration changes + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('files')) { + this.onFilesConfigurationChange(this.configurationService.getValue()); + } + })); + } - if (this.fileService.canHandleResource(dirtyResource)) { - const model = this.models.get(dirtyResource); - hasBackup = !!(model && model.hasBackup()); - } else if (dirtyResource.scheme === Schemas.untitled) { - hasBackup = this.untitledEditorService.hasBackup(dirtyResource); + protected onBeforeShutdown(reason: ShutdownReason): boolean | Promise { + + // Dirty files need treatment on shutdown + const dirty = this.getDirty(); + if (dirty.length) { + + // If auto save is enabled, save all files and then check again for dirty files + // We DO NOT run any save participant if we are in the shutdown phase for performance reasons + if (this.getAutoSaveMode() !== AutoSaveMode.OFF) { + return this.saveAll(false /* files only */, { skipSaveParticipants: true }).then(() => { + + // If we still have dirty files, we either have untitled ones or files that cannot be saved + const remainingDirty = this.getDirty(); + if (remainingDirty.length) { + return this.handleDirtyBeforeShutdown(remainingDirty, reason); + } + + return false; + }); } - if (!hasBackup) { - console.warn('Unload prevented: pending backups'); - return true; // dirty without backup: veto + // Auto save is not enabled + return this.handleDirtyBeforeShutdown(dirty, reason); + } + + // No dirty files: no veto + return this.noVeto({ cleanUpBackups: true }); + } + + private handleDirtyBeforeShutdown(dirty: URI[], reason: ShutdownReason): boolean | Promise { + + // If hot exit is enabled, backup dirty files and allow to exit without confirmation + if (this.isHotExitEnabled) { + return this.backupBeforeShutdown(dirty, reason).then(didBackup => { + if (didBackup) { + return this.noVeto({ cleanUpBackups: false }); // no veto and no backup cleanup (since backup was successful) + } + + // since a backup did not happen, we have to confirm for the dirty files now + return this.confirmBeforeShutdown(); + }, error => { + this.notificationService.error(nls.localize('files.backup.failSave', "Files that are dirty could not be written to the backup location (Error: {0}). Try saving your files first and then exit.", error.message)); + + return true; // veto, the backups failed + }); + } + + // Otherwise just confirm from the user what to do with the dirty files + return this.confirmBeforeShutdown(); + } + + private async backupBeforeShutdown(dirtyToBackup: URI[], reason: ShutdownReason): Promise { + const windowCount = await this.hostService.windowCount; + + // When quit is requested skip the confirm callback and attempt to backup all workspaces. + // When quit is not requested the confirm callback should be shown when the window being + // closed is the only VS Code window open, except for on Mac where hot exit is only + // ever activated when quit is requested. + + let doBackup: boolean | undefined; + switch (reason) { + case ShutdownReason.CLOSE: + if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.configuredHotExit === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { + doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured + } else if (windowCount > 1 || platform.isMacintosh) { + doBackup = false; // do not backup if a window is closed that does not cause quitting of the application + } else { + doBackup = true; // backup if last window is closed on win/linux where the application quits right after + } + break; + + case ShutdownReason.QUIT: + doBackup = true; // backup because next start we restore all backups + break; + + case ShutdownReason.RELOAD: + doBackup = true; // backup because after window reload, backups restore + break; + + case ShutdownReason.LOAD: + if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.configuredHotExit === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { + doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured + } else { + doBackup = false; // do not backup because we are switching contexts + } + break; + } + + if (!doBackup) { + return false; + } + + await this.backupAll(dirtyToBackup); + + return true; + } + + private backupAll(dirtyToBackup: URI[]): Promise { + + // split up between files and untitled + const filesToBackup: ITextFileEditorModel[] = []; + const untitledToBackup: URI[] = []; + dirtyToBackup.forEach(dirty => { + if (this.fileService.canHandleResource(dirty)) { + const model = this.models.get(dirty); + if (model) { + filesToBackup.push(model); + } + } else if (dirty.scheme === Schemas.untitled) { + untitledToBackup.push(dirty); + } + }); + + return this.doBackupAll(filesToBackup, untitledToBackup); + } + + private async doBackupAll(dirtyFileModels: ITextFileEditorModel[], untitledResources: URI[]): Promise { + + // Handle file resources first + await Promise.all(dirtyFileModels.map(model => model.backup())); + + // Handle untitled resources + await Promise.all(untitledResources + .filter(untitled => this.untitledEditorService.exists(untitled)) + .map(async untitled => (await this.untitledEditorService.loadOrCreate({ resource: untitled })).backup())); + } + + private async confirmBeforeShutdown(): Promise { + const confirm = await this.confirmSave(); + + // Save + if (confirm === ConfirmResult.SAVE) { + const result = await this.saveAll(true /* includeUntitled */, { skipSaveParticipants: true }); + + if (result.results.some(r => !r.success)) { + return true; // veto if some saves failed + } + + return this.noVeto({ cleanUpBackups: true }); + } + + // Don't Save + else if (confirm === ConfirmResult.DONT_SAVE) { + + // Make sure to revert untitled so that they do not restore + // see https://github.com/Microsoft/vscode/issues/29572 + this.untitledEditorService.revertAll(); + + return this.noVeto({ cleanUpBackups: true }); + } + + // Cancel + else if (confirm === ConfirmResult.CANCEL) { + return true; // veto + } + + return false; + } + + private noVeto(options: { cleanUpBackups: boolean }): boolean | Promise { + if (!options.cleanUpBackups) { + return false; + } + + if (this.lifecycleService.phase < LifecyclePhase.Restored) { + return false; // if editors have not restored, we are not up to speed with backups and thus should not clean them + } + + return this.cleanupBackupsBeforeShutdown().then(() => false, () => false); + } + + protected async cleanupBackupsBeforeShutdown(): Promise { + if (this.environmentService.isExtensionDevelopment) { + return; + } + + await this.backupFileService.discardAllWorkspaceBackups(); + } + + protected onFilesConfigurationChange(configuration: IFilesConfiguration): void { + const wasAutoSaveEnabled = (this.getAutoSaveMode() !== AutoSaveMode.OFF); + + const autoSaveMode = (configuration && configuration.files && configuration.files.autoSave) || AutoSaveConfiguration.OFF; + this.autoSaveContext.set(autoSaveMode); + switch (autoSaveMode) { + case AutoSaveConfiguration.AFTER_DELAY: + this.configuredAutoSaveDelay = configuration && configuration.files && configuration.files.autoSaveDelay; + this.configuredAutoSaveOnFocusChange = false; + this.configuredAutoSaveOnWindowChange = false; + break; + + case AutoSaveConfiguration.ON_FOCUS_CHANGE: + this.configuredAutoSaveDelay = undefined; + this.configuredAutoSaveOnFocusChange = true; + this.configuredAutoSaveOnWindowChange = false; + break; + + case AutoSaveConfiguration.ON_WINDOW_CHANGE: + this.configuredAutoSaveDelay = undefined; + this.configuredAutoSaveOnFocusChange = false; + this.configuredAutoSaveOnWindowChange = true; + break; + + default: + this.configuredAutoSaveDelay = undefined; + this.configuredAutoSaveOnFocusChange = false; + this.configuredAutoSaveOnWindowChange = false; + break; + } + + // Emit as event + this._onAutoSaveConfigurationChange.fire(this.getAutoSaveConfiguration()); + + // save all dirty when enabling auto save + if (!wasAutoSaveEnabled && this.getAutoSaveMode() !== AutoSaveMode.OFF) { + this.saveAll(); + } + + // Check for change in files associations + const filesAssociation = configuration && configuration.files && configuration.files.associations; + if (!objects.equals(this.currentFilesAssociationConfig, filesAssociation)) { + this.currentFilesAssociationConfig = filesAssociation; + this._onFilesAssociationChange.fire(); + } + + // Hot exit + const hotExitMode = configuration && configuration.files && configuration.files.hotExit; + if (hotExitMode === HotExitConfiguration.OFF || hotExitMode === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { + this.configuredHotExit = hotExitMode; + } else { + this.configuredHotExit = HotExitConfiguration.ON_EXIT; + } + } + + //#endregion + + //#region primitives (read, create, move, delete, update) + + async read(resource: URI, options?: IReadTextFileOptions): Promise { + const content = await this.fileService.readFile(resource, options); + + // in case of acceptTextOnly: true, we check the first + // chunk for possibly being binary by looking for 0-bytes + // we limit this check to the first 512 bytes + this.validateBinary(content.value, options); + + return { + ...content, + encoding: 'utf8', + value: content.value.toString() + }; + } + + async readStream(resource: URI, options?: IReadTextFileOptions): Promise { + const stream = await this.fileService.readFileStream(resource, options); + + // in case of acceptTextOnly: true, we check the first + // chunk for possibly being binary by looking for 0-bytes + // we limit this check to the first 512 bytes + let checkedForBinary = false; + const throwOnBinary = (data: VSBuffer): Error | undefined => { + if (!checkedForBinary) { + checkedForBinary = true; + + this.validateBinary(data, options); + } + + return undefined; + }; + + return { + ...stream, + encoding: 'utf8', + value: await createTextBufferFactoryFromStream(stream.value, undefined, options && options.acceptTextOnly ? throwOnBinary : undefined) + }; + } + + private validateBinary(buffer: VSBuffer, options?: IReadTextFileOptions): void { + if (!options || !options.acceptTextOnly) { + return; // no validation needed + } + + // in case of acceptTextOnly: true, we check the first + // chunk for possibly being binary by looking for 0-bytes + // we limit this check to the first 512 bytes + for (let i = 0; i < buffer.byteLength && i < 512; i++) { + if (buffer.readUInt8(i) === 0) { + throw new TextFileOperationError(nls.localize('fileBinaryError', "File seems to be binary and cannot be opened as text"), TextFileOperationResult.FILE_IS_BINARY, options); + } + } + } + + async create(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise { + const stat = await this.doCreate(resource, value, options); + + // If we had an existing model for the given resource, load + // it again to make sure it is up to date with the contents + // we just wrote into the underlying resource by calling + // revert() + const existingModel = this.models.get(resource); + if (existingModel && !existingModel.isDisposed()) { + await existingModel.revert(); + } + + return stat; + } + + protected doCreate(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise { + return this.fileService.createFile(resource, toBufferOrReadable(value), options); + } + + async write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise { + return this.fileService.writeFile(resource, toBufferOrReadable(value), options); + } + + async delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise { + const dirtyFiles = this.getDirty().filter(dirty => isEqualOrParent(dirty, resource)); + + await this.revertAll(dirtyFiles, { soft: true }); + + return this.fileService.del(resource, options); + } + + async move(source: URI, target: URI, overwrite?: boolean): Promise { + + // await onWillMove event joiners + await this.notifyOnWillMove(source, target); + + // find all models that related to either source or target (can be many if resource is a folder) + const sourceModels: ITextFileEditorModel[] = []; + const conflictingModels: ITextFileEditorModel[] = []; + for (const model of this.getFileModels()) { + const resource = model.getResource(); + + if (isEqualOrParent(resource, target, false /* do not ignorecase, see https://github.com/Microsoft/vscode/issues/56384 */)) { + conflictingModels.push(model); + } + + if (isEqualOrParent(resource, source)) { + sourceModels.push(model); } } - return false; // dirty with backups: no veto + // remember each source model to load again after move is done + // with optional content to restore if it was dirty + type ModelToRestore = { resource: URI; snapshot?: ITextSnapshot }; + const modelsToRestore: ModelToRestore[] = []; + for (const sourceModel of sourceModels) { + const sourceModelResource = sourceModel.getResource(); + + // If the source is the actual model, just use target as new resource + let modelToRestoreResource: URI; + if (isEqual(sourceModelResource, source)) { + modelToRestoreResource = target; + } + + // Otherwise a parent folder of the source is being moved, so we need + // to compute the target resource based on that + else { + modelToRestoreResource = joinPath(target, sourceModelResource.path.substr(source.path.length + 1)); + } + + const modelToRestore: ModelToRestore = { resource: modelToRestoreResource }; + if (sourceModel.isDirty()) { + modelToRestore.snapshot = sourceModel.createSnapshot(); + } + + modelsToRestore.push(modelToRestore); + } + + // in order to move, we need to soft revert all dirty models, + // both from the source as well as the target if any + const dirtyModels = [...sourceModels, ...conflictingModels].filter(model => model.isDirty()); + await this.revertAll(dirtyModels.map(dirtyModel => dirtyModel.getResource()), { soft: true }); + + // now we can rename the source to target via file operation + let stat: IFileStatWithMetadata; + try { + stat = await this.fileService.move(source, target, overwrite); + } catch (error) { + + // in case of any error, ensure to set dirty flag back + dirtyModels.forEach(dirtyModel => dirtyModel.makeDirty()); + + throw error; + } + + // finally, restore models that we had loaded previously + await Promise.all(modelsToRestore.map(async modelToRestore => { + + // restore the model, forcing a reload. this is important because + // we know the file has changed on disk after the move and the + // model might have still existed with the previous state. this + // ensures we are not tracking a stale state. + const restoredModel = await this.models.loadOrCreate(modelToRestore.resource, { reload: { async: false } }); + + // restore previous dirty content if any and ensure to mark + // the model as dirty + if (modelToRestore.snapshot && restoredModel.isResolved()) { + this.modelService.updateModel(restoredModel.textEditorModel, createTextBufferFactoryFromSnapshot(modelToRestore.snapshot)); + + restoredModel.makeDirty(); + } + })); + + return stat; + } + + private async notifyOnWillMove(source: URI, target: URI): Promise { + const waitForPromises: Promise[] = []; + + // fire event + this._onWillMove.fire({ + oldResource: source, + newResource: target, + waitUntil(promise: Promise) { + waitForPromises.push(promise.then(undefined, errors.onUnexpectedError)); + } + }); + + // prevent async waitUntil-calls + Object.freeze(waitForPromises); + + await Promise.all(waitForPromises); + } + + //#endregion + + //#region save/revert + + async save(resource: URI, options?: ISaveOptions): Promise { + + // Run a forced save if we detect the file is not dirty so that save participants can still run + if (options && options.force && this.fileService.canHandleResource(resource) && !this.isDirty(resource)) { + const model = this._models.get(resource); + if (model) { + options.reason = SaveReason.EXPLICIT; + + await model.save(options); + + return !model.isDirty(); + } + } + + const result = await this.saveAll([resource], options); + + return result.results.length === 1 && !!result.results[0].success; + } + + async confirmSave(resources?: URI[]): Promise { + if (this.environmentService.isExtensionDevelopment) { + if (!this.environmentService.args['extension-development-confirm-save']) { + return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) + } + } + + const resourcesToConfirm = this.getDirty(resources); + if (resourcesToConfirm.length === 0) { + return ConfirmResult.DONT_SAVE; + } + return promptSave(this.dialogService, resourcesToConfirm); + } + + async confirmOverwrite(resource: URI): Promise { + const confirm: IConfirmation = { + message: nls.localize('confirmOverwrite', "'{0}' already exists. Do you want to replace it?", basename(resource)), + detail: nls.localize('irreversible', "A file or folder with the same name already exists in the folder {0}. Replacing it will overwrite its current contents.", basename(dirname(resource))), + primaryButton: nls.localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"), + type: 'warning' + }; + + return (await this.dialogService.confirm(confirm)).confirmed; + } + + saveAll(includeUntitled?: boolean, options?: ISaveOptions): Promise; + saveAll(resources: URI[], options?: ISaveOptions): Promise; + saveAll(arg1?: boolean | URI[], options?: ISaveOptions): Promise { + + // get all dirty + let toSave: URI[] = []; + if (Array.isArray(arg1)) { + toSave = this.getDirty(arg1); + } else { + toSave = this.getDirty(); + } + + // split up between files and untitled + const filesToSave: URI[] = []; + const untitledToSave: URI[] = []; + toSave.forEach(resourceToSave => { + if ((Array.isArray(arg1) || arg1 === true /* includeUntitled */) && resourceToSave.scheme === Schemas.untitled) { + untitledToSave.push(resourceToSave); + } else { + filesToSave.push(resourceToSave); + } + }); + + return this.doSaveAll(filesToSave, untitledToSave, options); + } + + private async doSaveAll(fileResources: URI[], untitledResources: URI[], options?: ISaveOptions): Promise { + + // Handle files first that can just be saved + const result = await this.doSaveAllFiles(fileResources, options); + + // Preflight for untitled to handle cancellation from the dialog + const targetsForUntitled: URI[] = []; + for (const untitled of untitledResources) { + if (this.untitledEditorService.exists(untitled)) { + let targetUri: URI; + + // Untitled with associated file path don't need to prompt + if (this.untitledEditorService.hasAssociatedFilePath(untitled)) { + targetUri = toLocalResource(untitled, this.environmentService.configuration.remoteAuthority); + } + + // Otherwise ask user + else { + const targetPath = await this.promptForPath(untitled, this.suggestFileName(untitled)); + if (!targetPath) { + return { results: [...fileResources, ...untitledResources].map(r => ({ source: r })) }; + } + + targetUri = targetPath; + } + + targetsForUntitled.push(targetUri); + } + } + + // Handle untitled + await Promise.all(targetsForUntitled.map(async (target, index) => { + const uri = await this.saveAs(untitledResources[index], target); + + result.results.push({ + source: untitledResources[index], + target: uri, + success: !!uri + }); + })); + + return result; + } + + protected async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: string[]): Promise { + + // Help user to find a name for the file by opening it first + await this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true } }); + + return this.fileDialogService.pickFileToSave(this.getSaveDialogOptions(defaultUri, availableFileSystems)); + } + + private getSaveDialogOptions(defaultUri: URI, availableFileSystems?: string[]): ISaveDialogOptions { + const options: ISaveDialogOptions = { + defaultUri, + title: nls.localize('saveAsTitle', "Save As"), + availableFileSystems, + }; + + // Filters are only enabled on Windows where they work properly + if (!platform.isWindows) { + return options; + } + + interface IFilter { name: string; extensions: string[]; } + + // Build the file filter by using our known languages + const ext: string | undefined = defaultUri ? extname(defaultUri) : undefined; + let matchingFilter: IFilter | undefined; + const filters: IFilter[] = coalesce(this.modeService.getRegisteredLanguageNames().map(languageName => { + const extensions = this.modeService.getExtensions(languageName); + if (!extensions || !extensions.length) { + return null; + } + + const filter: IFilter = { name: languageName, extensions: extensions.slice(0, 10).map(e => trim(e, '.')) }; + + if (ext && extensions.indexOf(ext) >= 0) { + matchingFilter = filter; + + return null; // matching filter will be added last to the top + } + + return filter; + })); + + // Filters are a bit weird on Windows, based on having a match or not: + // Match: we put the matching filter first so that it shows up selected and the all files last + // No match: we put the all files filter first + const allFilesFilter = { name: nls.localize('allFiles', "All Files"), extensions: ['*'] }; + if (matchingFilter) { + filters.unshift(matchingFilter); + filters.unshift(allFilesFilter); + } else { + filters.unshift(allFilesFilter); + } + + // Allow to save file without extension + filters.push({ name: nls.localize('noExt', "No Extension"), extensions: [''] }); + + options.filters = filters; + + return options; + } + + private async doSaveAllFiles(resources?: URI[], options: ISaveOptions = Object.create(null)): Promise { + const dirtyFileModels = this.getDirtyFileModels(Array.isArray(resources) ? resources : undefined /* Save All */) + .filter(model => { + if ((model.hasState(ModelState.CONFLICT) || model.hasState(ModelState.ERROR)) && (options.reason === SaveReason.AUTO || options.reason === SaveReason.FOCUS_CHANGE || options.reason === SaveReason.WINDOW_CHANGE)) { + return false; // if model is in save conflict or error, do not save unless save reason is explicit or not provided at all + } + + return true; + }); + + const mapResourceToResult = new ResourceMap(); + dirtyFileModels.forEach(m => { + mapResourceToResult.set(m.getResource(), { + source: m.getResource() + }); + }); + + await Promise.all(dirtyFileModels.map(async model => { + await model.save(options); + + if (!model.isDirty()) { + const result = mapResourceToResult.get(model.getResource()); + if (result) { + result.success = true; + } + } + })); + + return { results: mapResourceToResult.values() }; + } + + private getFileModels(arg1?: URI | URI[]): ITextFileEditorModel[] { + if (Array.isArray(arg1)) { + const models: ITextFileEditorModel[] = []; + arg1.forEach(resource => { + models.push(...this.getFileModels(resource)); + }); + + return models; + } + + return this._models.getAll(arg1); + } + + private getDirtyFileModels(resources?: URI | URI[]): ITextFileEditorModel[] { + return this.getFileModels(resources).filter(model => model.isDirty()); + } + + async saveAs(resource: URI, targetResource?: URI, options?: ISaveOptions): Promise { + + // Get to target resource + if (!targetResource) { + let dialogPath = resource; + if (resource.scheme === Schemas.untitled) { + dialogPath = this.suggestFileName(resource); + } + + targetResource = await this.promptForPath(resource, dialogPath, options ? options.availableFileSystems : undefined); + } + + if (!targetResource) { + return; // user canceled + } + + // Just save if target is same as models own resource + if (resource.toString() === targetResource.toString()) { + await this.save(resource, options); + + return resource; + } + + // Do it + return this.doSaveAs(resource, targetResource, options); + } + + private async doSaveAs(resource: URI, target: URI, options?: ISaveOptions): Promise { + + // Retrieve text model from provided resource if any + let model: ITextFileEditorModel | UntitledEditorModel | undefined; + if (this.fileService.canHandleResource(resource)) { + model = this._models.get(resource); + } else if (resource.scheme === Schemas.untitled && this.untitledEditorService.exists(resource)) { + model = await this.untitledEditorService.loadOrCreate({ resource }); + } + + // We have a model: Use it (can be null e.g. if this file is binary and not a text file or was never opened before) + let result: boolean; + if (model) { + result = await this.doSaveTextFileAs(model, resource, target, options); + } + + // Otherwise we can only copy + else { + await this.fileService.copy(resource, target); + + result = true; + } + + // Return early if the operation was not running + if (!result) { + return target; + } + + // Revert the source + await this.revert(resource); + + return target; + } + + private async doSaveTextFileAs(sourceModel: ITextFileEditorModel | UntitledEditorModel, resource: URI, target: URI, options?: ISaveOptions): Promise { + + // Prefer an existing model if it is already loaded for the given target resource + let targetExists: boolean = false; + let targetModel = this.models.get(target); + if (targetModel && targetModel.isResolved()) { + targetExists = true; + } + + // Otherwise create the target file empty if it does not exist already and resolve it from there + else { + targetExists = await this.fileService.exists(target); + + // create target model adhoc if file does not exist yet + if (!targetExists) { + await this.create(target, ''); + } + + targetModel = await this.models.loadOrCreate(target); + } + + try { + + // Confirm to overwrite if we have an untitled file with associated file where + // the file actually exists on disk and we are instructed to save to that file + // path. This can happen if the file was created after the untitled file was opened. + // See https://github.com/Microsoft/vscode/issues/67946 + let write: boolean; + if (sourceModel instanceof UntitledEditorModel && sourceModel.hasAssociatedFilePath && targetExists && isEqual(target, toLocalResource(sourceModel.getResource(), this.environmentService.configuration.remoteAuthority))) { + write = await this.confirmOverwrite(target); + } else { + write = true; + } + + if (!write) { + return false; + } + + // take over model value, encoding and mode (only if more specific) from source model + targetModel.updatePreferredEncoding(sourceModel.getEncoding()); + if (sourceModel.isResolved() && targetModel.isResolved()) { + this.modelService.updateModel(targetModel.textEditorModel, createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot())); + + const sourceMode = sourceModel.textEditorModel.getLanguageIdentifier(); + const targetMode = targetModel.textEditorModel.getLanguageIdentifier(); + if (sourceMode.language !== PLAINTEXT_MODE_ID && targetMode.language === PLAINTEXT_MODE_ID) { + targetModel.textEditorModel.setMode(sourceMode); // only use if more specific than plain/text + } + } + + // save model + await targetModel.save(options); + + return true; + } catch (error) { + + // binary model: delete the file and run the operation again + if ( + (error).textFileOperationResult === TextFileOperationResult.FILE_IS_BINARY || + (error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE + ) { + await this.fileService.del(target); + + return this.doSaveTextFileAs(sourceModel, resource, target, options); + } + + throw error; + } + } + + private suggestFileName(untitledResource: URI): URI { + const untitledFileName = this.untitledEditorService.suggestFileName(untitledResource); + const remoteAuthority = this.environmentService.configuration.remoteAuthority; + const schemeFilter = remoteAuthority ? Schemas.vscodeRemote : Schemas.file; + + const lastActiveFile = this.historyService.getLastActiveFile(schemeFilter); + if (lastActiveFile) { + const lastDir = dirname(lastActiveFile); + return joinPath(lastDir, untitledFileName); + } + + const lastActiveFolder = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); + if (lastActiveFolder) { + return joinPath(lastActiveFolder, untitledFileName); + } + + return untitledResource.with({ path: untitledFileName }); + } + + async revert(resource: URI, options?: IRevertOptions): Promise { + const result = await this.revertAll([resource], options); + + return result.results.length === 1 && !!result.results[0].success; + } + + async revertAll(resources?: URI[], options?: IRevertOptions): Promise { + + // Revert files first + const revertOperationResult = await this.doRevertAllFiles(resources, options); + + // Revert untitled + const untitledReverted = this.untitledEditorService.revertAll(resources); + untitledReverted.forEach(untitled => revertOperationResult.results.push({ source: untitled, success: true })); + + return revertOperationResult; + } + + private async doRevertAllFiles(resources?: URI[], options?: IRevertOptions): Promise { + const fileModels = options && options.force ? this.getFileModels(resources) : this.getDirtyFileModels(resources); + + const mapResourceToResult = new ResourceMap(); + fileModels.forEach(m => { + mapResourceToResult.set(m.getResource(), { + source: m.getResource() + }); + }); + + await Promise.all(fileModels.map(async model => { + try { + await model.revert(options && options.soft); + + if (!model.isDirty()) { + const result = mapResourceToResult.get(model.getResource()); + if (result) { + result.success = true; + } + } + } catch (error) { + + // FileNotFound means the file got deleted meanwhile, so still record as successful revert + if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { + const result = mapResourceToResult.get(model.getResource()); + if (result) { + result.success = true; + } + } + + // Otherwise bubble up the error + else { + throw error; + } + } + })); + + return { results: mapResourceToResult.values() }; + } + + getDirty(resources?: URI[]): URI[] { + + // Collect files + const dirty = this.getDirtyFileModels(resources).map(m => m.getResource()); + + // Add untitled ones + dirty.push(...this.untitledEditorService.getDirty(resources)); + + return dirty; + } + + isDirty(resource?: URI): boolean { + + // Check for dirty file + if (this._models.getAll(resource).some(model => model.isDirty())) { + return true; + } + + // Check for dirty untitled + return this.untitledEditorService.getDirty().some(dirty => !resource || dirty.toString() === resource.toString()); + } + + //#endregion + + //#region config + + getAutoSaveMode(): AutoSaveMode { + if (this.configuredAutoSaveOnFocusChange) { + return AutoSaveMode.ON_FOCUS_CHANGE; + } + + if (this.configuredAutoSaveOnWindowChange) { + return AutoSaveMode.ON_WINDOW_CHANGE; + } + + if (this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0) { + return this.configuredAutoSaveDelay <= 1000 ? AutoSaveMode.AFTER_SHORT_DELAY : AutoSaveMode.AFTER_LONG_DELAY; + } + + return AutoSaveMode.OFF; + } + + getAutoSaveConfiguration(): IAutoSaveConfiguration { + return { + autoSaveDelay: this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0 ? this.configuredAutoSaveDelay : undefined, + autoSaveFocusChange: !!this.configuredAutoSaveOnFocusChange, + autoSaveApplicationChange: !!this.configuredAutoSaveOnWindowChange + }; + } + + get isHotExitEnabled(): boolean { + return !this.environmentService.isExtensionDevelopment && this.configuredHotExit !== HotExitConfiguration.OFF; + } + + //#endregion + + dispose(): void { + + // Clear all caches + this._models.clear(); + + super.dispose(); + } +} + +export async function promptSave(dialogService: IDialogService, resourcesToConfirm: readonly URI[]) { + const message = resourcesToConfirm.length === 1 + ? nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", basename(resourcesToConfirm[0])) + : getConfirmMessage(nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", resourcesToConfirm.length), resourcesToConfirm); + + const buttons: string[] = [ + resourcesToConfirm.length > 1 ? nls.localize({ key: 'saveAll', comment: ['&& denotes a mnemonic'] }, "&&Save All") : nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save"), + nls.localize({ key: 'dontSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save"), + nls.localize('cancel', "Cancel") + ]; + + const { choice } = await dialogService.show(Severity.Warning, message, buttons, { + cancelId: 2, + detail: nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them.") + }); + + switch (choice) { + case 0: return ConfirmResult.SAVE; + case 1: return ConfirmResult.DONT_SAVE; + default: return ConfirmResult.CANCEL; } } -registerSingleton(ITextFileService, BrowserTextFileService); diff --git a/src/vs/workbench/services/textfile/common/textFileService.ts b/src/vs/workbench/services/textfile/common/textFileService.ts deleted file mode 100644 index 8c619f82fb8..00000000000 --- a/src/vs/workbench/services/textfile/common/textFileService.ts +++ /dev/null @@ -1,1069 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { URI } from 'vs/base/common/uri'; -import * as errors from 'vs/base/common/errors'; -import * as objects from 'vs/base/common/objects'; -import { Event, Emitter } from 'vs/base/common/event'; -import * as platform from 'vs/base/common/platform'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, AutoSaveContext, IWillMoveEvent, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; -import { ConfirmResult, IRevertOptions } from 'vs/workbench/common/editor'; -import { ILifecycleService, ShutdownReason, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IFileService, IFilesConfiguration, FileOperationError, FileOperationResult, AutoSaveConfiguration, HotExitConfiguration, IFileStatWithMetadata, ICreateFileOptions } from 'vs/platform/files/common/files'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel'; -import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ResourceMap } from 'vs/base/common/map'; -import { Schemas } from 'vs/base/common/network'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { createTextBufferFactoryFromSnapshot, createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { isEqualOrParent, isEqual, joinPath, dirname, extname, basename, toLocalResource } from 'vs/base/common/resources'; -import { getConfirmMessage, IDialogService, IFileDialogService, ISaveDialogOptions, IConfirmation } from 'vs/platform/dialogs/common/dialogs'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { coalesce } from 'vs/base/common/arrays'; -import { trim } from 'vs/base/common/strings'; -import { VSBuffer } from 'vs/base/common/buffer'; -import { ITextSnapshot } from 'vs/editor/common/model'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; -import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; - -/** - * The workbench file service implementation implements the raw file service spec and adds additional methods on top. - */ -export abstract class TextFileService extends Disposable implements ITextFileService { - - _serviceBrand: undefined; - - private readonly _onAutoSaveConfigurationChange: Emitter = this._register(new Emitter()); - readonly onAutoSaveConfigurationChange: Event = this._onAutoSaveConfigurationChange.event; - - private readonly _onFilesAssociationChange: Emitter = this._register(new Emitter()); - readonly onFilesAssociationChange: Event = this._onFilesAssociationChange.event; - - private readonly _onWillMove = this._register(new Emitter()); - readonly onWillMove: Event = this._onWillMove.event; - - private _models: TextFileEditorModelManager; - get models(): ITextFileEditorModelManager { return this._models; } - - abstract get encoding(): IResourceEncodings; - - private currentFilesAssociationConfig: { [key: string]: string; }; - private configuredAutoSaveDelay?: number; - private configuredAutoSaveOnFocusChange: boolean | undefined; - private configuredAutoSaveOnWindowChange: boolean | undefined; - private configuredHotExit: string | undefined; - private autoSaveContext: IContextKey; - - constructor( - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IFileService protected readonly fileService: IFileService, - @IUntitledEditorService protected readonly untitledEditorService: IUntitledEditorService, - @ILifecycleService private readonly lifecycleService: ILifecycleService, - @IInstantiationService protected instantiationService: IInstantiationService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IModeService private readonly modeService: IModeService, - @IModelService private readonly modelService: IModelService, - @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, - @INotificationService private readonly notificationService: INotificationService, - @IBackupFileService private readonly backupFileService: IBackupFileService, - @IWindowsService private readonly windowsService: IWindowsService, - @IHistoryService private readonly historyService: IHistoryService, - @IContextKeyService contextKeyService: IContextKeyService, - @IDialogService private readonly dialogService: IDialogService, - @IFileDialogService private readonly fileDialogService: IFileDialogService, - @IEditorService private readonly editorService: IEditorService, - @ITextResourceConfigurationService protected readonly textResourceConfigurationService: ITextResourceConfigurationService - ) { - super(); - - this._models = this._register(instantiationService.createInstance(TextFileEditorModelManager)); - this.autoSaveContext = AutoSaveContext.bindTo(contextKeyService); - - const configuration = configurationService.getValue(); - this.currentFilesAssociationConfig = configuration && configuration.files && configuration.files.associations; - - this.onFilesConfigurationChange(configuration); - - this.registerListeners(); - } - - //#region event handling - - private registerListeners(): void { - - // Lifecycle - this.lifecycleService.onBeforeShutdown(event => event.veto(this.onBeforeShutdown(event.reason))); - this.lifecycleService.onShutdown(this.dispose, this); - - // Files configuration changes - this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('files')) { - this.onFilesConfigurationChange(this.configurationService.getValue()); - } - })); - } - - protected onBeforeShutdown(reason: ShutdownReason): boolean | Promise { - - // Dirty files need treatment on shutdown - const dirty = this.getDirty(); - if (dirty.length) { - - // If auto save is enabled, save all files and then check again for dirty files - // We DO NOT run any save participant if we are in the shutdown phase for performance reasons - if (this.getAutoSaveMode() !== AutoSaveMode.OFF) { - return this.saveAll(false /* files only */, { skipSaveParticipants: true }).then(() => { - - // If we still have dirty files, we either have untitled ones or files that cannot be saved - const remainingDirty = this.getDirty(); - if (remainingDirty.length) { - return this.handleDirtyBeforeShutdown(remainingDirty, reason); - } - - return false; - }); - } - - // Auto save is not enabled - return this.handleDirtyBeforeShutdown(dirty, reason); - } - - // No dirty files: no veto - return this.noVeto({ cleanUpBackups: true }); - } - - private handleDirtyBeforeShutdown(dirty: URI[], reason: ShutdownReason): boolean | Promise { - - // If hot exit is enabled, backup dirty files and allow to exit without confirmation - if (this.isHotExitEnabled) { - return this.backupBeforeShutdown(dirty, reason).then(didBackup => { - if (didBackup) { - return this.noVeto({ cleanUpBackups: false }); // no veto and no backup cleanup (since backup was successful) - } - - // since a backup did not happen, we have to confirm for the dirty files now - return this.confirmBeforeShutdown(); - }, error => { - this.notificationService.error(nls.localize('files.backup.failSave', "Files that are dirty could not be written to the backup location (Error: {0}). Try saving your files first and then exit.", error.message)); - - return true; // veto, the backups failed - }); - } - - // Otherwise just confirm from the user what to do with the dirty files - return this.confirmBeforeShutdown(); - } - - private async backupBeforeShutdown(dirtyToBackup: URI[], reason: ShutdownReason): Promise { - const windowCount = await this.windowsService.getWindowCount(); - - // When quit is requested skip the confirm callback and attempt to backup all workspaces. - // When quit is not requested the confirm callback should be shown when the window being - // closed is the only VS Code window open, except for on Mac where hot exit is only - // ever activated when quit is requested. - - let doBackup: boolean | undefined; - switch (reason) { - case ShutdownReason.CLOSE: - if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.configuredHotExit === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { - doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured - } else if (windowCount > 1 || platform.isMacintosh) { - doBackup = false; // do not backup if a window is closed that does not cause quitting of the application - } else { - doBackup = true; // backup if last window is closed on win/linux where the application quits right after - } - break; - - case ShutdownReason.QUIT: - doBackup = true; // backup because next start we restore all backups - break; - - case ShutdownReason.RELOAD: - doBackup = true; // backup because after window reload, backups restore - break; - - case ShutdownReason.LOAD: - if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.configuredHotExit === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { - doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured - } else { - doBackup = false; // do not backup because we are switching contexts - } - break; - } - - if (!doBackup) { - return false; - } - - await this.backupAll(dirtyToBackup); - - return true; - } - - private backupAll(dirtyToBackup: URI[]): Promise { - - // split up between files and untitled - const filesToBackup: ITextFileEditorModel[] = []; - const untitledToBackup: URI[] = []; - dirtyToBackup.forEach(dirty => { - if (this.fileService.canHandleResource(dirty)) { - const model = this.models.get(dirty); - if (model) { - filesToBackup.push(model); - } - } else if (dirty.scheme === Schemas.untitled) { - untitledToBackup.push(dirty); - } - }); - - return this.doBackupAll(filesToBackup, untitledToBackup); - } - - private async doBackupAll(dirtyFileModels: ITextFileEditorModel[], untitledResources: URI[]): Promise { - - // Handle file resources first - await Promise.all(dirtyFileModels.map(model => model.backup())); - - // Handle untitled resources - await Promise.all(untitledResources - .filter(untitled => this.untitledEditorService.exists(untitled)) - .map(async untitled => (await this.untitledEditorService.loadOrCreate({ resource: untitled })).backup())); - } - - private async confirmBeforeShutdown(): Promise { - const confirm = await this.confirmSave(); - - // Save - if (confirm === ConfirmResult.SAVE) { - const result = await this.saveAll(true /* includeUntitled */, { skipSaveParticipants: true }); - - if (result.results.some(r => !r.success)) { - return true; // veto if some saves failed - } - - return this.noVeto({ cleanUpBackups: true }); - } - - // Don't Save - else if (confirm === ConfirmResult.DONT_SAVE) { - - // Make sure to revert untitled so that they do not restore - // see https://github.com/Microsoft/vscode/issues/29572 - this.untitledEditorService.revertAll(); - - return this.noVeto({ cleanUpBackups: true }); - } - - // Cancel - else if (confirm === ConfirmResult.CANCEL) { - return true; // veto - } - - return false; - } - - private noVeto(options: { cleanUpBackups: boolean }): boolean | Promise { - if (!options.cleanUpBackups) { - return false; - } - - if (this.lifecycleService.phase < LifecyclePhase.Restored) { - return false; // if editors have not restored, we are not up to speed with backups and thus should not clean them - } - - return this.cleanupBackupsBeforeShutdown().then(() => false, () => false); - } - - protected async cleanupBackupsBeforeShutdown(): Promise { - if (this.environmentService.isExtensionDevelopment) { - return; - } - - await this.backupFileService.discardAllWorkspaceBackups(); - } - - protected onFilesConfigurationChange(configuration: IFilesConfiguration): void { - const wasAutoSaveEnabled = (this.getAutoSaveMode() !== AutoSaveMode.OFF); - - const autoSaveMode = (configuration && configuration.files && configuration.files.autoSave) || AutoSaveConfiguration.OFF; - this.autoSaveContext.set(autoSaveMode); - switch (autoSaveMode) { - case AutoSaveConfiguration.AFTER_DELAY: - this.configuredAutoSaveDelay = configuration && configuration.files && configuration.files.autoSaveDelay; - this.configuredAutoSaveOnFocusChange = false; - this.configuredAutoSaveOnWindowChange = false; - break; - - case AutoSaveConfiguration.ON_FOCUS_CHANGE: - this.configuredAutoSaveDelay = undefined; - this.configuredAutoSaveOnFocusChange = true; - this.configuredAutoSaveOnWindowChange = false; - break; - - case AutoSaveConfiguration.ON_WINDOW_CHANGE: - this.configuredAutoSaveDelay = undefined; - this.configuredAutoSaveOnFocusChange = false; - this.configuredAutoSaveOnWindowChange = true; - break; - - default: - this.configuredAutoSaveDelay = undefined; - this.configuredAutoSaveOnFocusChange = false; - this.configuredAutoSaveOnWindowChange = false; - break; - } - - // Emit as event - this._onAutoSaveConfigurationChange.fire(this.getAutoSaveConfiguration()); - - // save all dirty when enabling auto save - if (!wasAutoSaveEnabled && this.getAutoSaveMode() !== AutoSaveMode.OFF) { - this.saveAll(); - } - - // Check for change in files associations - const filesAssociation = configuration && configuration.files && configuration.files.associations; - if (!objects.equals(this.currentFilesAssociationConfig, filesAssociation)) { - this.currentFilesAssociationConfig = filesAssociation; - this._onFilesAssociationChange.fire(); - } - - // Hot exit - const hotExitMode = configuration && configuration.files && configuration.files.hotExit; - if (hotExitMode === HotExitConfiguration.OFF || hotExitMode === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { - this.configuredHotExit = hotExitMode; - } else { - this.configuredHotExit = HotExitConfiguration.ON_EXIT; - } - } - - //#endregion - - //#region primitives (read, create, move, delete, update) - - async read(resource: URI, options?: IReadTextFileOptions): Promise { - const content = await this.fileService.readFile(resource, options); - - // in case of acceptTextOnly: true, we check the first - // chunk for possibly being binary by looking for 0-bytes - // we limit this check to the first 512 bytes - this.validateBinary(content.value, options); - - return { - ...content, - encoding: 'utf8', - value: content.value.toString() - }; - } - - async readStream(resource: URI, options?: IReadTextFileOptions): Promise { - const stream = await this.fileService.readFileStream(resource, options); - - // in case of acceptTextOnly: true, we check the first - // chunk for possibly being binary by looking for 0-bytes - // we limit this check to the first 512 bytes - let checkedForBinary = false; - const throwOnBinary = (data: VSBuffer): Error | undefined => { - if (!checkedForBinary) { - checkedForBinary = true; - - this.validateBinary(data, options); - } - - return undefined; - }; - - return { - ...stream, - encoding: 'utf8', - value: await createTextBufferFactoryFromStream(stream.value, undefined, options && options.acceptTextOnly ? throwOnBinary : undefined) - }; - } - - private validateBinary(buffer: VSBuffer, options?: IReadTextFileOptions): void { - if (!options || !options.acceptTextOnly) { - return; // no validation needed - } - - // in case of acceptTextOnly: true, we check the first - // chunk for possibly being binary by looking for 0-bytes - // we limit this check to the first 512 bytes - for (let i = 0; i < buffer.byteLength && i < 512; i++) { - if (buffer.readUInt8(i) === 0) { - throw new TextFileOperationError(nls.localize('fileBinaryError', "File seems to be binary and cannot be opened as text"), TextFileOperationResult.FILE_IS_BINARY, options); - } - } - } - - async create(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise { - const stat = await this.doCreate(resource, value, options); - - // If we had an existing model for the given resource, load - // it again to make sure it is up to date with the contents - // we just wrote into the underlying resource by calling - // revert() - const existingModel = this.models.get(resource); - if (existingModel && !existingModel.isDisposed()) { - await existingModel.revert(); - } - - return stat; - } - - protected doCreate(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise { - return this.fileService.createFile(resource, toBufferOrReadable(value), options); - } - - async write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise { - return this.fileService.writeFile(resource, toBufferOrReadable(value), options); - } - - async delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise { - const dirtyFiles = this.getDirty().filter(dirty => isEqualOrParent(dirty, resource)); - - await this.revertAll(dirtyFiles, { soft: true }); - - return this.fileService.del(resource, options); - } - - async move(source: URI, target: URI, overwrite?: boolean): Promise { - - // await onWillMove event joiners - await this.notifyOnWillMove(source, target); - - // find all models that related to either source or target (can be many if resource is a folder) - const sourceModels: ITextFileEditorModel[] = []; - const conflictingModels: ITextFileEditorModel[] = []; - for (const model of this.getFileModels()) { - const resource = model.getResource(); - - if (isEqualOrParent(resource, target, false /* do not ignorecase, see https://github.com/Microsoft/vscode/issues/56384 */)) { - conflictingModels.push(model); - } - - if (isEqualOrParent(resource, source)) { - sourceModels.push(model); - } - } - - // remember each source model to load again after move is done - // with optional content to restore if it was dirty - type ModelToRestore = { resource: URI; snapshot?: ITextSnapshot }; - const modelsToRestore: ModelToRestore[] = []; - for (const sourceModel of sourceModels) { - const sourceModelResource = sourceModel.getResource(); - - // If the source is the actual model, just use target as new resource - let modelToRestoreResource: URI; - if (isEqual(sourceModelResource, source)) { - modelToRestoreResource = target; - } - - // Otherwise a parent folder of the source is being moved, so we need - // to compute the target resource based on that - else { - modelToRestoreResource = joinPath(target, sourceModelResource.path.substr(source.path.length + 1)); - } - - const modelToRestore: ModelToRestore = { resource: modelToRestoreResource }; - if (sourceModel.isDirty()) { - modelToRestore.snapshot = sourceModel.createSnapshot(); - } - - modelsToRestore.push(modelToRestore); - } - - // in order to move, we need to soft revert all dirty models, - // both from the source as well as the target if any - const dirtyModels = [...sourceModels, ...conflictingModels].filter(model => model.isDirty()); - await this.revertAll(dirtyModels.map(dirtyModel => dirtyModel.getResource()), { soft: true }); - - // now we can rename the source to target via file operation - let stat: IFileStatWithMetadata; - try { - stat = await this.fileService.move(source, target, overwrite); - } catch (error) { - - // in case of any error, ensure to set dirty flag back - dirtyModels.forEach(dirtyModel => dirtyModel.makeDirty()); - - throw error; - } - - // finally, restore models that we had loaded previously - await Promise.all(modelsToRestore.map(async modelToRestore => { - - // restore the model, forcing a reload. this is important because - // we know the file has changed on disk after the move and the - // model might have still existed with the previous state. this - // ensures we are not tracking a stale state. - const restoredModel = await this.models.loadOrCreate(modelToRestore.resource, { reload: { async: false } }); - - // restore previous dirty content if any and ensure to mark - // the model as dirty - if (modelToRestore.snapshot && restoredModel.isResolved()) { - this.modelService.updateModel(restoredModel.textEditorModel, createTextBufferFactoryFromSnapshot(modelToRestore.snapshot)); - - restoredModel.makeDirty(); - } - })); - - return stat; - } - - private async notifyOnWillMove(source: URI, target: URI): Promise { - const waitForPromises: Promise[] = []; - - // fire event - this._onWillMove.fire({ - oldResource: source, - newResource: target, - waitUntil(promise: Promise) { - waitForPromises.push(promise.then(undefined, errors.onUnexpectedError)); - } - }); - - // prevent async waitUntil-calls - Object.freeze(waitForPromises); - - await Promise.all(waitForPromises); - } - - //#endregion - - //#region save/revert - - async save(resource: URI, options?: ISaveOptions): Promise { - - // Run a forced save if we detect the file is not dirty so that save participants can still run - if (options && options.force && this.fileService.canHandleResource(resource) && !this.isDirty(resource)) { - const model = this._models.get(resource); - if (model) { - options.reason = SaveReason.EXPLICIT; - - await model.save(options); - - return !model.isDirty(); - } - } - - const result = await this.saveAll([resource], options); - - return result.results.length === 1 && !!result.results[0].success; - } - - async confirmSave(resources?: URI[]): Promise { - if (this.environmentService.isExtensionDevelopment) { - if (!this.environmentService.args['extension-development-confirm-save']) { - return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) - } - } - - const resourcesToConfirm = this.getDirty(resources); - if (resourcesToConfirm.length === 0) { - return ConfirmResult.DONT_SAVE; - } - return promptSave(this.dialogService, resourcesToConfirm); - } - - async confirmOverwrite(resource: URI): Promise { - const confirm: IConfirmation = { - message: nls.localize('confirmOverwrite', "'{0}' already exists. Do you want to replace it?", basename(resource)), - detail: nls.localize('irreversible', "A file or folder with the same name already exists in the folder {0}. Replacing it will overwrite its current contents.", basename(dirname(resource))), - primaryButton: nls.localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"), - type: 'warning' - }; - - return (await this.dialogService.confirm(confirm)).confirmed; - } - - saveAll(includeUntitled?: boolean, options?: ISaveOptions): Promise; - saveAll(resources: URI[], options?: ISaveOptions): Promise; - saveAll(arg1?: boolean | URI[], options?: ISaveOptions): Promise { - - // get all dirty - let toSave: URI[] = []; - if (Array.isArray(arg1)) { - toSave = this.getDirty(arg1); - } else { - toSave = this.getDirty(); - } - - // split up between files and untitled - const filesToSave: URI[] = []; - const untitledToSave: URI[] = []; - toSave.forEach(resourceToSave => { - if ((Array.isArray(arg1) || arg1 === true /* includeUntitled */) && resourceToSave.scheme === Schemas.untitled) { - untitledToSave.push(resourceToSave); - } else { - filesToSave.push(resourceToSave); - } - }); - - return this.doSaveAll(filesToSave, untitledToSave, options); - } - - private async doSaveAll(fileResources: URI[], untitledResources: URI[], options?: ISaveOptions): Promise { - - // Handle files first that can just be saved - const result = await this.doSaveAllFiles(fileResources, options); - - // Preflight for untitled to handle cancellation from the dialog - const targetsForUntitled: URI[] = []; - for (const untitled of untitledResources) { - if (this.untitledEditorService.exists(untitled)) { - let targetUri: URI; - - // Untitled with associated file path don't need to prompt - if (this.untitledEditorService.hasAssociatedFilePath(untitled)) { - targetUri = toLocalResource(untitled, this.environmentService.configuration.remoteAuthority); - } - - // Otherwise ask user - else { - const targetPath = await this.promptForPath(untitled, this.suggestFileName(untitled)); - if (!targetPath) { - return { results: [...fileResources, ...untitledResources].map(r => ({ source: r })) }; - } - - targetUri = targetPath; - } - - targetsForUntitled.push(targetUri); - } - } - - // Handle untitled - await Promise.all(targetsForUntitled.map(async (target, index) => { - const uri = await this.saveAs(untitledResources[index], target); - - result.results.push({ - source: untitledResources[index], - target: uri, - success: !!uri - }); - })); - - return result; - } - - protected async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: string[]): Promise { - - // Help user to find a name for the file by opening it first - await this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true } }); - - return this.fileDialogService.pickFileToSave(this.getSaveDialogOptions(defaultUri, availableFileSystems)); - } - - private getSaveDialogOptions(defaultUri: URI, availableFileSystems?: string[]): ISaveDialogOptions { - const options: ISaveDialogOptions = { - defaultUri, - title: nls.localize('saveAsTitle', "Save As"), - availableFileSystems, - }; - - // Filters are only enabled on Windows where they work properly - if (!platform.isWindows) { - return options; - } - - interface IFilter { name: string; extensions: string[]; } - - // Build the file filter by using our known languages - const ext: string | undefined = defaultUri ? extname(defaultUri) : undefined; - let matchingFilter: IFilter | undefined; - const filters: IFilter[] = coalesce(this.modeService.getRegisteredLanguageNames().map(languageName => { - const extensions = this.modeService.getExtensions(languageName); - if (!extensions || !extensions.length) { - return null; - } - - const filter: IFilter = { name: languageName, extensions: extensions.slice(0, 10).map(e => trim(e, '.')) }; - - if (ext && extensions.indexOf(ext) >= 0) { - matchingFilter = filter; - - return null; // matching filter will be added last to the top - } - - return filter; - })); - - // Filters are a bit weird on Windows, based on having a match or not: - // Match: we put the matching filter first so that it shows up selected and the all files last - // No match: we put the all files filter first - const allFilesFilter = { name: nls.localize('allFiles', "All Files"), extensions: ['*'] }; - if (matchingFilter) { - filters.unshift(matchingFilter); - filters.unshift(allFilesFilter); - } else { - filters.unshift(allFilesFilter); - } - - // Allow to save file without extension - filters.push({ name: nls.localize('noExt', "No Extension"), extensions: [''] }); - - options.filters = filters; - - return options; - } - - private async doSaveAllFiles(resources?: URI[], options: ISaveOptions = Object.create(null)): Promise { - const dirtyFileModels = this.getDirtyFileModels(Array.isArray(resources) ? resources : undefined /* Save All */) - .filter(model => { - if ((model.hasState(ModelState.CONFLICT) || model.hasState(ModelState.ERROR)) && (options.reason === SaveReason.AUTO || options.reason === SaveReason.FOCUS_CHANGE || options.reason === SaveReason.WINDOW_CHANGE)) { - return false; // if model is in save conflict or error, do not save unless save reason is explicit or not provided at all - } - - return true; - }); - - const mapResourceToResult = new ResourceMap(); - dirtyFileModels.forEach(m => { - mapResourceToResult.set(m.getResource(), { - source: m.getResource() - }); - }); - - await Promise.all(dirtyFileModels.map(async model => { - await model.save(options); - - if (!model.isDirty()) { - const result = mapResourceToResult.get(model.getResource()); - if (result) { - result.success = true; - } - } - })); - - return { results: mapResourceToResult.values() }; - } - - private getFileModels(arg1?: URI | URI[]): ITextFileEditorModel[] { - if (Array.isArray(arg1)) { - const models: ITextFileEditorModel[] = []; - arg1.forEach(resource => { - models.push(...this.getFileModels(resource)); - }); - - return models; - } - - return this._models.getAll(arg1); - } - - private getDirtyFileModels(resources?: URI | URI[]): ITextFileEditorModel[] { - return this.getFileModels(resources).filter(model => model.isDirty()); - } - - async saveAs(resource: URI, targetResource?: URI, options?: ISaveOptions): Promise { - - // Get to target resource - if (!targetResource) { - let dialogPath = resource; - if (resource.scheme === Schemas.untitled) { - dialogPath = this.suggestFileName(resource); - } - - targetResource = await this.promptForPath(resource, dialogPath, options ? options.availableFileSystems : undefined); - } - - if (!targetResource) { - return; // user canceled - } - - // Just save if target is same as models own resource - if (resource.toString() === targetResource.toString()) { - await this.save(resource, options); - - return resource; - } - - // Do it - return this.doSaveAs(resource, targetResource, options); - } - - private async doSaveAs(resource: URI, target: URI, options?: ISaveOptions): Promise { - - // Retrieve text model from provided resource if any - let model: ITextFileEditorModel | UntitledEditorModel | undefined; - if (this.fileService.canHandleResource(resource)) { - model = this._models.get(resource); - } else if (resource.scheme === Schemas.untitled && this.untitledEditorService.exists(resource)) { - model = await this.untitledEditorService.loadOrCreate({ resource }); - } - - // We have a model: Use it (can be null e.g. if this file is binary and not a text file or was never opened before) - let result: boolean; - if (model) { - result = await this.doSaveTextFileAs(model, resource, target, options); - } - - // Otherwise we can only copy - else { - await this.fileService.copy(resource, target); - - result = true; - } - - // Return early if the operation was not running - if (!result) { - return target; - } - - // Revert the source - await this.revert(resource); - - return target; - } - - private async doSaveTextFileAs(sourceModel: ITextFileEditorModel | UntitledEditorModel, resource: URI, target: URI, options?: ISaveOptions): Promise { - - // Prefer an existing model if it is already loaded for the given target resource - let targetExists: boolean = false; - let targetModel = this.models.get(target); - if (targetModel && targetModel.isResolved()) { - targetExists = true; - } - - // Otherwise create the target file empty if it does not exist already and resolve it from there - else { - targetExists = await this.fileService.exists(target); - - // create target model adhoc if file does not exist yet - if (!targetExists) { - await this.create(target, ''); - } - - targetModel = await this.models.loadOrCreate(target); - } - - try { - - // Confirm to overwrite if we have an untitled file with associated file where - // the file actually exists on disk and we are instructed to save to that file - // path. This can happen if the file was created after the untitled file was opened. - // See https://github.com/Microsoft/vscode/issues/67946 - let write: boolean; - if (sourceModel instanceof UntitledEditorModel && sourceModel.hasAssociatedFilePath && targetExists && isEqual(target, toLocalResource(sourceModel.getResource(), this.environmentService.configuration.remoteAuthority))) { - write = await this.confirmOverwrite(target); - } else { - write = true; - } - - if (!write) { - return false; - } - - // take over model value, encoding and mode (only if more specific) from source model - targetModel.updatePreferredEncoding(sourceModel.getEncoding()); - if (sourceModel.isResolved() && targetModel.isResolved()) { - this.modelService.updateModel(targetModel.textEditorModel, createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot())); - - const sourceMode = sourceModel.textEditorModel.getLanguageIdentifier(); - const targetMode = targetModel.textEditorModel.getLanguageIdentifier(); - if (sourceMode.language !== PLAINTEXT_MODE_ID && targetMode.language === PLAINTEXT_MODE_ID) { - targetModel.textEditorModel.setMode(sourceMode); // only use if more specific than plain/text - } - } - - // save model - await targetModel.save(options); - - return true; - } catch (error) { - - // binary model: delete the file and run the operation again - if ( - (error).textFileOperationResult === TextFileOperationResult.FILE_IS_BINARY || - (error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE - ) { - await this.fileService.del(target); - - return this.doSaveTextFileAs(sourceModel, resource, target, options); - } - - throw error; - } - } - - private suggestFileName(untitledResource: URI): URI { - const untitledFileName = this.untitledEditorService.suggestFileName(untitledResource); - const remoteAuthority = this.environmentService.configuration.remoteAuthority; - const schemeFilter = remoteAuthority ? Schemas.vscodeRemote : Schemas.file; - - const lastActiveFile = this.historyService.getLastActiveFile(schemeFilter); - if (lastActiveFile) { - const lastDir = dirname(lastActiveFile); - return joinPath(lastDir, untitledFileName); - } - - const lastActiveFolder = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); - if (lastActiveFolder) { - return joinPath(lastActiveFolder, untitledFileName); - } - - return untitledResource.with({ path: untitledFileName }); - } - - async revert(resource: URI, options?: IRevertOptions): Promise { - const result = await this.revertAll([resource], options); - - return result.results.length === 1 && !!result.results[0].success; - } - - async revertAll(resources?: URI[], options?: IRevertOptions): Promise { - - // Revert files first - const revertOperationResult = await this.doRevertAllFiles(resources, options); - - // Revert untitled - const untitledReverted = this.untitledEditorService.revertAll(resources); - untitledReverted.forEach(untitled => revertOperationResult.results.push({ source: untitled, success: true })); - - return revertOperationResult; - } - - private async doRevertAllFiles(resources?: URI[], options?: IRevertOptions): Promise { - const fileModels = options && options.force ? this.getFileModels(resources) : this.getDirtyFileModels(resources); - - const mapResourceToResult = new ResourceMap(); - fileModels.forEach(m => { - mapResourceToResult.set(m.getResource(), { - source: m.getResource() - }); - }); - - await Promise.all(fileModels.map(async model => { - try { - await model.revert(options && options.soft); - - if (!model.isDirty()) { - const result = mapResourceToResult.get(model.getResource()); - if (result) { - result.success = true; - } - } - } catch (error) { - - // FileNotFound means the file got deleted meanwhile, so still record as successful revert - if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { - const result = mapResourceToResult.get(model.getResource()); - if (result) { - result.success = true; - } - } - - // Otherwise bubble up the error - else { - throw error; - } - } - })); - - return { results: mapResourceToResult.values() }; - } - - getDirty(resources?: URI[]): URI[] { - - // Collect files - const dirty = this.getDirtyFileModels(resources).map(m => m.getResource()); - - // Add untitled ones - dirty.push(...this.untitledEditorService.getDirty(resources)); - - return dirty; - } - - isDirty(resource?: URI): boolean { - - // Check for dirty file - if (this._models.getAll(resource).some(model => model.isDirty())) { - return true; - } - - // Check for dirty untitled - return this.untitledEditorService.getDirty().some(dirty => !resource || dirty.toString() === resource.toString()); - } - - //#endregion - - //#region config - - getAutoSaveMode(): AutoSaveMode { - if (this.configuredAutoSaveOnFocusChange) { - return AutoSaveMode.ON_FOCUS_CHANGE; - } - - if (this.configuredAutoSaveOnWindowChange) { - return AutoSaveMode.ON_WINDOW_CHANGE; - } - - if (this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0) { - return this.configuredAutoSaveDelay <= 1000 ? AutoSaveMode.AFTER_SHORT_DELAY : AutoSaveMode.AFTER_LONG_DELAY; - } - - return AutoSaveMode.OFF; - } - - getAutoSaveConfiguration(): IAutoSaveConfiguration { - return { - autoSaveDelay: this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0 ? this.configuredAutoSaveDelay : undefined, - autoSaveFocusChange: !!this.configuredAutoSaveOnFocusChange, - autoSaveApplicationChange: !!this.configuredAutoSaveOnWindowChange - }; - } - - get isHotExitEnabled(): boolean { - return !this.environmentService.isExtensionDevelopment && this.configuredHotExit !== HotExitConfiguration.OFF; - } - - //#endregion - - dispose(): void { - - // Clear all caches - this._models.clear(); - - super.dispose(); - } -} - -export async function promptSave(dialogService: IDialogService, resourcesToConfirm: readonly URI[]) { - const message = resourcesToConfirm.length === 1 - ? nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", basename(resourcesToConfirm[0])) - : getConfirmMessage(nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", resourcesToConfirm.length), resourcesToConfirm); - - const buttons: string[] = [ - resourcesToConfirm.length > 1 ? nls.localize({ key: 'saveAll', comment: ['&& denotes a mnemonic'] }, "&&Save All") : nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save"), - nls.localize({ key: 'dontSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save"), - nls.localize('cancel', "Cancel") - ]; - - const { choice } = await dialogService.show(Severity.Warning, message, buttons, { - cancelId: 2, - detail: nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them.") - }); - - switch (choice) { - case 0: return ConfirmResult.SAVE; - case 1: return ConfirmResult.DONT_SAVE; - default: return ConfirmResult.CANCEL; - } -} - diff --git a/src/vs/workbench/services/textfile/node/textFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts similarity index 98% rename from src/vs/workbench/services/textfile/node/textFileService.ts rename to src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index ed012a4c702..d621479da05 100644 --- a/src/vs/workbench/services/textfile/node/textFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts @@ -5,7 +5,7 @@ import { tmpdir } from 'os'; import { localize } from 'vs/nls'; -import { TextFileService } from 'vs/workbench/services/textfile/common/textFileService'; +import { AbstractTextFileService } from 'vs/workbench/services/textfile/browser/textFileService'; import { ITextFileService, ITextFileStreamContent, ITextFileContent, IResourceEncodings, IResourceEncoding, IReadTextFileOptions, IWriteTextFileOptions, stringToSnapshot, TextFileOperationResult, TextFileOperationError } from 'vs/workbench/services/textfile/common/textfiles'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { URI } from 'vs/base/common/uri'; @@ -28,7 +28,7 @@ import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textMo import { ITextSnapshot } from 'vs/editor/common/model'; import { nodeReadableToString, streamToNodeReadable, nodeStreamToVSBufferReadable } from 'vs/base/node/stream'; -export class NodeTextFileService extends TextFileService { +export class NativeTextFileService extends AbstractTextFileService { private _encoding!: EncodingOracle; get encoding(): EncodingOracle { @@ -404,4 +404,4 @@ export class EncodingOracle extends Disposable implements IResourceEncodings { } } -registerSingleton(ITextFileService, NodeTextFileService); +registerSingleton(ITextFileService, NativeTextFileService); diff --git a/src/vs/workbench/services/textfile/test/textFileService.io.test.ts b/src/vs/workbench/services/textfile/test/textFileService.io.test.ts index eab223e0d4a..6d8282d7830 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.io.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.io.test.ts @@ -22,7 +22,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import { join, basename } from 'vs/base/common/path'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { UTF16be, UTF16le, UTF8_with_bom, UTF8 } from 'vs/base/node/encoding'; -import { NodeTextFileService, EncodingOracle, IEncodingOverride } from 'vs/workbench/services/textfile/node/textFileService'; +import { NativeTextFileService, EncodingOracle, IEncodingOverride } from 'vs/workbench/services/textfile/electron-browser/nativeTextFileService'; import { DefaultEndOfLine, ITextSnapshot } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; import { isWindows } from 'vs/base/common/platform'; @@ -37,7 +37,7 @@ class ServiceAccessor { } } -class TestNodeTextFileService extends NodeTextFileService { +class TestNativeTextFileService extends NativeTextFileService { private _testEncoding: TestEncodingOracle | undefined; get encoding(): TestEncodingOracle { @@ -84,7 +84,7 @@ suite('Files - TextFileService i/o', () => { const collection = new ServiceCollection(); collection.set(IFileService, fileService); - service = instantiationService.createChild(collection).createInstance(TestNodeTextFileService); + service = instantiationService.createChild(collection).createInstance(TestNativeTextFileService); const id = generateUuid(); testDir = join(parentDir, id); diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index 07676420066..c8e4de0a55a 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -7,7 +7,7 @@ import * as sinon from 'sinon'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { ILifecycleService, BeforeShutdownEvent, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; -import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestWindowsService, TestContextService, TestFileService } from 'vs/workbench/test/workbenchTestServices'; +import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestWindowsService, TestContextService, TestFileService, TestHostService } from 'vs/workbench/test/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IWindowsService } from 'vs/platform/windows/common/windows'; @@ -21,6 +21,7 @@ import { IWorkspaceContextService, Workspace } from 'vs/platform/workspace/commo import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { Schemas } from 'vs/base/common/network'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; class ServiceAccessor { constructor( @@ -30,7 +31,8 @@ class ServiceAccessor { @IWindowsService public windowsService: TestWindowsService, @IWorkspaceContextService public contextService: TestContextService, @IModelService public modelService: ModelServiceImpl, - @IFileService public fileService: TestFileService + @IFileService public fileService: TestFileService, + @IHostService public hostService: TestHostService ) { } } @@ -424,7 +426,7 @@ suite('Files - TextFileService', () => { } // Set multiple windows if required if (multipleWindows) { - accessor.windowsService.windowCount = 2; + accessor.hostService.windowCount = Promise.resolve(2); } // Set cancel to force a veto if hot exit does not trigger service.setConfirmResult(ConfirmResult.CANCEL); diff --git a/src/vs/workbench/services/timer/electron-browser/timerService.ts b/src/vs/workbench/services/timer/electron-browser/timerService.ts index 3bf0c2fee4e..aefb915e2b6 100644 --- a/src/vs/workbench/services/timer/electron-browser/timerService.ts +++ b/src/vs/workbench/services/timer/electron-browser/timerService.ts @@ -7,7 +7,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { virtualMachineHint } from 'vs/base/node/id'; import * as perf from 'vs/base/common/performance'; import * as os from 'os'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -308,7 +308,7 @@ class TimerService implements ITimerService { private _startupMetrics?: Promise; constructor( - @IWindowsService private readonly _windowsService: IWindowsService, + @IHostService private readonly _hostService: IHostService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @@ -380,7 +380,7 @@ class TimerService implements ITimerService { isLatestVersion: Boolean(await this._updateService.isLatestVersion()), didUseCachedData: didUseCachedData(), windowKind: this._lifecycleService.startupKind, - windowCount: await this._windowsService.getWindowCount(), + windowCount: await this._hostService.windowCount, viewletId: activeViewlet ? activeViewlet.getId() : undefined, editorIds: this._editorService.visibleEditors.map(input => input.getTypeId()), panelId: activePanel ? activePanel.getId() : undefined, diff --git a/src/vs/workbench/services/workspace/browser/workspaceEditingService.ts b/src/vs/workbench/services/workspace/browser/workspaceEditingService.ts index bffc453c49b..a97ea749b8a 100644 --- a/src/vs/workbench/services/workspace/browser/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspace/browser/workspaceEditingService.ts @@ -31,6 +31,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILabelService } from 'vs/platform/label/common/label'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export class WorkspaceEditingService implements IWorkspaceEditingService { @@ -54,7 +55,8 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { @IFileDialogService private readonly fileDialogService: IFileDialogService, @IDialogService private readonly dialogService: IDialogService, @ILifecycleService readonly lifecycleService: ILifecycleService, - @ILabelService readonly labelService: ILabelService + @ILabelService readonly labelService: ILabelService, + @IHostService private readonly hostService: IHostService ) { this.registerListeners(); } @@ -82,7 +84,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { return false; // only care about untitled workspaces to ask for saving } - const windowCount = await this.windowsService.getWindowCount(); + const windowCount = await this.hostService.windowCount; if (reason === ShutdownReason.CLOSE && !isMacintosh && windowCount === 1) { return false; // Windows/Linux: quits when last window is closed, so do not ask then diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 73a3517bd60..4ed07ea15ed 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -82,10 +82,11 @@ import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedPr import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer'; -import { NodeTextFileService } from 'vs/workbench/services/textfile/node/textFileService'; +import { NativeTextFileService } from 'vs/workbench/services/textfile/electron-browser/nativeTextFileService'; import { Schemas } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/productService'; import product from 'vs/platform/product/common/product'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); @@ -180,7 +181,7 @@ export class TestContextService implements IWorkspaceContextService { } } -export class TestTextFileService extends NodeTextFileService { +export class TestTextFileService extends NativeTextFileService { public cleanupBackupsBeforeShutdownCalled: boolean; private promptPath: URI; @@ -199,7 +200,7 @@ export class TestTextFileService extends NodeTextFileService { @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @INotificationService notificationService: INotificationService, @IBackupFileService backupFileService: IBackupFileService, - @IWindowsService windowsService: IWindowsService, + @IHostService hostService: IHostService, @IHistoryService historyService: IHistoryService, @IContextKeyService contextKeyService: IContextKeyService, @IDialogService dialogService: IDialogService, @@ -219,7 +220,7 @@ export class TestTextFileService extends NodeTextFileService { environmentService, notificationService, backupFileService, - windowsService, + hostService, historyService, contextKeyService, dialogService, @@ -311,6 +312,7 @@ export function workbenchInstantiationService(): IInstantiationService { instantiationService.stub(IDecorationsService, new TestDecorationsService()); instantiationService.stub(IExtensionService, new TestExtensionService()); instantiationService.stub(IWindowsService, new TestWindowsService()); + instantiationService.stub(IHostService, instantiationService.createInstance(TestHostService)); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); instantiationService.stub(IThemeService, new TestThemeService()); @@ -1349,8 +1351,6 @@ export class TestWindowsService implements IWindowsService { _serviceBrand: undefined; - public windowCount = 1; - readonly onWindowOpen: Event = Event.None; readonly onWindowFocus: Event = Event.None; readonly onWindowBlur: Event = Event.None; @@ -1490,10 +1490,6 @@ export class TestWindowsService implements IWindowsService { throw new Error('not implemented'); } - getWindowCount(): Promise { - return Promise.resolve(this.windowCount); - } - newWindowTab(): Promise { return Promise.resolve(); } @@ -1634,3 +1630,10 @@ export class RemoteFileSystemProvider implements IFileSystemProvider { } export const productService: IProductService = { _serviceBrand: undefined, ...product }; + +export class TestHostService implements IHostService { + + _serviceBrand: undefined; + + windowCount = Promise.resolve(1); +} diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 73fc63f220c..edecfd44bc6 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -81,6 +81,7 @@ import 'vs/workbench/services/notification/common/notificationService'; import 'vs/workbench/services/extensions/common/staticExtensions'; import 'vs/workbench/services/userData/common/settingsMergeService'; import 'vs/workbench/services/workspace/browser/workspaceEditingService'; +import 'vs/workbench/services/host/browser/browserHostService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 7739c33362a..9afb71f520c 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -31,7 +31,7 @@ import 'vs/workbench/services/integrity/node/integrityService'; import 'vs/workbench/services/textMate/electron-browser/textMateService'; import 'vs/workbench/services/search/node/searchService'; import 'vs/workbench/services/output/node/outputChannelModelService'; -import 'vs/workbench/services/textfile/node/textFileService'; +import 'vs/workbench/services/textfile/electron-browser/nativeTextFileService'; import 'vs/workbench/services/dialogs/electron-browser/dialogService'; import 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; import 'vs/workbench/services/keybinding/electron-browser/keybinding.contribution'; @@ -49,6 +49,7 @@ import 'vs/workbench/services/backup/node/backupFileService'; import 'vs/workbench/services/credentials/node/credentialsService'; import 'vs/workbench/services/url/electron-browser/urlService'; import 'vs/workbench/services/workspace/electron-browser/workspacesService'; +import 'vs/workbench/services/host/electron-browser/desktopHostService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index dc8eb1bb5e6..2416a9fc371 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -30,7 +30,7 @@ import 'vs/workbench/services/integrity/browser/integrityService'; import 'vs/workbench/services/textMate/browser/textMateService'; import 'vs/workbench/services/search/common/searchService'; import 'vs/workbench/services/output/common/outputChannelModelService'; -import 'vs/workbench/services/textfile/browser/textFileService'; +import 'vs/workbench/services/textfile/browser/browserTextFileService'; import 'vs/workbench/services/keybinding/browser/keymapService'; import 'vs/workbench/services/extensions/browser/extensionService'; import 'vs/workbench/services/extensionManagement/common/extensionManagementServerService';