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/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 15120a28b2a..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" }, 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/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/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 5369c742f31..14843de6649 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -9930,7 +9930,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. @@ -9940,7 +9940,7 @@ declare module 'vscode' { /** * An event signaling when the selected items have changed. */ - readonly onDidChangeSelection: Event; + readonly onDidChangeSelection: Event; } /** @@ -11455,7 +11455,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; } @@ -11523,7 +11523,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. @@ -11561,7 +11561,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 { @@ -11672,7 +11672,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. @@ -12168,7 +12168,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. @@ -13558,17 +13558,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/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 ed30c969a70..ecf921e4544 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'; @@ -3072,7 +3072,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/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/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/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/extensions/browser/abstractRuntimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts index ba3363c265b..fb02163cf28 100644 --- a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts @@ -14,7 +14,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IExtensionService, IExtensionsStatus, IExtensionHostProfile } from 'vs/workbench/services/extensions/common/extensions'; import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; -import { append, $, reset, Dimension, clearNode } from 'vs/base/browser/dom'; +import { append, $, reset, Dimension, clearNode, addDisposableListener } from 'vs/base/browser/dom'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -22,7 +22,6 @@ import { EnablementState } from 'vs/workbench/services/extensionManagement/commo import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { memoize } from 'vs/base/common/decorators'; import { isNonEmptyArray } from 'vs/base/common/arrays'; -import { Event } from 'vs/base/common/event'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -32,7 +31,6 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio import { Schemas } from 'vs/base/common/network'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; -import { domEvent } from 'vs/base/browser/event'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/common/runtimeExtensionsInput'; @@ -267,8 +265,7 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { data.root.classList.toggle('odd', index % 2 === 1); - const onError = Event.once(domEvent(data.icon, 'error')); - onError(() => 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/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 58f02ff1473..5502c274095 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 5f55e4926fe..c4774f45e6f 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 6d267dcabe4..d3115a4024e 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -826,7 +826,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 78fc3ff7654..6a8e0743b11 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts @@ -183,7 +183,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 2e6964f9de6..f595c7840d8 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -683,7 +683,7 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re } } - for (const cell of event.data.markdownPreviews) { + for (const cell of event.data.markupCells) { const container = document.getElementById(cell.id); if (container) { container.style.top = `${cell.top}px`; @@ -792,12 +792,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); - } - + notebookDocument.toggleDragDropEnabled(currentOptions.dragAndDropEnabled); break; } }); @@ -1014,11 +1009,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 { @@ -1076,13 +1071,19 @@ 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); + } + } }(); 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) { @@ -1100,6 +1101,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(); @@ -1158,18 +1160,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); }); } @@ -1226,6 +1226,16 @@ 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'); + } + } } vscode.postMessage({ @@ -1233,16 +1243,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 @@ -1254,13 +1254,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 78cb628688a..669b976936c 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 3e612b388c9..324596338b7 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', valueBytes: [3] }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 3 }], + ['x', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, valueBytes: [3] }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 3 }], ], [ - ['y', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: 'text/plain', valueBytes: [3] }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 3 }], + ['y', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, valueBytes: [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', valueBytes: [5] }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 5 }], + ['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, valueBytes: [5] }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 5 }], ['', 'javascript', CellKind.Code, [], {}] ], [ - ['x', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: 'text/plain', valueBytes: [3] }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 3 }], + ['x', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, valueBytes: [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', valueBytes: [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', valueBytes: [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, valueBytes: [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, valueBytes: [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', valueBytes: [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', valueBytes: [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, valueBytes: [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, valueBytes: [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', valueBytes: [3] }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 1 }], + ['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, valueBytes: [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', valueBytes: [3] }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 1 }] + ['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, valueBytes: [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', valueBytes: [3] }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 1 }], + ['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, valueBytes: [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', valueBytes: [5] }] }], {}], + ['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, valueBytes: [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', valueBytes: [3] }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 1 }], + ['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, valueBytes: [3] }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 1 }], ['x = 5', 'javascript', CellKind.Code, [], {}], - ['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: 'text/plain', valueBytes: [5] }] }], {}], + ['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, valueBytes: [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 7e73ddcac71..b08ff6b1e85 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 af1be838a68..e9c802d63c1 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, NotebookDataDto, TransientOptions } from 'vs/workbench/contri 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 1d6770c99d7..bd744bb633d 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 3e799efe395..2e4413b0c84 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', valueBytes: valueBytesFromString('_Hello_') }] + outputs: [{ mime: Mimes.markdown, valueBytes: valueBytesFromString('_Hello_') }] }] }], true, undefined, () => undefined, undefined); @@ -203,7 +204,7 @@ suite('NotebookTextModel', () => { append: true, outputs: [{ outputId: 'someId2', - outputs: [{ mime: 'text/markdown', valueBytes: valueBytesFromString('_Hello2_') }] + outputs: [{ mime: Mimes.markdown, valueBytes: valueBytesFromString('_Hello2_') }] }] }], true, undefined, () => undefined, undefined); @@ -219,7 +220,7 @@ suite('NotebookTextModel', () => { editType: CellEditType.Output, outputs: [{ outputId: 'someId3', - outputs: [{ mime: 'text/plain', valueBytes: valueBytesFromString('Last, replaced output') }] + outputs: [{ mime: Mimes.text, valueBytes: valueBytesFromString('Last, replaced output') }] }] }], true, undefined, () => undefined, undefined); @@ -247,7 +248,7 @@ suite('NotebookTextModel', () => { append: true, outputs: [{ outputId: 'append1', - outputs: [{ mime: 'text/markdown', valueBytes: valueBytesFromString('append 1') }] + outputs: [{ mime: Mimes.markdown, valueBytes: valueBytesFromString('append 1') }] }] }, { @@ -256,7 +257,7 @@ suite('NotebookTextModel', () => { append: true, outputs: [{ outputId: 'append2', - outputs: [{ mime: 'text/markdown', valueBytes: valueBytesFromString('append 2') }] + outputs: [{ mime: Mimes.markdown, valueBytes: 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', valueBytes: valueBytesFromString('cba') }, { mime: 'application/foo', valueBytes: valueBytesFromString('cba') }] + outputs: [{ mime: Mimes.text, valueBytes: valueBytesFromString('cba') }, { mime: 'application/foo', valueBytes: valueBytesFromString('cba') }] }] } ]; @@ -614,7 +615,7 @@ suite('NotebookTextModel', () => { { editType: CellEditType.Output, index: 2, append: true, outputs: [{ outputId: 'newOutput', - outputs: [{ mime: 'text/plain', valueBytes: valueBytesFromString('cba') }, { mime: 'application/foo', valueBytes: valueBytesFromString('cba') }] + outputs: [{ mime: Mimes.text, valueBytes: valueBytesFromString('cba') }, { mime: 'application/foo', valueBytes: valueBytesFromString('cba') }] }] } ]; @@ -641,7 +642,7 @@ suite('NotebookTextModel', () => { { editType: CellEditType.Output, index: 1, append: true, outputs: [{ outputId: 'newOutput', - outputs: [{ mime: 'text/plain', valueBytes: valueBytesFromString('cba') }, { mime: 'application/foo', valueBytes: valueBytesFromString('cba') }] + outputs: [{ mime: Mimes.text, valueBytes: valueBytesFromString('cba') }, { mime: 'application/foo', valueBytes: valueBytesFromString('cba') }] }] }, { @@ -650,7 +651,7 @@ suite('NotebookTextModel', () => { { editType: CellEditType.Output, index: 1, append: true, outputs: [{ outputId: 'newOutput2', - outputs: [{ mime: 'text/plain', valueBytes: valueBytesFromString('cba') }, { mime: 'application/foo', valueBytes: valueBytesFromString('cba') }] + outputs: [{ mime: Mimes.text, valueBytes: valueBytesFromString('cba') }, { mime: 'application/foo', valueBytes: valueBytesFromString('cba') }] }] } ]; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 188d5da6875..655fa611321 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,7 +32,7 @@ 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 { 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'; @@ -342,38 +342,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) { @@ -1361,15 +1333,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}`) - ]) } }); } @@ -1578,16 +1541,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) { @@ -1682,15 +1636,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/terminalMenus.ts b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts index e00ba01073e..659322dfb40 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts @@ -3,6 +3,7 @@ * 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'; @@ -35,7 +36,8 @@ export function setupTerminalMenus(): void { MenuRegistry.appendMenuItems( [ { - id: MenuId.MenubarTerminalMenu, item: { + id: MenuId.MenubarTerminalMenu, + item: { group: TerminalMenuBarGroup.Create, command: { id: TerminalCommandId.New, @@ -45,7 +47,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.MenubarTerminalMenu, item: { + id: MenuId.MenubarTerminalMenu, + item: { group: TerminalMenuBarGroup.Create, command: { id: TerminalCommandId.Split, @@ -57,7 +60,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.MenubarTerminalMenu, item: { + id: MenuId.MenubarTerminalMenu, + item: { group: TerminalMenuBarGroup.Run, command: { id: TerminalCommandId.RunActiveFile, @@ -68,7 +72,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.MenubarTerminalMenu, item: { + id: MenuId.MenubarTerminalMenu, + item: { group: TerminalMenuBarGroup.Run, command: { id: TerminalCommandId.RunSelectedText, @@ -84,7 +89,8 @@ export function setupTerminalMenus(): void { MenuRegistry.appendMenuItems( [ { - id: MenuId.TerminalInstanceContext, item: { + id: MenuId.TerminalInstanceContext, + item: { group: ContextMenuGroup.Create, command: { id: TerminalCommandId.Split, @@ -93,7 +99,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.TerminalInstanceContext, item: { + id: MenuId.TerminalInstanceContext, + item: { command: { id: TerminalCommandId.New, title: localize('workbench.action.terminal.new.short', "New Terminal") @@ -102,7 +109,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.TerminalInstanceContext, item: { + id: MenuId.TerminalInstanceContext, + item: { command: { id: TerminalCommandId.Kill, title: terminalStrings.kill.value @@ -111,7 +119,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.TerminalInstanceContext, item: { + id: MenuId.TerminalInstanceContext, + item: { command: { id: TerminalCommandId.CopySelection, title: localize('workbench.action.terminal.copySelection.short', "Copy") @@ -121,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") @@ -131,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") @@ -140,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") @@ -150,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"), @@ -165,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") @@ -174,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") @@ -188,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' } @@ -197,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") @@ -211,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' } @@ -223,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) + ]) + } } ] ); @@ -230,7 +328,8 @@ export function setupTerminalMenus(): void { MenuRegistry.appendMenuItems( [ { - id: MenuId.TerminalInlineTabContext, item: { + id: MenuId.TerminalInlineTabContext, + item: { group: ContextMenuGroup.Create, command: { id: TerminalCommandId.Split, @@ -239,7 +338,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.TerminalInlineTabContext, item: { + id: MenuId.TerminalInlineTabContext, + item: { command: { id: TerminalCommandId.ChangeIcon, title: localize('workbench.action.terminal.changeIcon', "Change Icon...") @@ -249,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...") @@ -259,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...") @@ -268,7 +370,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.TerminalInlineTabContext, item: { + id: MenuId.TerminalInlineTabContext, + item: { command: { id: TerminalCommandId.Kill, title: terminalStrings.kill.value @@ -282,7 +385,8 @@ export function setupTerminalMenus(): void { MenuRegistry.appendMenuItems( [ { - id: MenuId.TerminalTabContext, item: { + id: MenuId.TerminalTabContext, + item: { command: { id: TerminalCommandId.SplitInstance, title: terminalStrings.split.value, @@ -291,7 +395,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.TerminalTabContext, item: { + id: MenuId.TerminalTabContext, + item: { command: { id: TerminalCommandId.RenameInstance, title: localize('workbench.action.terminal.renameInstance', "Rename...") @@ -300,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...") @@ -309,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...") @@ -318,7 +425,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.TerminalTabContext, item: { + id: MenuId.TerminalTabContext, + item: { group: ContextMenuGroup.Config, command: { id: TerminalCommandId.JoinInstance, @@ -328,7 +436,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.TerminalTabContext, item: { + id: MenuId.TerminalTabContext, + item: { group: ContextMenuGroup.Config, command: { id: TerminalCommandId.UnsplitInstance, @@ -338,7 +447,8 @@ export function setupTerminalMenus(): void { } }, { - id: MenuId.TerminalTabContext, item: { + id: MenuId.TerminalTabContext, + item: { command: { id: TerminalCommandId.KillInstance, title: terminalStrings.kill.value diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 4069a5b1cbf..bc61666abbd 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -62,7 +62,7 @@ export class TerminalService implements ITerminalService { private _linkProviderDisposables: Map = 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; @@ -208,7 +208,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 @@ -374,12 +374,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 { @@ -439,7 +438,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) @@ -447,7 +446,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) @@ -455,7 +454,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 { 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 fff953de11b..854fb806931 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalStrings.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalStrings.ts @@ -17,6 +17,10 @@ export function formatMessageForTerminal(message: string, excludeLeadingNewLine: * 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', 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/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/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/yarn.lock b/yarn.lock index 1f35e720d72..e6485be780e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4197,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"