diff --git a/.eslintrc.json b/.eslintrc.json index df8c35d28ed..51e623e8f1b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -104,6 +104,7 @@ "restrictions": [ "assert", "sinon", + "sinon-test", "vs/nls", "**/vs/base/common/**", "**/vs/base/test/common/**" @@ -139,6 +140,7 @@ "restrictions": [ "assert", "sinon", + "sinon-test", "vs/nls", "**/vs/base/{common,browser}/**", "**/vs/base/test/{common,browser}/**" @@ -212,6 +214,7 @@ "restrictions": [ "assert", "sinon", + "sinon-test", "vs/nls", "**/vs/base/common/**", "**/vs/base/parts/*/common/**", @@ -225,6 +228,7 @@ "restrictions": [ "assert", "sinon", + "sinon-test", "vs/nls", "**/vs/base/{common,browser}/**", "**/vs/base/parts/*/{common,browser}/**", @@ -289,6 +293,7 @@ "restrictions": [ "assert", "sinon", + "sinon-test", "vs/nls", "**/vs/base/{common,browser}/**", "**/vs/base/parts/*/{common,browser}/**", @@ -311,6 +316,7 @@ "restrictions": [ "assert", "sinon", + "sinon-test", "vs/nls", "**/vs/base/common/**", "**/vs/platform/*/common/**", @@ -334,6 +340,7 @@ "restrictions": [ "assert", "sinon", + "sinon-test", "vs/nls", "**/vs/base/{common,browser}/**", "**/vs/platform/*/{common,browser}/**", @@ -357,6 +364,7 @@ "restrictions": [ "assert", "sinon", + "sinon-test", "vs/nls", "**/vs/base/common/**", "**/vs/platform/*/common/**", @@ -383,6 +391,7 @@ "restrictions": [ "assert", "sinon", + "sinon-test", "vs/nls", "**/vs/base/{common,browser}/**", "**/vs/platform/*/{common,browser}/**", @@ -397,6 +406,7 @@ "restrictions": [ "assert", "sinon", + "sinon-test", "vs/nls", "**/vs/base/{common,browser}/**", "**/vs/base/test/{common,browser}/**", @@ -921,6 +931,7 @@ "**/vs/**", "assert", "sinon", + "sinon-test", "crypto", "vscode" ] @@ -952,6 +963,7 @@ "**/vs/**", "assert", "sinon", + "sinon-test", "crypto", "xterm*" ] @@ -962,6 +974,7 @@ "**/vs/**", "assert", "sinon", + "sinon-test", "crypto", "xterm*" ] diff --git a/.github/classifier.json b/.github/classifier.json index 1783b82c879..fd27dc85ec6 100644 --- a/.github/classifier.json +++ b/.github/classifier.json @@ -7,7 +7,7 @@ "labels": { "L10N": {"assign": []}, "VIM": {"assign": []}, - "accessibility": { "assign": ["isidorn"]}, + "accessibility": { "assign": ["isidorn", "sana-ajani"]}, "api": {"assign": ["jrieken"]}, "api-finalization": {"assign": []}, "api-proposal": {"assign": ["jrieken"]}, diff --git a/.github/commands.json b/.github/commands.json index 388a9c3dbb3..030d48c62bb 100644 --- a/.github/commands.json +++ b/.github/commands.json @@ -409,5 +409,11 @@ "gjsjohnmurray", "IllusionMH" ] + }, + { + "type": "label", + "name": "*workspace-trust-docs", + "action": "close", + "comment": "This issue appears to be the result of the new workspace trust feature shipped in June 2021. This security-focused feature has major impact on the functionality of VS Code. Due to the volume of issues, we ask that you take some time to review our [comprehensive documentation](https://aka.ms/vscode-workspace-trust) on the feature. If your issue is still not resolved, please let us know." } ] diff --git a/build/lib/watch/yarn.lock b/build/lib/watch/yarn.lock index b0d7dd4a9f1..258c0edadad 100644 --- a/build/lib/watch/yarn.lock +++ b/build/lib/watch/yarn.lock @@ -148,9 +148,9 @@ fsevents@~2.3.1: integrity sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw== glob-parent@^5.1.1, glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" diff --git a/build/yarn.lock b/build/yarn.lock index f30b35e4830..ef2db1ff90c 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -1488,9 +1488,9 @@ node-fetch@^2.6.0: integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== normalize-url@^4.1.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" - integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== + version "4.5.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" + integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== nth-check@~1.0.1: version "1.0.2" diff --git a/extensions/markdown-language-features/notebook/index.ts b/extensions/markdown-language-features/notebook/index.ts index 4dc45856e45..699bd2129ad 100644 --- a/extensions/markdown-language-features/notebook/index.ts +++ b/extensions/markdown-language-features/notebook/index.ts @@ -11,7 +11,6 @@ export function activate() { }); const style = document.createElement('style'); - style.classList.add('markdown-style'); style.textContent = ` .emptyMarkdownCell::before { content: "${document.documentElement.style.getPropertyValue('--notebook-cell-markup-empty-content')}"; @@ -134,7 +133,10 @@ export function activate() { white-space: pre-wrap; } `; - document.head.append(style); + const template = document.createElement('template'); + template.classList.add('markdown-style'); + template.content.appendChild(style); + document.head.appendChild(template); return { renderOutputItem: (outputInfo: { text(): string }, element: HTMLElement) => { @@ -148,8 +150,12 @@ export function activate() { previewRoot.appendChild(defaultStyles.cloneNode(true)); // And then contributed styles - for (const markdownStyleNode of document.getElementsByClassName('markdown-style')) { - previewRoot.appendChild(markdownStyleNode.cloneNode(true)); + for (const element of document.getElementsByClassName('markdown-style')) { + if (element instanceof HTMLTemplateElement) { + previewRoot.appendChild(element.content.cloneNode(true)); + } else { + previewRoot.appendChild(element.cloneNode(true)); + } } previewNode = document.createElement('div'); diff --git a/extensions/markdown-math/notebook/katex.ts b/extensions/markdown-math/notebook/katex.ts index 27d7be58be7..0753199edad 100644 --- a/extensions/markdown-math/notebook/katex.ts +++ b/extensions/markdown-math/notebook/katex.ts @@ -18,16 +18,20 @@ export async function activate(ctx: { link.rel = 'stylesheet'; link.classList.add('markdown-style'); link.href = styleHref; - document.head.append(link); const style = document.createElement('style'); - style.classList.add('markdown-style'); style.textContent = ` .katex-error { color: var(--vscode-editorError-foreground); } `; - document.head.append(style); + + // Put Everything into a template + const styleTemplate = document.createElement('template'); + styleTemplate.classList.add('markdown-style'); + styleTemplate.content.appendChild(style); + styleTemplate.content.appendChild(link); + document.head.appendChild(styleTemplate); const katex = require('@iktakahiro/markdown-it-katex'); markdownItRenderer.extendMarkdownIt((md: markdownIt.MarkdownIt) => { diff --git a/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts b/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts index 0fd8dec5832..4479d3e267d 100644 --- a/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts +++ b/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts @@ -137,7 +137,7 @@ class DiagnosticSettings { const currentSettings = this.get(language); const newSettings = f(currentSettings); this._languageSettings.set(language, newSettings); - return areLanguageDiagnosticSettingsEqual(currentSettings, newSettings); + return !areLanguageDiagnosticSettingsEqual(currentSettings, newSettings); } } diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts index da2da9499fc..d2298c1dabe 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -668,8 +668,7 @@ import { assertNoRpc } from '../utils'; }); }); - // https://github.com/microsoft/vscode/issues/119826 - suite.skip('environmentVariableCollection', () => { + suite('environmentVariableCollection', () => { test('should have collection variables apply to terminals immediately after setting', (done) => { // Text to match on before passing the test const expectedText = [ diff --git a/package.json b/package.json index c7f45820652..69c6e472a98 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.58.0", - "distro": "3f43781e788d694f33746808cc5182852222f935", + "distro": "f9acd3d731fc630bf6b665d551d5fa12ce0560e6", "author": { "name": "Microsoft Corporation" }, @@ -103,7 +103,8 @@ "@types/minimist": "^1.2.1", "@types/mocha": "^8.2.0", "@types/node": "14.x", - "@types/sinon": "^1.16.36", + "@types/sinon": "^10.0.2", + "@types/sinon-test": "^2.4.2", "@types/trusted-types": "^1.0.6", "@types/vscode-windows-registry": "^1.0.0", "@types/webpack": "^4.41.25", @@ -183,7 +184,8 @@ "rcedit": "^1.1.0", "request": "^2.85.0", "rimraf": "^2.2.8", - "sinon": "^1.17.2", + "sinon": "^11.1.1", + "sinon-test": "^3.1.0", "source-map": "0.6.1", "source-map-support": "^0.3.2", "style-loader": "^1.0.0", @@ -196,7 +198,7 @@ "vinyl-fs": "^3.0.0", "vscode-debugprotocol": "1.47.0", "vscode-nls-dev": "^3.3.1", - "vscode-telemetry-extractor": "^1.7.0", + "vscode-telemetry-extractor": "^1.8.0", "webpack": "^4.43.0", "webpack-cli": "^3.3.12", "webpack-stream": "^5.2.1", diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index d4cda8cde87..c9055f4051a 100644 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -46,7 +46,7 @@ if %errorlevel% neq 0 exit /b %errorlevel% :: Tests in the extension host -set ALL_PLATFORMS_API_TESTS_EXTRA_ARGS=--disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --no-sandbox --no-cached-data --disable-updates --disable-keytar --disable-extensions --disable-workspace-trust --user-data-dir=%VSCODEUSERDATADIR% +set ALL_PLATFORMS_API_TESTS_EXTRA_ARGS=--disable-telemetry --skip-welcome --crash-reporter-directory=%VSCODECRASHDIR% --no-cached-data --disable-updates --disable-keytar --disable-extensions --disable-workspace-trust --user-data-dir=%VSCODEUSERDATADIR% call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\singlefolder-tests %ALL_PLATFORMS_API_TESTS_EXTRA_ARGS% if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index e147156da33..f88bcb2d0d3 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -62,7 +62,7 @@ after_suite # Tests in the extension host -ALL_PLATFORMS_API_TESTS_EXTRA_ARGS="--disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-keytar --disable-extensions --disable-workspace-trust --user-data-dir=$VSCODEUSERDATADIR" +ALL_PLATFORMS_API_TESTS_EXTRA_ARGS="--disable-telemetry --skip-welcome --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-keytar --disable-extensions --disable-workspace-trust --user-data-dir=$VSCODEUSERDATADIR" "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/vscode-api-tests/testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests $ALL_PLATFORMS_API_TESTS_EXTRA_ARGS after_suite diff --git a/src/vs/base/browser/dnd.ts b/src/vs/base/browser/dnd.ts index c9d07cccfc3..e1a0872271a 100644 --- a/src/vs/base/browser/dnd.ts +++ b/src/vs/base/browser/dnd.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; import { addDisposableListener } from 'vs/base/browser/dom'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Mimes } from 'vs/base/common/mime'; /** * A helper that will execute a provided function when the provided HTMLElement receives @@ -70,7 +71,7 @@ export const DataTransfers = { /** * Typically transfer type for copy/paste transfers. */ - TEXT: 'text/plain' + TEXT: Mimes.text }; export function applyDragImage(event: DragEvent, label: string | null, clazz: string): void { diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index a5d014dc8a8..041fbc8a0ad 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as browser from 'vs/base/browser/browser'; -import { domEvent } from 'vs/base/browser/event'; import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { TimeoutTimer } from 'vs/base/common/async'; @@ -975,8 +974,8 @@ class FocusTracker extends Disposable implements IFocusTracker { } }; - this._register(domEvent(element, EventType.FOCUS, true)(onFocus)); - this._register(domEvent(element, EventType.BLUR, true)(onBlur)); + this._register(addDisposableListener(element, EventType.FOCUS, onFocus, true)); + this._register(addDisposableListener(element, EventType.BLUR, onBlur, true)); } refreshState() { @@ -1480,7 +1479,7 @@ export class ModifierKeyEmitter extends Emitter { metaKey: false }; - this._subscriptions.add(domEvent(window, 'keydown', true)(e => { + this._subscriptions.add(addDisposableListener(window, 'keydown', e => { if (e.defaultPrevented) { return; } @@ -1515,9 +1514,9 @@ export class ModifierKeyEmitter extends Emitter { this._keyStatus.event = e; this.fire(this._keyStatus); } - })); + }, true)); - this._subscriptions.add(domEvent(window, 'keyup', true)(e => { + this._subscriptions.add(addDisposableListener(window, 'keyup', e => { if (e.defaultPrevented) { return; } @@ -1547,23 +1546,23 @@ export class ModifierKeyEmitter extends Emitter { this._keyStatus.event = e; this.fire(this._keyStatus); } - })); + }, true)); - this._subscriptions.add(domEvent(document.body, 'mousedown', true)(e => { + this._subscriptions.add(addDisposableListener(document.body, 'mousedown', () => { this._keyStatus.lastKeyPressed = undefined; - })); + }, true)); - this._subscriptions.add(domEvent(document.body, 'mouseup', true)(e => { + this._subscriptions.add(addDisposableListener(document.body, 'mouseup', () => { this._keyStatus.lastKeyPressed = undefined; - })); + }, true)); - this._subscriptions.add(domEvent(document.body, 'mousemove', true)(e => { + this._subscriptions.add(addDisposableListener(document.body, 'mousemove', e => { if (e.buttons) { this._keyStatus.lastKeyPressed = undefined; } - })); + }, true)); - this._subscriptions.add(domEvent(window, 'blur')(e => { + this._subscriptions.add(addDisposableListener(window, 'blur', () => { this.resetKeyStatus(); })); } diff --git a/src/vs/base/browser/event.ts b/src/vs/base/browser/event.ts index a1214db777d..a9d91853fe8 100644 --- a/src/vs/base/browser/event.ts +++ b/src/vs/base/browser/event.ts @@ -14,24 +14,7 @@ export interface IDomEvent { (element: EventHandler, type: string, useCapture?: boolean): BaseEvent; } -/** - * @deprecated Use `DomEmitter` instead - */ -export const domEvent: IDomEvent = (element: EventHandler, type: string, useCapture?: boolean) => { - const fn = (e: Event) => emitter.fire(e); - const emitter = new Emitter({ - onFirstListenerAdd: () => { - element.addEventListener(type, fn, useCapture); - }, - onLastListenerRemove: () => { - element.removeEventListener(type, fn, useCapture); - } - }); - - return emitter.event; -}; - -export interface DOMEventMap extends HTMLElementEventMap { +export interface DOMEventMap extends HTMLElementEventMap, DocumentEventMap { '-monaco-gesturetap': GestureEvent; '-monaco-gesturechange': GestureEvent; '-monaco-gesturestart': GestureEvent; @@ -47,6 +30,8 @@ export class DomEmitter implements IDisposable { return this.emitter.event; } + constructor(element: Document, type: DocumentEventMap, useCapture?: boolean); + constructor(element: EventHandler, type: K, useCapture?: boolean); constructor(element: EventHandler, type: K, useCapture?: boolean) { const fn = (e: Event) => this.emitter.fire(e as DOMEventMap[K]); this.emitter = new Emitter({ diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index 78451aa3776..b5aca5ee4d5 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -20,7 +20,7 @@ import { resolvePath } from 'vs/base/common/resources'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { Event } from 'vs/base/common/event'; -import { domEvent } from 'vs/base/browser/event'; +import { DomEmitter } from 'vs/base/browser/event'; export interface MarkedOptions extends marked.MarkedOptions { baseUrl?: never; @@ -186,7 +186,9 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende } if (options.actionHandler) { - options.actionHandler.disposeables.add(Event.any(domEvent(element, 'click'), domEvent(element, 'auxclick'))(e => { + const onClick = options.actionHandler.disposeables.add(new DomEmitter(element, 'click')); + const onAuxClick = options.actionHandler.disposeables.add(new DomEmitter(element, 'auxclick')); + options.actionHandler.disposeables.add(Event.any(onClick.event, onAuxClick.event)(e => { const mouseEvent = new StandardMouseEvent(e); if (!mouseEvent.leftButton && !mouseEvent.middleButton) { return; diff --git a/src/vs/base/browser/ui/dialog/dialog.ts b/src/vs/base/browser/ui/dialog/dialog.ts index 84757ba8cc8..0ebf7934c83 100644 --- a/src/vs/base/browser/ui/dialog/dialog.ts +++ b/src/vs/base/browser/ui/dialog/dialog.ts @@ -7,7 +7,6 @@ import 'vs/css!./dialog'; import * as nls from 'vs/nls'; import { Disposable } from 'vs/base/common/lifecycle'; import { $, hide, show, EventHelper, clearNode, isAncestor, addDisposableListener, EventType } from 'vs/base/browser/dom'; -import { domEvent } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Color } from 'vs/base/common/color'; @@ -220,7 +219,7 @@ export class Dialog extends Disposable { }); // Handle keyboard events gloably: Tab, Arrow-Left/Right - this._register(domEvent(window, 'keydown', true)((e: KeyboardEvent) => { + this._register(addDisposableListener(window, 'keydown', e => { const evt = new StandardKeyboardEvent(e); if (evt.equals(KeyMod.Alt)) { @@ -321,9 +320,9 @@ export class Dialog extends Disposable { } else if (this.options.keyEventProcessor) { this.options.keyEventProcessor(evt); } - })); + }, true)); - this._register(domEvent(window, 'keyup', true)((e: KeyboardEvent) => { + this._register(addDisposableListener(window, 'keyup', e => { EventHelper.stop(e, true); const evt = new StandardKeyboardEvent(e); @@ -333,10 +332,10 @@ export class Dialog extends Disposable { checkboxChecked: this.checkbox ? this.checkbox.checked : undefined }); } - })); + }, true)); // Detect focus out - this._register(domEvent(this.element, 'focusout', false)((e: FocusEvent) => { + this._register(addDisposableListener(this.element, 'focusout', e => { if (!!e.relatedTarget && !!this.element) { if (!isAncestor(e.relatedTarget as HTMLElement, this.element)) { this.focusToReturn = e.relatedTarget as HTMLElement; @@ -347,7 +346,7 @@ export class Dialog extends Disposable { } } } - })); + }, false)); const spinModifierClassName = 'codicon-modifier-spin'; diff --git a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts index a80a53e02bb..8d14e2c311b 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts @@ -6,13 +6,13 @@ import { isFunction, isString } from 'vs/base/common/types'; import * as dom from 'vs/base/browser/dom'; import { IIconLabelMarkdownString } from 'vs/base/browser/ui/iconLabel/iconLabel'; -import { IHoverDelegate, IHoverDelegateOptions, IHoverDelegateTarget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate, IHoverDelegateTarget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { DomEmitter } from 'vs/base/browser/event'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { localize } from 'vs/nls'; import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { RunOnceScheduler } from 'vs/base/common/async'; export function setupNativeHover(htmlElement: HTMLElement, tooltip: string | IIconLabelMarkdownString | undefined): void { @@ -32,79 +32,90 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM const tooltip = getTooltipForCustom(markdownTooltip); - let hoverOptions: IHoverDelegateOptions | undefined; - let mouseX: number | undefined; - let isHovering = false; - let tokenSource: CancellationTokenSource; - let hoverDisposable: IDisposable | undefined; + let hoverPreparation: IDisposable | undefined; - const mouseOverDomEmitter = new DomEmitter(htmlElement, dom.EventType.MOUSE_OVER, true); - mouseOverDomEmitter.event((e: MouseEvent) => { - if (isHovering) { + let hoverWidget: IDisposable | undefined; + + const mouseEnter = (e: MouseEvent) => { + if (hoverPreparation) { return; } - tokenSource = new CancellationTokenSource(); - function mouseLeaveOrDown(e: MouseEvent): void { + + const tokenSource = new CancellationTokenSource(); + + const mouseLeaveOrDown = (e: MouseEvent) => { const isMouseDown = e.type === dom.EventType.MOUSE_DOWN; if (isMouseDown) { - hoverDisposable?.dispose(); - hoverDisposable = undefined; + hoverWidget?.dispose(); + hoverWidget = undefined; } if (isMouseDown || (e).fromElement === htmlElement) { - isHovering = false; - hoverOptions = undefined; - tokenSource.dispose(true); - mouseLeaveDomEmitter.dispose(); - mouseDownDomEmitter.dispose(); + hoverPreparation?.dispose(); + hoverPreparation = undefined; } - } - const mouseLeaveDomEmitter = new DomEmitter(htmlElement, dom.EventType.MOUSE_LEAVE, true); - mouseLeaveDomEmitter.event(mouseLeaveOrDown); - const mouseDownDomEmitter = new DomEmitter(htmlElement, dom.EventType.MOUSE_DOWN, true); - mouseDownDomEmitter.event(mouseLeaveOrDown); - isHovering = true; + }; + const mouseLeaveDomListener = dom.addDisposableListener(htmlElement, dom.EventType.MOUSE_LEAVE, mouseLeaveOrDown, true); + const mouseDownDownListener = dom.addDisposableListener(htmlElement, dom.EventType.MOUSE_DOWN, mouseLeaveOrDown, true); - function mouseMove(e: MouseEvent): void { - mouseX = e.x; + const target: IHoverDelegateTarget = { + targetElements: [htmlElement], + dispose: () => { } + }; + + let mouseMoveDomListener: IDisposable | undefined; + if (hoverDelegate.placement === undefined || hoverDelegate.placement === 'mouse') { + const mouseMove = (e: MouseEvent) => target.x = e.x + 10; + mouseMoveDomListener = dom.addDisposableListener(htmlElement, dom.EventType.MOUSE_MOVE, mouseMove, true); } - const mouseMoveDomEmitter = new DomEmitter(htmlElement, dom.EventType.MOUSE_MOVE, true); - mouseMoveDomEmitter.event(mouseMove); - setTimeout(async () => { - if (isHovering && tooltip) { - // Re-use the already computed hover options if they exist. - if (!hoverOptions) { - const target: IHoverDelegateTarget = { - targetElements: [htmlElement], - dispose: () => { } - }; - hoverOptions = { - text: localize('iconLabel.loading', "Loading..."), + + const showHover = async () => { + if (hoverPreparation) { + + const hoverOptions = { + text: localize('iconLabel.loading', "Loading..."), + target, + hoverPosition: HoverPosition.BELOW + }; + hoverWidget?.dispose(); + hoverWidget = hoverDelegate.showHover(hoverOptions); + + const resolvedTooltip = (await tooltip(tokenSource.token)) ?? (!isString(markdownTooltip) ? markdownTooltip.markdownNotSupportedFallback : undefined); + + hoverWidget?.dispose(); + hoverWidget = undefined; + + // awaiting the tooltip could take a while. Make sure we're still preparing to hover. + if (resolvedTooltip && hoverPreparation) { + const hoverOptions = { + text: resolvedTooltip, target, + showPointer: hoverDelegate.placement === 'element', hoverPosition: HoverPosition.BELOW }; - hoverDisposable = adjustXAndShowCustomHover(hoverOptions, mouseX, hoverDelegate, isHovering); - const resolvedTooltip = (await tooltip(tokenSource.token)) ?? (!isString(markdownTooltip) ? markdownTooltip.markdownNotSupportedFallback : undefined); - if (resolvedTooltip) { - hoverOptions = { - text: resolvedTooltip, - target, - showPointer: hoverDelegate.placement === 'element', - hoverPosition: HoverPosition.BELOW - }; - // awaiting the tooltip could take a while. Make sure we're still hovering. - hoverDisposable = adjustXAndShowCustomHover(hoverOptions, mouseX, hoverDelegate, isHovering); - } else if (hoverDisposable) { - hoverDisposable.dispose(); - hoverDisposable = undefined; - } + hoverWidget = hoverDelegate.showHover(hoverOptions); } } - mouseMoveDomEmitter.dispose(); - }, hoverDelegate.delay); + mouseMoveDomListener?.dispose(); + }; + const timeout = new RunOnceScheduler(showHover, hoverDelegate.delay); + timeout.schedule(); + + hoverPreparation = toDisposable(() => { + timeout.dispose(); + mouseMoveDomListener?.dispose(); + mouseDownDownListener.dispose(); + mouseLeaveDomListener.dispose(); + tokenSource.dispose(true); + }); + }; + const mouseOverDomEmitter = dom.addDisposableListener(htmlElement, dom.EventType.MOUSE_OVER, mouseEnter, true); + return toDisposable(() => { + mouseOverDomEmitter.dispose(); + hoverPreparation?.dispose(); + hoverWidget?.dispose(); }); - return mouseOverDomEmitter; } @@ -118,13 +129,3 @@ function getTooltipForCustom(markdownTooltip: string | IIconLabelMarkdownString) return async () => markdown; } } - -function adjustXAndShowCustomHover(hoverOptions: IHoverDelegateOptions | undefined, mouseX: number | undefined, hoverDelegate: IHoverDelegate, isHovering: boolean): IDisposable | undefined { - if (hoverOptions && isHovering) { - if (mouseX !== undefined && (hoverDelegate.placement === undefined || hoverDelegate.placement === 'mouse')) { - (hoverOptions.target).x = mouseX + 10; - } - return hoverDelegate.showHover(hoverOptions); - } - return undefined; -} diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index 62638456615..34da4ee8a69 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -21,7 +21,7 @@ import { HistoryNavigator } from 'vs/base/common/history'; import { IHistoryNavigationWidget } from 'vs/base/browser/history'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; -import { domEvent } from 'vs/base/browser/event'; +import { DomEmitter } from 'vs/base/browser/event'; const $ = dom.$; @@ -189,13 +189,14 @@ export class InputBox extends Widget { // from ScrollableElement to DOM this._register(this.scrollableElement.onScroll(e => this.input.scrollTop = e.scrollTop)); - const onSelectionChange = Event.filter(domEvent(document, 'selectionchange'), () => { + const onSelectionChange = this._register(new DomEmitter(document, 'selectionchange')); + const onAnchoredSelectionChange = Event.filter(onSelectionChange.event, () => { const selection = document.getSelection(); return selection?.anchorNode === wrapper; }); // from DOM to ScrollableElement - this._register(onSelectionChange(this.updateScrollDimensions, this)); + this._register(onAnchoredSelectionChange(this.updateScrollDimensions, this)); this._register(this.onDidHeightChange(this.updateScrollDimensions, this)); } else { this.input.type = this.options.type || 'text'; diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 580812f1197..426d4acdc78 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -7,7 +7,6 @@ import { getOrDefault } from 'vs/base/common/objects'; import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Gesture, EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; import { Event, Emitter } from 'vs/base/common/event'; -import { domEvent } from 'vs/base/browser/event'; import { SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollEvent, ScrollbarVisibility, INewScrollDimensions, Scrollable } from 'vs/base/common/scrollable'; import { RangeMap, shift } from './rangeMap'; @@ -21,7 +20,8 @@ import { DataTransfers, StaticDND, IDragAndDropData } from 'vs/base/browser/dnd' import { disposableTimeout, Delayer } from 'vs/base/common/async'; import { isFirefox } from 'vs/base/browser/browser'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; -import { $, animate, getContentHeight, getContentWidth, getTopLeftOffset, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import { $, addDisposableListener, animate, getContentHeight, getContentWidth, getTopLeftOffset, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import { DomEmitter } from 'vs/base/browser/event'; interface IItem { readonly id: string; @@ -339,17 +339,16 @@ export class ListView implements ISpliceable, IDisposable { container.appendChild(this.domNode); this.scrollableElement.onScroll(this.onScroll, this, this.disposables); - domEvent(this.rowsContainer, TouchEventType.Change)(e => this.onTouchChange(e as GestureEvent), this, this.disposables); + this.disposables.add(addDisposableListener(this.rowsContainer, TouchEventType.Change, e => this.onTouchChange(e as GestureEvent))); // Prevent the monaco-scrollable-element from scrolling // https://github.com/microsoft/vscode/issues/44181 - domEvent(this.scrollableElement.getDomNode(), 'scroll') - (e => (e.target as HTMLElement).scrollTop = 0, null, this.disposables); + this.disposables.add(addDisposableListener(this.scrollableElement.getDomNode(), 'scroll', e => (e.target as HTMLElement).scrollTop = 0)); - Event.map(domEvent(this.domNode, 'dragover'), e => this.toDragEvent(e))(this.onDragOver, this, this.disposables); - Event.map(domEvent(this.domNode, 'drop'), e => this.toDragEvent(e))(this.onDrop, this, this.disposables); - domEvent(this.domNode, 'dragleave')(this.onDragLeave, this, this.disposables); - domEvent(window, 'dragend')(this.onDragEnd, this, this.disposables); + this.disposables.add(addDisposableListener(this.domNode, 'dragover', e => this.onDragOver(this.toDragEvent(e)))); + this.disposables.add(addDisposableListener(this.domNode, 'drop', e => this.onDrop(this.toDragEvent(e)))); + this.disposables.add(addDisposableListener(this.domNode, 'dragleave', _ => this.onDragLeave())); + this.disposables.add(addDisposableListener(this.domNode, 'dragend', e => this.onDragEnd(e))); this.setRowLineHeight = getOrDefault(options, o => o.setRowLineHeight, DefaultOptions.setRowLineHeight); this.setRowHeight = getOrDefault(options, o => o.setRowHeight, DefaultOptions.setRowHeight); @@ -775,8 +774,7 @@ export class ListView implements ISpliceable, IDisposable { item.row.domNode.draggable = !!uri; if (uri) { - const onDragStart = domEvent(item.row.domNode, 'dragstart'); - item.dragStartDisposable = onDragStart(event => this.onDragStart(item.element, uri, event)); + item.dragStartDisposable = addDisposableListener(item.row.domNode, 'dragstart', event => this.onDragStart(item.element, uri, event)); } if (this.horizontalScrolling) { @@ -890,17 +888,17 @@ export class ListView implements ISpliceable, IDisposable { // Events - @memoize get onMouseClick(): Event> { return Event.map(domEvent(this.domNode, 'click'), e => this.toMouseEvent(e)); } - @memoize get onMouseDblClick(): Event> { return Event.map(domEvent(this.domNode, 'dblclick'), e => this.toMouseEvent(e)); } - @memoize get onMouseMiddleClick(): Event> { return Event.filter(Event.map(domEvent(this.domNode, 'auxclick'), e => this.toMouseEvent(e as MouseEvent)), e => e.browserEvent.button === 1); } - @memoize get onMouseUp(): Event> { return Event.map(domEvent(this.domNode, 'mouseup'), e => this.toMouseEvent(e)); } - @memoize get onMouseDown(): Event> { return Event.map(domEvent(this.domNode, 'mousedown'), e => this.toMouseEvent(e)); } - @memoize get onMouseOver(): Event> { return Event.map(domEvent(this.domNode, 'mouseover'), e => this.toMouseEvent(e)); } - @memoize get onMouseMove(): Event> { return Event.map(domEvent(this.domNode, 'mousemove'), e => this.toMouseEvent(e)); } - @memoize get onMouseOut(): Event> { return Event.map(domEvent(this.domNode, 'mouseout'), e => this.toMouseEvent(e)); } - @memoize get onContextMenu(): Event | IListGestureEvent> { return Event.any(Event.map(domEvent(this.domNode, 'contextmenu'), e => this.toMouseEvent(e)), Event.map(domEvent(this.domNode, TouchEventType.Contextmenu) as Event, e => this.toGestureEvent(e))); } - @memoize get onTouchStart(): Event> { return Event.map(domEvent(this.domNode, 'touchstart'), e => this.toTouchEvent(e)); } - @memoize get onTap(): Event> { return Event.map(domEvent(this.rowsContainer, TouchEventType.Tap), e => this.toGestureEvent(e as GestureEvent)); } + @memoize get onMouseClick(): Event> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'click')).event, e => this.toMouseEvent(e)); } + @memoize get onMouseDblClick(): Event> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'dblclick')).event, e => this.toMouseEvent(e)); } + @memoize get onMouseMiddleClick(): Event> { return Event.filter(Event.map(this.disposables.add(new DomEmitter(this.domNode, 'auxclick')).event, e => this.toMouseEvent(e as MouseEvent)), e => e.browserEvent.button === 1); } + @memoize get onMouseUp(): Event> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'mouseup')).event, e => this.toMouseEvent(e)); } + @memoize get onMouseDown(): Event> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'mousedown')).event, e => this.toMouseEvent(e)); } + @memoize get onMouseOver(): Event> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'mouseover')).event, e => this.toMouseEvent(e)); } + @memoize get onMouseMove(): Event> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'mousemove')).event, e => this.toMouseEvent(e)); } + @memoize get onMouseOut(): Event> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'mouseout')).event, e => this.toMouseEvent(e)); } + @memoize get onContextMenu(): Event | IListGestureEvent> { return Event.any(Event.map(this.disposables.add(new DomEmitter(this.domNode, 'contextmenu')).event, e => this.toMouseEvent(e)), Event.map(this.disposables.add(new DomEmitter(this.domNode, TouchEventType.Contextmenu)).event as Event, e => this.toGestureEvent(e))); } + @memoize get onTouchStart(): Event> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'touchstart')).event, e => this.toTouchEvent(e)); } + @memoize get onTap(): Event> { return Event.map(this.disposables.add(new DomEmitter(this.rowsContainer, TouchEventType.Tap)).event, e => this.toGestureEvent(e as GestureEvent)); } private toMouseEvent(browserEvent: MouseEvent): IListMouseEvent { const index = this.getItemIndexFromEventTarget(browserEvent.target || null); diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 93906052348..d4a6bda722c 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -13,7 +13,7 @@ import { Gesture } from 'vs/base/browser/touch'; import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Event, Emitter, EventBufferer } from 'vs/base/common/event'; -import { domEvent, stopEvent } from 'vs/base/browser/event'; +import { DomEmitter, stopEvent } from 'vs/base/browser/event'; import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider, IKeyboardNavigationLabelProvider, IListDragAndDrop, IListDragOverReaction, ListError, IKeyboardNavigationDelegate } from './list'; import { ListView, IListViewOptions, IListViewDragAndDrop, IListViewAccessibilityProvider, IListViewOptionsUpdate } from './listView'; import { Color } from 'vs/base/common/color'; @@ -257,7 +257,7 @@ class KeyboardController implements IDisposable { ) { const multipleSelectionSupport = options.multipleSelectionSupport !== false; - const onKeyDown = Event.chain(domEvent(view.domNode, 'keydown')) + const onKeyDown = Event.chain(this.disposables.add(new DomEmitter(view.domNode, 'keydown')).event) .filter(e => !isInputElement(e.target as HTMLElement)) .map(e => new StandardKeyboardEvent(e)); @@ -394,7 +394,7 @@ class TypeLabelController implements IDisposable { return; } - const onChar = Event.chain(domEvent(this.view.domNode, 'keydown')) + const onChar = Event.chain(this.enabledDisposables.add(new DomEmitter(this.view.domNode, 'keydown')).event) .filter(e => !isInputElement(e.target as HTMLElement)) .filter(() => this.automaticKeyboardNavigation || this.triggered) .map(event => new StandardKeyboardEvent(event)) @@ -477,7 +477,7 @@ class DOMFocusController implements IDisposable { private list: List, private view: ListView ) { - const onKeyDown = Event.chain(domEvent(view.domNode, 'keydown')) + const onKeyDown = Event.chain(this.disposables.add(new DomEmitter(view.domNode, 'keydown')).event) .filter(e => !isInputElement(e.target as HTMLElement)) .map(e => new StandardKeyboardEvent(e)); @@ -1184,14 +1184,14 @@ export class List implements ISpliceable, IThemable, IDisposable { @memoize get onContextMenu(): Event> { let didJustPressContextMenuKey = false; - const fromKeyDown = Event.chain(domEvent(this.view.domNode, 'keydown')) + const fromKeyDown = Event.chain(this.disposables.add(new DomEmitter(this.view.domNode, 'keydown')).event) .map(e => new StandardKeyboardEvent(e)) .filter(e => didJustPressContextMenuKey = e.keyCode === KeyCode.ContextMenu || (e.shiftKey && e.keyCode === KeyCode.F10)) .map(stopEvent) .filter(() => false) .event as Event; - const fromKeyUp = Event.chain(domEvent(this.view.domNode, 'keyup')) + const fromKeyUp = Event.chain(this.disposables.add(new DomEmitter(this.view.domNode, 'keyup')).event) .forEach(() => didJustPressContextMenuKey = false) .map(e => new StandardKeyboardEvent(e)) .filter(e => e.keyCode === KeyCode.ContextMenu || (e.shiftKey && e.keyCode === KeyCode.F10)) @@ -1213,12 +1213,12 @@ export class List implements ISpliceable, IThemable, IDisposable { return Event.any>(fromKeyDown, fromKeyUp, fromMouse); } - get onKeyDown(): Event { return domEvent(this.view.domNode, 'keydown'); } - get onKeyUp(): Event { return domEvent(this.view.domNode, 'keyup'); } - get onKeyPress(): Event { return domEvent(this.view.domNode, 'keypress'); } + @memoize get onKeyDown(): Event { return this.disposables.add(new DomEmitter(this.view.domNode, 'keydown')).event; } + @memoize get onKeyUp(): Event { return this.disposables.add(new DomEmitter(this.view.domNode, 'keyup')).event; } + @memoize get onKeyPress(): Event { return this.disposables.add(new DomEmitter(this.view.domNode, 'keypress')).event; } - readonly onDidFocus: Event; - readonly onDidBlur: Event; + @memoize get onDidFocus(): Event { return Event.signal(this.disposables.add(new DomEmitter(this.view.domNode, 'focus', true)).event); } + @memoize get onDidBlur(): Event { return Event.signal(this.disposables.add(new DomEmitter(this.view.domNode, 'blur', true)).event); } private readonly _onDidDispose = new Emitter(); readonly onDidDispose: Event = this._onDidDispose.event; @@ -1277,9 +1277,6 @@ export class List implements ISpliceable, IThemable, IDisposable { this.disposables.add(this.view); this.disposables.add(this._onDidDispose); - this.onDidFocus = Event.map(domEvent(this.view.domNode, 'focus', true), () => null!); - this.onDidBlur = Event.map(domEvent(this.view.domNode, 'blur', true), () => null!); - this.disposables.add(new DOMFocusController(this, this.view)); if (typeof _options.keyboardSupport !== 'boolean' || _options.keyboardSupport) { diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index a2447f6db79..c2347525d84 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -14,7 +14,7 @@ import * as arrays from 'vs/base/common/arrays'; import { IContextViewProvider, AnchorPosition } from 'vs/base/browser/ui/contextview/contextview'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { IListVirtualDelegate, IListRenderer, IListEvent } from 'vs/base/browser/ui/list/list'; -import { domEvent } from 'vs/base/browser/event'; +import { DomEmitter } from 'vs/base/browser/event'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { ISelectBoxDelegate, ISelectOptionItem, ISelectBoxOptions, ISelectBoxStyles, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox'; import { isMacintosh } from 'vs/base/common/platform'; @@ -736,7 +736,8 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi } // SetUp list keyboard controller - control navigation, disabled items, focus - const onSelectDropDownKeyDown = Event.chain(domEvent(this.selectDropDownListContainer, 'keydown')) + const onKeyDown = this._register(new DomEmitter(this.selectDropDownListContainer, 'keydown')); + const onSelectDropDownKeyDown = Event.chain(onKeyDown.event) .filter(() => this.selectList.length > 0) .map(e => new StandardKeyboardEvent(e)); @@ -752,7 +753,8 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi // SetUp list mouse controller - control navigation, disabled items, focus - this._register(Event.chain(domEvent(this.selectList.getHTMLElement(), 'mouseup')) + const onMouseUp = this._register(new DomEmitter(this.selectList.getHTMLElement(), 'mouseup')); + this._register(Event.chain(onMouseUp.event) .filter(() => this.selectList.length > 0) .on(e => this.onMouseUp(e), this)); diff --git a/src/vs/base/browser/ui/splitview/paneview.ts b/src/vs/base/browser/ui/splitview/paneview.ts index a84c6f5837e..92bc2683838 100644 --- a/src/vs/base/browser/ui/splitview/paneview.ts +++ b/src/vs/base/browser/ui/splitview/paneview.ts @@ -6,10 +6,9 @@ import 'vs/css!./paneview'; import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; -import { domEvent } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { $, append, trackFocus, EventHelper, clearNode } from 'vs/base/browser/dom'; +import { $, append, trackFocus, EventHelper, clearNode, addDisposableListener } from 'vs/base/browser/dom'; import { Color, RGBA } from 'vs/base/common/color'; import { SplitView, IView } from './splitview'; import { isFirefox } from 'vs/base/browser/browser'; @@ -17,6 +16,7 @@ import { DataTransfers } from 'vs/base/browser/dnd'; import { Orientation } from 'vs/base/browser/ui/sash/sash'; import { localize } from 'vs/nls'; import { ScrollEvent } from 'vs/base/common/scrollable'; +import { DomEmitter } from 'vs/base/browser/event'; export interface IPaneOptions { minimumBodySize?: number; @@ -220,8 +220,8 @@ export abstract class Pane extends Disposable implements IView { this.updateHeader(); - - const onHeaderKeyDown = Event.chain(domEvent(this.header, 'keydown')) + const onKeyDown = this._register(new DomEmitter(this.header, 'keydown')); + const onHeaderKeyDown = Event.chain(onKeyDown.event) .map(e => new StandardKeyboardEvent(e)); this._register(onHeaderKeyDown.filter(e => e.keyCode === KeyCode.Enter || e.keyCode === KeyCode.Space) @@ -233,12 +233,11 @@ export abstract class Pane extends Disposable implements IView { this._register(onHeaderKeyDown.filter(e => e.keyCode === KeyCode.RightArrow) .event(() => this.setExpanded(true), null)); - this._register(domEvent(this.header, 'click') - (e => { - if (!e.defaultPrevented) { - this.setExpanded(!this.isExpanded()); - } - }, null)); + this._register(addDisposableListener(this.header, 'click', e => { + if (!e.defaultPrevented) { + this.setExpanded(!this.isExpanded()); + } + })); this.body = append(this.element, $('.pane-body')); this.renderBody(this.body); @@ -308,11 +307,11 @@ class PaneDraggable extends Disposable { super(); pane.draggableElement.draggable = true; - this._register(domEvent(pane.draggableElement, 'dragstart')(this.onDragStart, this)); - this._register(domEvent(pane.dropTargetElement, 'dragenter')(this.onDragEnter, this)); - this._register(domEvent(pane.dropTargetElement, 'dragleave')(this.onDragLeave, this)); - this._register(domEvent(pane.dropTargetElement, 'dragend')(this.onDragEnd, this)); - this._register(domEvent(pane.dropTargetElement, 'drop')(this.onDrop, this)); + this._register(addDisposableListener(pane.draggableElement, 'dragstart', e => this.onDragStart(e))); + this._register(addDisposableListener(pane.dropTargetElement, 'dragenter', e => this.onDragEnter(e))); + this._register(addDisposableListener(pane.dropTargetElement, 'dragleave', e => this.onDragLeave(e))); + this._register(addDisposableListener(pane.dropTargetElement, 'dragend', e => this.onDragEnd(e))); + this._register(addDisposableListener(pane.dropTargetElement, 'drop', e => this.onDrop(e))); } private onDragStart(e: DragEvent): void { diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 5996365a70f..2ee283f0a40 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -11,8 +11,7 @@ import { clamp } from 'vs/base/common/numbers'; import { range, pushToStart, pushToEnd } from 'vs/base/common/arrays'; import { Sash, Orientation, ISashEvent as IBaseSashEvent, SashState } from 'vs/base/browser/ui/sash/sash'; import { Color } from 'vs/base/common/color'; -import { domEvent } from 'vs/base/browser/event'; -import { $, append, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import { $, addDisposableListener, append, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { Scrollable, ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable'; export { Orientation } from 'vs/base/browser/ui/sash/sash'; @@ -488,8 +487,8 @@ export class SplitView extends Disposable { // This way, we can press Alt while we resize a sash, macOS style! const disposable = combinedDisposable( - domEvent(document.body, 'keydown')(e => resetSashDragState(this.sashDragState!.current, e.altKey)), - domEvent(document.body, 'keyup')(() => resetSashDragState(this.sashDragState!.current, false)) + addDisposableListener(document.body, 'keydown', e => resetSashDragState(this.sashDragState!.current, e.altKey)), + addDisposableListener(document.body, 'keyup', () => resetSashDragState(this.sashDragState!.current, false)) ); const resetSashDragState = (start: number, alt: boolean) => { diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index efc69fa4755..c4120542a9f 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/tree'; import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IListOptions, List, IListStyles, MouseController, DefaultKeyboardNavigationDelegate, isInputElement, isMonacoEditor } from 'vs/base/browser/ui/list/listWidget'; import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IKeyboardNavigationLabelProvider, IIdentityProvider, IKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/list'; -import { append, $, getDomNodePagePosition, hasParentWithClass, createStyleSheet, clearNode } from 'vs/base/browser/dom'; +import { append, $, getDomNodePagePosition, hasParentWithClass, createStyleSheet, clearNode, addDisposableListener } from 'vs/base/browser/dom'; import { Event, Relay, Emitter, EventBufferer } from 'vs/base/common/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -16,7 +16,7 @@ import { ISpliceable } from 'vs/base/common/sequence'; import { IDragAndDropData, StaticDND, DragAndDropData } from 'vs/base/browser/dnd'; import { range, equals, distinctES6, firstOrDefault } from 'vs/base/common/arrays'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; -import { domEvent } from 'vs/base/browser/event'; +import { DomEmitter } from 'vs/base/browser/event'; import { fuzzyScore, FuzzyScore } from 'vs/base/common/filters'; import { getVisibleState, isFilterResult } from 'vs/base/browser/ui/tree/indexTreeModel'; import { localize } from 'vs/nls'; @@ -648,7 +648,7 @@ class TypeFilterController implements IDisposable { ) { this.domNode = $(`.monaco-list-type-filter.${this.positionClassName}`); this.domNode.draggable = true; - domEvent(this.domNode, 'dragstart')(this.onDragStart, this, this.disposables); + this.disposables.add(addDisposableListener(this.domNode, 'dragstart', () => this.onDragStart())); this.messageDomNode = append(view.getHTMLElement(), $(`.monaco-list-type-filter-message`)); @@ -661,7 +661,7 @@ class TypeFilterController implements IDisposable { this.filterOnTypeDomNode.checked = this._filterOnType; this.filterOnTypeDomNode.tabIndex = -1; this.updateFilterOnTypeTitleAndIcon(); - domEvent(this.filterOnTypeDomNode, 'input')(this.onDidChangeFilterOnType, this, this.disposables); + this.disposables.add(addDisposableListener(this.filterOnTypeDomNode, 'input', () => this.onDidChangeFilterOnType())); this.clearDomNode = append(controls, $('button.clear' + treeFilterClearIcon.cssSelector)); this.clearDomNode.tabIndex = -1; @@ -711,7 +711,8 @@ class TypeFilterController implements IDisposable { return; } - const onKeyDown = Event.chain(domEvent(this.view.getHTMLElement(), 'keydown')) + const onRawKeyDown = this.enabledDisposables.add(new DomEmitter(this.view.getHTMLElement(), 'keydown')); + const onKeyDown = Event.chain(onRawKeyDown.event) .filter(e => !isInputElement(e.target as HTMLElement) || e.target === this.filterOnTypeDomNode) .filter(e => e.key !== 'Dead' && !/^Media/.test(e.key)) .map(e => new StandardKeyboardEvent(e)) @@ -721,9 +722,9 @@ class TypeFilterController implements IDisposable { .forEach(e => { e.stopPropagation(); e.preventDefault(); }) .event; - const onClear = domEvent(this.clearDomNode, 'click'); + const onClearClick = this.enabledDisposables.add(new DomEmitter(this.clearDomNode, 'click')); - Event.chain(Event.any(onKeyDown, onClear)) + Event.chain(Event.any(onKeyDown, onClearClick.event)) .event(this.onEventOrInput, this, this.enabledDisposables); this.filter.pattern = ''; @@ -849,8 +850,8 @@ class TypeFilterController implements IDisposable { this.domNode.classList.add('dragging'); disposables.add(toDisposable(() => this.domNode.classList.remove('dragging'))); - domEvent(document, 'dragover')(onDragOver, null, disposables); - domEvent(this.domNode, 'dragend')(onDragEnd, null, disposables); + disposables.add(addDisposableListener(document, 'dragover', e => onDragOver(e))); + disposables.add(addDisposableListener(this.domNode, 'dragend', () => onDragEnd())); StaticDND.CurrentDragAndDropData = new DragAndDropData('vscode-ui'); disposables.add(toDisposable(() => StaticDND.CurrentDragAndDropData = undefined)); diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index fba360da12c..b5a4bbbf029 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -298,7 +298,7 @@ export class Delayer implements IDisposable { } dispose(): void { - this.cancelTimeout(); + this.cancel(); } } diff --git a/src/vs/base/common/mime.ts b/src/vs/base/common/mime.ts index 368d77953e6..116248ccf12 100644 --- a/src/vs/base/common/mime.ts +++ b/src/vs/base/common/mime.ts @@ -10,9 +10,12 @@ import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { DataUri } from 'vs/base/common/resources'; -export const MIME_TEXT = 'text/plain'; -export const MIME_BINARY = 'application/octet-stream'; -export const MIME_UNKNOWN = 'application/unknown'; +export namespace Mimes { + export const text = 'text/plain'; + export const binary = 'application/octet-stream'; + export const unknown = 'application/unknown'; + export const markdown = 'text/markdown'; +} export interface ITextMimeAssociation { readonly id: string; @@ -125,7 +128,7 @@ export function guessMimeTypes(resource: URI | null, firstLine?: string): string } if (!path) { - return [MIME_UNKNOWN]; + return [Mimes.unknown]; } path = path.toLowerCase(); @@ -135,24 +138,24 @@ export function guessMimeTypes(resource: URI | null, firstLine?: string): string // 1.) User configured mappings have highest priority const configuredMime = guessMimeTypeByPath(path, filename, userRegisteredAssociations); if (configuredMime) { - return [configuredMime, MIME_TEXT]; + return [configuredMime, Mimes.text]; } // 2.) Registered mappings have middle priority const registeredMime = guessMimeTypeByPath(path, filename, nonUserRegisteredAssociations); if (registeredMime) { - return [registeredMime, MIME_TEXT]; + return [registeredMime, Mimes.text]; } // 3.) Firstline has lowest priority if (firstLine) { const firstlineMime = guessMimeTypeByFirstline(firstLine); if (firstlineMime) { - return [firstlineMime, MIME_TEXT]; + return [firstlineMime, Mimes.text]; } } - return [MIME_UNKNOWN]; + return [Mimes.unknown]; } function guessMimeTypeByPath(path: string, filename: string, associations: ITextMimeAssociationItem[]): string | null { @@ -240,7 +243,7 @@ export function isUnspecific(mime: string[] | string): boolean { } if (typeof mime === 'string') { - return mime === MIME_BINARY || mime === MIME_TEXT || mime === MIME_UNKNOWN; + return mime === Mimes.binary || mime === Mimes.text || mime === Mimes.unknown; } return mime.length === 1 && isUnspecific(mime[0]); diff --git a/src/vs/base/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts index aad4fb4e92a..5df18696784 100644 --- a/src/vs/base/parts/ipc/common/ipc.net.ts +++ b/src/vs/base/parts/ipc/common/ipc.net.ts @@ -10,9 +10,54 @@ import { VSBuffer } from 'vs/base/common/buffer'; import * as platform from 'vs/base/common/platform'; import * as process from 'vs/base/common/process'; +export const enum SocketCloseEventType { + NodeSocketCloseEvent = 0, + WebSocketCloseEvent = 1 +} + +export interface NodeSocketCloseEvent { + /** + * The type of the event + */ + readonly type: SocketCloseEventType.NodeSocketCloseEvent; + /** + * `true` if the socket had a transmission error. + */ + readonly hadError: boolean; + /** + * Underlying error. + */ + readonly error: Error | undefined +} + +export interface WebSocketCloseEvent { + /** + * The type of the event + */ + readonly type: SocketCloseEventType.WebSocketCloseEvent; + /** + * Returns the WebSocket connection close code provided by the server. + */ + readonly code: number; + /** + * Returns the WebSocket connection close reason provided by the server. + */ + readonly reason: string; + /** + * Returns true if the connection closed cleanly; false otherwise. + */ + readonly wasClean: boolean; + /** + * Underlying event. + */ + readonly event: any | undefined; +} + +export type SocketCloseEvent = NodeSocketCloseEvent | WebSocketCloseEvent | undefined; + export interface ISocket extends IDisposable { onData(listener: (e: VSBuffer) => void): IDisposable; - onClose(listener: () => void): IDisposable; + onClose(listener: (e: SocketCloseEvent) => void): IDisposable; onEnd(listener: () => void): IDisposable; write(buffer: VSBuffer): void; end(): void; @@ -624,8 +669,8 @@ export class PersistentProtocol implements IMessagePassingProtocol { private readonly _onDidDispose = new BufferedEmitter(); readonly onDidDispose: Event = this._onDidDispose.event; - private readonly _onSocketClose = new BufferedEmitter(); - readonly onSocketClose: Event = this._onSocketClose.event; + private readonly _onSocketClose = new BufferedEmitter(); + readonly onSocketClose: Event = this._onSocketClose.event; private readonly _onSocketTimeout = new BufferedEmitter(); readonly onSocketTimeout: Event = this._onSocketTimeout.event; @@ -658,7 +703,7 @@ export class PersistentProtocol implements IMessagePassingProtocol { this._socketReader = new ProtocolReader(this._socket); this._socketDisposables.push(this._socketReader); this._socketDisposables.push(this._socketReader.onMessage(msg => this._receiveMessage(msg))); - this._socketDisposables.push(this._socket.onClose(() => this._onSocketClose.fire())); + this._socketDisposables.push(this._socket.onClose((e) => this._onSocketClose.fire(e))); if (initialChunk) { this._socketReader.acceptChunk(initialChunk); } @@ -768,7 +813,7 @@ export class PersistentProtocol implements IMessagePassingProtocol { this._socketReader = new ProtocolReader(this._socket); this._socketDisposables.push(this._socketReader); this._socketDisposables.push(this._socketReader.onMessage(msg => this._receiveMessage(msg))); - this._socketDisposables.push(this._socket.onClose(() => this._onSocketClose.fire())); + this._socketDisposables.push(this._socket.onClose((e) => this._onSocketClose.fire(e))); this._socketReader.acceptChunk(initialDataChunk); } diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index aa2512c4272..7ce02bb986b 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -13,7 +13,7 @@ import { tmpdir } from 'os'; import { generateUuid } from 'vs/base/common/uuid'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { VSBuffer } from 'vs/base/common/buffer'; -import { ISocket, Protocol, Client, ChunkStream } from 'vs/base/parts/ipc/common/ipc.net'; +import { ISocket, Protocol, Client, ChunkStream, SocketCloseEvent, SocketCloseEventType } from 'vs/base/parts/ipc/common/ipc.net'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Platform, platform } from 'vs/base/common/platform'; @@ -54,10 +54,17 @@ export class NodeSocket implements ISocket { }; } - public onClose(listener: () => void): IDisposable { - this.socket.on('close', listener); + public onClose(listener: (e: SocketCloseEvent) => void): IDisposable { + const adapter = (hadError: boolean) => { + listener({ + type: SocketCloseEventType.NodeSocketCloseEvent, + hadError: hadError, + error: undefined + }); + }; + this.socket.on('close', adapter); return { - dispose: () => this.socket.off('close', listener) + dispose: () => this.socket.off('close', adapter) }; } @@ -167,7 +174,7 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { private readonly _pendingDeflateData: Buffer[] = []; private readonly _incomingData: ChunkStream; private readonly _onData = this._register(new Emitter()); - private readonly _onClose = this._register(new Emitter()); + private readonly _onClose = this._register(new Emitter()); private _isEnded: boolean = false; private readonly _state = { @@ -232,7 +239,11 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { // zlib errors are fatal, since we have no idea how to recover console.error(err); onUnexpectedError(err); - this._onClose.fire(); + this._onClose.fire({ + type: SocketCloseEventType.NodeSocketCloseEvent, + hadError: true, + error: err + }); }); this._zlibInflate.on('data', (data: Buffer) => { this._pendingInflateData.push(data); @@ -251,7 +262,11 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { // zlib errors are fatal, since we have no idea how to recover console.error(err); onUnexpectedError(err); - this._onClose.fire(); + this._onClose.fire({ + type: SocketCloseEventType.NodeSocketCloseEvent, + hadError: true, + error: err + }); }); this._zlibDeflate.on('data', (data: Buffer) => { this._pendingDeflateData.push(data); @@ -263,7 +278,7 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { this._zlibDeflateFlushWaitingCount = 0; this._incomingData = new ChunkStream(); this._register(this.socket.onData(data => this._acceptChunk(data))); - this._register(this.socket.onClose(() => this._onClose.fire())); + this._register(this.socket.onClose((e) => this._onClose.fire(e))); } public override dispose(): void { @@ -282,7 +297,7 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { return this._onData.event(listener); } - public onClose(listener: () => void): IDisposable { + public onClose(listener: (e: SocketCloseEvent) => void): IDisposable { return this._onClose.event(listener); } diff --git a/src/vs/base/test/common/async.test.ts b/src/vs/base/test/common/async.test.ts index 08ff52dfccd..1bbd7b813e0 100644 --- a/src/vs/base/test/common/async.test.ts +++ b/src/vs/base/test/common/async.test.ts @@ -12,235 +12,227 @@ import { Event } from 'vs/base/common/event'; suite('Async', () => { - test('cancelablePromise - set token, don\'t wait for inner promise', function () { - let canceled = 0; - let promise = async.createCancelablePromise(token => { - token.onCancellationRequested(_ => { canceled += 1; }); - return new Promise(resolve => { /*never*/ }); + suite('cancelablePromise', function () { + test('set token, don\'t wait for inner promise', function () { + let canceled = 0; + let promise = async.createCancelablePromise(token => { + token.onCancellationRequested(_ => { canceled += 1; }); + return new Promise(resolve => { /*never*/ }); + }); + let result = promise.then(_ => assert.ok(false), err => { + assert.strictEqual(canceled, 1); + assert.ok(isPromiseCanceledError(err)); + }); + promise.cancel(); + promise.cancel(); // cancel only once + return result; }); - let result = promise.then(_ => assert.ok(false), err => { - assert.strictEqual(canceled, 1); - assert.ok(isPromiseCanceledError(err)); + + test('cancel despite inner promise being resolved', function () { + let canceled = 0; + let promise = async.createCancelablePromise(token => { + token.onCancellationRequested(_ => { canceled += 1; }); + return Promise.resolve(1234); + }); + let result = promise.then(_ => assert.ok(false), err => { + assert.strictEqual(canceled, 1); + assert.ok(isPromiseCanceledError(err)); + }); + promise.cancel(); + return result; + }); + + // Cancelling a sync cancelable promise will fire the cancelled token. + // Also, every `then` callback runs in another execution frame. + test('execution order (sync)', function () { + const order: string[] = []; + + const cancellablePromise = async.createCancelablePromise(token => { + order.push('in callback'); + token.onCancellationRequested(_ => order.push('cancelled')); + return Promise.resolve(1234); + }); + + order.push('afterCreate'); + + const promise = cancellablePromise + .then(undefined, err => null) + .then(() => order.push('finally')); + + cancellablePromise.cancel(); + order.push('afterCancel'); + + return promise.then(() => assert.deepStrictEqual(order, ['in callback', 'afterCreate', 'cancelled', 'afterCancel', 'finally'])); + }); + + // Cancelling an async cancelable promise is just the same as a sync cancellable promise. + test('execution order (async)', function () { + const order: string[] = []; + + const cancellablePromise = async.createCancelablePromise(token => { + order.push('in callback'); + token.onCancellationRequested(_ => order.push('cancelled')); + return new Promise(c => setTimeout(c.bind(1234), 0)); + }); + + order.push('afterCreate'); + + const promise = cancellablePromise + .then(undefined, err => null) + .then(() => order.push('finally')); + + cancellablePromise.cancel(); + order.push('afterCancel'); + + return promise.then(() => assert.deepStrictEqual(order, ['in callback', 'afterCreate', 'cancelled', 'afterCancel', 'finally'])); + }); + + test('get inner result', async function () { + let promise = async.createCancelablePromise(token => { + return async.timeout(12).then(_ => 1234); + }); + + let result = await promise; + assert.strictEqual(result, 1234); }); - promise.cancel(); - promise.cancel(); // cancel only once - return result; }); - test('cancelablePromise - cancel despite inner promise being resolved', function () { - let canceled = 0; - let promise = async.createCancelablePromise(token => { - token.onCancellationRequested(_ => { canceled += 1; }); - return Promise.resolve(1234); - }); - let result = promise.then(_ => assert.ok(false), err => { - assert.strictEqual(canceled, 1); - assert.ok(isPromiseCanceledError(err)); - }); - promise.cancel(); - return result; - }); + suite('Throttler', function () { + test('non async', function () { + let count = 0; + let factory = () => { + return Promise.resolve(++count); + }; - // Cancelling a sync cancelable promise will fire the cancelled token. - // Also, every `then` callback runs in another execution frame. - test('CancelablePromise execution order (sync)', function () { - const order: string[] = []; + let throttler = new async.Throttler(); - const cancellablePromise = async.createCancelablePromise(token => { - order.push('in callback'); - token.onCancellationRequested(_ => order.push('cancelled')); - return Promise.resolve(1234); - }); - - order.push('afterCreate'); - - const promise = cancellablePromise - .then(undefined, err => null) - .then(() => order.push('finally')); - - cancellablePromise.cancel(); - order.push('afterCancel'); - - return promise.then(() => assert.deepStrictEqual(order, ['in callback', 'afterCreate', 'cancelled', 'afterCancel', 'finally'])); - }); - - // Cancelling an async cancelable promise is just the same as a sync cancellable promise. - test('CancelablePromise execution order (async)', function () { - const order: string[] = []; - - const cancellablePromise = async.createCancelablePromise(token => { - order.push('in callback'); - token.onCancellationRequested(_ => order.push('cancelled')); - return new Promise(c => setTimeout(c.bind(1234), 0)); - }); - - order.push('afterCreate'); - - const promise = cancellablePromise - .then(undefined, err => null) - .then(() => order.push('finally')); - - cancellablePromise.cancel(); - order.push('afterCancel'); - - return promise.then(() => assert.deepStrictEqual(order, ['in callback', 'afterCreate', 'cancelled', 'afterCancel', 'finally'])); - }); - - test('cancelablePromise - get inner result', async function () { - let promise = async.createCancelablePromise(token => { - return async.timeout(12).then(_ => 1234); - }); - - let result = await promise; - assert.strictEqual(result, 1234); - }); - - test('Throttler - non async', function () { - let count = 0; - let factory = () => { - return Promise.resolve(++count); - }; - - let throttler = new async.Throttler(); - - return Promise.all([ - throttler.queue(factory).then((result) => { assert.strictEqual(result, 1); }), - throttler.queue(factory).then((result) => { assert.strictEqual(result, 2); }), - throttler.queue(factory).then((result) => { assert.strictEqual(result, 2); }), - throttler.queue(factory).then((result) => { assert.strictEqual(result, 2); }), - throttler.queue(factory).then((result) => { assert.strictEqual(result, 2); }) - ]).then(() => assert.strictEqual(count, 2)); - }); - - test('Throttler', () => { - let count = 0; - let factory = () => async.timeout(0).then(() => ++count); - - let throttler = new async.Throttler(); - - return Promise.all([ - throttler.queue(factory).then((result) => { assert.strictEqual(result, 1); }), - throttler.queue(factory).then((result) => { assert.strictEqual(result, 2); }), - throttler.queue(factory).then((result) => { assert.strictEqual(result, 2); }), - throttler.queue(factory).then((result) => { assert.strictEqual(result, 2); }), - throttler.queue(factory).then((result) => { assert.strictEqual(result, 2); }) - ]).then(() => { return Promise.all([ - throttler.queue(factory).then((result) => { assert.strictEqual(result, 3); }), - throttler.queue(factory).then((result) => { assert.strictEqual(result, 4); }), - throttler.queue(factory).then((result) => { assert.strictEqual(result, 4); }), - throttler.queue(factory).then((result) => { assert.strictEqual(result, 4); }), - throttler.queue(factory).then((result) => { assert.strictEqual(result, 4); }) - ]); + throttler.queue(factory).then((result) => { assert.strictEqual(result, 1); }), + throttler.queue(factory).then((result) => { assert.strictEqual(result, 2); }), + throttler.queue(factory).then((result) => { assert.strictEqual(result, 2); }), + throttler.queue(factory).then((result) => { assert.strictEqual(result, 2); }), + throttler.queue(factory).then((result) => { assert.strictEqual(result, 2); }) + ]).then(() => assert.strictEqual(count, 2)); + }); + + test('async', () => { + let count = 0; + let factory = () => async.timeout(0).then(() => ++count); + + let throttler = new async.Throttler(); + + return Promise.all([ + throttler.queue(factory).then((result) => { assert.strictEqual(result, 1); }), + throttler.queue(factory).then((result) => { assert.strictEqual(result, 2); }), + throttler.queue(factory).then((result) => { assert.strictEqual(result, 2); }), + throttler.queue(factory).then((result) => { assert.strictEqual(result, 2); }), + throttler.queue(factory).then((result) => { assert.strictEqual(result, 2); }) + ]).then(() => { + return Promise.all([ + throttler.queue(factory).then((result) => { assert.strictEqual(result, 3); }), + throttler.queue(factory).then((result) => { assert.strictEqual(result, 4); }), + throttler.queue(factory).then((result) => { assert.strictEqual(result, 4); }), + throttler.queue(factory).then((result) => { assert.strictEqual(result, 4); }), + throttler.queue(factory).then((result) => { assert.strictEqual(result, 4); }) + ]); + }); + }); + + test('last factory should be the one getting called', function () { + let factoryFactory = (n: number) => () => { + return async.timeout(0).then(() => n); + }; + + let throttler = new async.Throttler(); + + let promises: Promise[] = []; + + promises.push(throttler.queue(factoryFactory(1)).then((n) => { assert.strictEqual(n, 1); })); + promises.push(throttler.queue(factoryFactory(2)).then((n) => { assert.strictEqual(n, 3); })); + promises.push(throttler.queue(factoryFactory(3)).then((n) => { assert.strictEqual(n, 3); })); + + return Promise.all(promises); }); }); - test('Throttler - last factory should be the one getting called', function () { - let factoryFactory = (n: number) => () => { - return async.timeout(0).then(() => n); - }; + suite('Delayer', function () { + test('simple', () => { + let count = 0; + let factory = () => { + return Promise.resolve(++count); + }; - let throttler = new async.Throttler(); + let delayer = new async.Delayer(0); + let promises: Promise[] = []; - let promises: Promise[] = []; - - promises.push(throttler.queue(factoryFactory(1)).then((n) => { assert.strictEqual(n, 1); })); - promises.push(throttler.queue(factoryFactory(2)).then((n) => { assert.strictEqual(n, 3); })); - promises.push(throttler.queue(factoryFactory(3)).then((n) => { assert.strictEqual(n, 3); })); - - return Promise.all(promises); - }); - - test('Delayer', () => { - let count = 0; - let factory = () => { - return Promise.resolve(++count); - }; - - let delayer = new async.Delayer(0); - let promises: Promise[] = []; - - assert(!delayer.isTriggered()); - - promises.push(delayer.trigger(factory).then((result) => { assert.strictEqual(result, 1); assert(!delayer.isTriggered()); })); - assert(delayer.isTriggered()); - - promises.push(delayer.trigger(factory).then((result) => { assert.strictEqual(result, 1); assert(!delayer.isTriggered()); })); - assert(delayer.isTriggered()); - - promises.push(delayer.trigger(factory).then((result) => { assert.strictEqual(result, 1); assert(!delayer.isTriggered()); })); - assert(delayer.isTriggered()); - - return Promise.all(promises).then(() => { assert(!delayer.isTriggered()); - }); - }); - test('Delayer - simple cancel', function () { - let count = 0; - let factory = () => { - return Promise.resolve(++count); - }; + promises.push(delayer.trigger(factory).then((result) => { assert.strictEqual(result, 1); assert(!delayer.isTriggered()); })); + assert(delayer.isTriggered()); - let delayer = new async.Delayer(0); + promises.push(delayer.trigger(factory).then((result) => { assert.strictEqual(result, 1); assert(!delayer.isTriggered()); })); + assert(delayer.isTriggered()); - assert(!delayer.isTriggered()); + promises.push(delayer.trigger(factory).then((result) => { assert.strictEqual(result, 1); assert(!delayer.isTriggered()); })); + assert(delayer.isTriggered()); - const p = delayer.trigger(factory).then(() => { - assert(false); - }, () => { - assert(true, 'yes, it was cancelled'); + return Promise.all(promises).then(() => { + assert(!delayer.isTriggered()); + }); }); - assert(delayer.isTriggered()); - delayer.cancel(); - assert(!delayer.isTriggered()); + suite('ThrottledDelayer', () => { + test('promise should resolve if disposed', async () => { + const throttledDelayer = new async.ThrottledDelayer(100); + const promise = throttledDelayer.trigger(async () => { }, 0); + throttledDelayer.dispose(); - return p; - }); + try { + await promise; + assert.fail('SHOULD NOT BE HERE'); + } catch (err) { + // OK + } + }); + }); - test('Delayer - cancel should cancel all calls to trigger', function () { - let count = 0; - let factory = () => { - return Promise.resolve(++count); - }; + test('simple cancel', function () { + let count = 0; + let factory = () => { + return Promise.resolve(++count); + }; - let delayer = new async.Delayer(0); - let promises: Promise[] = []; + let delayer = new async.Delayer(0); - assert(!delayer.isTriggered()); - - promises.push(delayer.trigger(factory).then(undefined, () => { assert(true, 'yes, it was cancelled'); })); - assert(delayer.isTriggered()); - - promises.push(delayer.trigger(factory).then(undefined, () => { assert(true, 'yes, it was cancelled'); })); - assert(delayer.isTriggered()); - - promises.push(delayer.trigger(factory).then(undefined, () => { assert(true, 'yes, it was cancelled'); })); - assert(delayer.isTriggered()); - - delayer.cancel(); - - return Promise.all(promises).then(() => { assert(!delayer.isTriggered()); - }); - }); - test('Delayer - trigger, cancel, then trigger again', function () { - let count = 0; - let factory = () => { - return Promise.resolve(++count); - }; + const p = delayer.trigger(factory).then(() => { + assert(false); + }, () => { + assert(true, 'yes, it was cancelled'); + }); - let delayer = new async.Delayer(0); - let promises: Promise[] = []; - - assert(!delayer.isTriggered()); - - const p = delayer.trigger(factory).then((result) => { - assert.strictEqual(result, 1); + assert(delayer.isTriggered()); + delayer.cancel(); assert(!delayer.isTriggered()); + return p; + }); + + test('cancel should cancel all calls to trigger', function () { + let count = 0; + let factory = () => { + return Promise.resolve(++count); + }; + + let delayer = new async.Delayer(0); + let promises: Promise[] = []; + + assert(!delayer.isTriggered()); + + promises.push(delayer.trigger(factory).then(undefined, () => { assert(true, 'yes, it was cancelled'); })); + assert(delayer.isTriggered()); + promises.push(delayer.trigger(factory).then(undefined, () => { assert(true, 'yes, it was cancelled'); })); assert(delayer.isTriggered()); @@ -249,404 +241,445 @@ suite('Async', () => { delayer.cancel(); - const p = Promise.all(promises).then(() => { - promises = []; + return Promise.all(promises).then(() => { + assert(!delayer.isTriggered()); + }); + }); + test('trigger, cancel, then trigger again', function () { + let count = 0; + let factory = () => { + return Promise.resolve(++count); + }; + + let delayer = new async.Delayer(0); + let promises: Promise[] = []; + + assert(!delayer.isTriggered()); + + const p = delayer.trigger(factory).then((result) => { + assert.strictEqual(result, 1); assert(!delayer.isTriggered()); - promises.push(delayer.trigger(factory).then(() => { assert.strictEqual(result, 1); assert(!delayer.isTriggered()); })); + promises.push(delayer.trigger(factory).then(undefined, () => { assert(true, 'yes, it was cancelled'); })); assert(delayer.isTriggered()); - promises.push(delayer.trigger(factory).then(() => { assert.strictEqual(result, 1); assert(!delayer.isTriggered()); })); + promises.push(delayer.trigger(factory).then(undefined, () => { assert(true, 'yes, it was cancelled'); })); assert(delayer.isTriggered()); + delayer.cancel(); + const p = Promise.all(promises).then(() => { - assert(!delayer.isTriggered()); - }); + promises = []; - assert(delayer.isTriggered()); + assert(!delayer.isTriggered()); + + promises.push(delayer.trigger(factory).then(() => { assert.strictEqual(result, 1); assert(!delayer.isTriggered()); })); + assert(delayer.isTriggered()); + + promises.push(delayer.trigger(factory).then(() => { assert.strictEqual(result, 1); assert(!delayer.isTriggered()); })); + assert(delayer.isTriggered()); + + const p = Promise.all(promises).then(() => { + assert(!delayer.isTriggered()); + }); + + assert(delayer.isTriggered()); + + return p; + }); return p; }); + assert(delayer.isTriggered()); + return p; }); - assert(delayer.isTriggered()); + test('last task should be the one getting called', function () { + let factoryFactory = (n: number) => () => { + return Promise.resolve(n); + }; - return p; - }); + let delayer = new async.Delayer(0); + let promises: Promise[] = []; - test('Delayer - last task should be the one getting called', function () { - let factoryFactory = (n: number) => () => { - return Promise.resolve(n); - }; - - let delayer = new async.Delayer(0); - let promises: Promise[] = []; - - assert(!delayer.isTriggered()); - - promises.push(delayer.trigger(factoryFactory(1)).then((n) => { assert.strictEqual(n, 3); })); - promises.push(delayer.trigger(factoryFactory(2)).then((n) => { assert.strictEqual(n, 3); })); - promises.push(delayer.trigger(factoryFactory(3)).then((n) => { assert.strictEqual(n, 3); })); - - const p = Promise.all(promises).then(() => { assert(!delayer.isTriggered()); - }); - assert(delayer.isTriggered()); + promises.push(delayer.trigger(factoryFactory(1)).then((n) => { assert.strictEqual(n, 3); })); + promises.push(delayer.trigger(factoryFactory(2)).then((n) => { assert.strictEqual(n, 3); })); + promises.push(delayer.trigger(factoryFactory(3)).then((n) => { assert.strictEqual(n, 3); })); - return p; - }); + const p = Promise.all(promises).then(() => { + assert(!delayer.isTriggered()); + }); - test('Sequence', () => { - let factoryFactory = (n: number) => () => { - return Promise.resolve(n); - }; + assert(delayer.isTriggered()); - return async.sequence([ - factoryFactory(1), - factoryFactory(2), - factoryFactory(3), - factoryFactory(4), - factoryFactory(5), - ]).then((result) => { - assert.strictEqual(5, result.length); - assert.strictEqual(1, result[0]); - assert.strictEqual(2, result[1]); - assert.strictEqual(3, result[2]); - assert.strictEqual(4, result[3]); - assert.strictEqual(5, result[4]); + return p; }); }); - test('Limiter - sync', function () { - let factoryFactory = (n: number) => () => { - return Promise.resolve(n); - }; + suite('sequence', () => { + test('simple', () => { + let factoryFactory = (n: number) => () => { + return Promise.resolve(n); + }; - let limiter = new async.Limiter(1); - - let promises: Promise[] = []; - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(n => promises.push(limiter.queue(factoryFactory(n)))); - - return Promise.all(promises).then((res) => { - assert.strictEqual(10, res.length); - - limiter = new async.Limiter(100); - - promises = []; - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(n => promises.push(limiter.queue(factoryFactory(n)))); - - return Promise.all(promises).then((res) => { - assert.strictEqual(10, res.length); + return async.sequence([ + factoryFactory(1), + factoryFactory(2), + factoryFactory(3), + factoryFactory(4), + factoryFactory(5), + ]).then((result) => { + assert.strictEqual(5, result.length); + assert.strictEqual(1, result[0]); + assert.strictEqual(2, result[1]); + assert.strictEqual(3, result[2]); + assert.strictEqual(4, result[3]); + assert.strictEqual(5, result[4]); }); }); }); - test('Limiter - async', function () { - let factoryFactory = (n: number) => () => async.timeout(0).then(() => n); + suite('Limiter', () => { + test('sync', function () { + let factoryFactory = (n: number) => () => { + return Promise.resolve(n); + }; - let limiter = new async.Limiter(1); - let promises: Promise[] = []; - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(n => promises.push(limiter.queue(factoryFactory(n)))); + let limiter = new async.Limiter(1); - return Promise.all(promises).then((res) => { - assert.strictEqual(10, res.length); - - limiter = new async.Limiter(100); - - promises = []; + let promises: Promise[] = []; [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(n => promises.push(limiter.queue(factoryFactory(n)))); return Promise.all(promises).then((res) => { assert.strictEqual(10, res.length); + + limiter = new async.Limiter(100); + + promises = []; + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(n => promises.push(limiter.queue(factoryFactory(n)))); + + return Promise.all(promises).then((res) => { + assert.strictEqual(10, res.length); + }); + }); + }); + + test('async', function () { + let factoryFactory = (n: number) => () => async.timeout(0).then(() => n); + + let limiter = new async.Limiter(1); + let promises: Promise[] = []; + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(n => promises.push(limiter.queue(factoryFactory(n)))); + + return Promise.all(promises).then((res) => { + assert.strictEqual(10, res.length); + + limiter = new async.Limiter(100); + + promises = []; + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(n => promises.push(limiter.queue(factoryFactory(n)))); + + return Promise.all(promises).then((res) => { + assert.strictEqual(10, res.length); + }); + }); + }); + + test('assert degree of paralellism', function () { + let activePromises = 0; + let factoryFactory = (n: number) => () => { + activePromises++; + assert(activePromises < 6); + return async.timeout(0).then(() => { activePromises--; return n; }); + }; + + let limiter = new async.Limiter(5); + + let promises: Promise[] = []; + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(n => promises.push(limiter.queue(factoryFactory(n)))); + + return Promise.all(promises).then((res) => { + assert.strictEqual(10, res.length); + assert.deepStrictEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], res); }); }); }); - test('Limiter - assert degree of paralellism', function () { - let activePromises = 0; - let factoryFactory = (n: number) => () => { - activePromises++; - assert(activePromises < 6); - return async.timeout(0).then(() => { activePromises--; return n; }); - }; + suite('Queue', () => { + test('simple', function () { + let queue = new async.Queue(); - let limiter = new async.Limiter(5); + let syncPromise = false; + let f1 = () => Promise.resolve(true).then(() => syncPromise = true); - let promises: Promise[] = []; - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(n => promises.push(limiter.queue(factoryFactory(n)))); + let asyncPromise = false; + let f2 = () => async.timeout(10).then(() => asyncPromise = true); - return Promise.all(promises).then((res) => { - assert.strictEqual(10, res.length); - assert.deepStrictEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], res); - }); - }); - - test('Queue - simple', function () { - let queue = new async.Queue(); - - let syncPromise = false; - let f1 = () => Promise.resolve(true).then(() => syncPromise = true); - - let asyncPromise = false; - let f2 = () => async.timeout(10).then(() => asyncPromise = true); - - assert.strictEqual(queue.size, 0); - - queue.queue(f1); - assert.strictEqual(queue.size, 1); - - const p = queue.queue(f2); - assert.strictEqual(queue.size, 2); - return p.then(() => { assert.strictEqual(queue.size, 0); - assert.ok(syncPromise); - assert.ok(asyncPromise); + + queue.queue(f1); + assert.strictEqual(queue.size, 1); + + const p = queue.queue(f2); + assert.strictEqual(queue.size, 2); + return p.then(() => { + assert.strictEqual(queue.size, 0); + assert.ok(syncPromise); + assert.ok(asyncPromise); + }); }); - }); - test('Queue - order is kept', function () { - let queue = new async.Queue(); + test('order is kept', function () { + let queue = new async.Queue(); - let res: number[] = []; + let res: number[] = []; - let f1 = () => Promise.resolve(true).then(() => res.push(1)); - let f2 = () => async.timeout(10).then(() => res.push(2)); - let f3 = () => Promise.resolve(true).then(() => res.push(3)); - let f4 = () => async.timeout(20).then(() => res.push(4)); - let f5 = () => async.timeout(0).then(() => res.push(5)); + let f1 = () => Promise.resolve(true).then(() => res.push(1)); + let f2 = () => async.timeout(10).then(() => res.push(2)); + let f3 = () => Promise.resolve(true).then(() => res.push(3)); + let f4 = () => async.timeout(20).then(() => res.push(4)); + let f5 = () => async.timeout(0).then(() => res.push(5)); - queue.queue(f1); - queue.queue(f2); - queue.queue(f3); - queue.queue(f4); - return queue.queue(f5).then(() => { - assert.strictEqual(res[0], 1); - assert.strictEqual(res[1], 2); - assert.strictEqual(res[2], 3); - assert.strictEqual(res[3], 4); - assert.strictEqual(res[4], 5); + queue.queue(f1); + queue.queue(f2); + queue.queue(f3); + queue.queue(f4); + return queue.queue(f5).then(() => { + assert.strictEqual(res[0], 1); + assert.strictEqual(res[1], 2); + assert.strictEqual(res[2], 3); + assert.strictEqual(res[3], 4); + assert.strictEqual(res[4], 5); + }); }); - }); - test('Queue - errors bubble individually but not cause stop', function () { - let queue = new async.Queue(); + test('errors bubble individually but not cause stop', function () { + let queue = new async.Queue(); - let res: number[] = []; - let error = false; + let res: number[] = []; + let error = false; - let f1 = () => Promise.resolve(true).then(() => res.push(1)); - let f2 = () => async.timeout(10).then(() => res.push(2)); - let f3 = () => Promise.resolve(true).then(() => Promise.reject(new Error('error'))); - let f4 = () => async.timeout(20).then(() => res.push(4)); - let f5 = () => async.timeout(0).then(() => res.push(5)); + let f1 = () => Promise.resolve(true).then(() => res.push(1)); + let f2 = () => async.timeout(10).then(() => res.push(2)); + let f3 = () => Promise.resolve(true).then(() => Promise.reject(new Error('error'))); + let f4 = () => async.timeout(20).then(() => res.push(4)); + let f5 = () => async.timeout(0).then(() => res.push(5)); - queue.queue(f1); - queue.queue(f2); - queue.queue(f3).then(undefined, () => error = true); - queue.queue(f4); - return queue.queue(f5).then(() => { - assert.strictEqual(res[0], 1); - assert.strictEqual(res[1], 2); - assert.ok(error); - assert.strictEqual(res[2], 4); - assert.strictEqual(res[3], 5); + queue.queue(f1); + queue.queue(f2); + queue.queue(f3).then(undefined, () => error = true); + queue.queue(f4); + return queue.queue(f5).then(() => { + assert.strictEqual(res[0], 1); + assert.strictEqual(res[1], 2); + assert.ok(error); + assert.strictEqual(res[2], 4); + assert.strictEqual(res[3], 5); + }); }); - }); - test('Queue - order is kept (chained)', function () { - let queue = new async.Queue(); + test('order is kept (chained)', function () { + let queue = new async.Queue(); - let res: number[] = []; + let res: number[] = []; - let f1 = () => Promise.resolve(true).then(() => res.push(1)); - let f2 = () => async.timeout(10).then(() => res.push(2)); - let f3 = () => Promise.resolve(true).then(() => res.push(3)); - let f4 = () => async.timeout(20).then(() => res.push(4)); - let f5 = () => async.timeout(0).then(() => res.push(5)); + let f1 = () => Promise.resolve(true).then(() => res.push(1)); + let f2 = () => async.timeout(10).then(() => res.push(2)); + let f3 = () => Promise.resolve(true).then(() => res.push(3)); + let f4 = () => async.timeout(20).then(() => res.push(4)); + let f5 = () => async.timeout(0).then(() => res.push(5)); - return queue.queue(f1).then(() => { - return queue.queue(f2).then(() => { - return queue.queue(f3).then(() => { - return queue.queue(f4).then(() => { - return queue.queue(f5).then(() => { - assert.strictEqual(res[0], 1); - assert.strictEqual(res[1], 2); - assert.strictEqual(res[2], 3); - assert.strictEqual(res[3], 4); - assert.strictEqual(res[4], 5); + return queue.queue(f1).then(() => { + return queue.queue(f2).then(() => { + return queue.queue(f3).then(() => { + return queue.queue(f4).then(() => { + return queue.queue(f5).then(() => { + assert.strictEqual(res[0], 1); + assert.strictEqual(res[1], 2); + assert.strictEqual(res[2], 3); + assert.strictEqual(res[3], 4); + assert.strictEqual(res[4], 5); + }); }); }); }); }); }); + + test('events', function () { + let queue = new async.Queue(); + + let finished = false; + const onFinished = Event.toPromise(queue.onFinished); + + let res: number[] = []; + + let f1 = () => async.timeout(10).then(() => res.push(2)); + let f2 = () => async.timeout(20).then(() => res.push(4)); + let f3 = () => async.timeout(0).then(() => res.push(5)); + + const q1 = queue.queue(f1); + const q2 = queue.queue(f2); + queue.queue(f3); + + q1.then(() => { + assert.ok(!finished); + q2.then(() => { + assert.ok(!finished); + }); + }); + + return onFinished; + }); }); - test('Queue - events', function () { - let queue = new async.Queue(); + suite('ResourceQueue', () => { + test('simple', function () { + let queue = new async.ResourceQueue(); - let finished = false; - const onFinished = Event.toPromise(queue.onFinished); + const r1Queue = queue.queueFor(URI.file('/some/path')); - let res: number[] = []; + r1Queue.onFinished(() => console.log('DONE')); - let f1 = () => async.timeout(10).then(() => res.push(2)); - let f2 = () => async.timeout(20).then(() => res.push(4)); - let f3 = () => async.timeout(0).then(() => res.push(5)); + const r2Queue = queue.queueFor(URI.file('/some/other/path')); - const q1 = queue.queue(f1); - const q2 = queue.queue(f2); - queue.queue(f3); + assert.ok(r1Queue); + assert.ok(r2Queue); + assert.strictEqual(r1Queue, queue.queueFor(URI.file('/some/path'))); // same queue returned - q1.then(() => { - assert.ok(!finished); - q2.then(() => { - assert.ok(!finished); + let syncPromiseFactory = () => Promise.resolve(undefined); + + r1Queue.queue(syncPromiseFactory); + + return new Promise(c => setTimeout(() => c(), 0)).then(() => { + const r1Queue2 = queue.queueFor(URI.file('/some/path')); + assert.notStrictEqual(r1Queue, r1Queue2); // previous one got disposed after finishing }); }); - - return onFinished; }); - test('ResourceQueue - simple', function () { - let queue = new async.ResourceQueue(); + suite('retry', () => { + test('success case', async () => { + let counter = 0; - const r1Queue = queue.queueFor(URI.file('/some/path')); + const res = await async.retry(() => { + counter++; + if (counter < 2) { + return Promise.reject(new Error('fail')); + } - r1Queue.onFinished(() => console.log('DONE')); + return Promise.resolve(true); + }, 10, 3); - const r2Queue = queue.queueFor(URI.file('/some/other/path')); + assert.strictEqual(res, true); + }); - assert.ok(r1Queue); - assert.ok(r2Queue); - assert.strictEqual(r1Queue, queue.queueFor(URI.file('/some/path'))); // same queue returned - - let syncPromiseFactory = () => Promise.resolve(undefined); - - r1Queue.queue(syncPromiseFactory); - - return new Promise(c => setTimeout(() => c(), 0)).then(() => { - const r1Queue2 = queue.queueFor(URI.file('/some/path')); - assert.notStrictEqual(r1Queue, r1Queue2); // previous one got disposed after finishing + test('error case', async () => { + let expectedError = new Error('fail'); + try { + await async.retry(() => { + return Promise.reject(expectedError); + }, 10, 3); + } catch (error) { + assert.strictEqual(error, error); + } }); }); - test('retry - success case', async () => { - let counter = 0; + suite('TaskSequentializer', () => { + test('pending basics', async function () { + const sequentializer = new async.TaskSequentializer(); - const res = await async.retry(() => { - counter++; - if (counter < 2) { - return Promise.reject(new Error('fail')); - } + assert.ok(!sequentializer.hasPending()); + assert.ok(!sequentializer.hasPending(2323)); + assert.ok(!sequentializer.pending); - return Promise.resolve(true); - }, 10, 3); + // pending removes itself after done + await sequentializer.setPending(1, Promise.resolve()); + assert.ok(!sequentializer.hasPending()); + assert.ok(!sequentializer.hasPending(1)); + assert.ok(!sequentializer.pending); - assert.strictEqual(res, true); - }); + // pending removes itself after done (use async.timeout) + sequentializer.setPending(2, async.timeout(1)); + assert.ok(sequentializer.hasPending()); + assert.ok(sequentializer.hasPending(2)); + assert.strictEqual(sequentializer.hasPending(1), false); + assert.ok(sequentializer.pending); - test('retry - error case', async () => { - let expectedError = new Error('fail'); - try { - await async.retry(() => { - return Promise.reject(expectedError); - }, 10, 3); - } catch (error) { - assert.strictEqual(error, error); - } - }); + await async.timeout(2); + assert.strictEqual(sequentializer.hasPending(), false); + assert.strictEqual(sequentializer.hasPending(2), false); + assert.ok(!sequentializer.pending); + }); - test('TaskSequentializer - pending basics', async function () { - const sequentializer = new async.TaskSequentializer(); + test('pending and next (finishes instantly)', async function () { + const sequentializer = new async.TaskSequentializer(); - assert.ok(!sequentializer.hasPending()); - assert.ok(!sequentializer.hasPending(2323)); - assert.ok(!sequentializer.pending); + let pendingDone = false; + sequentializer.setPending(1, async.timeout(1).then(() => { pendingDone = true; return; })); - // pending removes itself after done - await sequentializer.setPending(1, Promise.resolve()); - assert.ok(!sequentializer.hasPending()); - assert.ok(!sequentializer.hasPending(1)); - assert.ok(!sequentializer.pending); + // next finishes instantly + let nextDone = false; + const res = sequentializer.setNext(() => Promise.resolve(null).then(() => { nextDone = true; return; })); - // pending removes itself after done (use async.timeout) - sequentializer.setPending(2, async.timeout(1)); - assert.ok(sequentializer.hasPending()); - assert.ok(sequentializer.hasPending(2)); - assert.strictEqual(sequentializer.hasPending(1), false); - assert.ok(sequentializer.pending); + await res; + assert.ok(pendingDone); + assert.ok(nextDone); + }); - await async.timeout(2); - assert.strictEqual(sequentializer.hasPending(), false); - assert.strictEqual(sequentializer.hasPending(2), false); - assert.ok(!sequentializer.pending); - }); + test('pending and next (finishes after timeout)', async function () { + const sequentializer = new async.TaskSequentializer(); - test('TaskSequentializer - pending and next (finishes instantly)', async function () { - const sequentializer = new async.TaskSequentializer(); + let pendingDone = false; + sequentializer.setPending(1, async.timeout(1).then(() => { pendingDone = true; return; })); - let pendingDone = false; - sequentializer.setPending(1, async.timeout(1).then(() => { pendingDone = true; return; })); + // next finishes after async.timeout + let nextDone = false; + const res = sequentializer.setNext(() => async.timeout(1).then(() => { nextDone = true; return; })); - // next finishes instantly - let nextDone = false; - const res = sequentializer.setNext(() => Promise.resolve(null).then(() => { nextDone = true; return; })); + await res; + assert.ok(pendingDone); + assert.ok(nextDone); + }); - await res; - assert.ok(pendingDone); - assert.ok(nextDone); - }); + test('pending and multiple next (last one wins)', async function () { + const sequentializer = new async.TaskSequentializer(); - test('TaskSequentializer - pending and next (finishes after timeout)', async function () { - const sequentializer = new async.TaskSequentializer(); + let pendingDone = false; + sequentializer.setPending(1, async.timeout(1).then(() => { pendingDone = true; return; })); - let pendingDone = false; - sequentializer.setPending(1, async.timeout(1).then(() => { pendingDone = true; return; })); + // next finishes after async.timeout + let firstDone = false; + let firstRes = sequentializer.setNext(() => async.timeout(2).then(() => { firstDone = true; return; })); - // next finishes after async.timeout - let nextDone = false; - const res = sequentializer.setNext(() => async.timeout(1).then(() => { nextDone = true; return; })); + let secondDone = false; + let secondRes = sequentializer.setNext(() => async.timeout(3).then(() => { secondDone = true; return; })); - await res; - assert.ok(pendingDone); - assert.ok(nextDone); - }); + let thirdDone = false; + let thirdRes = sequentializer.setNext(() => async.timeout(4).then(() => { thirdDone = true; return; })); - test('TaskSequentializer - pending and multiple next (last one wins)', async function () { - const sequentializer = new async.TaskSequentializer(); + await Promise.all([firstRes, secondRes, thirdRes]); + assert.ok(pendingDone); + assert.ok(!firstDone); + assert.ok(!secondDone); + assert.ok(thirdDone); + }); - let pendingDone = false; - sequentializer.setPending(1, async.timeout(1).then(() => { pendingDone = true; return; })); + test('cancel pending', async function () { + const sequentializer = new async.TaskSequentializer(); - // next finishes after async.timeout - let firstDone = false; - let firstRes = sequentializer.setNext(() => async.timeout(2).then(() => { firstDone = true; return; })); + let pendingCancelled = false; + sequentializer.setPending(1, async.timeout(1), () => pendingCancelled = true); + sequentializer.cancelPending(); - let secondDone = false; - let secondRes = sequentializer.setNext(() => async.timeout(3).then(() => { secondDone = true; return; })); - - let thirdDone = false; - let thirdRes = sequentializer.setNext(() => async.timeout(4).then(() => { thirdDone = true; return; })); - - await Promise.all([firstRes, secondRes, thirdRes]); - assert.ok(pendingDone); - assert.ok(!firstDone); - assert.ok(!secondDone); - assert.ok(thirdDone); - }); - - test('TaskSequentializer - cancel pending', async function () { - const sequentializer = new async.TaskSequentializer(); - - let pendingCancelled = false; - sequentializer.setPending(1, async.timeout(1), () => pendingCancelled = true); - sequentializer.cancelPending(); - - assert.ok(pendingCancelled); + assert.ok(pendingCancelled); + }); }); test('raceCancellation', async () => { @@ -723,63 +756,65 @@ suite('Async', () => { assert.strictEqual(counter.increment(), 3); }); - test('firstParallel - simple', async () => { - const a = await async.firstParallel([ - Promise.resolve(1), - Promise.resolve(2), - Promise.resolve(3), - ], v => v === 2); - assert.strictEqual(a, 2); - }); - - test('firstParallel - uses null default', async () => { - assert.strictEqual(await async.firstParallel([Promise.resolve(1)], v => v === 2), null); - }); - - test('firstParallel - uses value default', async () => { - assert.strictEqual(await async.firstParallel([Promise.resolve(1)], v => v === 2, 4), 4); - }); - - test('firstParallel - empty', async () => { - assert.strictEqual(await async.firstParallel([], v => v === 2, 4), 4); - }); - - test('firstParallel - cancels', async () => { - let ct1: CancellationToken; - const p1 = async.createCancelablePromise(async (ct) => { - ct1 = ct; - await async.timeout(200, ct); - return 1; - }); - let ct2: CancellationToken; - const p2 = async.createCancelablePromise(async (ct) => { - ct2 = ct; - await async.timeout(2, ct); - return 2; + suite('firstParallel', () => { + test('simple', async () => { + const a = await async.firstParallel([ + Promise.resolve(1), + Promise.resolve(2), + Promise.resolve(3), + ], v => v === 2); + assert.strictEqual(a, 2); }); - assert.strictEqual(await async.firstParallel([p1, p2], v => v === 2, 4), 2); - assert.strictEqual(ct1!.isCancellationRequested, true, 'should cancel a'); - assert.strictEqual(ct2!.isCancellationRequested, true, 'should cancel b'); - }); - - test('firstParallel - rejection handling', async () => { - let ct1: CancellationToken; - const p1 = async.createCancelablePromise(async (ct) => { - ct1 = ct; - await async.timeout(200, ct); - return 1; - }); - let ct2: CancellationToken; - const p2 = async.createCancelablePromise(async (ct) => { - ct2 = ct; - await async.timeout(2, ct); - throw new Error('oh no'); + test('uses null default', async () => { + assert.strictEqual(await async.firstParallel([Promise.resolve(1)], v => v === 2), null); }); - assert.strictEqual(await async.firstParallel([p1, p2], v => v === 2, 4).catch(() => 'ok'), 'ok'); - assert.strictEqual(ct1!.isCancellationRequested, true, 'should cancel a'); - assert.strictEqual(ct2!.isCancellationRequested, true, 'should cancel b'); + test('uses value default', async () => { + assert.strictEqual(await async.firstParallel([Promise.resolve(1)], v => v === 2, 4), 4); + }); + + test('empty', async () => { + assert.strictEqual(await async.firstParallel([], v => v === 2, 4), 4); + }); + + test('cancels', async () => { + let ct1: CancellationToken; + const p1 = async.createCancelablePromise(async (ct) => { + ct1 = ct; + await async.timeout(200, ct); + return 1; + }); + let ct2: CancellationToken; + const p2 = async.createCancelablePromise(async (ct) => { + ct2 = ct; + await async.timeout(2, ct); + return 2; + }); + + assert.strictEqual(await async.firstParallel([p1, p2], v => v === 2, 4), 2); + assert.strictEqual(ct1!.isCancellationRequested, true, 'should cancel a'); + assert.strictEqual(ct2!.isCancellationRequested, true, 'should cancel b'); + }); + + test('rejection handling', async () => { + let ct1: CancellationToken; + const p1 = async.createCancelablePromise(async (ct) => { + ct1 = ct; + await async.timeout(200, ct); + return 1; + }); + let ct2: CancellationToken; + const p2 = async.createCancelablePromise(async (ct) => { + ct2 = ct; + await async.timeout(2, ct); + throw new Error('oh no'); + }); + + assert.strictEqual(await async.firstParallel([p1, p2], v => v === 2, 4).catch(() => 'ok'), 'ok'); + assert.strictEqual(ct1!.isCancellationRequested, true, 'should cancel a'); + assert.strictEqual(ct2!.isCancellationRequested, true, 'should cancel b'); + }); }); suite('DeferredPromise', () => { diff --git a/src/vs/base/test/common/decorators.test.ts b/src/vs/base/test/common/decorators.test.ts index 87300310650..ef0268cd864 100644 --- a/src/vs/base/test/common/decorators.test.ts +++ b/src/vs/base/test/common/decorators.test.ts @@ -161,7 +161,7 @@ suite('Decorators', () => { clock.tick(200); assert.deepStrictEqual(spy.args, [[1], [5]]); - spy.reset(); + spy.resetHistory(); t.report(4); t.report(5); diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index 37dbdb7f67b..5234f57cd9b 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -11,6 +11,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Mimes } from 'vs/base/common/mime'; import * as platform from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; import { ITextAreaWrapper, ITypeData, TextAreaState, _debugComposition } from 'vs/editor/browser/controller/textAreaState'; @@ -653,7 +654,7 @@ class ClipboardEventUtils { if (e.clipboardData) { e.preventDefault(); - const text = e.clipboardData.getData('text/plain'); + const text = e.clipboardData.getData(Mimes.text); let metadata: ClipboardStoredMetadata | null = null; const rawmetadata = e.clipboardData.getData('vscode-editor-data'); if (typeof rawmetadata === 'string') { @@ -681,7 +682,7 @@ class ClipboardEventUtils { public static setTextData(e: ClipboardEvent, text: string, html: string | null | undefined, metadata: ClipboardStoredMetadata): void { if (e.clipboardData) { - e.clipboardData.setData('text/plain', text); + e.clipboardData.setData(Mimes.text, text); if (typeof html === 'string') { e.clipboardData.setData('text/html', html); } diff --git a/src/vs/editor/common/modes/modesRegistry.ts b/src/vs/editor/common/modes/modesRegistry.ts index 95877a9bea6..8001a55a159 100644 --- a/src/vs/editor/common/modes/modesRegistry.ts +++ b/src/vs/editor/common/modes/modesRegistry.ts @@ -10,6 +10,7 @@ import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageCo import { ILanguageExtensionPoint } from 'vs/editor/common/services/modeService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { Mimes } from 'vs/base/common/mime'; // Define extension point ids export const Extensions = { @@ -65,7 +66,7 @@ ModesRegistry.registerLanguage({ id: PLAINTEXT_MODE_ID, extensions: [PLAINTEXT_EXTENSION], aliases: [nls.localize('plainText.alias', "Plain Text"), 'text'], - mimetypes: ['text/plain'] + mimetypes: [Mimes.text] }); LanguageConfigurationRegistry.register(PLAINTEXT_LANGUAGE_IDENTIFIER, { brackets: [ diff --git a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts index 2f41f489214..04bf8790174 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { domEvent, stop } from 'vs/base/browser/event'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { Event } from 'vs/base/common/event'; @@ -90,11 +89,15 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { const overloads = dom.append(controls, $('.overloads')); const next = dom.append(controls, $('.button' + ThemeIcon.asCSSSelector(parameterHintsNextIcon))); - const onPreviousClick = stop(domEvent(previous, 'click')); - this._register(onPreviousClick(this.previous, this)); + this._register(dom.addDisposableListener(previous, 'click', e => { + dom.EventHelper.stop(e); + this.previous(); + })); - const onNextClick = stop(domEvent(next, 'click')); - this._register(onNextClick(this.next, this)); + this._register(dom.addDisposableListener(next, 'click', e => { + dom.EventHelper.stop(e); + this.next(); + })); const body = $('.body'); const scrollbar = new DomScrollableElement(body, {}); diff --git a/src/vs/editor/standalone/browser/standalone-tokens.css b/src/vs/editor/standalone/browser/standalone-tokens.css index 5e6bbadb732..1933d35edae 100644 --- a/src/vs/editor/standalone/browser/standalone-tokens.css +++ b/src/vs/editor/standalone/browser/standalone-tokens.css @@ -26,6 +26,7 @@ /* See https://github.com/microsoft/monaco-editor/issues/2168#issuecomment-780078600 */ .monaco-aria-container { position: absolute !important; + top: 0; /* avoid being placed underneath a sibling element */ height: 1px; width: 1px; margin: -1px; diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 77884d5ffde..de8998e0fa0 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -35,6 +35,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { ILanguageSelection, IModeService } from 'vs/editor/common/services/modeService'; import { URI } from 'vs/base/common/uri'; import { StandaloneCodeEditorServiceImpl } from 'vs/editor/standalone/browser/standaloneCodeServiceImpl'; +import { Mimes } from 'vs/base/common/mime'; /** * Description of an action contribution @@ -425,7 +426,7 @@ export class StandaloneEditor extends StandaloneCodeEditor implements IStandalon let model: ITextModel | null; if (typeof _model === 'undefined') { - model = createTextModel(modelService, modeService, options.value || '', options.language || 'text/plain', undefined); + model = createTextModel(modelService, modeService, options.value || '', options.language || Mimes.text, undefined); this._ownsModel = true; } else { model = _model; diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index 581b30f2879..ae5c10aa2d1 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./menuEntryActionViewItem'; -import { asCSSUrl, ModifierKeyEmitter } from 'vs/base/browser/dom'; -import { domEvent } from 'vs/base/browser/event'; +import { addDisposableListener, asCSSUrl, ModifierKeyEmitter } from 'vs/base/browser/dom'; import { IAction, Separator, SubmenuAction } from 'vs/base/common/actions'; import { IDisposable, toDisposable, MutableDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; @@ -177,12 +176,12 @@ export class MenuEntryActionViewItem extends ActionViewItem { })); } - this._register(domEvent(container, 'mouseleave')(_ => { + this._register(addDisposableListener(container, 'mouseleave', _ => { mouseOver = false; updateAltState(); })); - this._register(domEvent(container, 'mouseenter')(e => { + this._register(addDisposableListener(container, 'mouseenter', _ => { mouseOver = true; updateAltState(); })); diff --git a/src/vs/platform/contextview/browser/contextMenuHandler.ts b/src/vs/platform/contextview/browser/contextMenuHandler.ts index bf0652d0571..5597070aff0 100644 --- a/src/vs/platform/contextview/browser/contextMenuHandler.ts +++ b/src/vs/platform/contextview/browser/contextMenuHandler.ts @@ -14,9 +14,8 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IContextMenuDelegate } from 'vs/base/browser/contextmenu'; -import { EventType, $, isHTMLElement } from 'vs/base/browser/dom'; +import { EventType, $, isHTMLElement, addDisposableListener } from 'vs/base/browser/dom'; import { attachMenuStyler } from 'vs/platform/theme/common/styler'; -import { domEvent } from 'vs/base/browser/event'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { isPromiseCanceledError } from 'vs/base/common/errors'; @@ -75,7 +74,9 @@ export class ContextMenuHandler { this.block.style.width = '100%'; this.block.style.height = '100%'; this.block.style.zIndex = '-1'; - domEvent(this.block, EventType.MOUSE_DOWN)((e: MouseEvent) => e.stopPropagation()); + + // TODO@Steven: this is never getting disposed + addDisposableListener(this.block, EventType.MOUSE_DOWN, e => e.stopPropagation()); } const menuDisposables = new DisposableStore(); @@ -94,8 +95,8 @@ export class ContextMenuHandler { menu.onDidCancel(() => this.contextViewService.hideContextView(true), null, menuDisposables); menu.onDidBlur(() => this.contextViewService.hideContextView(true), null, menuDisposables); - domEvent(window, EventType.BLUR)(() => { this.contextViewService.hideContextView(true); }, null, menuDisposables); - domEvent(window, EventType.MOUSE_DOWN)((e: MouseEvent) => { + menuDisposables.add(addDisposableListener(window, EventType.BLUR, () => this.contextViewService.hideContextView(true))); + menuDisposables.add(addDisposableListener(window, EventType.MOUSE_DOWN, (e: MouseEvent) => { if (e.defaultPrevented) { return; } @@ -117,7 +118,7 @@ export class ContextMenuHandler { } this.contextViewService.hideContextView(true); - }, null, menuDisposables); + })); return combinedDisposable(menuDisposables, menu); }, diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index ded07ffcd9e..d13c087349f 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -60,6 +60,7 @@ export interface NativeParsedArgs { 'enable-proposed-api'?: string[]; // undefined or array of 1 or more 'open-url'?: boolean; 'skip-release-notes'?: boolean; + 'skip-welcome'?: boolean; 'disable-telemetry'?: boolean; 'export-default-configuration'?: string; 'install-source'?: string; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 238098af3a0..a312a4209c3 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -94,6 +94,7 @@ export const OPTIONS: OptionDescriptions> = { 'driver': { type: 'string' }, 'logExtensionHostCommunication': { type: 'boolean' }, 'skip-release-notes': { type: 'boolean' }, + 'skip-welcome': { type: 'boolean' }, 'disable-telemetry': { type: 'boolean' }, 'disable-updates': { type: 'boolean' }, 'disable-keytar': { type: 'boolean' }, diff --git a/src/vs/platform/opener/browser/link.ts b/src/vs/platform/opener/browser/link.ts index 86a95a1cec4..f16859dc459 100644 --- a/src/vs/platform/opener/browser/link.ts +++ b/src/vs/platform/opener/browser/link.ts @@ -6,7 +6,7 @@ import { Event } from 'vs/base/common/event'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { $, EventHelper, EventLike } from 'vs/base/browser/dom'; -import { DomEmitter, domEvent } from 'vs/base/browser/event'; +import { DomEmitter } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -67,7 +67,8 @@ export class Link extends Disposable { }, link.label); const onClickEmitter = this._register(new DomEmitter(this.el, 'click')); - const onEnterPress = Event.chain(domEvent(this.el, 'keypress')) + const onKeyPress = this._register(new DomEmitter(this.el, 'keypress')); + const onEnterPress = Event.chain(onKeyPress.event) .map(e => new StandardKeyboardEvent(e)) .filter(e => e.keyCode === KeyCode.Enter) .event; diff --git a/src/vs/platform/remote/browser/browserSocketFactory.ts b/src/vs/platform/remote/browser/browserSocketFactory.ts index 2f343e841ab..abf7428a8ff 100644 --- a/src/vs/platform/remote/browser/browserSocketFactory.ts +++ b/src/vs/platform/remote/browser/browserSocketFactory.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ISocketFactory, IConnectCallback } from 'vs/platform/remote/common/remoteAgentConnection'; -import { ISocket } from 'vs/base/parts/ipc/common/ipc.net'; +import { ISocket, SocketCloseEvent, SocketCloseEventType } from 'vs/base/parts/ipc/common/ipc.net'; import { VSBuffer } from 'vs/base/common/buffer'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; @@ -16,10 +16,29 @@ export interface IWebSocketFactory { create(url: string): IWebSocket; } +export interface IWebSocketCloseEvent { + /** + * Returns the WebSocket connection close code provided by the server. + */ + readonly code: number; + /** + * Returns the WebSocket connection close reason provided by the server. + */ + readonly reason: string; + /** + * Returns true if the connection closed cleanly; false otherwise. + */ + readonly wasClean: boolean; + /** + * Underlying event. + */ + readonly event: any | undefined; +} + export interface IWebSocket { readonly onData: Event; readonly onOpen: Event; - readonly onClose: Event; + readonly onClose: Event; readonly onError: Event; send(data: ArrayBuffer | ArrayBufferView): void; @@ -33,7 +52,7 @@ class BrowserWebSocket extends Disposable implements IWebSocket { public readonly onOpen: Event; - private readonly _onClose = this._register(new Emitter()); + private readonly _onClose = this._register(new Emitter()); public readonly onClose = this._onClose.event; private readonly _onError = this._register(new Emitter()); @@ -135,7 +154,7 @@ class BrowserWebSocket extends Disposable implements IWebSocket { } } - this._onClose.fire(); + this._onClose.fire({ code: e.code, reason: e.reason, wasClean: e.wasClean, event: e }); })); this._register(dom.addDisposableListener(this._socket, 'error', sendErrorSoon)); @@ -178,8 +197,21 @@ class BrowserSocket implements ISocket { return this.socket.onData((data) => listener(VSBuffer.wrap(new Uint8Array(data)))); } - public onClose(listener: () => void): IDisposable { - return this.socket.onClose(listener); + public onClose(listener: (e: SocketCloseEvent) => void): IDisposable { + const adapter = (e: IWebSocketCloseEvent | void) => { + if (typeof e === 'undefined') { + listener(e); + } else { + listener({ + type: SocketCloseEventType.WebSocketCloseEvent, + code: e.code, + reason: e.reason, + wasClean: e.wasClean, + event: e.event + }); + } + }; + return this.socket.onClose(adapter); } public onEnd(listener: () => void): IDisposable { diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index 41475982dd3..cf5ce3c714b 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Client, PersistentProtocol, ISocket, ProtocolConstants } from 'vs/base/parts/ipc/common/ipc.net'; +import { Client, PersistentProtocol, ISocket, ProtocolConstants, SocketCloseEventType } from 'vs/base/parts/ipc/common/ipc.net'; import { generateUuid } from 'vs/base/common/uuid'; import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; @@ -532,8 +532,28 @@ abstract class PersistentConnection extends Disposable { this._onDidStateChange.fire(new ConnectionGainEvent(this.reconnectionToken, 0, 0)); - this._register(protocol.onSocketClose(() => this._beginReconnecting())); - this._register(protocol.onSocketTimeout(() => this._beginReconnecting())); + this._register(protocol.onSocketClose((e) => { + const logPrefix = commonLogPrefix(this._connectionType, this.reconnectionToken, true); + if (!e) { + this._options.logService.info(`${logPrefix} received socket close event.`); + } else if (e.type === SocketCloseEventType.NodeSocketCloseEvent) { + this._options.logService.info(`${logPrefix} received socket close event (hadError: ${e.hadError}).`); + if (e.error) { + this._options.logService.error(e.error); + } + } else { + this._options.logService.info(`${logPrefix} received socket close event (wasClean: ${e.wasClean}, code: ${e.code}, reason: ${e.reason}).`); + if (e.event) { + this._options.logService.error(e.event); + } + } + this._beginReconnecting(); + })); + this._register(protocol.onSocketTimeout(() => { + const logPrefix = commonLogPrefix(this._connectionType, this.reconnectionToken, true); + this._options.logService.trace(`${logPrefix} received socket timeout event.`); + this._beginReconnecting(); + })); PersistentConnection._instances.push(this); diff --git a/src/vs/platform/telemetry/test/browser/telemetryService.test.ts b/src/vs/platform/telemetry/test/browser/telemetryService.test.ts index 9b8969ffa14..e5ebba5ead4 100644 --- a/src/vs/platform/telemetry/test/browser/telemetryService.test.ts +++ b/src/vs/platform/telemetry/test/browser/telemetryService.test.ts @@ -9,10 +9,13 @@ import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry'; import { NullAppender, ITelemetryAppender } from 'vs/platform/telemetry/common/telemetryUtils'; import * as Errors from 'vs/base/common/errors'; import * as sinon from 'sinon'; +import * as sinonTest from 'sinon-test'; import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +const sinonTestFn = sinonTest(sinon); + class TestTelemetryAppender implements ITelemetryAppender { public events: any[]; @@ -85,7 +88,7 @@ class ErrorTestingSettings { suite('TelemetryService', () => { - test('Disposing', sinon.test(function () { + test('Disposing', sinonTestFn(function () { let testAppender = new TestTelemetryAppender(); let service = new TelemetryService({ appender: testAppender }, undefined!); @@ -98,7 +101,7 @@ suite('TelemetryService', () => { })); // event reporting - test('Simple event', sinon.test(function () { + test('Simple event', sinonTestFn(function () { let testAppender = new TestTelemetryAppender(); let service = new TelemetryService({ appender: testAppender }, undefined!); @@ -111,7 +114,7 @@ suite('TelemetryService', () => { }); })); - test('Event with data', sinon.test(function () { + test('Event with data', sinonTestFn(function () { let testAppender = new TestTelemetryAppender(); let service = new TelemetryService({ appender: testAppender }, undefined!); @@ -193,7 +196,7 @@ suite('TelemetryService', () => { }); }); - test('enableTelemetry on by default', sinon.test(function () { + test('enableTelemetry on by default', sinonTestFn(function () { let testAppender = new TestTelemetryAppender(); let service = new TelemetryService({ appender: testAppender }, undefined!); @@ -224,7 +227,7 @@ suite('TelemetryService', () => { } } - test('Error events', sinon.test(async function (this: any) { + test('Error events', sinonTestFn(async function (this: any) { let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); Errors.setUnexpectedErrorHandler(() => { }); @@ -256,7 +259,7 @@ suite('TelemetryService', () => { } })); - // test('Unhandled Promise Error events', sinon.test(function() { + // test('Unhandled Promise Error events', sinonTestFn(function() { // // let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); // Errors.setUnexpectedErrorHandler(() => {}); @@ -285,7 +288,7 @@ suite('TelemetryService', () => { // } // })); - test('Handle global errors', sinon.test(async function (this: any) { + test('Handle global errors', sinonTestFn(async function (this: any) { let errorStub = sinon.stub(); window.onerror = errorStub; @@ -313,7 +316,7 @@ suite('TelemetryService', () => { service.dispose(); })); - test('Error Telemetry removes PII from filename with spaces', sinon.test(async function (this: any) { + test('Error Telemetry removes PII from filename with spaces', sinonTestFn(async function (this: any) { let errorStub = sinon.stub(); window.onerror = errorStub; let settings = new ErrorTestingSettings(); @@ -336,7 +339,7 @@ suite('TelemetryService', () => { service.dispose(); })); - test('Uncaught Error Telemetry removes PII from filename', sinon.test(function (this: any) { + test('Uncaught Error Telemetry removes PII from filename', sinonTestFn(function (this: any) { let clock = this.clock; let errorStub = sinon.stub(); window.onerror = errorStub; @@ -368,7 +371,7 @@ suite('TelemetryService', () => { }); })); - test('Unexpected Error Telemetry removes PII', sinon.test(async function (this: any) { + test('Unexpected Error Telemetry removes PII', sinonTestFn(async function (this: any) { let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); Errors.setUnexpectedErrorHandler(() => { }); try { @@ -399,7 +402,7 @@ suite('TelemetryService', () => { } })); - test('Uncaught Error Telemetry removes PII', sinon.test(async function (this: any) { + test('Uncaught Error Telemetry removes PII', sinonTestFn(async function (this: any) { let errorStub = sinon.stub(); window.onerror = errorStub; let settings = new ErrorTestingSettings(); @@ -426,7 +429,7 @@ suite('TelemetryService', () => { service.dispose(); })); - test('Unexpected Error Telemetry removes PII but preserves Code file path', sinon.test(async function (this: any) { + test('Unexpected Error Telemetry removes PII but preserves Code file path', sinonTestFn(async function (this: any) { let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); Errors.setUnexpectedErrorHandler(() => { }); @@ -462,7 +465,7 @@ suite('TelemetryService', () => { } })); - test('Uncaught Error Telemetry removes PII but preserves Code file path', sinon.test(async function (this: any) { + test('Uncaught Error Telemetry removes PII but preserves Code file path', sinonTestFn(async function (this: any) { let errorStub = sinon.stub(); window.onerror = errorStub; let settings = new ErrorTestingSettings(); @@ -491,7 +494,7 @@ suite('TelemetryService', () => { service.dispose(); })); - test('Unexpected Error Telemetry removes PII but preserves Code file path with node modules', sinon.test(async function (this: any) { + test('Unexpected Error Telemetry removes PII but preserves Code file path with node modules', sinonTestFn(async function (this: any) { let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); Errors.setUnexpectedErrorHandler(() => { }); @@ -523,7 +526,7 @@ suite('TelemetryService', () => { } })); - test('Uncaught Error Telemetry removes PII but preserves Code file path', sinon.test(async function (this: any) { + test('Uncaught Error Telemetry removes PII but preserves Code file path', sinonTestFn(async function (this: any) { let errorStub = sinon.stub(); window.onerror = errorStub; let settings = new ErrorTestingSettings(); @@ -549,7 +552,7 @@ suite('TelemetryService', () => { })); - test('Unexpected Error Telemetry removes PII but preserves Code file path when PIIPath is configured', sinon.test(async function (this: any) { + test('Unexpected Error Telemetry removes PII but preserves Code file path when PIIPath is configured', sinonTestFn(async function (this: any) { let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); Errors.setUnexpectedErrorHandler(() => { }); @@ -585,7 +588,7 @@ suite('TelemetryService', () => { } })); - test('Uncaught Error Telemetry removes PII but preserves Code file path when PIIPath is configured', sinon.test(async function (this: any) { + test('Uncaught Error Telemetry removes PII but preserves Code file path when PIIPath is configured', sinonTestFn(async function (this: any) { let errorStub = sinon.stub(); window.onerror = errorStub; let settings = new ErrorTestingSettings(); @@ -614,7 +617,7 @@ suite('TelemetryService', () => { service.dispose(); })); - test('Unexpected Error Telemetry removes PII but preserves Missing Model error message', sinon.test(async function (this: any) { + test('Unexpected Error Telemetry removes PII but preserves Missing Model error message', sinonTestFn(async function (this: any) { let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); Errors.setUnexpectedErrorHandler(() => { }); @@ -650,7 +653,7 @@ suite('TelemetryService', () => { } })); - test('Uncaught Error Telemetry removes PII but preserves Missing Model error message', sinon.test(async function (this: any) { + test('Uncaught Error Telemetry removes PII but preserves Missing Model error message', sinonTestFn(async function (this: any) { let errorStub = sinon.stub(); window.onerror = errorStub; let settings = new ErrorTestingSettings(); @@ -680,7 +683,7 @@ suite('TelemetryService', () => { service.dispose(); })); - test('Unexpected Error Telemetry removes PII but preserves No Such File error message', sinon.test(async function (this: any) { + test('Unexpected Error Telemetry removes PII but preserves No Such File error message', sinonTestFn(async function (this: any) { let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); Errors.setUnexpectedErrorHandler(() => { }); @@ -716,7 +719,7 @@ suite('TelemetryService', () => { } })); - test('Uncaught Error Telemetry removes PII but preserves No Such File error message', sinon.test(async function (this: any) { + test('Uncaught Error Telemetry removes PII but preserves No Such File error message', sinonTestFn(async function (this: any) { let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); Errors.setUnexpectedErrorHandler(() => { }); @@ -754,7 +757,7 @@ suite('TelemetryService', () => { } })); - test('Telemetry Service sends events when enableTelemetry is on', sinon.test(function () { + test('Telemetry Service sends events when enableTelemetry is on', sinonTestFn(function () { let testAppender = new TestTelemetryAppender(); let service = new TelemetryService({ appender: testAppender }, undefined!); diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index 803296e4d8c..bc79d037959 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -23,6 +23,7 @@ import { createCancelablePromise, timeout, CancelablePromise } from 'vs/base/com import { isString, isObject, isArray } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors'; +import { Mimes } from 'vs/base/common/mime'; const SYNC_PREVIOUS_STORE = 'sync.previous.store'; const DONOT_MAKE_REQUESTS_UNTIL_KEY = 'sync.donot-make-requests-until'; @@ -304,7 +305,7 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync const url = joinPath(this.userDataSyncStoreUrl, 'resource', resource).toString(); headers = { ...headers }; - headers['Content-Type'] = 'text/plain'; + headers['Content-Type'] = Mimes.text; if (ref) { headers['If-Match'] = ref; } @@ -356,7 +357,7 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync } const url = joinPath(this.userDataSyncStoreUrl, 'resource').toString(); - const headers: IHeaders = { 'Content-Type': 'text/plain' }; + const headers: IHeaders = { 'Content-Type': Mimes.text }; await this.request(url, { type: 'DELETE', headers }, [], CancellationToken.None); diff --git a/src/vs/platform/webview/common/mimeTypes.ts b/src/vs/platform/webview/common/mimeTypes.ts index f9a54488d19..d862e9bd65e 100644 --- a/src/vs/platform/webview/common/mimeTypes.ts +++ b/src/vs/platform/webview/common/mimeTypes.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getMediaMime, MIME_UNKNOWN } from 'vs/base/common/mime'; +import { getMediaMime, Mimes } from 'vs/base/common/mime'; import { extname } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; const webviewMimeTypes = new Map([ ['.svg', 'image/svg+xml'], - ['.txt', 'text/plain'], + ['.txt', Mimes.text], ['.css', 'text/css'], ['.js', 'application/javascript'], ['.json', 'application/json'], @@ -18,9 +18,10 @@ const webviewMimeTypes = new Map([ ['.xhtml', 'application/xhtml+xml'], ['.oft', 'font/otf'], ['.xml', 'application/xml'], + ['.wasm', 'application/wasm'], ]); export function getWebviewContentMimeType(resource: URI): string { const ext = extname(resource.fsPath).toLowerCase(); - return webviewMimeTypes.get(ext) || getMediaMime(resource.fsPath) || MIME_UNKNOWN; + return webviewMimeTypes.get(ext) || getMediaMime(resource.fsPath) || Mimes.unknown; } diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 327f4f98c18..dca0e8a61fa 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -9489,6 +9489,24 @@ declare module 'vscode' { */ onDidClose?: Event; + /** + * An event that when fired allows changing the name of the terminal. + * + * **Example:** Change the terminal name to "My new terminal". + * ```typescript + * const writeEmitter = new vscode.EventEmitter(); + * const changeNameEmitter = new vscode.EventEmitter(); + * const pty: vscode.Pseudoterminal = { + * onDidWrite: writeEmitter.event, + * onDidChangeName: changeNameEmitter.event, + * open: () => changeNameEmitter.fire('My new terminal'), + * close: () => {} + * }; + * vscode.window.createTerminal({ name: 'My terminal', pty }); + * ``` + */ + onDidChangeName?: Event; + /** * Implement to handle when the pty is open and ready to start firing events. * @@ -9884,7 +9902,7 @@ declare module 'vscode' { /** * An event signaling when the active items have changed. */ - readonly onDidChangeActive: Event; + readonly onDidChangeActive: Event; /** * Selected items. This can be read and updated by the extension. @@ -9894,7 +9912,7 @@ declare module 'vscode' { /** * An event signaling when the selected items have changed. */ - readonly onDidChangeSelection: Event; + readonly onDidChangeSelection: Event; } /** @@ -11409,7 +11427,7 @@ declare module 'vscode' { readonly outputs: readonly NotebookCellOutput[]; /** - * The most recent {@link NotebookCellExecutionSummary excution summary} for this cell. + * The most recent {@link NotebookCellExecutionSummary execution summary} for this cell. */ readonly executionSummary?: NotebookCellExecutionSummary; } @@ -11477,7 +11495,7 @@ declare module 'vscode' { /** * Get the cells of this notebook. A subset can be retrieved by providing - * a range. The range will be adjuset to the notebook. + * a range. The range will be adjusted to the notebook. * * @param range A notebook range. * @returns The cells contained by the range or all cells. @@ -11515,7 +11533,7 @@ declare module 'vscode' { } /** - * A notebook range represents an ordered pair of two cell indicies. + * A notebook range represents an ordered pair of two cell indices. * It is guaranteed that start is less than or equal to end. */ export class NotebookRange { @@ -11626,7 +11644,7 @@ declare module 'vscode' { data: Uint8Array; /** - * Create a new notbook cell output item. + * Create a new notebook cell output item. * * @param data The value of the output item. * @param mime The mime type of the output item. @@ -12122,7 +12140,7 @@ declare module 'vscode' { /** * Namespace for notebooks. * - * The notebooks functionality is composed of three loosly coupled components: + * The notebooks functionality is composed of three loosely coupled components: * * 1. {@link NotebookSerializer} enable the editor to open, show, and save notebooks * 2. {@link NotebookController} own the execution of notebooks, e.g they create output from code cells. @@ -13512,17 +13530,17 @@ declare module 'vscode' { */ export interface AuthenticationProviderAuthenticationSessionsChangeEvent { /** - * The {@link AuthenticationSession}s of the {@link AuthentiationProvider AuthenticationProvider} that have been added. + * The {@link AuthenticationSession}s of the {@link AuthenticationProvider} that have been added. */ readonly added?: readonly AuthenticationSession[]; /** - * The {@link AuthenticationSession}s of the {@link AuthentiationProvider AuthenticationProvider} that have been removed. + * The {@link AuthenticationSession}s of the {@link AuthenticationProvider} that have been removed. */ readonly removed?: readonly AuthenticationSession[]; /** - * The {@link AuthenticationSession}s of the {@link AuthentiationProvider AuthenticationProvider} that have been changed. + * The {@link AuthenticationSession}s of the {@link AuthenticationProvider} that have been changed. * A session changes when its data excluding the id are updated. An example of this is a session refresh that results in a new * access token being set for the session. */ diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 79244353bf5..472e6340889 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -874,30 +874,6 @@ declare module 'vscode' { //#endregion - //#region Terminal name change event https://github.com/microsoft/vscode/issues/114898 - - export interface Pseudoterminal { - /** - * An event that when fired allows changing the name of the terminal. - * - * **Example:** Change the terminal name to "My new terminal". - * ```typescript - * const writeEmitter = new vscode.EventEmitter(); - * const changeNameEmitter = new vscode.EventEmitter(); - * const pty: vscode.Pseudoterminal = { - * onDidWrite: writeEmitter.event, - * onDidChangeName: changeNameEmitter.event, - * open: () => changeNameEmitter.fire('My new terminal'), - * close: () => {} - * }; - * vscode.window.createTerminal({ name: 'My terminal', pty }); - * ``` - */ - onDidChangeName?: Event; - } - - //#endregion - //#region Terminal icon https://github.com/microsoft/vscode/issues/120538 export interface TerminalOptions { diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index abb761d9dbe..e927e9afa65 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -551,7 +551,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme public async $extensionTestsExecute(): Promise { await this._eagerExtensionsActivated.wait(); try { - return this._doHandleExtensionTests(); + return await this._doHandleExtensionTests(); } catch (error) { console.error(error); // ensure any error message makes it onto the console throw error; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 42f29f0a2e7..7dc7f02c4de 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -8,7 +8,7 @@ import { illegalArgument } from 'vs/base/common/errors'; import { IRelativePattern } from 'vs/base/common/glob'; import { isMarkdownString, MarkdownString as BaseMarkdownString } from 'vs/base/common/htmlContent'; import { ReadonlyMapView, ResourceMap } from 'vs/base/common/map'; -import { normalizeMimeType } from 'vs/base/common/mime'; +import { Mimes, normalizeMimeType } from 'vs/base/common/mime'; import { isArray, isStringArray } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; @@ -3050,7 +3050,7 @@ export class NotebookCellOutputItem { static #encoder = new TextEncoder(); - static text(value: string, mime: string = 'text/plain'): NotebookCellOutputItem { + static text(value: string, mime: string = Mimes.text): NotebookCellOutputItem { const bytes = NotebookCellOutputItem.#encoder.encode(String(value)); return new NotebookCellOutputItem(bytes, mime); } diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index d52307d18f6..f2ad1ba12d5 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -13,7 +13,7 @@ import { FileAccess, Schemas } from 'vs/base/common/network'; import { IBaseTextResourceEditorInput } from 'vs/platform/editor/common/editor'; import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd'; import { DragMouseEvent } from 'vs/base/browser/mouseEvent'; -import { MIME_BINARY } from 'vs/base/common/mime'; +import { Mimes } from 'vs/base/common/mime'; import { isWindows } from 'vs/base/common/platform'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -277,7 +277,7 @@ export function fillEditorsDragData(accessor: ServicesAccessor, resourcesOrEdito // TODO@sandbox this will no longer work when `vscode-file` // is enabled because we block loading resources that are not // inside installation dir - event.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [MIME_BINARY, basename(firstFile.resource), FileAccess.asBrowserUri(firstFile.resource).toString()].join(':')); + event.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [Mimes.binary, basename(firstFile.resource), FileAccess.asBrowserUri(firstFile.resource).toString()].join(':')); } // Resource URLs: allows to drop multiple file resources to a target in VS Code diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index bb7970f5b36..efd32050a42 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -22,6 +22,7 @@ import { ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/comm import { IPaneComposite } from 'vs/workbench/common/panecomposite'; import { IComposite } from 'vs/workbench/common/composite'; import { CompositeDragAndDropData, CompositeDragAndDropObserver, IDraggedCompositeData, ICompositeDragAndDrop, Before2D, toggleDropEffect } from 'vs/workbench/browser/dnd'; +import { Gesture, EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; export interface ICompositeBarItem { id: string; @@ -233,6 +234,8 @@ export class CompositeBar extends Widget implements ICompositeBar { // Contextmenu for composites this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, e => this.showContextMenu(e))); + this._register(Gesture.addTarget(parent)); + this._register(addDisposableListener(parent, TouchEventType.Contextmenu, e => this.showContextMenu(e))); let insertDropBefore: Before2D | undefined = undefined; // Register a drop target on the whole bar to prevent forbidden feedback @@ -619,7 +622,7 @@ export class CompositeBar extends Widget implements ICompositeBar { return this.model.visibleItems.filter(c => overflowingIds.includes(c.id)).map(item => { return { id: item.id, name: this.getAction(item.id)?.label || item.name }; }); } - private showContextMenu(e: MouseEvent): void { + private showContextMenu(e: MouseEvent | GestureEvent): void { EventHelper.stop(e, true); const event = new StandardMouseEvent(e); this.contextMenuService.showContextMenu({ diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 485670a0a69..0e7b3222932 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -22,7 +22,6 @@ import { Color } from 'vs/base/common/color'; import { IBaseActionViewItemOptions, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { Codicon } from 'vs/base/common/codicons'; import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; -import { domEvent } from 'vs/base/browser/event'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; @@ -395,15 +394,15 @@ export class ActivityActionViewItem extends BaseActionViewItem { this.updateTitle(); if (this.useCustomHover) { - this.hoverDisposables.add(domEvent(this.container, EventType.MOUSE_OVER, true)(() => { + this.hoverDisposables.add(addDisposableListener(this.container, EventType.MOUSE_OVER, () => { if (!this.showHoverScheduler.isScheduled()) { this.showHoverScheduler.schedule(this.options.hoverOptions!.delay() || 150); } - })); - this.hoverDisposables.add(domEvent(this.container, EventType.MOUSE_LEAVE, true)(() => { + }, true)); + this.hoverDisposables.add(addDisposableListener(this.container, EventType.MOUSE_LEAVE, () => { this.hover.value = undefined; this.showHoverScheduler.cancel(); - })); + }, true)); this.hoverDisposables.add(toDisposable(() => { this.hover.value = undefined; this.showHoverScheduler.cancel(); diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index b08f327c670..e32697e2200 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -35,6 +35,7 @@ import { CompositeDragAndDropObserver } from 'vs/workbench/browser/dnd'; import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CATEGORIES } from 'vs/workbench/common/actions'; +import { Gesture, EventType as GestureEventType } from 'vs/base/browser/touch'; export class SidebarPart extends CompositePart implements IViewletService { @@ -171,6 +172,10 @@ export class SidebarPart extends CompositePart implements IViewletServi this._register(addDisposableListener(titleArea, EventType.CONTEXT_MENU, e => { this.onTitleAreaContextMenu(new StandardMouseEvent(e)); })); + this._register(Gesture.addTarget(titleArea)); + this._register(addDisposableListener(titleArea, GestureEventType.Contextmenu, e => { + this.onTitleAreaContextMenu(new StandardMouseEvent(e)); + })); this.titleLabelElement!.draggable = true; diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index f2dbea288b4..95b8a1fcdb7 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -10,6 +10,7 @@ import { dispose, IDisposable, Disposable, toDisposable, MutableDisposable } fro import { SimpleIconLabel } from 'vs/base/browser/ui/iconLabel/simpleIconLabel'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { Part } from 'vs/workbench/browser/part'; +import { EventType as TouchEventType, Gesture, GestureEvent } from 'vs/base/browser/touch'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntry, IStatusbarEntryAccessor } from 'vs/workbench/services/statusbar/common/statusbar'; @@ -586,6 +587,8 @@ export class StatusbarPart extends Part implements IStatusbarService { // Context menu support this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, e => this.showContextMenu(e))); + this._register(Gesture.addTarget(parent)); + this._register(addDisposableListener(parent, TouchEventType.Contextmenu, e => this.showContextMenu(e))); // Initial status bar entries this.createInitialStatusbarEntries(); @@ -660,7 +663,7 @@ export class StatusbarPart extends Part implements IStatusbarService { } } - private showContextMenu(e: MouseEvent): void { + private showContextMenu(e: MouseEvent | GestureEvent): void { EventHelper.stop(e, true); const event = new StandardMouseEvent(e); diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 0ed15426be7..6f6b9c90acd 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -38,6 +38,7 @@ import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { CompositeMenuActions } from 'vs/workbench/browser/actions'; import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch'; export const ViewsSubMenu = new MenuId('Views'); MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, { @@ -409,6 +410,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { this._register(this.paneview.onDidDrop(({ from, to }) => this.movePane(from as ViewPane, to as ViewPane))); this._register(this.paneview.onDidScroll(_ => this.onDidScrollPane())); this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, (e: MouseEvent) => this.showContextMenu(new StandardMouseEvent(e)))); + this._register(Gesture.addTarget(parent)); + this._register(addDisposableListener(parent, TouchEventType.Contextmenu, (e: MouseEvent) => this.showContextMenu(new StandardMouseEvent(e)))); this._menuActions = this._register(this.instantiationService.createInstance(ViewContainerMenuActions, this.paneview.element, this.viewContainer)); this._register(this._menuActions.onDidChange(() => this.updateTitleArea())); diff --git a/src/vs/workbench/common/editor/binaryEditorModel.ts b/src/vs/workbench/common/editor/binaryEditorModel.ts index 7e4e6a7ae46..af36ab952ed 100644 --- a/src/vs/workbench/common/editor/binaryEditorModel.ts +++ b/src/vs/workbench/common/editor/binaryEditorModel.ts @@ -6,14 +6,14 @@ import { EditorModel } from 'vs/workbench/common/editor/editorModel'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; -import { MIME_BINARY } from 'vs/base/common/mime'; +import { Mimes } from 'vs/base/common/mime'; /** * An editor model that just represents a resource that can be loaded. */ export class BinaryEditorModel extends EditorModel { - private readonly mime = MIME_BINARY; + private readonly mime = Mimes.binary; private size: number | undefined; private etag: string | undefined; diff --git a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts index ce600feb10c..df8e4821422 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts @@ -233,9 +233,10 @@ class FormatOnSaveParticipant implements ITextFileSaveParticipant { const nestedProgress = new Progress<{ displayName?: string, extensionId?: ExtensionIdentifier }>(provider => { progress.report({ message: localize( - 'formatting', - "Running '{0}' Formatter ([configure](command:workbench.action.openSettings?%5B%22editor.formatOnSave%22%5D)).", - provider.displayName || provider.extensionId && provider.extensionId.value || '???' + { key: 'formatting2', comment: ['[configure]({1}) is a link. Only translate `configure`. Do not change brackets and parentheses or {1}'] }, + "Running '{0}' Formatter ([configure]({1})).", + provider.displayName || provider.extensionId && provider.extensionId.value || '???', + 'command:workbench.action.openSettings?%5B%22editor.formatOnSave%22%5D' ) }); }); @@ -336,9 +337,10 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant { private _report(): void { progress.report({ message: localize( - 'codeaction.get', - "Getting code actions from '{0}' ([configure](command:workbench.action.openSettings?%5B%22editor.codeActionsOnSave%22%5D)).", - [...this._names].map(name => `'${name}'`).join(', ') + { key: 'codeaction.get2', comment: ['[configure]({1}) is a link. Only translate `configure`. Do not change brackets and parentheses or {1}'] }, + "Getting code actions from '{0}' ([configure]({1})).", + [...this._names].map(name => `'${name}'`).join(', '), + 'command:workbench.action.openSettings?%5B%22editor.codeActionsOnSave%22%5D' ) }); } diff --git a/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts b/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts index 1e7a864680e..2849ff4ed7a 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts @@ -110,7 +110,7 @@ class UntitledTextEditorHintContentWidget implements IContentWidget { this.domNode.style.width = 'max-content'; const language = $('a.language-mode'); language.style.cursor = 'pointer'; - language.innerText = localize('selectAlanguage', "Select a language"); + language.innerText = localize('selectAlanguage2', "Select a language"); this.domNode.appendChild(language); const toGetStarted = $('span'); toGetStarted.innerText = localize('toGetStarted', " to get started. Start typing to dismiss, or ",); diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index fb6c58472b7..b037c9c5d18 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -21,6 +21,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ADD_CONFIGURATION_ID } from 'vs/workbench/contrib/debug/browser/debugCommands'; import { BaseActionViewItem, SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { debugStart } from 'vs/workbench/contrib/debug/browser/debugIcons'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; const $ = dom.$; @@ -45,6 +46,7 @@ export class StartDebugActionViewItem extends BaseActionViewItem { @ICommandService private readonly commandService: ICommandService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IContextViewService contextViewService: IContextViewService, + @IKeybindingService private readonly keybindingService: IKeybindingService ) { super(context, action); this.toDispose = []; @@ -71,7 +73,9 @@ export class StartDebugActionViewItem extends BaseActionViewItem { this.container = container; container.classList.add('start-debug-action-item'); this.start = dom.append(container, $(ThemeIcon.asCSSSelector(debugStart))); - this.start.title = this.action.label; + const keybinding = this.keybindingService.lookupKeybinding(this.action.id)?.getLabel(); + let keybindingLabel = keybinding ? ` (${keybinding})` : ''; + this.start.title = this.action.label + keybindingLabel; this.start.setAttribute('role', 'button'); this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.CLICK, () => { diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 850045df6e8..b5284a3389d 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -37,7 +37,6 @@ import { ITextModel } from 'vs/editor/common/model'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { basename } from 'vs/base/common/path'; -import { domEvent } from 'vs/base/browser/event'; import { ModesHoverController } from 'vs/editor/contrib/hover/hover'; import { HoverStartMode } from 'vs/editor/contrib/hover/hoverOperation'; import { IHostService } from 'vs/workbench/services/host/browser/host'; @@ -47,6 +46,8 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { Expression } from 'vs/workbench/contrib/debug/common/debugModel'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; +import { addDisposableListener } from 'vs/base/browser/dom'; +import { DomEmitter } from 'vs/base/browser/event'; const LAUNCH_JSON_REGEX = /\.vscode\/launch\.json$/; const INLINE_VALUE_DECORATION_KEY = 'inlinevaluedecoration'; @@ -284,7 +285,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { this.altListener.dispose(); } // When the alt key is pressed show regular editor hover and hide the debug hover #84561 - this.altListener = domEvent(document, 'keydown')(keydownEvent => { + this.altListener = addDisposableListener(document, 'keydown', keydownEvent => { const standardKeyboardEvent = new StandardKeyboardEvent(keydownEvent); if (standardKeyboardEvent.keyCode === KeyCode.Alt) { this.altPressed = true; @@ -297,7 +298,8 @@ export class DebugEditorContribution implements IDebugEditorContribution { hoverController.showContentHover(this.hoverRange, HoverStartMode.Immediate, false); } - const listener = Event.any(this.hostService.onDidChangeFocus, domEvent(document, 'keyup'))(keyupEvent => { + const onKeyUp = new DomEmitter(document, 'keyup'); + const listener = Event.any(this.hostService.onDidChangeFocus, onKeyUp.event)(keyupEvent => { let standardKeyboardEvent = undefined; if (keyupEvent instanceof KeyboardEvent) { standardKeyboardEvent = new StandardKeyboardEvent(keyupEvent); @@ -306,6 +308,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { this.altPressed = false; this.editor.updateOptions({ hover: { enabled: false } }); listener.dispose(); + onKeyUp.dispose(); } }); } diff --git a/src/vs/workbench/contrib/debug/common/debugContentProvider.ts b/src/vs/workbench/contrib/debug/common/debugContentProvider.ts index d2a0d2298f8..4cacca17688 100644 --- a/src/vs/workbench/contrib/debug/common/debugContentProvider.ts +++ b/src/vs/workbench/contrib/debug/common/debugContentProvider.ts @@ -5,7 +5,7 @@ import { URI as uri } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; -import { guessMimeTypes, MIME_TEXT } from 'vs/base/common/mime'; +import { guessMimeTypes, Mimes } from 'vs/base/common/mime'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -94,7 +94,7 @@ export class DebugContentProvider implements IWorkbenchContribution, ITextModelC } const createErrModel = (errMsg?: string) => { this.debugService.sourceIsNotAvailable(resource); - const languageSelection = this.modeService.create(MIME_TEXT); + const languageSelection = this.modeService.create(Mimes.text); const message = errMsg ? localize('canNotResolveSourceWithError', "Could not load source '{0}': {1}.", resource.path, errMsg) : localize('canNotResolveSource', "Could not load source '{0}'.", resource.path); diff --git a/src/vs/workbench/contrib/debug/common/debugUtils.ts b/src/vs/workbench/contrib/debug/common/debugUtils.ts index 9d4fa4ce1ef..c7ee9b0c9fc 100644 --- a/src/vs/workbench/contrib/debug/common/debugUtils.ts +++ b/src/vs/workbench/contrib/debug/common/debugUtils.ts @@ -41,7 +41,7 @@ export function filterExceptionsFromTelemetry data.icon.src = element.marketplaceInfo?.iconUrlFallback || DefaultIconPath, null, data.elementDisposables); + data.elementDisposables.push(addDisposableListener(data.icon, 'error', () => data.icon.src = element.marketplaceInfo?.iconUrlFallback || DefaultIconPath, { once: true })); data.icon.src = element.marketplaceInfo?.iconUrl || DefaultIconPath; if (!data.icon.complete) { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index eb1a614bcc7..2649cb4d954 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -13,7 +13,6 @@ import { Cache, CacheResult } from 'vs/base/common/cache'; import { Action, IAction } from 'vs/base/common/actions'; import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors'; import { dispose, toDisposable, Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { domEvent } from 'vs/base/browser/event'; import { append, $, finalHandler, join, hide, show, addDisposableListener, EventType, setParentFlowTo } from 'vs/base/browser/dom'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -349,8 +348,7 @@ export class ExtensionEditor extends EditorPane { this.extensionManifest = new Cache(() => createCancelablePromise(token => extension.getManifest(token))); const remoteBadge = this.instantiationService.createInstance(RemoteBadgeWidget, template.iconContainer, true); - const onError = Event.once(domEvent(template.icon, 'error')); - onError(() => template.icon.src = extension.iconUrlFallback, null, this.transientDisposables); + this.transientDisposables.add(addDisposableListener(template.icon, 'error', () => template.icon.src = extension.iconUrlFallback, { once: true })); template.icon.src = extension.iconUrl; template.name.textContent = extension.displayName; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts index d9aadc6c9f5..4cf7ed865ec 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/extension'; -import { append, $ } from 'vs/base/browser/dom'; +import { append, $, addDisposableListener } from 'vs/base/browser/dom'; import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle'; import { IAction } from 'vs/base/common/actions'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -12,7 +12,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging'; import { Event } from 'vs/base/common/event'; -import { domEvent } from 'vs/base/browser/event'; import { IExtension, ExtensionContainers, ExtensionState, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; import { UpdateAction, ManageExtensionAction, ReloadAction, MaliciousStatusLabelAction, StatusLabelAction, RemoteInstallAction, SystemDisabledWarningAction, ExtensionToolTipAction, LocalInstallAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, WebInstallAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -190,8 +189,7 @@ export class Renderer implements IPagedRenderer { updateEnablement(); this.extensionService.onDidChangeExtensions(() => updateEnablement(), this, data.extensionDisposables); - const onError = Event.once(domEvent(data.icon, 'error')); - onError(() => data.icon.src = extension.iconUrlFallback, null, data.extensionDisposables); + data.extensionDisposables.push(addDisposableListener(data.icon, 'error', () => data.icon.src = extension.iconUrlFallback, { once: true })); data.icon.src = extension.iconUrl; if (!data.icon.complete) { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts index 4185fb859ec..08cae35d94e 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts @@ -9,7 +9,6 @@ import { IDisposable, dispose, Disposable, DisposableStore, toDisposable } from import { Action } from 'vs/base/common/actions'; import { IExtensionsWorkbenchService, IExtension } from 'vs/workbench/contrib/extensions/common/extensions'; import { Event } from 'vs/base/common/event'; -import { domEvent } from 'vs/base/browser/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IListService, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -167,8 +166,7 @@ export class ExtensionRenderer implements IListRenderer, index: number, data: IExtensionTemplateData): void { const extension = node.element.extension; - const onError = Event.once(domEvent(data.icon, 'error')); - onError(() => data.icon.src = extension.iconUrlFallback, null, data.extensionDisposables); + data.extensionDisposables.push(dom.addDisposableListener(data.icon, 'error', () => data.icon.src = extension.iconUrlFallback, { once: true })); data.icon.src = extension.iconUrl; if (!data.icon.complete) { diff --git a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts index 984defd93ce..3665a7c5fb2 100644 --- a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts @@ -20,7 +20,7 @@ import { Schemas } from 'vs/base/common/network'; import { basename, extname } from 'vs/base/common/resources'; import { match } from 'vs/base/common/glob'; import { URI } from 'vs/base/common/uri'; -import { MIME_UNKNOWN, guessMimeTypes } from 'vs/base/common/mime'; +import { Mimes, guessMimeTypes } from 'vs/base/common/mime'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -230,7 +230,7 @@ export class FileBasedRecommendations extends ExtensionRecommendations { } const mimeTypes = guessMimeTypes(uri); - if (mimeTypes.length !== 1 || mimeTypes[0] !== MIME_UNKNOWN) { + if (mimeTypes.length !== 1 || mimeTypes[0] !== Mimes.unknown) { return; } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 91d8c0d7932..27b458daf84 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -487,12 +487,13 @@ export class ExplorerView extends ViewPane { } private setContextKeys(stat: ExplorerItem | null | undefined): void { - const isSingleFolder = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER; - const resource = stat ? stat.resource : isSingleFolder ? this.contextService.getWorkspace().folders[0].uri : null; + const folders = this.contextService.getWorkspace().folders; + const resource = stat ? stat.resource : folders[folders.length - 1].uri; + stat = stat || this.explorerService.findClosest(resource); this.resourceContext.set(resource); - this.folderContext.set((isSingleFolder && !stat) || !!stat && stat.isDirectory); + this.folderContext.set(!!stat && stat.isDirectory); this.readonlyContext.set(!!stat && stat.isReadonly); - this.rootContext.set(!stat || (stat && stat.isRoot)); + this.rootContext.set(!!stat && stat.isRoot); if (resource) { const overrides = resource ? this.editorOverrideService.getEditorIds(resource) : []; diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index b7c0896ab08..342387826ba 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -49,7 +49,6 @@ import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; import { ILabelService } from 'vs/platform/label/common/label'; import { isNumber } from 'vs/base/common/types'; -import { domEvent } from 'vs/base/browser/event'; import { IEditableData } from 'vs/workbench/common/views'; import { IEditorInput } from 'vs/workbench/common/editor'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; @@ -328,13 +327,13 @@ export class FilesRenderer implements ICompressibleTreeRenderer { + disposables.add(DOM.addDisposableListener(templateData.container, 'mousedown', e => { const result = getIconLabelNameFromHTMLElement(e.target); if (result) { compressedNavigationController.setIndex(result.index); } - }, undefined, disposables); + })); disposables.add(toDisposable(() => this.compressedNavigationControllers.delete(stat))); diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 9b97b4d6bb1..4a6565b15f2 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -48,13 +48,13 @@ import { textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { OS, OperatingSystem } from 'vs/base/common/platform'; import { IFileService } from 'vs/platform/files/common/files'; -import { domEvent } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Progress } from 'vs/platform/progress/common/progress'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; +import { DomEmitter } from 'vs/base/browser/event'; interface IResourceMarkersTemplateData { resourceLabel: IResourceLabel; @@ -417,10 +417,10 @@ class MarkerWidget extends Disposable { this._codeLink.setAttribute('href', codeLink); this._codeLink.tabIndex = 0; - const onClick = Event.chain(domEvent(this._codeLink, 'click')) + const onClick = Event.chain(this._register(new DomEmitter(this._codeLink, 'click')).event) .filter(e => ((this._clickModifierKey === 'meta' && e.metaKey) || (this._clickModifierKey === 'ctrl' && e.ctrlKey) || (this._clickModifierKey === 'alt' && e.altKey))) .event; - const onEnterPress = Event.chain(domEvent(this._codeLink, 'keydown')) + const onEnterPress = Event.chain(this._register(new DomEmitter(this._codeLink, 'keydown')).event) .map(e => new StandardKeyboardEvent(e)) .filter(e => e.keyCode === KeyCode.Enter) .event; diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index ddab4379a4a..d99d3813322 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -36,7 +36,6 @@ import { ActionBar, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionb import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { domEvent } from 'vs/base/browser/event'; import { ResourceLabels } from 'vs/workbench/browser/labels'; import { IMarker, IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { withUndefinedAsNull } from 'vs/base/common/types'; @@ -443,7 +442,7 @@ export class MarkersView extends ViewPane implements IMarkersView { })); // move focus to input, whenever a key is pressed in the panel container - this._register(domEvent(parent, 'keydown')(e => { + this._register(dom.addDisposableListener(parent, 'keydown', e => { if (this.keybindingService.mightProducePrintableCharacter(new StandardKeyboardEvent(e))) { this.focusFilter(); } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 6e00f849c18..c68c0f782b3 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -263,6 +263,7 @@ export interface ICellViewModel extends IGenericCellViewModel { handle: number; uri: URI; language: string; + readonly mime: string; cellKind: CellKind; lineNumbers: 'on' | 'off' | 'inherit'; focusMode: CellFocusMode; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 8eab5d90049..0cabf8ea35d 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -72,6 +72,7 @@ import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOp import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext'; import { NotebookEditorToolbar } from 'vs/workbench/contrib/notebook/browser/notebookEditorToolbar'; import { INotebookRendererMessagingService } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService'; +import { IMarkupCellInitialization } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages'; const $ = DOM.$; @@ -1385,18 +1386,13 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } } - await this._webview!.initializeMarkup(requests.map(request => ({ - mime: 'text/markdown', - cellId: request[0].id, - cellHandle: request[0].handle, - content: request[0].getText(), - offset: request[1], - visible: false, - }))); + await this._webview!.initializeMarkup(requests.map(([model, offset]) => this.createMarkupCellInitialization(model, offset))); } else { - const initRequests = viewModel.viewCells.filter(cell => cell.cellKind === CellKind.Markup).slice(0, 5).map(cell => ({ - cellId: cell.id, cellHandle: cell.handle, content: cell.getText(), offset: -10000, visible: false, mime: 'text/markdown', - })); + const initRequests = viewModel.viewCells + .filter(cell => cell.cellKind === CellKind.Markup) + .slice(0, 5) + .map(cell => this.createMarkupCellInitialization(cell, -10000)); + await this._webview!.initializeMarkup(initRequests); // no cached view state so we are rendering the first viewport @@ -1420,6 +1416,17 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } } + private createMarkupCellInitialization(model: ICellViewModel, offset: number): IMarkupCellInitialization { + return ({ + mime: model.mime, + cellId: model.id, + cellHandle: model.handle, + content: model.getText(), + offset: offset, + visible: false, + }); + } + restoreListViewState(viewState: INotebookEditorViewState | undefined): void { if (viewState?.scrollPosition !== undefined) { this._list.scrollTop = viewState!.scrollPosition.top; @@ -2264,7 +2271,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor const cellTop = this._list.getAbsoluteTopOfElement(cell); await this._webview.showMarkdownPreview({ - mime: 'text/markdown', + mime: cell.mime, cellHandle: cell.handle, cellId: cell.id, content: cell.getText(), diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts index d40252bbb03..bdb2488b557 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts @@ -5,6 +5,7 @@ import * as DOM from 'vs/base/browser/dom'; import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { Mimes } from 'vs/base/common/mime'; import { dirname } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; @@ -215,7 +216,7 @@ class PlainTextRendererContrib extends Disposable implements IOutputRendererCont } getMimetypes() { - return ['text/plain']; + return [Mimes.text]; } constructor( @@ -270,7 +271,7 @@ class MdRendererContrib extends Disposable implements IOutputRendererContributio } getMimetypes() { - return ['text/markdown']; + return [Mimes.markdown]; } constructor( diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 4046b299395..80f7e152d21 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -525,17 +525,12 @@ var requirejs = (function() { } case 'focus-editor': { - const resolvedResult = this.resolveOutputId(data.id); - if (resolvedResult) { - const latestCell = this.notebookEditor.getCellByInfo(resolvedResult.cellInfo); - if (!latestCell) { - return; - } - + const cell = this.notebookEditor.getCellById(data.cellId); + if (cell) { if (data.focusNext) { - this.notebookEditor.focusNextNotebookCell(latestCell, 'editor'); + this.notebookEditor.focusNextNotebookCell(cell, 'editor'); } else { - this.notebookEditor.focusNotebookCell(latestCell, 'editor'); + this.notebookEditor.focusNotebookCell(cell, 'editor'); } } break; @@ -812,6 +807,7 @@ var requirejs = (function() { this.hiddenInsetMapping.delete(request.output); return { + cellId: request.cell.id, outputId: id, cellTop: request.cellTop, outputOffset: request.outputOffset, @@ -826,7 +822,7 @@ var requirejs = (function() { this._sendMessageToWebview({ type: 'view-scroll', widgets: widgets, - markdownPreviews, + markupCells: markdownPreviews, }); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellDnd.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellDnd.ts index 377b19bfdce..962f7faf2a5 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellDnd.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellDnd.ts @@ -3,9 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as DOM from 'vs/base/browser/dom'; -import { domEvent } from 'vs/base/browser/event'; import { Delayer } from 'vs/base/common/async'; import { Disposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; @@ -49,8 +47,8 @@ export class CellDragAndDropController extends Disposable { this.listInsertionIndicator = DOM.append(insertionIndicatorContainer, $('.cell-list-insertion-indicator')); - this._register(domEvent(document.body, DOM.EventType.DRAG_START, true)(this.onGlobalDragStart.bind(this))); - this._register(domEvent(document.body, DOM.EventType.DRAG_END, true)(this.onGlobalDragEnd.bind(this))); + this._register(DOM.addDisposableListener(document.body, DOM.EventType.DRAG_START, this.onGlobalDragStart.bind(this), true)); + this._register(DOM.addDisposableListener(document.body, DOM.EventType.DRAG_END, this.onGlobalDragEnd.bind(this), true)); const addCellDragListener = (eventType: string, handler: (e: CellDragEvent) => void) => { this._register(DOM.addDisposableListener( @@ -300,7 +298,7 @@ export class CellDragAndDropController extends Disposable { const container = templateData.container; dragHandle.setAttribute('draggable', 'true'); - templateData.disposables.add(domEvent(dragHandle, DOM.EventType.DRAG_END)(() => { + templateData.disposables.add(DOM.addDisposableListener(dragHandle, DOM.EventType.DRAG_END, () => { if (!this.notebookEditor.notebookOptions.getLayoutConfiguration().dragAndDropEnabled) { return; } @@ -310,7 +308,7 @@ export class CellDragAndDropController extends Disposable { this.dragCleanup(); })); - templateData.disposables.add(domEvent(dragHandle, DOM.EventType.DRAG_START)(event => { + templateData.disposables.add(DOM.addDisposableListener(dragHandle, DOM.EventType.DRAG_START, event => { if (!event.dataTransfer) { return; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 84f8542a453..78f77c53573 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -5,7 +5,6 @@ import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser'; import * as DOM from 'vs/base/browser/dom'; -import { domEvent } from 'vs/base/browser/event'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; @@ -261,7 +260,7 @@ abstract class AbstractCellRenderer { } protected addExpandListener(templateData: BaseCellRenderTemplate): void { - templateData.disposables.add(domEvent(templateData.expandButton, DOM.EventType.CLICK)(() => { + templateData.disposables.add(DOM.addDisposableListener(templateData.expandButton, DOM.EventType.CLICK, () => { if (!templateData.currentRenderedCell) { return; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts index 3229217f037..71fff9a97dd 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts @@ -59,7 +59,7 @@ export interface IScrollAckMessage extends BaseToWebviewMessage { export interface IBlurOutputMessage extends BaseToWebviewMessage { readonly type: 'focus-editor'; - readonly id: string; + readonly cellId: string; readonly focusNext?: boolean; } @@ -174,6 +174,7 @@ export interface ICreationRequestMessage { } export interface IContentWidgetTopRequest { + readonly cellId: string; readonly outputId: string; readonly cellTop: number; readonly outputOffset: number; @@ -183,7 +184,7 @@ export interface IContentWidgetTopRequest { export interface IViewScrollTopRequestMessage { readonly type: 'view-scroll'; readonly widgets: IContentWidgetTopRequest[]; - readonly markdownPreviews: { id: string; top: number; }[]; + readonly markupCells: { id: string; top: number; }[]; } export interface IScrollRequestMessage { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 3f2620b98af..cf920965b48 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -320,12 +320,12 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re } } - function createFocusSink(cellId: string, outputId: string, focusNext?: boolean) { + function createFocusSink(cellId: string, focusNext?: boolean) { const element = document.createElement('div'); element.tabIndex = 0; element.addEventListener('focus', () => { postNotebookMessage('focus-editor', { - id: outputId, + cellId: cellId, focusNext }); }); @@ -357,7 +357,7 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re return false; } - class FocusTracker { + class OutputFocusTracker { private _outputId: string; private _hasFocus: boolean = false; private _loosingFocus: boolean = false; @@ -405,14 +405,14 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re } } - const focusTrackers = new Map(); + const outputFocusTrackers = new Map(); - function addFocusTracker(element: HTMLElement, outputId: string): void { - if (focusTrackers.has(outputId)) { - focusTrackers.get(outputId)?.dispose(); + function addOutputFocusTracker(element: HTMLElement, outputId: string): void { + if (outputFocusTrackers.has(outputId)) { + outputFocusTrackers.get(outputId)?.dispose(); } - focusTrackers.set(outputId, new FocusTracker(element, outputId)); + outputFocusTrackers.set(outputId, new OutputFocusTracker(element, outputId)); } function createEmitter(listenerChange: (listeners: Set>) => void = () => undefined): EmitterLike { @@ -510,44 +510,46 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re switch (event.data.type) { case 'initializeMarkup': - await notebookDocument.ensureMarkupCells(event.data.cells); + await viewModel.ensureMarkupCells(event.data.cells); dimensionUpdater.updateImmediately(); postNotebookMessage('initializedMarkup', {}); break; case 'createMarkupCell': - notebookDocument.ensureMarkupCells([event.data.cell]); + viewModel.ensureMarkupCells([event.data.cell]); break; case 'showMarkupCell': - notebookDocument.showMarkupCell(event.data.id, event.data.top, event.data.content); + viewModel.showMarkupCell(event.data.id, event.data.top, event.data.content); break; case 'hideMarkupCells': for (const id of event.data.ids) { - notebookDocument.hideMarkupCell(id); + viewModel.hideMarkupCell(id); } break; case 'unhideMarkupCells': for (const id of event.data.ids) { - notebookDocument.unhideMarkupCell(id); + viewModel.unhideMarkupCell(id); } break; case 'deleteMarkupCell': for (const id of event.data.ids) { - notebookDocument.deleteMarkupCell(id); + viewModel.deleteMarkupCell(id); } break; case 'updateSelectedMarkupCells': - notebookDocument.updateSelectedCells(event.data.selectedCellIds); + viewModel.updateSelectedCells(event.data.selectedCellIds); break; case 'html': { const data = event.data; - outputs.enqueue(event.data.outputId, async (state) => { + const outputId = data.outputId; + + outputRunner.enqueue(event.data.outputId, async (state) => { const preloadsAndErrors = await Promise.all([ data.rendererId ? renderers.load(data.rendererId) : undefined, ...data.requiredPreloads.map(p => kernelPreloads.waitFor(p.uri)), @@ -557,48 +559,9 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re return; } - let cellOutputContainer = document.getElementById(data.cellId); - const outputId = data.outputId; - if (!cellOutputContainer) { - const container = document.getElementById('container')!; + const cellOutput = viewModel.ensureOutputCell(data.cellId, data.cellTop); + const outputNode = cellOutput.createOutputNode(outputId, data.outputOffset, data.left); - const upperWrapperElement = createFocusSink(data.cellId, outputId); - container.appendChild(upperWrapperElement); - - const newElement = document.createElement('div'); - - newElement.id = data.cellId; - newElement.classList.add('cell_container'); - - container.appendChild(newElement); - cellOutputContainer = newElement; - - const lowerWrapperElement = createFocusSink(data.cellId, outputId, true); - container.appendChild(lowerWrapperElement); - } - - cellOutputContainer.style.position = 'absolute'; - cellOutputContainer.style.top = data.cellTop + 'px'; - - const outputContainer = document.createElement('div'); - outputContainer.classList.add('output_container'); - outputContainer.style.position = 'absolute'; - outputContainer.style.overflow = 'hidden'; - outputContainer.style.maxHeight = '0px'; - outputContainer.style.top = `${data.outputOffset}px`; - - const outputNode = document.createElement('div'); - outputNode.classList.add('output'); - outputNode.style.position = 'absolute'; - outputNode.style.top = `0px`; - outputNode.style.left = data.left + 'px'; - // outputNode.style.width = 'calc(100% - ' + data.left + 'px)'; - // outputNode.style.minHeight = '32px'; - outputNode.style.padding = '0px'; - outputNode.id = outputId; - - addMouseoverListeners(outputNode, outputId); - addFocusTracker(outputNode, outputId); const content = data.content; if (content.type === RenderOutputType.Html) { const trustedHtml = ttPolicy?.createHTML(content.htmlContent) ?? content.htmlContent; @@ -634,8 +597,6 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re } } - cellOutputContainer.appendChild(outputContainer); - outputContainer.appendChild(outputNode); resizeObserver.observe(outputNode, outputId, true); if (content.type === RenderOutputType.Html) { @@ -661,7 +622,7 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re } // don't hide until after this step so that the height is right - cellOutputContainer.style.visibility = data.initiallyHidden ? 'hidden' : 'visible'; + cellOutput.element.style.visibility = data.initiallyHidden ? 'hidden' : 'visible'; }); break; } @@ -670,84 +631,46 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re // const date = new Date(); // console.log('----- will scroll ---- ', date.getMinutes() + ':' + date.getSeconds() + ':' + date.getMilliseconds()); - for (const request of event.data.widgets) { - const widget = document.getElementById(request.outputId); - if (widget) { - widget.parentElement!.parentElement!.style.top = `${request.cellTop}px`; - widget.parentElement!.style.top = `${request.outputOffset}px`; - if (request.forceDisplay) { - widget.parentElement!.parentElement!.style.visibility = 'visible'; - } - } - } - - for (const cell of event.data.markdownPreviews) { - const container = document.getElementById(cell.id); - if (container) { - container.style.top = `${cell.top}px`; - } - } - + viewModel.updateOutputsScroll(event.data.widgets); + viewModel.updateMarkupScrolls(event.data.markupCells); break; } case 'clear': renderers.clearAll(); + viewModel.clearAll(); document.getElementById('container')!.innerText = ''; - focusTrackers.forEach(ft => { + outputFocusTrackers.forEach(ft => { ft.dispose(); }); - focusTrackers.clear(); + outputFocusTrackers.clear(); break; + case 'clearOutput': { - const output = document.getElementById(event.data.outputId); - const { rendererId, outputId } = event.data; - - outputs.cancelOutput(outputId); - if (output && output.parentNode) { - if (rendererId) { - renderers.clearOutput(rendererId, outputId); - } - output.parentNode.removeChild(output); - } - + const { cellId, rendererId, outputId } = event.data; + outputRunner.cancelOutput(outputId); + viewModel.clearOutput(cellId, outputId, rendererId); break; } case 'hideOutput': { - const { outputId } = event.data; - outputs.enqueue(event.data.outputId, () => { - const container = document.getElementById(outputId)?.parentElement?.parentElement; - if (container) { - container.style.visibility = 'hidden'; - } + const { cellId, outputId } = event.data; + outputRunner.enqueue(outputId, () => { + viewModel.hideOutput(cellId); }); break; } case 'showOutput': { - const { outputId, cellTop: top } = event.data; - outputs.enqueue(event.data.outputId, () => { - const output = document.getElementById(outputId); - if (output) { - output.parentElement!.parentElement!.style.visibility = 'visible'; - output.parentElement!.parentElement!.style.top = top + 'px'; - - dimensionUpdater.update(outputId, output.clientHeight, { - isOutput: true, - }); - } + const { outputId, cellTop, cellId } = event.data; + outputRunner.enqueue(outputId, () => { + viewModel.showOutput(cellId, outputId, cellTop); }); break; } - case 'ack-dimension': - { - const { outputId, height } = event.data; - const output = document.getElementById(outputId); - if (output) { - output.parentElement!.style.maxHeight = `${height}px`; - output.parentElement!.style.height = `${height}px`; - } - break; - } + case 'ack-dimension': { + const { cellId, outputId, height } = event.data; + viewModel.updateOutputHeight(cellId, outputId, height); + break; + } case 'preload': const resources = event.data.resources; for (const { uri, originalUri } of resources) { @@ -790,12 +713,7 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re break; case 'notebookOptions': currentOptions = event.data.options; - - // Update markdown previews - for (const markdownContainer of document.querySelectorAll('.preview')) { - setMarkupContainerDraggable(markdownContainer, currentOptions.dragAndDropEnabled); - } - + viewModel.toggleDragDropEnabled(currentOptions.dragAndDropEnabled); break; } }); @@ -905,8 +823,9 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re } }; - const outputs = new class { - private outputs = new Map }>(); + const outputRunner = new class { + private readonly outputs = new Map }>(); + /** * Pushes the action onto the list of actions for the given output ID, * ensuring that it's run in-order. @@ -973,14 +892,14 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re public clearAll() { - outputs.cancelAll(); + outputRunner.cancelAll(); for (const renderer of this._renderers.values()) { renderer.api?.disposeOutputItem?.(); } } public clearOutput(rendererId: string, outputId: string) { - outputs.cancelOutput(outputId); + outputRunner.cancelOutput(outputId); this._renderers.get(rendererId)?.api?.disposeOutputItem?.(outputId); } @@ -1001,9 +920,10 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re let hasPostedRenderedMathTelemetry = false; const unsupportedKatexTermsRegex = /(\\(?:abovewithdelims|array|Arrowvert|arrowvert|atopwithdelims|bbox|bracevert|buildrel|cancelto|cases|class|cssId|ddddot|dddot|DeclareMathOperator|definecolor|displaylines|enclose|eqalign|eqalignno|eqref|hfil|hfill|idotsint|iiiint|label|leftarrowtail|leftroot|leqalignno|lower|mathtip|matrix|mbox|mit|mmlToken|moveleft|moveright|mspace|newenvironment|Newextarrow|notag|oldstyle|overparen|overwithdelims|pmatrix|raise|ref|renewenvironment|require|root|Rule|scr|shoveleft|shoveright|sideset|skew|Space|strut|style|texttip|Tiny|toggle|underparen|unicode|uproot)\b)/gi; - const notebookDocument = new class { + const viewModel = new class ViewModel { private readonly _markupCells = new Map(); + private readonly _outputCells = new Map(); private async createMarkupCell(init: webviewMessages.IMarkupCellInitialization, top: number): Promise { const existing = this._markupCells.get(init.cellId); @@ -1012,11 +932,11 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re return existing; } - const markdownCell = new MarkupCell(init.cellId, init.mime, init.content, top); - this._markupCells.set(init.cellId, markdownCell); + const cell = new MarkupCell(init.cellId, init.mime, init.content, top); + this._markupCells.set(init.cellId, cell); - await markdownCell.ready; - return markdownCell; + await cell.ready; + return cell; } public async ensureMarkupCells(update: readonly webviewMessages.IMarkupCellInitialization[]): Promise { @@ -1074,13 +994,71 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re cell.setSelected(selectedCellSet.has(cell.id)); } } + + public toggleDragDropEnabled(dragAndDropEnabled: boolean) { + for (const cell of this._markupCells.values()) { + cell.toggleDragDropEnabled(dragAndDropEnabled); + } + } + + public updateMarkupScrolls(markupCells: { id: string; top: number; }[]) { + for (const { id, top } of markupCells) { + const cell = this._markupCells.get(id); + if (cell) { + cell.element.style.top = `${top}px`; + } + } + } + + public clearAll() { + this._markupCells.clear(); + this._outputCells.clear(); + } + + public ensureOutputCell(cellId: string, cellTop: number): OutputCell { + let cell = this._outputCells.get(cellId); + if (!cell) { + cell = new OutputCell(cellId); + this._outputCells.set(cellId, cell); + } + + cell.element.style.top = cellTop + 'px'; + return cell; + } + + public clearOutput(cellId: string, outputId: string, rendererId: string | undefined) { + const cell = this._outputCells.get(cellId); + cell?.clearOutput(outputId, rendererId); + } + + public showOutput(cellId: string, outputId: string, top: number) { + const cell = this._outputCells.get(cellId); + cell?.show(outputId, top); + } + + public hideOutput(cellId: string) { + const cell = this._outputCells.get(cellId); + cell?.hide(); + } + + public updateOutputHeight(cellId: string, outputId: string, height: number) { + const cell = this._outputCells.get(cellId); + cell?.updateOutputHeight(outputId, height); + } + + public updateOutputsScroll(updates: webviewMessages.IContentWidgetTopRequest[]) { + for (const request of updates) { + const cell = this._outputCells.get(request.cellId); + cell?.updateScroll(request); + } + } }(); class MarkupCell implements IOutputItem { public readonly ready: Promise; - /// Internal field that holds markdown text + /// Internal field that holds text content private _content: string; constructor(id: string, mime: string, content: string, top: number) { @@ -1098,6 +1076,7 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re this.element.classList.add('preview'); this.element.style.position = 'absolute'; this.element.style.top = top + 'px'; + this.toggleDragDropEnabled(currentOptions.dragAndDropEnabled); root.appendChild(this.element); this.addEventListeners(); @@ -1155,18 +1134,16 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re postNotebookMessage('mouseLeaveMarkupCell', { cellId: this.id }); }); - setMarkupContainerDraggable(this.element, currentOptions.dragAndDropEnabled); - this.element.addEventListener('dragstart', e => { - markdownPreviewDragManager.startDrag(e, this.id); + markupCellDragManager.startDrag(e, this.id); }); this.element.addEventListener('drag', e => { - markdownPreviewDragManager.updateDrag(e, this.id); + markupCellDragManager.updateDrag(e, this.id); }); this.element.addEventListener('dragend', e => { - markdownPreviewDragManager.endDrag(e, this.id); + markupCellDragManager.endDrag(e, this.id); }); } @@ -1223,6 +1200,125 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re public setSelected(selected: boolean) { this.element.classList.toggle('selected', selected); } + + public toggleDragDropEnabled(enabled: boolean) { + if (enabled) { + this.element.classList.add('draggable'); + this.element.setAttribute('draggable', 'true'); + } else { + this.element.classList.remove('draggable'); + this.element.removeAttribute('draggable'); + } + } + } + + class OutputCell { + + public readonly element: HTMLElement; + + public readonly outputElements = new Map(); + + constructor(cellId: string) { + const container = document.getElementById('container')!; + + const upperWrapperElement = createFocusSink(cellId); + container.appendChild(upperWrapperElement); + + this.element = document.createElement('div'); + this.element.style.position = 'absolute'; + + this.element.id = cellId; + this.element.classList.add('cell_container'); + + container.appendChild(this.element); + this.element = this.element; + + const lowerWrapperElement = createFocusSink(cellId, true); + container.appendChild(lowerWrapperElement); + } + + public createOutputNode(outputId: string, outputOffset: number, left: number): HTMLElement { + let outputContainer = this.outputElements.get(outputId); + if (!outputContainer) { + outputContainer = document.createElement('div'); + outputContainer.classList.add('output_container'); + outputContainer.style.position = 'absolute'; + outputContainer.style.overflow = 'hidden'; + this.element.appendChild(outputContainer); + this.outputElements.set(outputId, outputContainer); + } + outputContainer.innerText = ''; + outputContainer.style.maxHeight = '0px'; + outputContainer.style.top = `${outputOffset}px`; + + const outputNode = document.createElement('div'); + outputNode.id = outputId; + outputNode.classList.add('output'); + outputNode.style.position = 'absolute'; + outputNode.style.top = `0px`; + outputNode.style.left = left + 'px'; + outputNode.style.padding = '0px'; + outputContainer.appendChild(outputNode); + + addMouseoverListeners(outputNode, outputId); + addOutputFocusTracker(outputNode, outputId); + + return outputNode; + } + + public clearOutput(outputId: string, rendererId: string | undefined) { + const outputContainer = this.outputElements.get(outputId); + if (!outputContainer) { + return; + } + + if (rendererId) { + renderers.clearOutput(rendererId, outputId); + } + outputContainer.remove(); + this.outputElements.delete(outputId); + } + + public show(outputId: string, top: number) { + const outputContainer = this.outputElements.get(outputId); + if (!outputContainer) { + return; + } + + this.element.style.visibility = 'visible'; + this.element.style.top = `${top}px`; + + dimensionUpdater.update(outputId, outputContainer.clientHeight, { + isOutput: true, + }); + } + + public hide() { + this.element.style.visibility = 'hidden'; + } + + public updateOutputHeight(outputId: string, height: number) { + const outputContainer = this.outputElements.get(outputId); + if (!outputContainer) { + return; + } + + outputContainer.style.maxHeight = `${height}px`; + outputContainer.style.height = `${height}px`; + } + + public updateScroll(request: webviewMessages.IContentWidgetTopRequest) { + this.element.style.top = `${request.cellTop}px`; + + const outputContainer = this.outputElements.get(request.outputId); + if (outputContainer) { + outputContainer.style.top = `${request.outputOffset}px`; + } + + if (request.forceDisplay) { + this.element.style.visibility = 'visible'; + } + } } vscode.postMessage({ @@ -1230,16 +1326,6 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re type: 'initialized' }); - function setMarkupContainerDraggable(element: Element, isDraggable: boolean) { - if (isDraggable) { - element.classList.add('draggable'); - element.setAttribute('draggable', 'true'); - } else { - element.classList.remove('draggable'); - element.removeAttribute('draggable'); - } - } - function postNotebookMessage( type: T['type'], properties: Omit @@ -1251,13 +1337,13 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re }); } - const markdownPreviewDragManager = new class MarkdownPreviewDragManager { + const markupCellDragManager = new class MarkupCellDragManager { private currentDrag: { cellId: string, clientY: number } | undefined; constructor() { document.addEventListener('dragover', e => { - // Allow dropping dragged markdown cells + // Allow dropping dragged markup cells e.preventDefault(); }); diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts index bfb8c5789fc..c6de02de5e0 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts @@ -18,6 +18,7 @@ import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/mode import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext'; +import { Mimes } from 'vs/base/common/mime'; export abstract class BaseCellViewModel extends Disposable { @@ -46,6 +47,16 @@ export abstract class BaseCellViewModel extends Disposable { return this.model.language; } + get mime(): string { + switch (this.language) { + case 'markdown': + return Mimes.markdown; + + default: + return Mimes.text; + } + } + abstract cellKind: CellKind; private _editState: CellEditState = CellEditState.Preview; diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index d6ee63a642a..4ecf1ed1bc0 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -7,6 +7,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IDiffResult, ISequence } from 'vs/base/common/diff/diff'; import { Event } from 'vs/base/common/event'; import * as glob from 'vs/base/common/glob'; +import { Mimes } from 'vs/base/common/mime'; import { Schemas } from 'vs/base/common/network'; import { basename } from 'vs/base/common/path'; import { isWindows } from 'vs/base/common/platform'; @@ -34,16 +35,16 @@ export const NOTEBOOK_DISPLAY_ORDER = [ 'application/javascript', 'text/html', 'image/svg+xml', - 'text/markdown', + Mimes.markdown, 'image/png', 'image/jpeg', - 'text/plain' + Mimes.text ]; export const ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER = [ - 'text/markdown', + Mimes.markdown, 'application/json', - 'text/plain', + Mimes.text, 'text/html', 'image/svg+xml', 'image/png', @@ -586,8 +587,8 @@ const _mimeTypeInfo = new Map([ ['image/git', { alwaysSecure: true, supportedByCore: true }], ['image/svg+xml', { supportedByCore: true }], ['application/json', { alwaysSecure: true, supportedByCore: true }], - ['text/markdown', { alwaysSecure: true, supportedByCore: true }], - ['text/plain', { alwaysSecure: true, supportedByCore: true }], + [Mimes.markdown, { alwaysSecure: true, supportedByCore: true }], + [Mimes.text, { alwaysSecure: true, supportedByCore: true }], ['text/html', { supportedByCore: true }], ['text/x-javascript', { alwaysSecure: true, supportedByCore: true }], // secure because rendered as text, not executed ['application/vnd.code.notebook.error', { alwaysSecure: true, supportedByCore: true }], diff --git a/src/vs/workbench/contrib/notebook/test/notebookCommon.test.ts b/src/vs/workbench/contrib/notebook/test/notebookCommon.test.ts index 88da6bd5e79..82dbeb15c7c 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookCommon.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookCommon.test.ts @@ -4,11 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { NOTEBOOK_DISPLAY_ORDER, sortMimeTypes, CellKind, diff, CellUri, NotebookWorkingCopyTypeIdentifier } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { cellRangesToIndexes, cellIndexesToRanges } from 'vs/workbench/contrib/notebook/common/notebookRange'; -import { TestCell, setupInstantiationService } from 'vs/workbench/contrib/notebook/test/testNotebookEditor'; +import { Mimes } from 'vs/base/common/mime'; import { URI } from 'vs/base/common/uri'; import { IModeService } from 'vs/editor/common/services/modeService'; +import { CellKind, CellUri, diff, NotebookWorkingCopyTypeIdentifier, NOTEBOOK_DISPLAY_ORDER, sortMimeTypes } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { cellIndexesToRanges, cellRangesToIndexes } from 'vs/workbench/contrib/notebook/common/notebookRange'; +import { setupInstantiationService, TestCell } from 'vs/workbench/contrib/notebook/test/testNotebookEditor'; suite('NotebookCommon', () => { const instantiationService = setupInstantiationService(); @@ -23,30 +24,30 @@ suite('NotebookCommon', () => { 'application/javascript', 'text/html', 'image/svg+xml', - 'text/markdown', + Mimes.markdown, 'image/png', 'image/jpeg', - 'text/plain' + Mimes.text ], [], defaultDisplayOrder), [ 'application/json', 'application/javascript', 'text/html', 'image/svg+xml', - 'text/markdown', + Mimes.markdown, 'image/png', 'image/jpeg', - 'text/plain' + Mimes.text ] ); assert.deepStrictEqual(sortMimeTypes( [ 'application/json', - 'text/markdown', + Mimes.markdown, 'application/javascript', 'text/html', - 'text/plain', + Mimes.text, 'image/png', 'image/jpeg', 'image/svg+xml' @@ -56,18 +57,18 @@ suite('NotebookCommon', () => { 'application/javascript', 'text/html', 'image/svg+xml', - 'text/markdown', + Mimes.markdown, 'image/png', 'image/jpeg', - 'text/plain' + Mimes.text ] ); assert.deepStrictEqual(sortMimeTypes( [ - 'text/markdown', + Mimes.markdown, 'application/json', - 'text/plain', + Mimes.text, 'image/jpeg', 'application/javascript', 'text/html', @@ -79,10 +80,10 @@ suite('NotebookCommon', () => { 'application/javascript', 'text/html', 'image/svg+xml', - 'text/markdown', + Mimes.markdown, 'image/png', 'image/jpeg', - 'text/plain' + Mimes.text ] ); }); @@ -97,22 +98,22 @@ suite('NotebookCommon', () => { 'application/javascript', 'text/html', 'image/svg+xml', - 'text/markdown', + Mimes.markdown, 'image/png', 'image/jpeg', - 'text/plain' + Mimes.text ], [ 'image/png', - 'text/plain', - 'text/markdown', + Mimes.text, + Mimes.markdown, 'text/html', 'application/json' ], defaultDisplayOrder), [ 'image/png', - 'text/plain', - 'text/markdown', + Mimes.text, + Mimes.markdown, 'text/html', 'application/json', 'application/javascript', @@ -123,9 +124,9 @@ suite('NotebookCommon', () => { assert.deepStrictEqual(sortMimeTypes( [ - 'text/markdown', + Mimes.markdown, 'application/json', - 'text/plain', + Mimes.text, 'application/javascript', 'text/html', 'image/svg+xml', @@ -136,18 +137,18 @@ suite('NotebookCommon', () => { 'application/json', 'text/html', 'text/html', - 'text/markdown', + Mimes.markdown, 'application/json' ], defaultDisplayOrder), [ 'application/json', 'text/html', - 'text/markdown', + Mimes.markdown, 'application/javascript', 'image/svg+xml', 'image/png', 'image/jpeg', - 'text/plain' + Mimes.text ] ); }); @@ -165,7 +166,7 @@ suite('NotebookCommon', () => { 'text/html' ], [ - 'text/markdown', + Mimes.markdown, 'text/html', 'application/json' ], defaultDisplayOrder), @@ -189,7 +190,7 @@ suite('NotebookCommon', () => { ], [ 'application/vnd-vega*', - 'text/markdown', + Mimes.markdown, 'text/html', 'application/json' ], defaultDisplayOrder), diff --git a/src/vs/workbench/contrib/notebook/test/notebookDiff.test.ts b/src/vs/workbench/contrib/notebook/test/notebookDiff.test.ts index 70ab2ffaecf..bf204572dd3 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookDiff.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookDiff.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import { LcsDiff } from 'vs/base/common/diff/diff'; +import { Mimes } from 'vs/base/common/mime'; import { NotebookDiffEditorEventDispatcher } from 'vs/workbench/contrib/notebook/browser/diff/eventDispatcher'; import { NotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor'; import { CellKind, CellSequence } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -14,9 +15,9 @@ suite('NotebookCommon', () => { test('diff different source', async () => { await withTestNotebookDiffModel([ - ['x', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: 'text/plain', data: new Uint8Array([3]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 3 }], + ['x', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: new Uint8Array([3]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 3 }], ], [ - ['y', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: 'text/plain', data: new Uint8Array([3]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 3 }], + ['y', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: new Uint8Array([3]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 3 }], ], (model, accessor) => { const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); const diffResult = diff.ComputeDiff(false); @@ -44,10 +45,10 @@ suite('NotebookCommon', () => { test('diff different output', async () => { await withTestNotebookDiffModel([ - ['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: 'text/plain', data: new Uint8Array([5]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 5 }], + ['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: new Uint8Array([5]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 5 }], ['', 'javascript', CellKind.Code, [], {}] ], [ - ['x', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: 'text/plain', data: new Uint8Array([3]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 3 }], + ['x', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: new Uint8Array([3]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 3 }], ['', 'javascript', CellKind.Code, [], {}] ], (model, accessor) => { const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); @@ -145,12 +146,12 @@ suite('NotebookCommon', () => { test('diff foo/foe', async () => { await withTestNotebookDiffModel([ - [['def foe(x, y):\n', ' return x + y\n', 'foe(3, 2)'].join(''), 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: 'text/plain', data: new Uint8Array([6]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 5 }], - [['def foo(x, y):\n', ' return x * y\n', 'foo(1, 2)'].join(''), 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: 'text/plain', data: new Uint8Array([2]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 6 }], + [['def foe(x, y):\n', ' return x + y\n', 'foe(3, 2)'].join(''), 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: new Uint8Array([6]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 5 }], + [['def foo(x, y):\n', ' return x * y\n', 'foo(1, 2)'].join(''), 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: new Uint8Array([2]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 6 }], ['', 'javascript', CellKind.Code, [], {}] ], [ - [['def foo(x, y):\n', ' return x * y\n', 'foo(1, 2)'].join(''), 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: 'text/plain', data: new Uint8Array([6]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 5 }], - [['def foe(x, y):\n', ' return x + y\n', 'foe(3, 2)'].join(''), 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: 'text/plain', data: new Uint8Array([2]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 6 }], + [['def foo(x, y):\n', ' return x * y\n', 'foo(1, 2)'].join(''), 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: new Uint8Array([6]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 5 }], + [['def foe(x, y):\n', ' return x + y\n', 'foe(3, 2)'].join(''), 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: new Uint8Array([2]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 6 }], ['', 'javascript', CellKind.Code, [], {}] ], (model, accessor) => { const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); @@ -272,13 +273,13 @@ suite('NotebookCommon', () => { await withTestNotebookDiffModel([ ['# Description', 'markdown', CellKind.Markup, [], { custom: { metadata: {} } }], ['x = 3', 'javascript', CellKind.Code, [], { custom: { metadata: { collapsed: true } }, executionOrder: 1 }], - ['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: 'text/plain', data: new Uint8Array([3]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 1 }], + ['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: new Uint8Array([3]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 1 }], ['x', 'javascript', CellKind.Code, [], { custom: { metadata: { collapsed: false } } }] ], [ ['# Description', 'markdown', CellKind.Markup, [], { custom: { metadata: {} } }], ['x = 3', 'javascript', CellKind.Code, [], { custom: { metadata: { collapsed: true } }, executionOrder: 1 }], ['x', 'javascript', CellKind.Code, [], { custom: { metadata: { collapsed: false } } }], - ['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: 'text/plain', data: new Uint8Array([3]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 1 }] + ['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: new Uint8Array([3]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 1 }] ], async (model) => { const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); const diffResult = diff.ComputeDiff(false); @@ -305,18 +306,18 @@ suite('NotebookCommon', () => { await withTestNotebookDiffModel([ ['# Description', 'markdown', CellKind.Markup, [], { custom: { metadata: {} } }], ['x = 3', 'javascript', CellKind.Code, [], { custom: { metadata: { collapsed: true } }, executionOrder: 1 }], - ['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: 'text/plain', data: new Uint8Array([3]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 1 }], + ['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: new Uint8Array([3]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 1 }], ['x', 'javascript', CellKind.Code, [], { custom: { metadata: { collapsed: false } } }], ['x = 5', 'javascript', CellKind.Code, [], {}], ['x', 'javascript', CellKind.Code, [], {}], - ['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: 'text/plain', data: new Uint8Array([5]) }] }], {}], + ['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: new Uint8Array([5]) }] }], {}], ], [ ['# Description', 'markdown', CellKind.Markup, [], { custom: { metadata: {} } }], ['x = 3', 'javascript', CellKind.Code, [], { custom: { metadata: { collapsed: true } }, executionOrder: 1 }], ['x', 'javascript', CellKind.Code, [], { custom: { metadata: { collapsed: false } } }], - ['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: 'text/plain', data: new Uint8Array([3]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 1 }], + ['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: new Uint8Array([3]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 1 }], ['x = 5', 'javascript', CellKind.Code, [], {}], - ['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: 'text/plain', data: new Uint8Array([5]) }] }], {}], + ['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: new Uint8Array([5]) }] }], {}], ['x', 'javascript', CellKind.Code, [], {}], ], async (model) => { const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); diff --git a/src/vs/workbench/contrib/notebook/test/notebookEditorKernelManager.test.ts b/src/vs/workbench/contrib/notebook/test/notebookEditorKernelManager.test.ts index 28c25e4f7f1..1e0eaaedfdd 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookEditorKernelManager.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookEditorKernelManager.test.ts @@ -20,6 +20,7 @@ import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookS import { mock } from 'vs/base/test/common/mock'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { Mimes } from 'vs/base/common/mime'; suite('NotebookEditorKernelManager', () => { @@ -151,6 +152,6 @@ class TestNotebookKernel implements INotebookKernel { } constructor(opts?: { languages: string[] }) { - this.supportedLanguages = opts?.languages ?? ['text/plain']; + this.supportedLanguages = opts?.languages ?? [Mimes.text]; } } diff --git a/src/vs/workbench/contrib/notebook/test/notebookEditorModel.test.ts b/src/vs/workbench/contrib/notebook/test/notebookEditorModel.test.ts index 664ae0b0957..e6dd9ea292f 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookEditorModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookEditorModel.test.ts @@ -25,6 +25,7 @@ import { CellKind, NotebookData, TransientOptions } from 'vs/workbench/contrib/n import { setupInstantiationService } from 'vs/workbench/contrib/notebook/test/testNotebookEditor'; import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { Mimes } from 'vs/base/common/mime'; suite('NotebookFileWorkingCopyModel', function () { @@ -35,7 +36,7 @@ suite('NotebookFileWorkingCopyModel', function () { const notebook = instantiationService.createInstance(NotebookTextModel, 'notebook', URI.file('test'), - [{ cellKind: CellKind.Code, language: 'foo', source: 'foo', outputs: [{ outputId: 'id', outputs: [{ mime: 'text/plain', value: 'Hello Out' }] }] }], + [{ cellKind: CellKind.Code, language: 'foo', source: 'foo', outputs: [{ outputId: 'id', outputs: [{ mime: Mimes.text, value: 'Hello Out' }] }] }], {}, { transientCellMetadata: {}, transientDocumentMetadata: {}, transientOutputs: false } ); diff --git a/src/vs/workbench/contrib/notebook/test/notebookKernelService.test.ts b/src/vs/workbench/contrib/notebook/test/notebookKernelService.test.ts index e59660a42bf..59623436b1c 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookKernelService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookKernelService.test.ts @@ -16,6 +16,7 @@ import { mock } from 'vs/base/test/common/mock'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { Mimes } from 'vs/base/common/mime'; suite('NotebookKernelService', () => { @@ -176,7 +177,7 @@ class TestNotebookKernel implements INotebookKernel { } constructor(opts?: { languages?: string[], label?: string, viewType?: string }) { - this.supportedLanguages = opts?.languages ?? ['text/plain']; + this.supportedLanguages = opts?.languages ?? [Mimes.text]; this.label = opts?.label ?? this.label; this.viewType = opts?.viewType ?? this.viewType; } diff --git a/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts b/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts index 39433c04539..293402965ca 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts @@ -4,10 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { CellKind, CellEditType, NotebookTextModelChangedEvent, SelectionStateType, ICellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { withTestNotebook, TestCell, setupInstantiationService } from 'vs/workbench/contrib/notebook/test/testNotebookEditor'; -import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { Mimes } from 'vs/base/common/mime'; import { IModeService } from 'vs/editor/common/services/modeService'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { CellEditType, CellKind, ICellEditOperation, NotebookTextModelChangedEvent, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { setupInstantiationService, TestCell, withTestNotebook } from 'vs/workbench/contrib/notebook/test/testNotebookEditor'; suite('NotebookTextModel', () => { @@ -189,7 +190,7 @@ suite('NotebookTextModel', () => { editType: CellEditType.Output, outputs: [{ outputId: 'someId', - outputs: [{ mime: 'text/markdown', data: valueBytesFromString('_Hello_') }] + outputs: [{ mime: Mimes.markdown, data: valueBytesFromString('_Hello_') }] }] }], true, undefined, () => undefined, undefined); @@ -203,7 +204,7 @@ suite('NotebookTextModel', () => { append: true, outputs: [{ outputId: 'someId2', - outputs: [{ mime: 'text/markdown', data: valueBytesFromString('_Hello2_') }] + outputs: [{ mime: Mimes.markdown, data: valueBytesFromString('_Hello2_') }] }] }], true, undefined, () => undefined, undefined); @@ -219,7 +220,7 @@ suite('NotebookTextModel', () => { editType: CellEditType.Output, outputs: [{ outputId: 'someId3', - outputs: [{ mime: 'text/plain', data: valueBytesFromString('Last, replaced output') }] + outputs: [{ mime: Mimes.text, data: valueBytesFromString('Last, replaced output') }] }] }], true, undefined, () => undefined, undefined); @@ -247,7 +248,7 @@ suite('NotebookTextModel', () => { append: true, outputs: [{ outputId: 'append1', - outputs: [{ mime: 'text/markdown', data: valueBytesFromString('append 1') }] + outputs: [{ mime: Mimes.markdown, data: valueBytesFromString('append 1') }] }] }, { @@ -256,7 +257,7 @@ suite('NotebookTextModel', () => { append: true, outputs: [{ outputId: 'append2', - outputs: [{ mime: 'text/markdown', data: valueBytesFromString('append 2') }] + outputs: [{ mime: Mimes.markdown, data: valueBytesFromString('append 2') }] }] } ], true, undefined, () => undefined, undefined); @@ -584,7 +585,7 @@ suite('NotebookTextModel', () => { { editType: CellEditType.Output, handle: 0, append: true, outputs: [{ outputId: 'newOutput', - outputs: [{ mime: 'text/plain', data: valueBytesFromString('cba') }, { mime: 'application/foo', data: valueBytesFromString('cba') }] + outputs: [{ mime: Mimes.text, data: valueBytesFromString('cba') }, { mime: 'application/foo', data: valueBytesFromString('cba') }] }] } ]; @@ -614,7 +615,7 @@ suite('NotebookTextModel', () => { { editType: CellEditType.Output, index: 2, append: true, outputs: [{ outputId: 'newOutput', - outputs: [{ mime: 'text/plain', data: valueBytesFromString('cba') }, { mime: 'application/foo', data: valueBytesFromString('cba') }] + outputs: [{ mime: Mimes.text, data: valueBytesFromString('cba') }, { mime: 'application/foo', data: valueBytesFromString('cba') }] }] } ]; @@ -641,7 +642,7 @@ suite('NotebookTextModel', () => { { editType: CellEditType.Output, index: 1, append: true, outputs: [{ outputId: 'newOutput', - outputs: [{ mime: 'text/plain', data: valueBytesFromString('cba') }, { mime: 'application/foo', data: valueBytesFromString('cba') }] + outputs: [{ mime: Mimes.text, data: valueBytesFromString('cba') }, { mime: 'application/foo', data: valueBytesFromString('cba') }] }] }, { @@ -650,7 +651,7 @@ suite('NotebookTextModel', () => { { editType: CellEditType.Output, index: 1, append: true, outputs: [{ outputId: 'newOutput2', - outputs: [{ mime: 'text/plain', data: valueBytesFromString('cba') }, { mime: 'application/foo', data: valueBytesFromString('cba') }] + outputs: [{ mime: Mimes.text, data: valueBytesFromString('cba') }, { mime: 'application/foo', data: valueBytesFromString('cba') }] }] } ]; diff --git a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts index 33a5e367bc0..db88121bccb 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts @@ -314,7 +314,9 @@ class OnAutoForwardedAction extends Disposable { } private linkMessage() { - return nls.localize('remote.tunnelsView.notificationLink', "[See all forwarded ports](command:{0}.focus)", TunnelPanel.ID); + return nls.localize( + { key: 'remote.tunnelsView.notificationLink2', comment: ['[See all forwarded ports]({0}) is a link. Only translate `See all forwarded ports`. Do not change brackets and parentheses or {0}'] }, + "[See all forwarded ports]({0})", `command:${TunnelPanel.ID}.focus`); } private async showNotification(tunnel: RemoteTunnel) { diff --git a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts index cd9414e6fa5..a1f3501b511 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts @@ -301,7 +301,11 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr const workspaceLabel = this.getWorkspaceLabel(); if (workspaceLabel) { const toolTip: IMarkdownString = { - value: nls.localize('workspace.tooltip', "Virtual workspace on {0}\n\n[Some features](command:{1}) are not available for resources located on a virtual file system.", workspaceLabel, LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID), + value: nls.localize( + { key: 'workspace.tooltip2', comment: ['{0} is a remote location name, e.g. GitHub', '[Some features]({1}) is a link. Only translate `Some features`. Do not change brackets and parentheses or {1}'] }, + "Virtual workspace on {0}\n\n[Some features]({1}) are not available for resources located on a virtual file system.", + workspaceLabel, `command:${LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID}` + ), isTrusted: true }; this.renderRemoteStatusIndicator(`$(remote) ${truncate(workspaceLabel, RemoteStatusIndicator.REMOTE_STATUS_LABEL_MAX_LENGTH)}`, toolTip); diff --git a/src/vs/workbench/contrib/search/test/common/searchModel.test.ts b/src/vs/workbench/contrib/search/test/common/searchModel.test.ts index 9868b338127..3e48164a893 100644 --- a/src/vs/workbench/contrib/search/test/common/searchModel.test.ts +++ b/src/vs/workbench/contrib/search/test/common/searchModel.test.ts @@ -327,7 +327,7 @@ suite('SearchModel', () => { } function stub(arg1: any, arg2: any, arg3: any): sinon.SinonStub { - const stub = sinon.stub(arg1, arg2, arg3); + const stub = sinon.stub(arg1, arg2).callsFake(arg3); restoreStubs.push(stub); return stub; } diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 71af1d5eace..ae12653ae6f 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -16,7 +16,8 @@ .monaco-workbench .pane-body.integrated-terminal .terminal-outer-container, .monaco-workbench .pane-body.integrated-terminal .terminal-groups-container, -.monaco-workbench .pane-body.integrated-terminal .terminal-group { +.monaco-workbench .pane-body.integrated-terminal .terminal-group, +.monaco-workbench .pane-body.integrated-terminal .terminal-split-pane { height: 100%; } @@ -314,14 +315,21 @@ position: absolute; left: 0; right: 0; - height: 100%; + top: 0; + bottom: 0; pointer-events: none; opacity: 0; /* hidden initially */ - transition: left 70ms ease-out, right 70ms ease-out, opacity 150ms ease-out; + transition: left 70ms ease-out, right 70ms ease-out, top 70ms ease-out, bottom 70ms ease-out, opacity 150ms ease-out; } -.monaco-workbench .pane-body.integrated-terminal .terminal-drop-overlay.drop-left { +.monaco-workbench .pane-body.integrated-terminal .terminal-group > .monaco-split-view2.horizontal .terminal-drop-overlay.drop-before { right: 50%; } -.monaco-workbench .pane-body.integrated-terminal .terminal-drop-overlay.drop-right { +.monaco-workbench .pane-body.integrated-terminal .terminal-group > .monaco-split-view2.horizontal .terminal-drop-overlay.drop-after { left: 50% } +.monaco-workbench .pane-body.integrated-terminal .terminal-group > .monaco-split-view2.vertical .terminal-drop-overlay.drop-before { + bottom: 50%; +} +.monaco-workbench .pane-body.integrated-terminal .terminal-group > .monaco-split-view2.vertical .terminal-drop-overlay.drop-after { + top: 50%; +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 31cac615fef..7880eef8126 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -162,7 +162,7 @@ export interface ITerminalService { * Moves a terminal instance's group to the target instance group's position. */ moveGroup(source: ITerminalInstance, target: ITerminalInstance): void; - moveInstance(source: ITerminalInstance, target: ITerminalInstance, side: 'left' | 'right'): void; + moveInstance(source: ITerminalInstance, target: ITerminalInstance, side: 'before' | 'after'): void; /** * Perform an action with the active terminal instance, if the terminal does @@ -631,7 +631,7 @@ export interface ITerminalInstance { export interface IRequestAddInstanceToGroupEvent { uri: URI; - side: 'left' | 'right' + side: 'before' | 'after' } export const enum LinuxDistro { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 32066104ed9..b26b6fd6743 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -15,10 +15,10 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { EndOfLinePreference } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; -import { Action2, ICommandActionTitle, ILocalizedString, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { Action2, ICommandActionTitle, ILocalizedString, registerAction2 } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ContextKeyAndExpr, ContextKeyEqualsExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -32,8 +32,9 @@ import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/w import { FindInFilesCommand, IFindInFilesArgs } from 'vs/workbench/contrib/search/browser/searchActions'; import { Direction, IRemoteTerminalService, ITerminalInstance, ITerminalInstanceService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalQuickAccessProvider } from 'vs/workbench/contrib/terminal/browser/terminalQuickAccess'; -import { IRemoteTerminalAttachTarget, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, KEYBINDING_CONTEXT_TERMINAL_ALT_BUFFER_ACTIVE, KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, KEYBINDING_CONTEXT_TERMINAL_TABS_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TABS_SINGULAR_SELECTION, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_ACTION_CATEGORY, TerminalCommandId, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IRemoteTerminalAttachTarget, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, KEYBINDING_CONTEXT_TERMINAL_ALT_BUFFER_ACTIVE, KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, KEYBINDING_CONTEXT_TERMINAL_TABS_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TABS_SINGULAR_SELECTION, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_ACTION_CATEGORY, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { ITerminalContributionService } from 'vs/workbench/contrib/terminal/common/terminalExtensionPoints'; +import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; @@ -342,38 +343,10 @@ export function registerTerminalActions() { constructor() { super({ id: TerminalCommandId.Focus, - title: { value: localize('workbench.action.terminal.focus', "Focus Terminal"), original: 'Focus Terminal' }, + title: terminalStrings.focus, f1: true, category, - precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, - // This command is used to show instead of tabs when there is only a single terminal - menu: { - id: MenuId.ViewTitle, - group: 'navigation', - order: 0, - when: ContextKeyAndExpr.create([ - ContextKeyEqualsExpr.create('view', TERMINAL_VIEW_ID), - ContextKeyExpr.has(`config.${TerminalSettingId.TabsEnabled}`), - ContextKeyExpr.or( - ContextKeyExpr.and( - ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActiveTerminal}`, 'singleTerminal'), - ContextKeyExpr.equals('terminalCount', 1) - ), - ContextKeyExpr.and( - ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActiveTerminal}`, 'singleTerminalOrNarrow'), - ContextKeyExpr.or( - ContextKeyExpr.equals('terminalCount', 1), - ContextKeyExpr.has('isTerminalTabsNarrow') - ) - ), - ContextKeyExpr.and( - ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActiveTerminal}`, 'singleGroup'), - ContextKeyExpr.equals('terminalGroupCount', 1) - ), - ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActiveTerminal}`, 'always') - ) - ]), - } + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } async run(accessor: ServicesAccessor) { @@ -1339,7 +1312,7 @@ export function registerTerminalActions() { constructor() { super({ id: TerminalCommandId.Split, - title: { value: localize('workbench.action.terminal.split', "Split Terminal"), original: 'Split Terminal' }, + title: terminalStrings.split, f1: true, category, precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, @@ -1361,15 +1334,6 @@ export function registerTerminalActions() { type: 'object' } }] - }, - menu: { - id: MenuId.ViewTitle, - group: 'navigation', - order: 2, - when: ContextKeyAndExpr.create([ - ContextKeyEqualsExpr.create('view', TERMINAL_VIEW_ID), - ContextKeyExpr.not(`config.${TerminalSettingId.TabsEnabled}`) - ]) } }); } @@ -1389,7 +1353,7 @@ export function registerTerminalActions() { constructor() { super({ id: TerminalCommandId.SplitInstance, - title: { value: localize('workbench.action.terminal.splitInstance', "Split Terminal"), original: 'Split Terminal' }, + title: terminalStrings.split, f1: false, category, precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, @@ -1423,7 +1387,7 @@ export function registerTerminalActions() { constructor() { super({ id: TerminalCommandId.Unsplit, - title: { value: localize('workbench.action.terminal.unsplit', "Unsplit Terminal"), original: 'Unsplit Terminal' }, + title: terminalStrings.unsplit, f1: true, category, precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED @@ -1438,7 +1402,7 @@ export function registerTerminalActions() { constructor() { super({ id: TerminalCommandId.UnsplitInstance, - title: { value: localize('workbench.action.terminal.unsplit', "Unsplit Terminal"), original: 'Unsplit Terminal' }, + title: terminalStrings.unsplit, f1: true, category, precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED @@ -1578,16 +1542,7 @@ export function registerTerminalActions() { f1: true, category, precondition: ContextKeyExpr.or(KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN), - icon: Codicon.trash, - menu: { - id: MenuId.ViewTitle, - group: 'navigation', - order: 3, - when: ContextKeyAndExpr.create([ - ContextKeyEqualsExpr.create('view', TERMINAL_VIEW_ID), - ContextKeyExpr.not(`config.${TerminalSettingId.TabsEnabled}`) - ]) - } + icon: Codicon.trash }); } async run(accessor: ServicesAccessor) { @@ -1605,9 +1560,7 @@ export function registerTerminalActions() { constructor() { super({ id: TerminalCommandId.KillInstance, - title: { - value: localize('workbench.action.terminal.kill.short', "Kill Terminal"), original: 'Kill Terminal' - }, + title: terminalStrings.kill, f1: false, category, precondition: ContextKeyExpr.or(KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN), @@ -1656,10 +1609,7 @@ export function registerTerminalActions() { }); } run(accessor: ServicesAccessor) { - accessor.get(ITerminalService).doWithActiveInstance(t => { - t.clear(); - t.focus(); - }); + accessor.get(ITerminalService).doWithActiveInstance(t => t.clear()); } }); registerAction2(class extends Action2 { @@ -1684,15 +1634,7 @@ export function registerTerminalActions() { title: TerminalCommandId.CreateWithProfileButton, f1: false, category, - precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, - menu: [{ - id: MenuId.ViewTitle, - group: 'navigation', - order: 0, - when: ContextKeyAndExpr.create([ - ContextKeyEqualsExpr.create('view', TERMINAL_VIEW_ID) - ]), - }] + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } async run(accessor: ServicesAccessor) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 611101f0046..d58ea4d13fe 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -58,8 +58,10 @@ import { isMacintosh, isWindows, OperatingSystem, OS } from 'vs/base/common/plat import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { DataTransfers } from 'vs/base/browser/dnd'; -import { DragAndDropObserver, IDragAndDropObserverCallbacks } from 'vs/workbench/browser/dnd'; +import { containsDragType, DragAndDropObserver, IDragAndDropObserverCallbacks } from 'vs/workbench/browser/dnd'; import { getColorClass } from 'vs/workbench/contrib/terminal/browser/terminalIcon'; +import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService'; +import { Orientation } from 'vs/base/browser/ui/sash/sash'; // How long in milliseconds should an average frame take to render for a notification to appear // which suggests the fallback DOM-based renderer @@ -832,14 +834,14 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private _initDragAndDrop(container: HTMLElement) { this._dndObserver?.dispose(); - const dndController = new TerminalInstanceDragAndDropController(container); + const dndController = this._instantiationService.createInstance(TerminalInstanceDragAndDropController, container); dndController.onDropTerminal(e => this._onRequestAddInstanceToGroup.fire(e)); dndController.onDropFile(async path => { const preparedPath = await this._terminalInstanceService.preparePathForTerminalAsync(path, this.shellLaunchConfig.executable, this.title, this.shellType, this.isRemote); this.sendText(preparedPath, false); this.focus(); }); - this._dndObserver = new DragAndDropObserver(container.parentElement!, dndController); + this._dndObserver = new DragAndDropObserver(container, dndController); } private async _measureRenderTime(): Promise { @@ -1936,7 +1938,9 @@ class TerminalInstanceDragAndDropController extends Disposable implements IDragA get onDropTerminal(): Event { return this._onDropTerminal.event; } constructor( - private readonly _container: HTMLElement + private readonly _container: HTMLElement, + @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, + @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService, ) { super(); this._register(toDisposable(() => this._clearDropOverlay())); @@ -1950,6 +1954,10 @@ class TerminalInstanceDragAndDropController extends Disposable implements IDragA } onDragEnter(e: DragEvent) { + if (!containsDragType(e, DataTransfers.FILES, DataTransfers.RESOURCES, 'terminals')) { + return; + } + if (!this._dropOverlay) { this._dropOverlay = document.createElement('div'); this._dropOverlay.classList.add('terminal-drop-overlay'); @@ -1960,8 +1968,8 @@ class TerminalInstanceDragAndDropController extends Disposable implements IDragA // Dragging terminals if (types.includes('terminals')) { const side = this._getDropSide(e); - this._dropOverlay.classList.toggle('drop-left', side === 'left'); - this._dropOverlay.classList.toggle('drop-right', side === 'right'); + this._dropOverlay.classList.toggle('drop-before', side === 'before'); + this._dropOverlay.classList.toggle('drop-after', side === 'after'); } if (!this._dropOverlay.parentElement) { @@ -1986,8 +1994,8 @@ class TerminalInstanceDragAndDropController extends Disposable implements IDragA // Dragging terminals if (types.includes('terminals')) { const side = this._getDropSide(e); - this._dropOverlay.classList.toggle('drop-left', side === 'left'); - this._dropOverlay.classList.toggle('drop-right', side === 'right'); + this._dropOverlay.classList.toggle('drop-before', side === 'before'); + this._dropOverlay.classList.toggle('drop-after', side === 'after'); } this._dropOverlay.style.opacity = '1'; @@ -2026,13 +2034,24 @@ class TerminalInstanceDragAndDropController extends Disposable implements IDragA this._onDropFile.fire(path); } - private _getDropSide(e: DragEvent): 'left' | 'right' { + private _getDropSide(e: DragEvent): 'before' | 'after' { const target = this._container; if (!target) { - return 'right'; + return 'after'; } + const rect = target.getBoundingClientRect(); - return e.clientX - rect.left < rect.width / 2 ? 'left' : 'right'; + return this._getViewOrientation() === Orientation.HORIZONTAL + ? (e.clientX - rect.left < rect.width / 2 ? 'before' : 'after') + : (e.clientY - rect.top < rect.height / 2 ? 'before' : 'after'); + } + + private _getViewOrientation(): Orientation { + const panelPosition = this._layoutService.getPanelPosition(); + const terminalLocation = this._viewDescriptorService.getViewLocationById(TERMINAL_VIEW_ID); + return terminalLocation === ViewContainerLocation.Panel && panelPosition === Position.BOTTOM + ? Orientation.HORIZONTAL + : Orientation.VERTICAL; } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts index 4fdd6818f14..659322dfb40 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts @@ -3,11 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Codicon } from 'vs/base/common/codicons'; import { localize } from 'vs/nls'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { ContextKeyAndExpr, ContextKeyEqualsExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { IS_SPLIT_TERMINAL_CONTEXT_KEY, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, KEYBINDING_CONTEXT_TERMINAL_TABS_SINGULAR_SELECTION, TerminalCommandId, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; +import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings'; const enum ContextMenuGroup { Create = '1_create', @@ -34,7 +36,8 @@ export function setupTerminalMenus(): void { MenuRegistry.appendMenuItems( [ { - id: MenuId.MenubarTerminalMenu, item: { + id: MenuId.MenubarTerminalMenu, + item: { group: TerminalMenuBarGroup.Create, command: { id: TerminalCommandId.New, @@ -44,7 +47,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.MenubarTerminalMenu, item: { + id: MenuId.MenubarTerminalMenu, + item: { group: TerminalMenuBarGroup.Create, command: { id: TerminalCommandId.Split, @@ -56,7 +60,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.MenubarTerminalMenu, item: { + id: MenuId.MenubarTerminalMenu, + item: { group: TerminalMenuBarGroup.Run, command: { id: TerminalCommandId.RunActiveFile, @@ -67,7 +72,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.MenubarTerminalMenu, item: { + id: MenuId.MenubarTerminalMenu, + item: { group: TerminalMenuBarGroup.Run, command: { id: TerminalCommandId.RunSelectedText, @@ -83,16 +89,18 @@ export function setupTerminalMenus(): void { MenuRegistry.appendMenuItems( [ { - id: MenuId.TerminalInstanceContext, item: { + id: MenuId.TerminalInstanceContext, + item: { group: ContextMenuGroup.Create, command: { id: TerminalCommandId.Split, - title: localize('workbench.action.terminal.split', "Split Terminal") + title: terminalStrings.split.value } } }, { - id: MenuId.TerminalInstanceContext, item: { + id: MenuId.TerminalInstanceContext, + item: { command: { id: TerminalCommandId.New, title: localize('workbench.action.terminal.new.short', "New Terminal") @@ -101,16 +109,18 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.TerminalInstanceContext, item: { + id: MenuId.TerminalInstanceContext, + item: { command: { id: TerminalCommandId.Kill, - title: localize('workbench.action.terminal.kill.short', "Kill Terminal") + title: terminalStrings.kill.value }, group: ContextMenuGroup.Kill } }, { - id: MenuId.TerminalInstanceContext, item: { + id: MenuId.TerminalInstanceContext, + item: { command: { id: TerminalCommandId.CopySelection, title: localize('workbench.action.terminal.copySelection.short', "Copy") @@ -120,7 +130,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.TerminalInstanceContext, item: { + id: MenuId.TerminalInstanceContext, + item: { command: { id: TerminalCommandId.Paste, title: localize('workbench.action.terminal.paste.short', "Paste") @@ -130,7 +141,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.TerminalInstanceContext, item: { + id: MenuId.TerminalInstanceContext, + item: { command: { id: TerminalCommandId.Clear, title: localize('workbench.action.terminal.clear', "Clear") @@ -139,7 +151,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.TerminalInstanceContext, item: { + id: MenuId.TerminalInstanceContext, + item: { command: { id: TerminalCommandId.ShowTabs, title: localize('workbench.action.terminal.showsTabs', "Show Tabs") @@ -149,7 +162,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.TerminalInstanceContext, item: { + id: MenuId.TerminalInstanceContext, + item: { command: { id: TerminalCommandId.SelectAll, title: localize('workbench.action.terminal.selectAll', "Select All"), @@ -164,7 +178,8 @@ export function setupTerminalMenus(): void { MenuRegistry.appendMenuItems( [ { - id: MenuId.TerminalTabEmptyAreaContext, item: { + id: MenuId.TerminalTabEmptyAreaContext, + item: { command: { id: TerminalCommandId.NewWithProfile, title: localize('workbench.action.terminal.newWithProfile.short', "New Terminal With Profile") @@ -173,7 +188,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.TerminalTabEmptyAreaContext, item: { + id: MenuId.TerminalTabEmptyAreaContext, + item: { command: { id: TerminalCommandId.New, title: localize('workbench.action.terminal.new.short', "New Terminal") @@ -187,7 +203,8 @@ export function setupTerminalMenus(): void { MenuRegistry.appendMenuItems( [ { - id: MenuId.TerminalNewDropdownContext, item: { + id: MenuId.TerminalNewDropdownContext, + item: { command: { id: TerminalCommandId.SelectDefaultProfile, title: { value: localize('workbench.action.terminal.selectDefaultProfile', "Select Default Profile"), original: 'Select Default Profile' } @@ -196,7 +213,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.TerminalNewDropdownContext, item: { + id: MenuId.TerminalNewDropdownContext, + item: { command: { id: TerminalCommandId.ConfigureTerminalSettings, title: localize('workbench.action.terminal.openSettings', "Configure Terminal Settings") @@ -210,7 +228,8 @@ export function setupTerminalMenus(): void { MenuRegistry.appendMenuItems( [ { - id: MenuId.ViewTitle, item: { + id: MenuId.ViewTitle, + item: { command: { id: TerminalCommandId.SwitchTerminal, title: { value: localize('workbench.action.terminal.switchTerminal', "Switch Terminal"), original: 'Switch Terminal' } @@ -222,6 +241,86 @@ export function setupTerminalMenus(): void { ContextKeyExpr.not(`config.${TerminalSettingId.TabsEnabled}`) ]), } + }, + { + // This is used to show instead of tabs when there is only a single terminal + id: MenuId.ViewTitle, + item: { + command: { + id: TerminalCommandId.Focus, + title: terminalStrings.focus + }, + group: 'navigation', + order: 0, + when: ContextKeyAndExpr.create([ + ContextKeyEqualsExpr.create('view', TERMINAL_VIEW_ID), + ContextKeyExpr.has(`config.${TerminalSettingId.TabsEnabled}`), + ContextKeyExpr.or( + ContextKeyExpr.and( + ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActiveTerminal}`, 'singleTerminal'), + ContextKeyExpr.equals('terminalCount', 1) + ), + ContextKeyExpr.and( + ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActiveTerminal}`, 'singleTerminalOrNarrow'), + ContextKeyExpr.or( + ContextKeyExpr.equals('terminalCount', 1), + ContextKeyExpr.has('isTerminalTabsNarrow') + ) + ), + ContextKeyExpr.and( + ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActiveTerminal}`, 'singleGroup'), + ContextKeyExpr.equals('terminalGroupCount', 1) + ), + ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActiveTerminal}`, 'always') + ) + ]), + } + }, + { + id: MenuId.ViewTitle, + item: { + command: { + id: TerminalCommandId.Split, + title: terminalStrings.split, + icon: Codicon.splitHorizontal + }, + group: 'navigation', + order: 2, + when: ContextKeyAndExpr.create([ + ContextKeyEqualsExpr.create('view', TERMINAL_VIEW_ID), + ContextKeyExpr.not(`config.${TerminalSettingId.TabsEnabled}`) + ]) + } + }, + { + id: MenuId.ViewTitle, + item: { + command: { + id: TerminalCommandId.Kill, + title: terminalStrings.kill, + icon: Codicon.trash + }, + group: 'navigation', + order: 3, + when: ContextKeyAndExpr.create([ + ContextKeyEqualsExpr.create('view', TERMINAL_VIEW_ID), + ContextKeyExpr.not(`config.${TerminalSettingId.TabsEnabled}`) + ]) + } + }, + { + id: MenuId.ViewTitle, + item: { + command: { + id: TerminalCommandId.CreateWithProfileButton, + title: TerminalCommandId.CreateWithProfileButton + }, + group: 'navigation', + order: 0, + when: ContextKeyAndExpr.create([ + ContextKeyEqualsExpr.create('view', TERMINAL_VIEW_ID) + ]) + } } ] ); @@ -229,16 +328,18 @@ export function setupTerminalMenus(): void { MenuRegistry.appendMenuItems( [ { - id: MenuId.TerminalInlineTabContext, item: { + id: MenuId.TerminalInlineTabContext, + item: { group: ContextMenuGroup.Create, command: { id: TerminalCommandId.Split, - title: localize('workbench.action.terminal.split', "Split Terminal") + title: terminalStrings.split.value } } }, { - id: MenuId.TerminalInlineTabContext, item: { + id: MenuId.TerminalInlineTabContext, + item: { command: { id: TerminalCommandId.ChangeIcon, title: localize('workbench.action.terminal.changeIcon', "Change Icon...") @@ -248,7 +349,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.TerminalInlineTabContext, item: { + id: MenuId.TerminalInlineTabContext, + item: { command: { id: TerminalCommandId.ChangeColor, title: localize('workbench.action.terminal.changeColor', "Change Color...") @@ -258,7 +360,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.TerminalInlineTabContext, item: { + id: MenuId.TerminalInlineTabContext, + item: { command: { id: TerminalCommandId.Rename, title: localize('workbench.action.terminal.rename', "Rename...") @@ -267,10 +370,11 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.TerminalInlineTabContext, item: { + id: MenuId.TerminalInlineTabContext, + item: { command: { id: TerminalCommandId.Kill, - title: localize('workbench.action.terminal.kill.short', "Kill Terminal") + title: terminalStrings.kill.value }, group: ContextMenuGroup.Kill } @@ -281,16 +385,18 @@ export function setupTerminalMenus(): void { MenuRegistry.appendMenuItems( [ { - id: MenuId.TerminalTabContext, item: { + id: MenuId.TerminalTabContext, + item: { command: { id: TerminalCommandId.SplitInstance, - title: localize('workbench.action.terminal.splitInstance', "Split Terminal"), + title: terminalStrings.split.value, }, group: ContextMenuGroup.Create } }, { - id: MenuId.TerminalTabContext, item: { + id: MenuId.TerminalTabContext, + item: { command: { id: TerminalCommandId.RenameInstance, title: localize('workbench.action.terminal.renameInstance', "Rename...") @@ -299,7 +405,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.TerminalTabContext, item: { + id: MenuId.TerminalTabContext, + item: { command: { id: TerminalCommandId.ChangeIconInstance, title: localize('workbench.action.terminal.changeIcon', "Change Icon...") @@ -308,7 +415,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.TerminalTabContext, item: { + id: MenuId.TerminalTabContext, + item: { command: { id: TerminalCommandId.ChangeColorInstance, title: localize('workbench.action.terminal.changeColor', "Change Color...") @@ -317,7 +425,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.TerminalTabContext, item: { + id: MenuId.TerminalTabContext, + item: { group: ContextMenuGroup.Config, command: { id: TerminalCommandId.JoinInstance, @@ -327,20 +436,22 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.TerminalTabContext, item: { + id: MenuId.TerminalTabContext, + item: { group: ContextMenuGroup.Config, command: { id: TerminalCommandId.UnsplitInstance, - title: localize('workbench.action.terminal.unsplitInstance', "Unsplit Terminal") + title: terminalStrings.unsplit.value }, when: ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_TABS_SINGULAR_SELECTION, IS_SPLIT_TERMINAL_CONTEXT_KEY) } }, { - id: MenuId.TerminalTabContext, item: { + id: MenuId.TerminalTabContext, + item: { command: { id: TerminalCommandId.KillInstance, - title: localize('workbench.action.terminal.killInstance', "Kill Terminal") + title: terminalStrings.kill.value }, group: ContextMenuGroup.Kill, } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts b/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts index d62c06f238c..4842e563fc0 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts @@ -13,6 +13,7 @@ import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { killTerminalIcon, renameTerminalIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons'; import { getColorClass, getIconId, getUriClasses } from 'vs/workbench/contrib/terminal/browser/terminalIcon'; +import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings'; export class TerminalQuickAccessProvider extends PickerQuickAccessProvider { @@ -57,7 +58,7 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider = new Map(); private _processSupportContextKey: IContextKey; private readonly _localTerminalService?: ILocalTerminalService; - private readonly _offProcessTerminalService?: IOffProcessTerminalService; + private readonly _primaryOffProcessTerminalService?: IOffProcessTerminalService; private _profilesReadyBarrier: AutoOpenBarrier; private _availableProfiles: ITerminalProfile[] | undefined; private _configHelper: TerminalConfigHelper; @@ -210,7 +210,7 @@ export class TerminalService implements ITerminalService { : enableTerminalReconnection ? this._localTerminalsInitPromise = this._reconnectToLocalTerminals() : Promise.resolve(); - this._offProcessTerminalService = !!this._environmentService.remoteAuthority ? this._remoteTerminalService : this._localTerminalService; + this._primaryOffProcessTerminalService = !!this._environmentService.remoteAuthority ? this._remoteTerminalService : this._localTerminalService; initPromise.then(() => this._setConnected()); // Wait up to 5 seconds for profiles to be ready so it's assured that we know the actual @@ -376,12 +376,11 @@ export class TerminalService implements ITerminalService { } private async _detectProfiles(includeDetectedProfiles?: boolean): Promise { - const offProcService = this._offProcessTerminalService; - if (!offProcService) { + if (!this._primaryOffProcessTerminalService) { return this._availableProfiles || []; } const platform = await this._getPlatformKey(); - return offProcService?.getProfiles(this._configurationService.getValue(`${TerminalSettingPrefix.Profiles}${platform}`), this._configurationService.getValue(`${TerminalSettingPrefix.DefaultProfile}${platform}`), includeDetectedProfiles); + return this._primaryOffProcessTerminalService?.getProfiles(this._configurationService.getValue(`${TerminalSettingPrefix.Profiles}${platform}`), this._configurationService.getValue(`${TerminalSettingPrefix.DefaultProfile}${platform}`), includeDetectedProfiles); } private _onBeforeShutdown(reason: ShutdownReason): boolean | Promise { @@ -441,7 +440,7 @@ export class TerminalService implements ITerminalService { const state: ITerminalsLayoutInfoById = { tabs: this.terminalGroups.map(g => g.getLayoutInfo(g === this.getActiveGroup())) }; - this._offProcessTerminalService?.setTerminalLayoutInfo(state); + this._primaryOffProcessTerminalService?.setTerminalLayoutInfo(state); } @debounce(500) @@ -449,7 +448,7 @@ export class TerminalService implements ITerminalService { if (!this.configHelper.config.enablePersistentSessions || !instance || !instance.persistentProcessId || !instance.title) { return; } - this._offProcessTerminalService?.updateTitle(instance.persistentProcessId, instance.title, instance.titleSource); + this._primaryOffProcessTerminalService?.updateTitle(instance.persistentProcessId, instance.title, instance.titleSource); } @debounce(500) @@ -457,7 +456,7 @@ export class TerminalService implements ITerminalService { if (!this.configHelper.config.enablePersistentSessions || !instance || !instance.persistentProcessId || !instance.icon) { return; } - this._offProcessTerminalService?.updateIcon(instance.persistentProcessId, instance.icon, instance.color); + this._primaryOffProcessTerminalService?.updateIcon(instance.persistentProcessId, instance.icon, instance.color); } private _removeGroup(group: ITerminalGroup): void { @@ -743,7 +742,7 @@ export class TerminalService implements ITerminalService { this._onInstancesChanged.fire(); } - moveInstance(source: ITerminalInstance, target: ITerminalInstance, side: 'left' | 'right'): void { + moveInstance(source: ITerminalInstance, target: ITerminalInstance, side: 'before' | 'after'): void { const sourceGroup = this.getGroupForInstance(source); const targetGroup = this.getGroupForInstance(target); if (!sourceGroup || !targetGroup) { @@ -758,7 +757,7 @@ export class TerminalService implements ITerminalService { } // Rearrange within the target group - const index = targetGroup.terminalInstances.indexOf(target) + (side === 'right' ? 1 : 0); + const index = targetGroup.terminalInstances.indexOf(target) + (side === 'after' ? 1 : 0); targetGroup.moveInstance(source, index); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index b67adc0272c..f615a386793 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -30,7 +30,7 @@ import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from import { IListDragAndDrop, IListDragOverReaction, IListRenderer, ListDragOverEffect } from 'vs/base/browser/ui/list/list'; import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd'; import { disposableTimeout } from 'vs/base/common/async'; -import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; +import { ElementsDragAndDropData, NativeDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { URI } from 'vs/base/common/uri'; import { getColorClass, getIconId, getUriClasses } from 'vs/workbench/contrib/terminal/browser/terminalIcon'; import { Schemas } from 'vs/base/common/network'; @@ -41,6 +41,8 @@ import { once } from 'vs/base/common/functional'; import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; +import { containsDragType } from 'vs/workbench/browser/dnd'; +import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings'; const $ = DOM.$; @@ -436,10 +438,10 @@ class TerminalTabsRenderer implements IListRenderer { + new Action(TerminalCommandId.SplitInstance, terminalStrings.split.short, ThemeIcon.asClassName(Codicon.splitHorizontal), true, async () => { this._runForSelectionOrInstance(instance, e => this._terminalService.splitInstance(e)); }), - new Action(TerminalCommandId.KillInstance, localize('terminal.kill', "Kill"), ThemeIcon.asClassName(Codicon.trashcan), true, async () => { + new Action(TerminalCommandId.KillInstance, terminalStrings.kill.short, ThemeIcon.asClassName(Codicon.trashcan), true, async () => { this._runForSelectionOrInstance(instance, e => e.dispose()); }) ]; @@ -547,7 +549,11 @@ class TerminalTabsDragAndDrop implements IListDragAndDrop { } onDragOver(data: IDragAndDropData, targetInstance: ITerminalInstance | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | IListDragOverReaction { - let result = true; + if (data instanceof NativeDragAndDropData) { + if (!containsDragType(originalEvent, DataTransfers.FILES, DataTransfers.RESOURCES)) { + return false; + } + } const didChangeAutoFocusInstance = this._autoFocusInstance !== targetInstance; if (didChangeAutoFocusInstance) { @@ -556,7 +562,7 @@ class TerminalTabsDragAndDrop implements IListDragAndDrop { } if (!targetInstance) { - return result; + return data instanceof ElementsDragAndDropData; } if (didChangeAutoFocusInstance) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index 6c69c92e18b..862e5cb7b7a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -42,6 +42,7 @@ import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ColorScheme } from 'vs/platform/theme/common/theme'; import { getColorClass, getUriClasses } from 'vs/workbench/contrib/terminal/browser/terminalIcon'; +import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings'; export class TerminalViewPane extends ViewPane { private _actions: IAction[] | undefined; @@ -257,7 +258,7 @@ export class TerminalViewPane extends ViewPane { }, { id: TerminalCommandId.Split, - title: nls.localize('terminal.split', "Split Terminal"), + title: terminalStrings.split.value, icon: Codicon.splitHorizontal }, undefined); @@ -385,7 +386,7 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem { }, { id: TerminalCommandId.Split, - title: nls.localize('workbench.action.terminal.split', "Split Terminal"), + title: terminalStrings.split.value, icon: Codicon.splitHorizontal }, undefined, diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index 19b41687764..33523697796 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -248,7 +248,12 @@ const terminalConfiguration: IConfigurationNode = { default: false }, [TerminalSettingId.CommandsToSkipShell]: { - markdownDescription: localize('terminal.integrated.commandsToSkipShell', "A set of command IDs whose keybindings will not be sent to the shell but instead always be handled by VS Code. This allows keybindings that would normally be consumed by the shell to act instead the same as when the terminal is not focused, for example `Ctrl+P` to launch Quick Open.\n\n \n\nMany commands are skipped by default. To override a default and pass that command's keybinding to the shell instead, add the command prefixed with the `-` character. For example add `-workbench.action.quickOpen` to allow `Ctrl+P` to reach the shell.\n\n \n\nThe following list of default skipped commands is truncated when viewed in Settings Editor. To see the full list, [open the default settings JSON](command:workbench.action.openRawDefaultSettings 'Open Default Settings (JSON)') and search for the first command from the list below.\n\n \n\nDefault Skipped Commands:\n\n{0}", DEFAULT_COMMANDS_TO_SKIP_SHELL.sort().map(command => `- ${command}`).join('\n')), + markdownDescription: localize( + 'terminal.integrated.commandsToSkipShell', + "A set of command IDs whose keybindings will not be sent to the shell but instead always be handled by VS Code. This allows keybindings that would normally be consumed by the shell to act instead the same as when the terminal is not focused, for example `Ctrl+P` to launch Quick Open.\n\n \n\nMany commands are skipped by default. To override a default and pass that command's keybinding to the shell instead, add the command prefixed with the `-` character. For example add `-workbench.action.quickOpen` to allow `Ctrl+P` to reach the shell.\n\n \n\nThe following list of default skipped commands is truncated when viewed in Settings Editor. To see the full list, {1} and search for the first command from the list below.\n\n \n\nDefault Skipped Commands:\n\n{0}", + DEFAULT_COMMANDS_TO_SKIP_SHELL.sort().map(command => `- ${command}`).join('\n'), + `[${localize('openDefaultSettingsJson', "open the default settings JSON")}](command:workbench.action.openRawDefaultSettings '${localize('openDefaultSettingsJson.capitalized', "Open Default Settings (JSON)")}')` + ), type: 'array', items: { type: 'string' diff --git a/src/vs/workbench/contrib/terminal/common/terminalStrings.ts b/src/vs/workbench/contrib/terminal/common/terminalStrings.ts index dd96605d965..854fb806931 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalStrings.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalStrings.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; + /** * Formats a message from the product to be written to the terminal. */ @@ -10,3 +12,27 @@ export function formatMessageForTerminal(message: string, excludeLeadingNewLine: // Wrap in bold and ensure it's on a new line return `${excludeLeadingNewLine ? '' : '\r\n'}\x1b[1m${message}\x1b[0m\n\r`; } + +/** + * An object holding strings shared by multiple parts of the terminal + */ +export const terminalStrings = { + focus: { + value: localize('workbench.action.terminal.focus', "Focus Terminal"), + original: 'Focus Terminal' + }, + kill: { + value: localize('killTerminal', "Kill Terminal"), + original: 'Kill Terminal', + short: localize('killTerminal.short', "Kill"), + }, + split: { + value: localize('splitTerminal', "Split Terminal"), + original: 'Split Terminal', + short: localize('splitTerminal.short', "Split"), + }, + unsplit: { + value: localize('unsplitTerminal', "Unsplit Terminal"), + original: 'Unsplit Terminal' + } +}; diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index 58e4db282f8..6e86032e748 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -13,7 +13,7 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { localize } from 'vs/nls'; import { Action2, IAction2Options, MenuId } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { ContextKeyAndExpr, ContextKeyEqualsExpr, ContextKeyFalseExpr, ContextKeyTrueExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyAndExpr, ContextKeyEqualsExpr, ContextKeyFalseExpr, ContextKeyGreaterExpr, ContextKeyTrueExpr } from 'vs/platform/contextkey/common/contextkey'; import { IFileService } from 'vs/platform/files/common/files'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -59,13 +59,14 @@ const enum ActionOrder { Refresh, } +const hasAnyTestProvider = ContextKeyGreaterExpr.create(TestingContextKeys.providerCount.key, 0); + export class HideTestAction extends Action2 { public static readonly ID = 'testing.hideTest'; constructor() { super({ id: HideTestAction.ID, title: localize('hideTest', 'Hide Test'), - f1: false, menu: { id: MenuId.TestItem, when: TestingContextKeys.testItemIsHidden.isEqualTo(false) @@ -90,7 +91,6 @@ export class UnhideTestAction extends Action2 { super({ id: UnhideTestAction.ID, title: localize('unhideTest', 'Unhide Test'), - f1: false, menu: { id: MenuId.TestItem, when: TestingContextKeys.testItemIsHidden.isEqualTo(true) @@ -116,7 +116,6 @@ export class DebugAction extends Action2 { id: DebugAction.ID, title: localize('debug test', 'Debug Test'), icon: icons.testingDebugIcon, - f1: false, menu: { id: MenuId.TestItem, group: 'inline', @@ -142,7 +141,6 @@ export class RunAction extends Action2 { id: RunAction.ID, title: localize('run test', 'Run Test'), icon: icons.testingRunIcon, - f1: false, menu: { id: MenuId.TestItem, group: 'inline', @@ -268,10 +266,9 @@ abstract class RunOrDebugAllAllAction extends Action2 { id, title, icon, - f1: true, category, keybinding, - menu: { + menu: [{ id: MenuId.ViewTitle, order: debug ? ActionOrder.Debug : ActionOrder.Run, group: 'navigation', @@ -282,7 +279,10 @@ abstract class RunOrDebugAllAllAction extends Action2 { ? TestingContextKeys.hasDebuggableTests.isEqualTo(true) : TestingContextKeys.hasRunnableTests.isEqualTo(true), ]) - } + }, { + id: MenuId.CommandPalette, + when: hasAnyTestProvider, + }] }); } @@ -394,7 +394,6 @@ export class TestingViewAsListAction extends ViewAction { id: TestingViewAsListAction.ID, viewId: Testing.ExplorerViewId, title: localize('testing.viewAsList', "View as List"), - f1: false, toggled: TestingContextKeys.viewMode.isEqualTo(TestExplorerViewMode.List), menu: { id: MenuId.ViewTitle, @@ -420,7 +419,6 @@ export class TestingViewAsTreeAction extends ViewAction { id: TestingViewAsTreeAction.ID, viewId: Testing.ExplorerViewId, title: localize('testing.viewAsTree', "View as Tree"), - f1: false, toggled: TestingContextKeys.viewMode.isEqualTo(TestExplorerViewMode.Tree), menu: { id: MenuId.ViewTitle, @@ -447,7 +445,6 @@ export class TestingSortByNameAction extends ViewAction { id: TestingSortByNameAction.ID, viewId: Testing.ExplorerViewId, title: localize('testing.sortByName', "Sort by Name"), - f1: false, toggled: TestingContextKeys.viewSorting.isEqualTo(TestExplorerViewSorting.ByName), menu: { id: MenuId.ViewTitle, @@ -473,7 +470,6 @@ export class TestingSortByLocationAction extends ViewAction id: TestingSortByLocationAction.ID, viewId: Testing.ExplorerViewId, title: localize('testing.sortByLocation', "Sort by Location"), - f1: false, toggled: TestingContextKeys.viewSorting.isEqualTo(TestExplorerViewSorting.ByLocation), menu: { id: MenuId.ViewTitle, @@ -498,19 +494,22 @@ export class ShowMostRecentOutputAction extends Action2 { super({ id: ShowMostRecentOutputAction.ID, title: localize('testing.showMostRecentOutput', "Show Output"), - f1: true, category, icon: Codicon.terminal, keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.US_SEMICOLON, KeyMod.CtrlCmd | KeyCode.KEY_O), }, - menu: { + precondition: TestingContextKeys.hasAnyResults.isEqualTo(true), + menu: [{ id: MenuId.ViewTitle, order: ActionOrder.Collapse, group: 'navigation', - when: ContextKeyEqualsExpr.create('view', Testing.ExplorerViewId) - } + when: ContextKeyEqualsExpr.create('view', Testing.ExplorerViewId), + }, { + id: MenuId.CommandPalette, + when: TestingContextKeys.hasAnyResults.isEqualTo(true) + }] }); } @@ -527,7 +526,6 @@ export class CollapseAllAction extends ViewAction { id: CollapseAllAction.ID, viewId: Testing.ExplorerViewId, title: localize('testing.collapseAll', "Collapse All Tests"), - f1: false, icon: Codicon.collapseAll, menu: { id: MenuId.ViewTitle, @@ -553,13 +551,15 @@ export class RefreshTestsAction extends Action2 { id: RefreshTestsAction.ID, title: localize('testing.refresh', "Refresh Tests"), category, - f1: true, - menu: { + menu: [{ id: MenuId.ViewTitle, order: ActionOrder.Refresh, group: 'refresh', when: ContextKeyEqualsExpr.create('view', Testing.ExplorerViewId) - } + }, { + id: MenuId.CommandPalette, + when: TestingContextKeys.providerCount.isEqualTo(true), + }], }); } @@ -578,11 +578,13 @@ export class ClearTestResultsAction extends Action2 { id: ClearTestResultsAction.ID, title: localize('testing.clearResults', "Clear All Results"), category, - f1: true, icon: Codicon.trash, - menu: { + menu: [{ id: MenuId.TestPeekTitle, - }, + }, { + id: MenuId.CommandPalette, + when: TestingContextKeys.hasAnyResults.isEqualTo(true), + },], }); } @@ -600,7 +602,6 @@ export class GoToTest extends Action2 { super({ id: GoToTest.ID, title: localize('testing.editFocusedTest', "Go to Test"), - f1: false, menu: { id: MenuId.TestItem, when: TestingContextKeys.testItemHasUri.isEqualTo(true), @@ -709,7 +710,6 @@ abstract class ToggleAutoRun extends Action2 { super({ id: ToggleAutoRun.ID, title, - f1: true, icon: icons.testingAutorunIcon, toggled: whenToggleIs === true ? ContextKeyTrueExpr.INSTANCE : ContextKeyFalseExpr.INSTANCE, menu: { @@ -746,6 +746,16 @@ export class AutoRunOffAction extends ToggleAutoRun { abstract class RunOrDebugAtCursor extends Action2 { + constructor(options: IAction2Options) { + super({ + ...options, + menu: { + id: MenuId.CommandPalette, + when: hasAnyTestProvider, + }, + }); + } + /** * @override */ @@ -801,7 +811,6 @@ export class RunAtCursor extends RunOrDebugAtCursor { super({ id: RunAtCursor.ID, title: localize('testing.runAtCursor', "Run Test at Cursor"), - f1: true, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -829,7 +838,6 @@ export class DebugAtCursor extends RunOrDebugAtCursor { super({ id: DebugAtCursor.ID, title: localize('testing.debugAtCursor', "Debug Test at Cursor"), - f1: true, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -852,6 +860,16 @@ export class DebugAtCursor extends RunOrDebugAtCursor { } abstract class RunOrDebugCurrentFile extends Action2 { + constructor(options: IAction2Options) { + super({ + ...options, + menu: { + id: MenuId.CommandPalette, + when: hasAnyTestProvider, + }, + }); + } + /** * @override */ @@ -893,13 +911,16 @@ export class RunCurrentFile extends RunOrDebugCurrentFile { super({ id: RunCurrentFile.ID, title: localize('testing.runCurrentFile', "Run Tests in Current File"), - f1: true, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, when: EditorContextKeys.editorTextFocus, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.US_SEMICOLON, KeyCode.KEY_F), }, + menu: { + id: MenuId.CommandPalette, + when: hasAnyTestProvider, + }, }); } @@ -921,7 +942,6 @@ export class DebugCurrentFile extends RunOrDebugCurrentFile { super({ id: DebugCurrentFile.ID, title: localize('testing.debugCurrentFile', "Debug Tests in Current File"), - f1: true, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -984,6 +1004,15 @@ abstract class RunOrDebugExtsByPath extends Action2 { } abstract class RunOrDebugFailedTests extends RunOrDebugExtsByPath { + constructor(options: IAction2Options) { + super({ + ...options, + menu: { + id: MenuId.CommandPalette, + when: hasAnyTestProvider, + }, + }); + } /** * @inheritdoc */ @@ -1008,6 +1037,19 @@ abstract class RunOrDebugFailedTests extends RunOrDebugExtsByPath { } abstract class RunOrDebugLastRun extends RunOrDebugExtsByPath { + constructor(options: IAction2Options) { + super({ + ...options, + menu: { + id: MenuId.CommandPalette, + when: ContextKeyAndExpr.create([ + hasAnyTestProvider, + TestingContextKeys.hasAnyResults.isEqualTo(true), + ]), + }, + }); + } + /** * @inheritdoc */ @@ -1032,7 +1074,6 @@ export class ReRunFailedTests extends RunOrDebugFailedTests { super({ id: ReRunFailedTests.ID, title: localize('testing.reRunFailTests', "Rerun Failed Tests"), - f1: true, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1059,7 +1100,6 @@ export class DebugFailedTests extends RunOrDebugFailedTests { super({ id: DebugFailedTests.ID, title: localize('testing.debugFailTests', "Debug Failed Tests"), - f1: true, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1086,7 +1126,6 @@ export class ReRunLastRun extends RunOrDebugLastRun { super({ id: ReRunLastRun.ID, title: localize('testing.reRunLastRun', "Rerun Last Run"), - f1: true, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1113,7 +1152,6 @@ export class DebugLastRun extends RunOrDebugLastRun { super({ id: DebugLastRun.ID, title: localize('testing.debugLastRun', "Debug Last Run"), - f1: true, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1140,7 +1178,6 @@ export class SearchForTestExtension extends Action2 { super({ id: SearchForTestExtension.ID, title: localize('testing.searchForTestExtension', "Search for Test Extension"), - f1: false, }); } @@ -1158,12 +1195,15 @@ export class OpenOutputPeek extends Action2 { super({ id: OpenOutputPeek.ID, title: localize('testing.openOutputPeek', "Peek Output"), - f1: true, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.US_SEMICOLON, KeyCode.KEY_M), }, + menu: { + id: MenuId.CommandPalette, + when: TestingContextKeys.hasAnyResults.isEqualTo(true), + }, }); } diff --git a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts index a110688e378..f18c8fd63ed 100644 --- a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts @@ -37,11 +37,11 @@ import { buildTestUri, TestUriType } from 'vs/workbench/contrib/testing/common/t import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; import { IMainThreadTestCollection, ITestService } from 'vs/workbench/contrib/testing/common/testService'; -function isInDiffEditor(codeEditorService: ICodeEditorService, codeEditor: ICodeEditor): boolean { +function isOriginalInDiffEditor(codeEditorService: ICodeEditorService, codeEditor: ICodeEditor): boolean { const diffEditors = codeEditorService.listDiffEditors(); for (const diffEditor of diffEditors) { - if (diffEditor.getModifiedEditor() === codeEditor || diffEditor.getOriginalEditor() === codeEditor) { + if (diffEditor.getOriginalEditor() === codeEditor) { return true; } } @@ -130,7 +130,7 @@ export class TestingDecorations extends Disposable implements IEditorContributio } private attachModel(uri?: URI) { - if (isInDiffEditor(this.codeEditorService, this.editor)) { + if (isOriginalInDiffEditor(this.codeEditorService, this.editor)) { uri = undefined; } diff --git a/src/vs/workbench/contrib/testing/common/testResultService.ts b/src/vs/workbench/contrib/testing/common/testResultService.ts index ea4070e565f..688c80c38c0 100644 --- a/src/vs/workbench/contrib/testing/common/testResultService.ts +++ b/src/vs/workbench/contrib/testing/common/testResultService.ts @@ -95,6 +95,7 @@ export class TestResultService implements ITestResultService { public readonly onTestChanged = this.testChangeEmitter.event; private readonly isRunning: IContextKey; + private readonly hasAnyResults: IContextKey; private readonly loadResults = once(() => this.storage.read().then(loaded => { for (let i = loaded.length - 1; i >= 0; i--) { this.push(loaded[i]); @@ -108,6 +109,7 @@ export class TestResultService implements ITestResultService { @ITestResultStorage private readonly storage: ITestResultStorage, ) { this.isRunning = TestingContextKeys.isRunning.bindTo(contextKeyService); + this.hasAnyResults = TestingContextKeys.hasAnyResults.bindTo(contextKeyService); } /** @@ -148,6 +150,7 @@ export class TestResultService implements ITestResultService { this.persistScheduler.schedule(); } + this.hasAnyResults.set(true); if (this.results.length > RETAIN_MAX_RESULTS) { this.results.pop(); } @@ -200,6 +203,7 @@ export class TestResultService implements ITestResultService { this._results = keep; this.persistScheduler.schedule(); + this.hasAnyResults.set(false); this.changeResultEmitter.fire({ removed }); } diff --git a/src/vs/workbench/contrib/testing/common/testingContextKeys.ts b/src/vs/workbench/contrib/testing/common/testingContextKeys.ts index b373dc35c01..b9bfb497595 100644 --- a/src/vs/workbench/contrib/testing/common/testingContextKeys.ts +++ b/src/vs/workbench/contrib/testing/common/testingContextKeys.ts @@ -12,6 +12,7 @@ export namespace TestingContextKeys { export const providerCount = new RawContextKey('testing.providerCount', 0); export const hasDebuggableTests = new RawContextKey('testing.hasDebuggableTests', false); export const hasRunnableTests = new RawContextKey('testing.hasRunnableTests', false); + export const hasAnyResults = new RawContextKey('testing.hasAnyResults', false); export const viewMode = new RawContextKey('testing.explorerViewMode', TestExplorerViewMode.List); export const viewSorting = new RawContextKey('testing.explorerViewSorting', TestExplorerViewSorting.ByLocation); export const isRunning = new RawContextKey('testing.isRunning', false); diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css index d6a257a8632..ada1f483fcc 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css @@ -515,6 +515,7 @@ color: inherit; text-align: left; padding: 16px; + font-size: 13px; margin: 1px 0; /* makes room for focus border */ font-family: inherit; } @@ -524,6 +525,7 @@ .monaco-workbench .part.editor > .content .gettingStartedContainer button:focus { outline-style: solid; + outline-width: 1px; } .monaco-workbench .part.editor > .content .gettingStartedContainer .prev-button.button-link { diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index 0824a9edbbb..150af22a056 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -49,6 +49,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { GettingStartedInput, gettingStartedInputTypeId } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedInput'; import { welcomeButtonBackground, welcomeButtonHoverBackground, welcomePageBackground } from 'vs/workbench/contrib/welcome/page/browser/welcomePageColors'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; const configurationKey = 'workbench.startupEditor'; @@ -68,6 +69,7 @@ export class WelcomePageContribution implements IWorkbenchContribution { @ILifecycleService private readonly lifecycleService: ILifecycleService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @ICommandService private readonly commandService: ICommandService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService ) { // Run immediately to minimize time spent waiting for exp service. @@ -84,7 +86,7 @@ export class WelcomePageContribution implements IWorkbenchContribution { } private async run() { - const enabled = isWelcomePageEnabled(this.configurationService, this.contextService); + const enabled = isWelcomePageEnabled(this.configurationService, this.contextService, this.environmentService); if (enabled && this.lifecycleService.startupKind !== StartupKind.ReloadedWindow) { const hasBackups = await this.workingCopyBackupService.hasBackups(); if (hasBackups) { return; } @@ -152,7 +154,11 @@ export class WelcomePageContribution implements IWorkbenchContribution { } } -function isWelcomePageEnabled(configurationService: IConfigurationService, contextService: IWorkspaceContextService) { +function isWelcomePageEnabled(configurationService: IConfigurationService, contextService: IWorkspaceContextService, environmentService: IWorkbenchEnvironmentService) { + if (environmentService.skipWelcome) { + return false; + } + const startupEditor = configurationService.inspect(configurationKey); if (!startupEditor.userValue && !startupEditor.workspaceValue) { const welcomeEnabled = configurationService.inspect(oldConfigurationKey); diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts index 676462d52b6..58982ff59cb 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts @@ -34,10 +34,9 @@ import { UILabelProvider } from 'vs/base/common/keybindingLabels'; import { OS, OperatingSystem } from 'vs/base/common/platform'; import { deepClone } from 'vs/base/common/objects'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { Dimension, safeInnerHtml, size } from 'vs/base/browser/dom'; +import { addDisposableListener, Dimension, safeInnerHtml, size } from 'vs/base/browser/dom'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { domEvent } from 'vs/base/browser/event'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; @@ -417,7 +416,7 @@ export class WalkThroughPart extends EditorPane { this.loadTextEditorViewState(input); this.updatedScrollPosition(); this.contentDisposables.push(Gesture.addTarget(innerContent)); - this.contentDisposables.push(domEvent(innerContent, TouchEventType.Change)(e => this.onTouchChange(e as GestureEvent), this, this.disposables)); + this.contentDisposables.push(addDisposableListener(innerContent, TouchEventType.Change, e => this.onTouchChange(e as GestureEvent))); }); } diff --git a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts index ed873c7a9f7..c1cb8189190 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts @@ -374,7 +374,12 @@ export class WorkspaceTrustUXHandler extends Disposable implements IWorkbenchCon ariaLabel = trusted ? localize('status.ariaTrustedWindow', "This window is trusted.") : localize('status.ariaUntrustedWindow', "Restricted Mode: Some features are disabled because this window is not trusted."); toolTip = trusted ? ariaLabel : { - value: localize('status.tooltipUntrustedWindow', "Running in Restricted Mode\n\n\Some [features are disabled](command:{0}) because this [window is not trusted](command:{1}).", LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, MANAGE_TRUST_COMMAND_ID), + value: localize( + { key: 'status.tooltipUntrustedWindow2', comment: ['[abc]({n}) are links. Only translate `features are disabled` and `window is not trusted`. Do not change brackets and parentheses or {n}'] }, + "Running in Restricted Mode\n\nSome [features are disabled]({0}) because this [window is not trusted]({1}).", + `command:${LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID}`, + `command:${MANAGE_TRUST_COMMAND_ID}` + ), isTrusted: true, supportThemeIcons: true }; @@ -384,7 +389,12 @@ export class WorkspaceTrustUXHandler extends Disposable implements IWorkbenchCon ariaLabel = trusted ? localize('status.ariaTrustedFolder', "This folder is trusted.") : localize('status.ariaUntrustedFolder', "Restricted Mode: Some features are disabled because this folder is not trusted."); toolTip = trusted ? ariaLabel : { - value: localize('status.tooltipUntrustedFolder', "Running in Restricted Mode\n\n\Some [features are disabled](command:{0}) because this [folder is not trusted](command:{1}).", LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, MANAGE_TRUST_COMMAND_ID), + value: localize( + { key: 'status.tooltipUntrustedFolder2', comment: ['[abc]({n}) are links. Only translate `features are disabled` and `folder is not trusted`. Do not change brackets and parentheses or {n}'] }, + "Running in Restricted Mode\n\nSome [features are disabled]({0}) because this [folder is not trusted]({1}).", + `command:${LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID}`, + `command:${MANAGE_TRUST_COMMAND_ID}` + ), isTrusted: true, supportThemeIcons: true }; @@ -394,7 +404,12 @@ export class WorkspaceTrustUXHandler extends Disposable implements IWorkbenchCon ariaLabel = trusted ? localize('status.ariaTrustedWorkspace', "This workspace is trusted.") : localize('status.ariaUntrustedWorkspace', "Restricted Mode: Some features are disabled because this workspace is not trusted."); toolTip = trusted ? ariaLabel : { - value: localize('status.tooltipUntrustedWorkspace', "Running in Restricted Mode\n\n\Some [features are disabled](command:{0}) because this [workspace is not trusted](command:{1}).", LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, MANAGE_TRUST_COMMAND_ID), + value: localize( + { key: 'status.tooltipUntrustedWorkspace2', comment: ['[abc]({n}) are links. Only translate `features are disabled` and `workspace is not trusted`. Do not change brackets and parentheses or {n}'] }, + "Running in Restricted Mode\n\nSome [features are disabled]({0}) because this [workspace is not trusted]({1}).", + `command:${LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID}`, + `command:${MANAGE_TRUST_COMMAND_ID}` + ), isTrusted: true, supportThemeIcons: true }; diff --git a/src/vs/workbench/services/editor/common/editorOverrideService.ts b/src/vs/workbench/services/editor/common/editorOverrideService.ts index c2bfe9712ca..41f0725def5 100644 --- a/src/vs/workbench/services/editor/common/editorOverrideService.ts +++ b/src/vs/workbench/services/editor/common/editorOverrideService.ts @@ -171,6 +171,8 @@ export function globMatchesResource(globPattern: string | glob.IRelativePattern, const excludedSchemes = new Set([ Schemas.extension, Schemas.webviewPanel, + Schemas.vscodeWorkspaceTrust, + Schemas.walkThrough ]); // We want to say that the above schemes match no glob patterns if (excludedSchemes.has(resource.scheme)) { diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 00b781462ea..c9aca19246d 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -245,6 +245,7 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment get logExtensionHostCommunication(): boolean { return this.payload?.get('logExtensionHostCommunication') === 'true'; } get skipReleaseNotes(): boolean { return false; } + get skipWelcome(): boolean { return false; } @memoize get disableWorkspaceTrust(): boolean { return true; } diff --git a/src/vs/workbench/services/environment/common/environmentService.ts b/src/vs/workbench/services/environment/common/environmentService.ts index f8950d42da8..61b26abff10 100644 --- a/src/vs/workbench/services/environment/common/environmentService.ts +++ b/src/vs/workbench/services/environment/common/environmentService.ts @@ -38,6 +38,7 @@ export interface IWorkbenchEnvironmentService extends IEnvironmentService { readonly webviewExternalEndpoint: string; readonly skipReleaseNotes: boolean; + readonly skipWelcome: boolean; readonly debugRenderer: boolean; diff --git a/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts b/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts index 948a3aa8935..6ebcaf2855a 100644 --- a/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts +++ b/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts @@ -71,6 +71,9 @@ export class NativeWorkbenchEnvironmentService extends AbstractNativeEnvironment @memoize get skipReleaseNotes(): boolean { return !!this.args['skip-release-notes']; } + @memoize + get skipWelcome(): boolean { return !!this.args['skip-welcome']; } + @memoize get logExtensionHostCommunication(): boolean { return !!this.args.logExtensionHostCommunication; } diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index 7c5c8b61da8..21ab06e1c6a 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -18,7 +18,6 @@ import { IModifierKeyStatus, ModifierKeyEmitter, trackFocus } from 'vs/base/brow import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { domEvent } from 'vs/base/browser/event'; import { memoize } from 'vs/base/common/decorators'; import { parseLineAndColumnAware } from 'vs/base/common/extpath'; import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; @@ -30,6 +29,7 @@ import { getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser import { localize } from 'vs/nls'; import Severity from 'vs/base/common/severity'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { DomEmitter } from 'vs/base/browser/event'; /** * A workspace to open in the workbench can either be: @@ -170,11 +170,12 @@ export class BrowserHostService extends Disposable implements IHostService { @memoize get onDidChangeFocus(): Event { const focusTracker = this._register(trackFocus(window)); + const onVisibilityChange = this._register(new DomEmitter(window.document, 'visibilitychange')); return Event.latch(Event.any( Event.map(focusTracker.onDidFocus, () => this.hasFocus), Event.map(focusTracker.onDidBlur, () => this.hasFocus), - Event.map(domEvent(window.document, 'visibilitychange'), () => this.hasFocus) + Event.map(onVisibilityChange.event, () => this.hasFocus) )); } diff --git a/src/vs/workbench/test/browser/api/extHostTreeViews.test.ts b/src/vs/workbench/test/browser/api/extHostTreeViews.test.ts index 5182f39f390..2cda2972fde 100644 --- a/src/vs/workbench/test/browser/api/extHostTreeViews.test.ts +++ b/src/vs/workbench/test/browser/api/extHostTreeViews.test.ts @@ -15,7 +15,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { MainThreadCommands } from 'vs/workbench/api/browser/mainThreadCommands'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { mock } from 'vs/base/test/common/mock'; -import { TreeItemCollapsibleState, ITreeItem } from 'vs/workbench/common/views'; +import { TreeItemCollapsibleState, ITreeItem, IRevealOptions } from 'vs/workbench/common/views'; import { NullLogService } from 'vs/platform/log/common/log'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import type { IDisposable } from 'vs/base/common/lifecycle'; @@ -35,7 +35,7 @@ suite('ExtHostTreeView', function () { }); } - override $reveal(): Promise { + override $reveal(treeViewId: string, itemInfo: { item: ITreeItem, parentChain: ITreeItem[] } | undefined, options: IRevealOptions): Promise { return Promise.resolve(); } @@ -515,8 +515,8 @@ suite('ExtHostTreeView', function () { .then(() => { assert.ok(revealTarget.calledOnce); assert.deepStrictEqual('treeDataProvider', revealTarget.args[0][0]); - assert.deepStrictEqual(expected.item, removeUnsetKeys(revealTarget.args[0][1].item)); - assert.deepStrictEqual(expected.parentChain, (>(revealTarget.args[0][1].parentChain)).map(arg => removeUnsetKeys(arg))); + assert.deepStrictEqual(expected.item, removeUnsetKeys(revealTarget.args[0][1]!.item)); + assert.deepStrictEqual(expected.parentChain, (>(revealTarget.args[0][1]!.parentChain)).map(arg => removeUnsetKeys(arg))); assert.deepStrictEqual({ select: true, focus: false, expand: false }, revealTarget.args[0][2]); }); }); @@ -534,8 +534,8 @@ suite('ExtHostTreeView', function () { .then(() => { assert.ok(revealTarget.calledOnce); assert.deepStrictEqual('treeDataProvider', revealTarget.args[0][0]); - assert.deepStrictEqual(expected.item, removeUnsetKeys(revealTarget.args[0][1].item)); - assert.deepStrictEqual(expected.parentChain, (>(revealTarget.args[0][1].parentChain)).map(arg => removeUnsetKeys(arg))); + assert.deepStrictEqual(expected.item, removeUnsetKeys(revealTarget.args[0][1]!.item)); + assert.deepStrictEqual(expected.parentChain, (>(revealTarget.args[0][1]!.parentChain)).map(arg => removeUnsetKeys(arg))); assert.deepStrictEqual({ select: true, focus: false, expand: false }, revealTarget.args[0][2]); })); }); @@ -561,8 +561,8 @@ suite('ExtHostTreeView', function () { .then(() => { assert.ok(revealTarget.calledOnce); assert.deepStrictEqual('treeDataProvider', revealTarget.args[0][0]); - assert.deepStrictEqual(expected.item, removeUnsetKeys(revealTarget.args[0][1].item)); - assert.deepStrictEqual(expected.parentChain, (>(revealTarget.args[0][1].parentChain)).map(arg => removeUnsetKeys(arg))); + assert.deepStrictEqual(expected.item, removeUnsetKeys(revealTarget.args[0][1]!.item)); + assert.deepStrictEqual(expected.parentChain, (>(revealTarget.args[0][1]!.parentChain)).map(arg => removeUnsetKeys(arg))); assert.deepStrictEqual({ select: false, focus: false, expand: false }, revealTarget.args[0][2]); }); }); @@ -592,8 +592,8 @@ suite('ExtHostTreeView', function () { .then(() => { assert.ok(revealTarget.calledOnce); assert.deepStrictEqual('treeDataProvider', revealTarget.args[0][0]); - assert.deepStrictEqual(expected.item, removeUnsetKeys(revealTarget.args[0][1].item)); - assert.deepStrictEqual(expected.parentChain, (>(revealTarget.args[0][1].parentChain)).map(arg => removeUnsetKeys(arg))); + assert.deepStrictEqual(expected.item, removeUnsetKeys(revealTarget.args[0][1]!.item)); + assert.deepStrictEqual(expected.parentChain, (>(revealTarget.args[0][1]!.parentChain)).map(arg => removeUnsetKeys(arg))); assert.deepStrictEqual({ select: true, focus: false, expand: false }, revealTarget.args[0][2]); }); }); @@ -633,8 +633,8 @@ suite('ExtHostTreeView', function () { .then(() => { assert.ok(revealTarget.calledOnce); assert.deepStrictEqual('treeDataProvider', revealTarget.args[0][0]); - assert.deepStrictEqual({ handle: '0/0:b/0:bc', label: { label: 'bc' }, collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:b' }, removeUnsetKeys(revealTarget.args[0][1].item)); - assert.deepStrictEqual([{ handle: '0/0:b', label: { label: 'b' }, collapsibleState: TreeItemCollapsibleState.Collapsed }], (>revealTarget.args[0][1].parentChain).map(arg => removeUnsetKeys(arg))); + assert.deepStrictEqual({ handle: '0/0:b/0:bc', label: { label: 'bc' }, collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:b' }, removeUnsetKeys(revealTarget.args[0][1]!.item)); + assert.deepStrictEqual([{ handle: '0/0:b', label: { label: 'b' }, collapsibleState: TreeItemCollapsibleState.Collapsed }], (>revealTarget.args[0][1]!.parentChain).map(arg => removeUnsetKeys(arg))); assert.deepStrictEqual({ select: true, focus: false, expand: false }, revealTarget.args[0][2]); }); }); diff --git a/src/vs/workbench/test/browser/api/extHostTypes.test.ts b/src/vs/workbench/test/browser/api/extHostTypes.test.ts index 3d756f8a8be..dd3ab006228 100644 --- a/src/vs/workbench/test/browser/api/extHostTypes.test.ts +++ b/src/vs/workbench/test/browser/api/extHostTypes.test.ts @@ -8,6 +8,7 @@ import { URI } from 'vs/base/common/uri'; import * as types from 'vs/workbench/api/common/extHostTypes'; import { isWindows } from 'vs/base/common/platform'; import { assertType } from 'vs/base/common/types'; +import { Mimes } from 'vs/base/common/mime'; function assertToJSON(a: any, expected: any) { const raw = JSON.stringify(a); @@ -683,7 +684,7 @@ suite('ExtHostTypes', function () { // --- text item = types.NotebookCellOutputItem.text('Hęłlö'); - assert.strictEqual(item.mime, 'text/plain'); + assert.strictEqual(item.mime, Mimes.text); assert.deepStrictEqual(item.data, new TextEncoder().encode('Hęłlö')); item = types.NotebookCellOutputItem.text('Hęłlö', 'foo/bar'); diff --git a/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts index 00bade60da7..0a9a487b11f 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts @@ -26,6 +26,7 @@ import { TestTextResourcePropertiesService } from 'vs/workbench/test/common/work import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; +import { Mimes } from 'vs/base/common/mime'; suite('EditorModel', () => { @@ -86,7 +87,7 @@ suite('EditorModel', () => { const model = new MyTextEditorModel(modelService, modeService); await model.resolve(); - model.createTextEditorModel(createTextBufferFactory('foo'), null!, 'text/plain'); + model.createTextEditorModel(createTextBufferFactory('foo'), null!, Mimes.text); assert.strictEqual(model.isResolved(), true); model.dispose(); }); diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index 8333471680a..ed64781f1cf 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -135,6 +135,7 @@ export async function spawn(options: SpawnOptions): Promise { const args = [ options.workspacePath, '--skip-release-notes', + '--skip-welcome', '--disable-telemetry', '--no-cached-data', '--disable-updates', diff --git a/test/unit/browser/renderer.html b/test/unit/browser/renderer.html index 4cde5243af3..57d75bd21ba 100644 --- a/test/unit/browser/renderer.html +++ b/test/unit/browser/renderer.html @@ -53,7 +53,8 @@ paths: { vs: new URL(`../../../${!!isBuild ? 'out-build' : 'out'}/vs`, baseUrl).href, assert: new URL('../assert.js', baseUrl).href, - sinon: new URL('../../../node_modules/sinon/pkg/sinon-1.17.7.js', baseUrl).href, + sinon: new URL('../../../node_modules/sinon/pkg/sinon.js', baseUrl).href, + 'sinon-test': new URL('../../../node_modules/sinon-test/dist/sinon-test.js', baseUrl).href, xterm: new URL('../../../node_modules/xterm/lib/xterm.js', baseUrl).href, 'iconv-lite-umd': new URL('../../../node_modules/iconv-lite-umd/lib/iconv-lite-umd.js', baseUrl).href, jschardet: new URL('../../../node_modules/jschardet/dist/jschardet.min.js', baseUrl).href diff --git a/test/unit/node/index.html b/test/unit/node/index.html index 55f9bb0f708..b0b65c6b2c8 100644 --- a/test/unit/node/index.html +++ b/test/unit/node/index.html @@ -17,7 +17,8 @@ baseUrl: '/out', paths: { assert: '/test/unit/assert.js', - sinon: '/node_modules/sinon/pkg/sinon-1.17.7.js' + sinon: '/node_modules/sinon/pkg/sinon.js', + 'sinon-test': '/node_modules/sinon-test/dist/sinon-test.js' } }); diff --git a/yarn.lock b/yarn.lock index ffa8c1fac3c..baaa9a3dd6a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -338,6 +338,34 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== +"@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.3": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" + integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^7.0.4", "@sinonjs/fake-timers@^7.1.0": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz#2524eae70c4910edccf99b2f4e6efc5894aff7b5" + integrity sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@sinonjs/samsam@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-6.0.2.tgz#a0117d823260f282c04bff5f8704bdc2ac6910bb" + integrity sha512-jxPRPp9n93ci7b8hMfJOFDPRLFYadN6FSpeROFTR4UNF4i5b+EK6m4QXPO46BDhFgRy1JuS87zAnFOzCUwMJcQ== + dependencies: + "@sinonjs/commons" "^1.6.0" + lodash.get "^4.4.2" + type-detect "^4.0.8" + +"@sinonjs/text-encoding@^0.7.1": + version "0.7.1" + resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" + integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== + "@szmarczak/http-timer@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" @@ -350,14 +378,15 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== -"@ts-morph/common@~0.9.0": - version "0.9.0" - resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.9.0.tgz#a306355bad82cff22a1881f7f2f2c710bbb4d69d" - integrity sha512-yPcW6koNVK1hVKUu+KhPzhfgMb0uwzr2FewF+q8kxLerl0b+YZwmjvFMU2qbIawytIHT2VBI4bi+C09EFPB4aw== +"@ts-morph/common@~0.10.0": + version "0.10.0" + resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.10.0.tgz#d032ea6f6d4115b72fa50ad56009baebcc1e71b8" + integrity sha512-6wC+CovwzxLP+bQZcqHJEbZ7ViaIfsid8VzsVjJRkdfCQ8C8K5mm1+9/wkgmn814BPATtgSgFuDmVJnIb8/leg== dependencies: fast-glob "^3.2.5" minimatch "^3.0.4" mkdirp "^1.0.4" + path-browserify "^1.0.1" "@types/anymatch@*": version "1.3.1" @@ -519,10 +548,19 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45" integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ== -"@types/sinon@^1.16.36": - version "1.16.36" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-1.16.36.tgz#74bb6ed7928597c1b3fb1b009005e94dc6eae357" - integrity sha1-dLtu15KFl8Gz+xsAkAXpTcbq41c= +"@types/sinon-test@^2.4.2": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@types/sinon-test/-/sinon-test-2.4.2.tgz#f55bdf5486e7b7a4dd7257789fcc2b7b125c4164" + integrity sha512-3BX9mk5+o//Xzs5N4bFYxPT+QlPLrqbyNfDWkIGtk9pVIp2Nl8ctsIGXsY3F01DsCd1Zlin3FqAk6V5XqkCyJA== + dependencies: + "@types/sinon" "*" + +"@types/sinon@*", "@types/sinon@^10.0.2": + version "10.0.2" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-10.0.2.tgz#f360d2f189c0fd433d14aeb97b9d705d7e4cc0e4" + integrity sha512-BHn8Bpkapj8Wdfxvh2jWIUoaYB/9/XhsL0oOvBfRagJtKlSl9NWPcFOz2lRukI9szwGxFtYZCTejJSqsGDbdmw== + dependencies: + "@sinonjs/fake-timers" "^7.1.0" "@types/source-list-map@*": version "0.1.2" @@ -1388,9 +1426,9 @@ bach@^1.0.0: now-and-later "^2.0.0" balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== base64-js@^1.0.2, base64-js@^1.2.3, base64-js@^1.3.1: version "1.5.1" @@ -2868,6 +2906,11 @@ diff@4.0.2: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +diff@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" + integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -3934,13 +3977,6 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" -formatio@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.1.1.tgz#5ed3ccd636551097383465d996199100e86161e9" - integrity sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek= - dependencies: - samsam "~1.1" - fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -4028,7 +4064,7 @@ fsevents@~2.3.1: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.1.tgz#b209ab14c61012636c8863507edf7fb68cc54e9f" integrity sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw== -fstream@^1.0.2: +fstream@^1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== @@ -4161,9 +4197,9 @@ glob-parent@^3.0.0, glob-parent@^3.1.0: path-dirname "^1.0.0" glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@^5.1.1, glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" @@ -4210,7 +4246,7 @@ glob-watcher@^5.0.3: normalize-path "^3.0.0" object.defaults "^1.1.0" -glob@7.1.6, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob@7.1.6, glob@^7.1.1, glob@^7.1.2, glob@^7.1.4, glob@^7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -4233,6 +4269,18 @@ glob@^5.0.13, glob@^5.0.3: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.3: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + global-agent@^2.0.2: version "2.1.12" resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-2.1.12.tgz#e4ae3812b731a9e81cbf825f9377ef450a8e4195" @@ -4347,12 +4395,12 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@4.2.6, graceful-fs@^4.2.4: +graceful-fs@4.2.6, graceful-fs@^4.1.2, graceful-fs@^4.2.4: version "4.2.6" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== -graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0: +graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.6, graceful-fs@^4.2.0: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== @@ -5665,6 +5713,11 @@ just-debounce@^1.0.0: resolved "https://registry.yarnpkg.com/just-debounce/-/just-debounce-1.0.0.tgz#87fccfaeffc0b68cd19d55f6722943f929ea35ea" integrity sha1-h/zPrv/AtozRnVX2cilD+SnqNeo= +just-extend@^4.0.2: + version "4.2.1" + resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744" + integrity sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg== + keytar@7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/keytar/-/keytar-7.2.0.tgz#4db2bec4f9700743ffd9eda22eebb658965c8440" @@ -5862,6 +5915,11 @@ lodash.clone@^4.3.2: resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + lodash.isequal@^4.0.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" @@ -5909,11 +5967,6 @@ log-symbols@4.0.0: dependencies: chalk "^4.0.0" -lolex@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.3.2.tgz#7c3da62ffcb30f0f5a80a2566ca24e45d8a01f31" - integrity sha1-fD2mL/yzDw9agKJWbKJORdigHzE= - lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" @@ -6514,6 +6567,17 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +nise@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.0.tgz#713ef3ed138252daef20ec035ab62b7a28be645c" + integrity sha512-W5WlHu+wvo3PaKLsJJkgPup2LrsXCcm7AWwyNZkUnn5rwPkuPBi3Iwk5SQtN0mv+K65k7nKKjwNQ30wg3wLAQQ== + dependencies: + "@sinonjs/commons" "^1.7.0" + "@sinonjs/fake-timers" "^7.0.4" + "@sinonjs/text-encoding" "^0.7.1" + just-extend "^4.0.2" + path-to-regexp "^1.7.0" + node-abi@^2.21.0: version "2.21.0" resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.21.0.tgz#c2dc9ebad6f4f53d6ea9b531e7b8faad81041d48" @@ -7088,6 +7152,11 @@ path-browserify@0.0.1: resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== +path-browserify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + path-dirname@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" @@ -7142,6 +7211,13 @@ path-root@^0.1.1: dependencies: path-root-regex "^0.1.0" +path-to-regexp@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + dependencies: + isarray "0.0.1" + path-type@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -8359,16 +8435,6 @@ safe-regex@^1.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -samsam@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567" - integrity sha1-vsEf3IOp/aBjQBIQ5AF2wwJNFWc= - -samsam@~1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.3.tgz#9f5087419b4d091f232571e7fa52e90b0f552621" - integrity sha1-n1CHQZtNCR8jJXHn+lLpCw9VJiE= - sax@0.5.x: version "0.5.8" resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1" @@ -8544,15 +8610,22 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" -sinon@^1.17.2: - version "1.17.7" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-1.17.7.tgz#4542a4f49ba0c45c05eb2e9dd9d203e2b8efe0bf" - integrity sha1-RUKk9JugxFwF6y6d2dID4rjv4L8= +sinon-test@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/sinon-test/-/sinon-test-3.1.0.tgz#25a3f4d9a9deb172252407041d577d67b73fefd5" + integrity sha512-aGQwq6Xl9eJg/8Ugv4Ko4LQWUqjwRYNI8UtxnKa9hmcMEz3HBTR3nnzYrbW4isuRLsJWFuJTUcPGuz7f4XvODg== + +sinon@^11.1.1: + version "11.1.1" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-11.1.1.tgz#99a295a8b6f0fadbbb7e004076f3ae54fc6eab91" + integrity sha512-ZSSmlkSyhUWbkF01Z9tEbxZLF/5tRC9eojCdFh33gtQaP7ITQVaMWQHGuFM7Cuf/KEfihuh1tTl3/ABju3AQMg== dependencies: - formatio "1.1.1" - lolex "1.3.2" - samsam "1.1.2" - util ">=0.10.3 <1" + "@sinonjs/commons" "^1.8.3" + "@sinonjs/fake-timers" "^7.1.0" + "@sinonjs/samsam" "^6.0.2" + diff "^5.0.0" + nise "^5.1.0" + supports-color "^7.2.0" slash@^3.0.0: version "3.0.0" @@ -9063,7 +9136,7 @@ sumchecker@^3.0.1: dependencies: debug "^4.1.0" -supports-color@7.2.0: +supports-color@7.2.0, supports-color@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -9160,12 +9233,12 @@ tar-stream@^2.1.4: readable-stream "^3.1.1" tar@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" - integrity sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE= + version "2.2.2" + resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40" + integrity sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA== dependencies: block-stream "*" - fstream "^1.0.2" + fstream "^1.0.12" inherits "2" tar@^6.0.2: @@ -9435,12 +9508,12 @@ ts-loader@^6.2.1: micromatch "^4.0.0" semver "^6.0.0" -ts-morph@^10.0.2: - version "10.0.2" - resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-10.0.2.tgz#292418207db467326231b2be92828b5e295e7946" - integrity sha512-TVuIfEqtr9dW25K3Jajqpqx7t/zLRFxKu2rXQZSDjTm4MO4lfmuj1hn8WEryjeDDBFcNOCi+yOmYUYR4HucrAg== +ts-morph@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-11.0.0.tgz#511b3caa194739fef0619367f8e65de9b475e1d4" + integrity sha512-u5y0jaft5c0sRFnU0K8cZhhsvPUtXjZK5L31JLIhP17qcqo9MDjwsSYLg3UryQDzlktv8wyf/UtoqpFLDYHijg== dependencies: - "@ts-morph/common" "~0.9.0" + "@ts-morph/common" "~0.10.0" code-block-writer "^10.1.1" tsec@0.1.4: @@ -9497,6 +9570,11 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" +type-detect@4.0.8, type-detect@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + type-fest@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" @@ -9708,7 +9786,7 @@ util.promisify@~1.0.0: has-symbols "^1.0.1" object.getownpropertydescriptors "^2.1.0" -util@0.10.3, "util@>=0.10.3 <1": +util@0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= @@ -9965,14 +10043,6 @@ vscode-regexpp@^3.1.0: resolved "https://registry.yarnpkg.com/vscode-regexpp/-/vscode-regexpp-3.1.0.tgz#42d059b6fffe99bd42939c0d013f632f0cad823f" integrity sha512-pqtN65VC1jRLawfluX4Y80MMG0DHJydWhe5ZwMHewZD6sys4LbU6lHwFAHxeuaVE6Y6+xZOtAw+9hvq7/0ejkg== -vscode-ripgrep@^1.11.2: - version "1.11.2" - resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.11.2.tgz#a1d9c717a20f625b7e14680cc7db25ffafd132d4" - integrity sha512-qMARNpPh/m6h9NbAQs4unGUnlAP2vrxt3a3nzbscrJcd5X9onoSdAYKG9vCkcxFJtOcQQm44a2Vf369mrrz8Sw== - dependencies: - https-proxy-agent "^4.0.0" - proxy-from-env "^1.1.0" - vscode-ripgrep@^1.11.3: version "1.11.3" resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.11.3.tgz#a997f4f4535dfeb9d775f04053c1247454d7a37a" @@ -9988,14 +10058,14 @@ vscode-sqlite3@4.0.11: dependencies: nan "^2.14.0" -vscode-telemetry-extractor@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/vscode-telemetry-extractor/-/vscode-telemetry-extractor-1.7.0.tgz#f99b1a90a4cad0f75454f2f57615a155e55eb960" - integrity sha512-UC/N/uqPuQIuNnXg52XJnejeId2+Nuq04rj4H1rSZsqj9a56pigs6ogLPdZSi+OVLI21LU9PnJ/ZKrBrLm1roA== +vscode-telemetry-extractor@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/vscode-telemetry-extractor/-/vscode-telemetry-extractor-1.8.0.tgz#5562106fe2eebfce0593f336c91f5a5ddc154cee" + integrity sha512-jWe+caeLyB/F3V0EqsdkCC98wXx9+XLbm6EoPngz0sC4GOM7lcDSnVhUXzrIhZD/TSRPSPGlxp5r4/CrvhbmMQ== dependencies: command-line-args "^5.1.1" - ts-morph "^10.0.2" - vscode-ripgrep "^1.11.2" + ts-morph "^11.0.0" + vscode-ripgrep "^1.11.3" vscode-textmate@5.4.0: version "5.4.0"