From e1becd2eb899b7da53bc7c8d0a6bdf12587a69ba Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 12 Jul 2023 12:49:48 +0200 Subject: [PATCH 01/27] cleaning the code, it works now --- .../lib/stylelint/vscode-known-variables.json | 2 ++ src/vs/base/browser/ui/hover/hover.css | 3 +- .../contrib/hover/browser/contentHover.ts | 30 +++++++++++++++++-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 67ec595ed30..7ff08b74263 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -306,6 +306,8 @@ "--vscode-extensionIcon-verifiedForeground", "--vscode-focusBorder", "--vscode-foreground", + "--vscode-hover-whiteSpace", + "--vscode-hoverSource-whiteSpace", "--vscode-icon-foreground", "--vscode-inlineChat-background", "--vscode-inlineChat-border", diff --git a/src/vs/base/browser/ui/hover/hover.css b/src/vs/base/browser/ui/hover/hover.css index 0ce58199354..68907c6a062 100644 --- a/src/vs/base/browser/ui/hover/hover.css +++ b/src/vs/base/browser/ui/hover/hover.css @@ -12,6 +12,7 @@ box-sizing: border-box; animation: fadein 100ms linear; line-height: 1.5em; + white-space: var(--vscode-hover-whiteSpace); } .monaco-hover.hidden { @@ -105,7 +106,7 @@ } .monaco-hover .monaco-tokenized-source { - white-space: pre-wrap; + white-space: var(--vscode-hoverSource-whiteSpace); } .monaco-hover .hover-row.status-bar { diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index e284a838116..885b1f41c7d 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -459,6 +459,7 @@ export class ContentHoverWidget extends ResizableContentWidget { private _visibleData: ContentHoverVisibleData | undefined; private _positionPreference: ContentWidgetPositionPreference | undefined; + private _initialWidth: number | undefined; private readonly _hover: HoverWidget = this._register(new HoverWidget()); private readonly _hoverVisibleKey: IContextKey; @@ -612,13 +613,29 @@ export class ContentHoverWidget extends ResizableContentWidget { return Math.min(availableSpace, maximumHeight); } + private _isHoverTextOverflowing(): boolean { + let overflowing = false; + Array.from(this._hover.contentsDomNode.children).forEach((hoverPart) => { + overflowing = overflowing || hoverPart.scrollWidth > hoverPart.clientWidth; + }); + return overflowing; + } + private _findMaximumRenderingWidth(): number | undefined { if (!this._editor || !this._editor.hasModel()) { return; } - const bodyBoxWidth = dom.getClientArea(document.body).width; - const horizontalPadding = 14; - return bodyBoxWidth - horizontalPadding; + this._setWhiteSpaceProperties('nowrap', 'nowrap'); + const overflowing = this._isHoverTextOverflowing(); + this._setWhiteSpaceProperties('normal', 'pre-wrap'); + + if (overflowing || this._initialWidth && this._hover.containerDomNode.clientWidth < this._initialWidth) { + const bodyBoxWidth = dom.getClientArea(document.body).width; + const horizontalPadding = 14; + return bodyBoxWidth - horizontalPadding; + } else { + return this._hover.containerDomNode.clientWidth + 2; + } } public isMouseGettingCloser(posx: number, posy: number): boolean { @@ -707,10 +724,16 @@ export class ContentHoverWidget extends ResizableContentWidget { }; } + private _setWhiteSpaceProperties(hoverWhiteSpace: 'normal' | 'nowrap', hoverSourceWhiteSpace: 'pre-wrap' | 'nowrap') { + this._hover.containerDomNode.style.setProperty('--vscode-hover-whiteSpace', hoverWhiteSpace); + this._hover.containerDomNode.style.setProperty('--vscode-hoverSource-whiteSpace', hoverSourceWhiteSpace); + } + public showAt(node: DocumentFragment, hoverData: ContentHoverVisibleData): void { if (!this._editor || !this._editor.hasModel()) { return; } + this._setWhiteSpaceProperties('normal', 'pre-wrap'); this._render(node, hoverData); const widgetHeight = dom.getTotalHeight(this._hover.containerDomNode); const widgetPosition = hoverData.showAtPosition; @@ -774,6 +797,7 @@ export class ContentHoverWidget extends ResizableContentWidget { this._adjustHoverHeightForScrollbar(height); } this._layoutContentWidget(); + this._initialWidth = this._hover.containerDomNode.clientWidth; } public focus(): void { From 101c247b5bb061d9274509745175b5cd5a2bb840 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 12 Jul 2023 15:12:37 +0200 Subject: [PATCH 02/27] commiting in order to retrigger the checks on github --- src/vs/editor/contrib/hover/browser/contentHover.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index 885b1f41c7d..12c126e30bd 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -604,8 +604,8 @@ export class ContentHoverWidget extends ResizableContentWidget { } // Padding needed in order to stop the resizing down to a smaller height let maximumHeight = CONTAINER_HEIGHT_PADDING; - Array.from(this._hover.contentsDomNode.children).forEach((hoverPart) => { - maximumHeight += hoverPart.clientHeight; + Array.from(this._hover.contentsDomNode.children).forEach((hoverElement) => { + maximumHeight += hoverElement.clientHeight; }); if (this._hasHorizontalScrollbar()) { maximumHeight += SCROLLBAR_WIDTH; @@ -615,8 +615,8 @@ export class ContentHoverWidget extends ResizableContentWidget { private _isHoverTextOverflowing(): boolean { let overflowing = false; - Array.from(this._hover.contentsDomNode.children).forEach((hoverPart) => { - overflowing = overflowing || hoverPart.scrollWidth > hoverPart.clientWidth; + Array.from(this._hover.contentsDomNode.children).forEach((hoverElement) => { + overflowing = overflowing || hoverElement.scrollWidth > hoverElement.clientWidth; }); return overflowing; } From 9f82da859c235b2931d7e4547c062abe4526af10 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com> Date: Thu, 10 Aug 2023 10:20:43 -0700 Subject: [PATCH 03/27] Guard `$IsWindows` So it works with PowerShell 5.1. --- .../contrib/terminal/browser/media/shellIntegration.ps1 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 index e3201449086..6ce62d540d8 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 @@ -120,7 +120,11 @@ if (Get-Module -Name PSReadLine) { } # Set IsWindows property -[Console]::Write("$([char]0x1b)]633;P;IsWindows=$($IsWindows)`a") +if ($PSVersionTable.PSVersion -lt "6.0") { + [Console]::Write("$([char]0x1b)]633;P;IsWindows=$true`a") +} else { + [Console]::Write("$([char]0x1b)]633;P;IsWindows=$IsWindows`a") +} # Set always on key handlers which map to default VS Code keybindings function Set-MappedKeyHandler { From 684270ac2efa142c4401bb3d3937c97f02d5704b Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 11 Aug 2023 17:39:24 +0200 Subject: [PATCH 04/27] Use consistent names for variables, fall back to defaults when wrapping is on --- .../lib/stylelint/vscode-known-variables.json | 8 ++++---- src/vs/base/browser/ui/hover/hover.css | 6 +++--- .../contrib/hover/browser/contentHover.ts | 20 +++++++++++-------- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index ca1f5eab147..577b3f13669 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -310,8 +310,6 @@ "--vscode-extensionIcon-verifiedForeground", "--vscode-focusBorder", "--vscode-foreground", - "--vscode-hover-whiteSpace", - "--vscode-hoverSource-whiteSpace", "--vscode-icon-foreground", "--vscode-inlineChat-background", "--vscode-inlineChat-border", @@ -705,7 +703,6 @@ "--background-light", "--dropdown-padding-bottom", "--dropdown-padding-top", - "--hover-maxWidth", "--insert-border-color", "--last-tab-margin-right", "--monaco-monospace-font", @@ -735,6 +732,9 @@ "--vscode-editorCodeLens-fontSize", "--vscode-editorCodeLens-lineHeight", "--vscode-explorer-align-offset-margin-left", + "--vscode-hover-maxWidth", + "--vscode-hover-sourceWhiteSpace", + "--vscode-hover-whiteSpace", "--vscode-inline-chat-cropped", "--vscode-inline-chat-expanded", "--vscode-interactive-session-foreground", @@ -768,4 +768,4 @@ "--z-index-notebook-sticky-scroll", "--zoom-factor" ] -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/hover/hover.css b/src/vs/base/browser/ui/hover/hover.css index 5e12d070057..ee294b766fb 100644 --- a/src/vs/base/browser/ui/hover/hover.css +++ b/src/vs/base/browser/ui/hover/hover.css @@ -12,7 +12,7 @@ box-sizing: border-box; animation: fadein 100ms linear; line-height: 1.5em; - white-space: var(--vscode-hover-whiteSpace); + white-space: var(--vscode-hover-whiteSpace, normal); } .monaco-hover.hidden { @@ -28,7 +28,7 @@ } .monaco-hover .markdown-hover > .hover-contents:not(.code-hover-contents) { - max-width: var(--hover-maxWidth, 500px); + max-width: var(--vscode-hover-maxWidth, 500px); word-wrap: break-word; } @@ -106,7 +106,7 @@ } .monaco-hover .monaco-tokenized-source { - white-space: var(--vscode-hoverSource-whiteSpace); + white-space: var(--vscode-hover-sourceWhiteSpace, pre-wrap); } .monaco-hover .hover-row.status-bar { diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index 841c5cd2a34..4e6002b5880 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -576,7 +576,7 @@ export class ContentHoverWidget extends ResizableContentWidget { private _setHoverWidgetMaxDimensions(width: number | string, height: number | string): void { ContentHoverWidget._applyMaxDimensions(this._hover.contentsDomNode, width, height); ContentHoverWidget._applyMaxDimensions(this._hover.containerDomNode, width, height); - this._hover.containerDomNode.style.setProperty('--hover-maxWidth', typeof width === 'number' ? `${width}px` : width); + this._hover.containerDomNode.style.setProperty('--vscode-hover-maxWidth', typeof width === 'number' ? `${width}px` : width); this._layoutContentWidget(); } @@ -659,11 +659,11 @@ export class ContentHoverWidget extends ResizableContentWidget { if (!this._editor || !this._editor.hasModel()) { return; } - this._setWhiteSpaceProperties('nowrap', 'nowrap'); + this._setHoverWrapping(false); const overflowing = this._isHoverTextOverflowing(); - this._setWhiteSpaceProperties('normal', 'pre-wrap'); + this._setHoverWrapping(true); - if (overflowing || this._initialWidth && this._hover.containerDomNode.clientWidth < this._initialWidth) { + if (overflowing || (this._initialWidth && this._hover.containerDomNode.clientWidth < this._initialWidth)) { const bodyBoxWidth = dom.getClientArea(document.body).width; const horizontalPadding = 14; return bodyBoxWidth - horizontalPadding; @@ -756,16 +756,20 @@ export class ContentHoverWidget extends ResizableContentWidget { }; } - private _setWhiteSpaceProperties(hoverWhiteSpace: 'normal' | 'nowrap', hoverSourceWhiteSpace: 'pre-wrap' | 'nowrap') { - this._hover.containerDomNode.style.setProperty('--vscode-hover-whiteSpace', hoverWhiteSpace); - this._hover.containerDomNode.style.setProperty('--vscode-hoverSource-whiteSpace', hoverSourceWhiteSpace); + private _setHoverWrapping(shouldWrap: boolean): void { + if (shouldWrap) { + this._hover.containerDomNode.style.removeProperty('--vscode-hover-whiteSpace'); + this._hover.containerDomNode.style.removeProperty('--vscode-hover-sourceWhiteSpace'); + } else { + this._hover.containerDomNode.style.setProperty('--vscode-hover-whiteSpace', 'nowrap'); + this._hover.containerDomNode.style.setProperty('--vscode-hover-sourceWhiteSpace', 'nowrap'); + } } public showAt(node: DocumentFragment, hoverData: ContentHoverVisibleData): void { if (!this._editor || !this._editor.hasModel()) { return; } - this._setWhiteSpaceProperties('normal', 'pre-wrap'); this._render(node, hoverData); const widgetHeight = dom.getTotalHeight(this._hover.containerDomNode); const widgetPosition = hoverData.showAtPosition; From 00b98a892a01c797f64d9fdf8c7a8d9daf7c26d5 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 11 Aug 2023 18:07:22 +0200 Subject: [PATCH 05/27] Reuse `_contentWidth` --- .../contrib/hover/browser/contentHover.ts | 57 ++++++++++--------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index 191d3f23aef..dba7c1531b5 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -474,8 +474,7 @@ export class ContentHoverWidget extends ResizableContentWidget { private _visibleData: ContentHoverVisibleData | undefined; private _positionPreference: ContentWidgetPositionPreference | undefined; private _minimumSize: dom.Dimension; - private _contentWidth: number; - private _initialWidth: number | undefined; + private _contentWidth: number | undefined; private readonly _hover: HoverWidget = this._register(new HoverWidget()); private readonly _hoverVisibleKey: IContextKey; @@ -509,7 +508,6 @@ export class ContentHoverWidget extends ResizableContentWidget { const minimumSize = new dom.Dimension(minimumWidth, minimumHeight); super(editor, minimumSize); this._minimumSize = minimumSize; - this._contentWidth = minimumWidth; // we initially assume the content width to be the minimum width this._hoverVisibleKey = EditorContextKeys.hoverVisible.bindTo(contextKeyService); this._hoverFocusedKey = EditorContextKeys.hoverFocused.bindTo(contextKeyService); @@ -609,7 +607,7 @@ export class ContentHoverWidget extends ResizableContentWidget { } } - private _setResizableNodeMaxDimensions(): void { + private _updateResizableNodeMaxDimensions(): void { const maxRenderingWidth = this._findMaximumRenderingWidth() ?? Infinity; const maxRenderingHeight = this._findMaximumRenderingHeight() ?? Infinity; this._resizableNode.maxSize = new dom.Dimension(maxRenderingWidth, maxRenderingHeight); @@ -620,7 +618,7 @@ export class ContentHoverWidget extends ResizableContentWidget { ContentHoverWidget._lastDimensions = new dom.Dimension(size.width, size.height); this._setAdjustedHoverWidgetDimensions(size); this._resizableNode.layout(size.height, size.width); - this._setResizableNodeMaxDimensions(); + this._updateResizableNodeMaxDimensions(); this._hover.scrollbar.scanDomNode(); this._editor.layoutContentWidget(this); this._visibleData?.colorPicker?.layout(); @@ -641,8 +639,8 @@ export class ContentHoverWidget extends ResizableContentWidget { } // Padding needed in order to stop the resizing down to a smaller height let maximumHeight = CONTAINER_HEIGHT_PADDING; - Array.from(this._hover.contentsDomNode.children).forEach((hoverElement) => { - maximumHeight += hoverElement.clientHeight; + Array.from(this._hover.contentsDomNode.children).forEach((hoverPart) => { + maximumHeight += hoverPart.clientHeight; }); if (this._hasHorizontalScrollbar()) { maximumHeight += SCROLLBAR_WIDTH; @@ -651,10 +649,17 @@ export class ContentHoverWidget extends ResizableContentWidget { } private _isHoverTextOverflowing(): boolean { - let overflowing = false; - Array.from(this._hover.contentsDomNode.children).forEach((hoverElement) => { - overflowing = overflowing || hoverElement.scrollWidth > hoverElement.clientWidth; + // To find out if the text is overflowing, we will disable wrapping, check the widths, and then re-enable wrapping + this._hover.containerDomNode.style.setProperty('--vscode-hover-whiteSpace', 'nowrap'); + this._hover.containerDomNode.style.setProperty('--vscode-hover-sourceWhiteSpace', 'nowrap'); + + const overflowing = Array.from(this._hover.contentsDomNode.children).some((hoverElement) => { + return hoverElement.scrollWidth > hoverElement.clientWidth; }); + + this._hover.containerDomNode.style.removeProperty('--vscode-hover-whiteSpace'); + this._hover.containerDomNode.style.removeProperty('--vscode-hover-sourceWhiteSpace'); + return overflowing; } @@ -662,11 +667,16 @@ export class ContentHoverWidget extends ResizableContentWidget { if (!this._editor || !this._editor.hasModel()) { return; } - this._setHoverWrapping(false); - const overflowing = this._isHoverTextOverflowing(); - this._setHoverWrapping(true); - if (overflowing || (this._initialWidth && this._hover.containerDomNode.clientWidth < this._initialWidth)) { + const overflowing = this._isHoverTextOverflowing(); + + const initialWidth = ( + typeof this._contentWidth === 'undefined' + ? 0 + : this._contentWidth - 2 // - 2 for the borders + ); + + if (overflowing || this._hover.containerDomNode.clientWidth < initialWidth) { const bodyBoxWidth = dom.getClientArea(document.body).width; const horizontalPadding = 14; return bodyBoxWidth - horizontalPadding; @@ -759,16 +769,6 @@ export class ContentHoverWidget extends ResizableContentWidget { }; } - private _setHoverWrapping(shouldWrap: boolean): void { - if (shouldWrap) { - this._hover.containerDomNode.style.removeProperty('--vscode-hover-whiteSpace'); - this._hover.containerDomNode.style.removeProperty('--vscode-hover-sourceWhiteSpace'); - } else { - this._hover.containerDomNode.style.setProperty('--vscode-hover-whiteSpace', 'nowrap'); - this._hover.containerDomNode.style.setProperty('--vscode-hover-sourceWhiteSpace', 'nowrap'); - } - } - public showAt(node: DocumentFragment, hoverData: ContentHoverVisibleData): void { if (!this._editor || !this._editor.hasModel()) { return; @@ -827,8 +827,13 @@ export class ContentHoverWidget extends ResizableContentWidget { } private _updateMinimumWidth(): void { + const width = ( + typeof this._contentWidth === 'undefined' + ? this._minimumSize.width + : Math.min(this._contentWidth, this._minimumSize.width) + ); // We want to avoid that the hover is artificially large, so we use the content width as minimum width - this._resizableNode.minSize = new dom.Dimension(Math.min(this._contentWidth, this._minimumSize.width), this._minimumSize.height); + this._resizableNode.minSize = new dom.Dimension(width, this._minimumSize.height); } public onContentsChanged(): void { @@ -845,6 +850,7 @@ export class ContentHoverWidget extends ResizableContentWidget { width = dom.getTotalWidth(containerDomNode); this._contentWidth = width; this._updateMinimumWidth(); + this._updateResizableNodeMaxDimensions(); this._resizableNode.layout(height, width); if (this._hasHorizontalScrollbar()) { @@ -856,7 +862,6 @@ export class ContentHoverWidget extends ResizableContentWidget { this._positionPreference = this._findPositionPreference(widgetHeight, this._visibleData.showAtPosition); } this._layoutContentWidget(); - this._initialWidth = this._hover.containerDomNode.clientWidth; } public focus(): void { From 25d06a85b7c84c7542be0d799685f6f95621f672 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 11 Aug 2023 10:47:11 -0700 Subject: [PATCH 06/27] bump the version as part of resetting renderers so it wont assume the version is already rendered --- .../notebook/browser/contrib/cellCommands/cellCommands.ts | 1 - .../contrib/notebook/browser/viewModel/cellOutputViewModel.ts | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts index ed90ad32894..41d051edbb8 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts @@ -617,7 +617,6 @@ registerAction2(class ToggleCellOutputScrolling extends NotebookMultiCellAction const currentlyEnabled = cellMetadata['scrollable'] !== undefined ? cellMetadata['scrollable'] : globalScrollSetting; const shouldEnableScrolling = collapsed || !currentlyEnabled; cellMetadata['scrollable'] = shouldEnableScrolling; - viewModel.model.bumpVersion(); viewModel.resetRenderer(); } } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/cellOutputViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/cellOutputViewModel.ts index d705f81a86e..e8cf31df9e3 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/cellOutputViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/cellOutputViewModel.ts @@ -55,6 +55,7 @@ export class CellOutputViewModel extends Disposable implements ICellOutputViewMo resetRenderer() { // reset the output renderer this._pickedMimeType = undefined; + this.model.bumpVersion(); this._onDidResetRendererEmitter.fire(); } From 081ebb4f8d8ae11f89eda3187975ff9ab131c44f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 11 Aug 2023 11:52:49 -0700 Subject: [PATCH 07/27] fix #190261 --- src/vs/base/browser/ui/hover/hoverWidget.ts | 2 +- src/vs/editor/contrib/hover/browser/contentHover.ts | 11 ++++++++--- .../workbench/services/hover/browser/hoverWidget.ts | 6 +++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/vs/base/browser/ui/hover/hoverWidget.ts b/src/vs/base/browser/ui/hover/hoverWidget.ts index 032af07dbd5..dc0af66ff5a 100644 --- a/src/vs/base/browser/ui/hover/hoverWidget.ts +++ b/src/vs/base/browser/ui/hover/hoverWidget.ts @@ -96,6 +96,6 @@ export class HoverAction extends Disposable { } } -export function getHoverAriaLabel(shouldHaveHint?: boolean, keybinding?: string | null): string | undefined { +export function getHoverAccessibleViewHint(shouldHaveHint?: boolean, keybinding?: string | null): string | undefined { return shouldHaveHint && keybinding ? localize('acessibleViewHint', "Inspect this in the accessible view with {0}.", keybinding) : shouldHaveHint ? localize('acessibleViewHintNoKbOpen', "Inspect this in the accessible view via the command Open Accessible View which is currently not triggerable via keybinding.") : ''; } diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index 25b460057dc..641ecffdade 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { HoverAction, HoverWidget, getHoverAriaLabel } from 'vs/base/browser/ui/hover/hoverWidget'; +import { HoverAction, HoverWidget, getHoverAccessibleViewHint } from 'vs/base/browser/ui/hover/hoverWidget'; import { coalesce } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -531,8 +531,6 @@ export class ContentHoverWidget extends ResizableContentWidget { this._setHoverData(undefined); this._layout(); this._editor.addContentWidget(this); - - this._hover.containerDomNode.ariaLabel = getHoverAriaLabel(this._configurationService.getValue('accessibility.verbosity.hover') === true && this._accessibilityService.isScreenReaderOptimized(), this._keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel()) ?? ''; } public override dispose(): void { @@ -758,6 +756,13 @@ export class ContentHoverWidget extends ResizableContentWidget { this._hover.containerDomNode.focus(); } hoverData.colorPicker?.layout(); + + // The aria label overrides the label, so if we add to it, add the contents of the hover + let accessibleViewHint = getHoverAccessibleViewHint(this._configurationService.getValue('accessibility.verbosity.hover') === true && this._accessibilityService.isScreenReaderOptimized(), this._keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel() ?? ''); + if (accessibleViewHint) { + accessibleViewHint = ', ' + accessibleViewHint; + } + this._hover.contentsDomNode.ariaLabel = accessibleViewHint ? this._hover.contentsDomNode.textContent + accessibleViewHint : ''; } public hide(): void { diff --git a/src/vs/workbench/services/hover/browser/hoverWidget.ts b/src/vs/workbench/services/hover/browser/hoverWidget.ts index e3d8e4362ed..b3f39327e36 100644 --- a/src/vs/workbench/services/hover/browser/hoverWidget.ts +++ b/src/vs/workbench/services/hover/browser/hoverWidget.ts @@ -11,7 +11,7 @@ import { IHoverTarget, IHoverOptions } from 'vs/workbench/services/hover/browser import { KeyCode } from 'vs/base/common/keyCodes'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { HoverAction, HoverPosition, HoverWidget as BaseHoverWidget, getHoverAriaLabel } from 'vs/base/browser/ui/hover/hoverWidget'; +import { HoverAction, HoverPosition, HoverWidget as BaseHoverWidget, getHoverAccessibleViewHint } from 'vs/base/browser/ui/hover/hoverWidget'; import { Widget } from 'vs/base/browser/ui/widget'; import { AnchorPosition } from 'vs/base/browser/ui/contextview/contextview'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -21,6 +21,7 @@ import { isMarkdownString } from 'vs/base/common/htmlContent'; import { localize } from 'vs/nls'; import { isMacintosh } from 'vs/base/common/platform'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { status } from 'vs/base/browser/ui/aria/aria'; const $ = dom.$; type TargetRect = { @@ -294,8 +295,7 @@ export class HoverWidget extends Widget { public render(container: HTMLElement): void { container.appendChild(this._hoverContainer); - this._hover.containerDomNode.ariaLabel = getHoverAriaLabel(this._configurationService.getValue('accessibility.verbosity.hover') === true && this._accessibilityService.isScreenReaderOptimized(), this._keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel()) ?? ''; - console.log('aria label', this._hover.containerDomNode.ariaLabel); + status(getHoverAccessibleViewHint(this._configurationService.getValue('accessibility.verbosity.hover') === true && this._accessibilityService.isScreenReaderOptimized(), this._keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel()) ?? ''); this.layout(); this.addFocusTrap(); } From 5f06a7d563db0efb92da4e3877c9b2287fbbc70c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 11 Aug 2023 12:06:42 -0700 Subject: [PATCH 08/27] fix #190272 --- .../workbench/contrib/accessibility/browser/accessibleView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index d8643ad5a33..133b9c6615f 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -311,7 +311,7 @@ class AccessibleView extends Disposable { let ariaLabel = ''; let helpHint = ''; const verbose = this._configurationService.getValue(provider.verbositySettingKey); - if (verbose && provider.options.type === AccessibleViewType.View) { + if (verbose && provider.options.type === AccessibleViewType.View && !showAccessibleViewHelp) { const accessibilityHelpKeybinding = this._keybindingService.lookupKeybinding(AccessibilityCommandId.OpenAccessibilityHelp)?.getLabel(); if (accessibilityHelpKeybinding) { helpHint = localize('ariaAccessibilityHelp', "Use {0} for accessibility help", accessibilityHelpKeybinding); From aa72eda3250317644d143d1156066466335f559e Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 11 Aug 2023 12:53:36 -0700 Subject: [PATCH 09/27] strip object down to dto for event --- .../notebook/common/model/notebookCellOutputTextModel.ts | 2 +- .../contrib/notebook/common/model/notebookTextModel.ts | 2 +- src/vs/workbench/contrib/notebook/common/notebookCommon.ts | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts index 82d4fcd2a11..85ed20571d1 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts @@ -109,7 +109,7 @@ export class NotebookCellOutputTextModel extends Disposable implements ICellOutp } } - toJSON(): IOutputDto { + asDto(): IOutputDto { return { // data: this._data, metadata: this._rawOutput.metadata, diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index cc14c4f07e8..417114d32e2 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -1066,7 +1066,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel rawEvents: [{ kind: NotebookCellsChangeType.Output, index: this._cells.indexOf(cell), - outputs: cell.outputs ?? [], + outputs: cell.outputs.map(output => output.asDto()) ?? [], append, transient: this.transientOptions.transientOutputs, }], diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index f3fdad08b01..e01a5c37361 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -218,6 +218,7 @@ export interface ICellOutput { replaceData(items: IOutputDto): void; appendData(items: IOutputItemDto[]): void; appendedSinceVersion(versionId: number, mime: string): VSBuffer | undefined; + asDto(): IOutputDto; bumpVersion(): void; } From ee88f3f4d2f3e848d642e96c4d578f81ed273b9a Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 11 Aug 2023 22:10:56 +0200 Subject: [PATCH 10/27] Update no ports welcome to include local case (#190284) --- src/vs/workbench/contrib/remote/browser/remoteExplorer.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts index 48eaa89fb58..e5ffe893a2a 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts @@ -51,7 +51,8 @@ export class ForwardedPortsView extends Disposable implements IWorkbenchContribu ) { super(); this._register(Registry.as(Extensions.ViewsRegistry).registerViewWelcomeContent(TUNNEL_VIEW_ID, { - content: `No forwarded ports. Forward a port to access your running services locally.\n[Forward a Port](command:${ForwardPortAction.INLINE_ID})`, + content: this.environmentService.remoteAuthority ? nls.localize('remoteNoPorts', "No forwarded ports. Forward a port to access your running services locally.\n[Forward a Port]({0})", `command:${ForwardPortAction.INLINE_ID}`) + : nls.localize('noRemoteNoPorts', "No forwarded ports. Forward a port to access your locally running services over the internet.\n[Forward a Port]({0})", `command:${ForwardPortAction.INLINE_ID}`), })); this.enableBadgeAndStatusBar(); this.enableForwardedPortsView(); From a7a8242b703bb9804066f3ce22404344b7978863 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 11 Aug 2023 13:22:04 -0700 Subject: [PATCH 11/27] simplify --- src/vs/editor/contrib/hover/browser/contentHover.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index 641ecffdade..9f5c8dab06f 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -758,11 +758,10 @@ export class ContentHoverWidget extends ResizableContentWidget { hoverData.colorPicker?.layout(); // The aria label overrides the label, so if we add to it, add the contents of the hover - let accessibleViewHint = getHoverAccessibleViewHint(this._configurationService.getValue('accessibility.verbosity.hover') === true && this._accessibilityService.isScreenReaderOptimized(), this._keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel() ?? ''); + const accessibleViewHint = getHoverAccessibleViewHint(this._configurationService.getValue('accessibility.verbosity.hover') === true && this._accessibilityService.isScreenReaderOptimized(), this._keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel() ?? ''); if (accessibleViewHint) { - accessibleViewHint = ', ' + accessibleViewHint; + this._hover.contentsDomNode.ariaLabel = this._hover.contentsDomNode.textContent + ', ' + accessibleViewHint; } - this._hover.contentsDomNode.ariaLabel = accessibleViewHint ? this._hover.contentsDomNode.textContent + accessibleViewHint : ''; } public hide(): void { From 40e49b473fef0b115f6df628b98f821ed80d66e6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 11 Aug 2023 13:39:47 -0700 Subject: [PATCH 12/27] xterm@5.3.0-beta.32 Fixes #187082 Fixes #190270 Part of #190246 --- package.json | 8 ++++---- remote/package.json | 8 ++++---- remote/web/package.json | 6 +++--- remote/web/yarn.lock | 24 ++++++++++++------------ remote/yarn.lock | 32 ++++++++++++++++---------------- yarn.lock | 32 ++++++++++++++++---------------- 6 files changed, 55 insertions(+), 55 deletions(-) diff --git a/package.json b/package.json index 22b189fb441..dc990e0f996 100644 --- a/package.json +++ b/package.json @@ -95,14 +95,14 @@ "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "9.0.0", - "xterm": "5.3.0-beta.28", - "xterm-addon-canvas": "0.5.0-beta.8", + "xterm": "5.3.0-beta.32", + "xterm-addon-canvas": "0.5.0-beta.9", "xterm-addon-image": "0.5.0", "xterm-addon-search": "0.13.0-beta.4", "xterm-addon-serialize": "0.11.0-beta.6", "xterm-addon-unicode11": "0.5.0", - "xterm-addon-webgl": "0.16.0-beta.7", - "xterm-headless": "5.3.0-beta.28", + "xterm-addon-webgl": "0.16.0-beta.12", + "xterm-headless": "5.3.0-beta.32", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/package.json b/remote/package.json index 9382ef726b2..7f86adaaf61 100644 --- a/remote/package.json +++ b/remote/package.json @@ -27,14 +27,14 @@ "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "9.0.0", - "xterm": "5.3.0-beta.28", - "xterm-addon-canvas": "0.5.0-beta.8", + "xterm": "5.3.0-beta.32", + "xterm-addon-canvas": "0.5.0-beta.9", "xterm-addon-image": "0.5.0", "xterm-addon-search": "0.13.0-beta.4", "xterm-addon-serialize": "0.11.0-beta.6", "xterm-addon-unicode11": "0.5.0", - "xterm-addon-webgl": "0.16.0-beta.7", - "xterm-headless": "5.3.0-beta.28", + "xterm-addon-webgl": "0.16.0-beta.12", + "xterm-headless": "5.3.0-beta.32", "yauzl": "^2.9.2", "yazl": "^2.4.3" } diff --git a/remote/web/package.json b/remote/web/package.json index 42c42a7672a..89f528d7d7b 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -11,11 +11,11 @@ "tas-client-umd": "0.1.8", "vscode-oniguruma": "1.7.0", "vscode-textmate": "9.0.0", - "xterm": "5.3.0-beta.28", - "xterm-addon-canvas": "0.5.0-beta.8", + "xterm": "5.3.0-beta.32", + "xterm-addon-canvas": "0.5.0-beta.9", "xterm-addon-image": "0.5.0", "xterm-addon-search": "0.13.0-beta.4", "xterm-addon-unicode11": "0.5.0", - "xterm-addon-webgl": "0.16.0-beta.7" + "xterm-addon-webgl": "0.16.0-beta.12" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 27c44cbcdc3..c22cc2d061d 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -90,10 +90,10 @@ vscode-textmate@9.0.0: resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-9.0.0.tgz#313c6c8792b0507aef35aeb81b6b370b37c44d6c" integrity sha512-Cl65diFGxz7gpwbav10HqiY/eVYTO1sjQpmRmV991Bj7wAoOAjGQ97PpQcXorDE2Uc4hnGWLY17xme+5t6MlSg== -xterm-addon-canvas@0.5.0-beta.8: - version "0.5.0-beta.8" - resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.5.0-beta.8.tgz#243d1161cc68441a1d531f25e9c4f1d6b3cbe79d" - integrity sha512-gCqoqFqfc4N8YZLlj3U5+247r3pjLc9ERRHpuBWSJF2GF623LLk4aSjexnEE0M1b7rIDa3xSKo5limHQXx2mWg== +xterm-addon-canvas@0.5.0-beta.9: + version "0.5.0-beta.9" + resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.5.0-beta.9.tgz#d4a9536cf586f78a54527751e03abf6445613886" + integrity sha512-AMyFctHbIWx0aLcACINuZqFFNoz4ndGtIAb4pQguIl9t8r+2otiV11MY4FJmPM0kUzEzoLWzVFJaGxOzykCWuw== xterm-addon-image@0.5.0: version "0.5.0" @@ -110,12 +110,12 @@ xterm-addon-unicode11@0.5.0: resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.5.0.tgz#41c0d96acc1e3bb6c6596eee64e163b6bca74be7" integrity sha512-Jm4/g4QiTxiKiTbYICQgC791ubhIZyoIwxAIgOW8z8HWFNY+lwk+dwaKEaEeGBfM48Vk8fklsUW9u/PlenYEBg== -xterm-addon-webgl@0.16.0-beta.7: - version "0.16.0-beta.7" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.7.tgz#d9febd140e2020e05c39e66214dae685769144ed" - integrity sha512-g657v/ah5JSjeTnHL3myCxiqPx0cA1J70kWXCqP4rsb1nPPlXtbq69cH5AsV/mNN5jnXjd6vHPa7CWt3ptOSgQ== +xterm-addon-webgl@0.16.0-beta.12: + version "0.16.0-beta.12" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.12.tgz#c4ee008e3768ae283ed281bddf6913dbc0b99971" + integrity sha512-H1lmX/fPVPZtgLMMevzLQbk2pB2ChZ55mTG3yOfe+dVgyNPw15suM2sL4UGwZUh3GFwT5gXL1rFuq0beQzA0Iw== -xterm@5.3.0-beta.28: - version "5.3.0-beta.28" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.28.tgz#1a5176fa6d9f424913a85dd0371d7384d2f882db" - integrity sha512-WA7aX2nE+ptxU4EConXGf0vKwkRisaXNt8ExEw/MRnfDudpZo2J30raJ2SQV2n/xWCGoeVIE5njYmR/hhiGOQA== +xterm@5.3.0-beta.32: + version "5.3.0-beta.32" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.32.tgz#1bc445e87d18c675702339c7eeb0d8ad66f412e6" + integrity sha512-QeK7HZ3SSXHFZvfMSIyVFMhGzh/cesu8cGmFDrsvnS62uDiWOGs/8RCAz3mupFvTZlxRd09IYOXAtqwjM+QPqQ== diff --git a/remote/yarn.lock b/remote/yarn.lock index fa9597cee0a..0f5a02945bb 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -899,10 +899,10 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -xterm-addon-canvas@0.5.0-beta.8: - version "0.5.0-beta.8" - resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.5.0-beta.8.tgz#243d1161cc68441a1d531f25e9c4f1d6b3cbe79d" - integrity sha512-gCqoqFqfc4N8YZLlj3U5+247r3pjLc9ERRHpuBWSJF2GF623LLk4aSjexnEE0M1b7rIDa3xSKo5limHQXx2mWg== +xterm-addon-canvas@0.5.0-beta.9: + version "0.5.0-beta.9" + resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.5.0-beta.9.tgz#d4a9536cf586f78a54527751e03abf6445613886" + integrity sha512-AMyFctHbIWx0aLcACINuZqFFNoz4ndGtIAb4pQguIl9t8r+2otiV11MY4FJmPM0kUzEzoLWzVFJaGxOzykCWuw== xterm-addon-image@0.5.0: version "0.5.0" @@ -924,20 +924,20 @@ xterm-addon-unicode11@0.5.0: resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.5.0.tgz#41c0d96acc1e3bb6c6596eee64e163b6bca74be7" integrity sha512-Jm4/g4QiTxiKiTbYICQgC791ubhIZyoIwxAIgOW8z8HWFNY+lwk+dwaKEaEeGBfM48Vk8fklsUW9u/PlenYEBg== -xterm-addon-webgl@0.16.0-beta.7: - version "0.16.0-beta.7" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.7.tgz#d9febd140e2020e05c39e66214dae685769144ed" - integrity sha512-g657v/ah5JSjeTnHL3myCxiqPx0cA1J70kWXCqP4rsb1nPPlXtbq69cH5AsV/mNN5jnXjd6vHPa7CWt3ptOSgQ== +xterm-addon-webgl@0.16.0-beta.12: + version "0.16.0-beta.12" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.12.tgz#c4ee008e3768ae283ed281bddf6913dbc0b99971" + integrity sha512-H1lmX/fPVPZtgLMMevzLQbk2pB2ChZ55mTG3yOfe+dVgyNPw15suM2sL4UGwZUh3GFwT5gXL1rFuq0beQzA0Iw== -xterm-headless@5.3.0-beta.28: - version "5.3.0-beta.28" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.3.0-beta.28.tgz#c0307c7363fe290fc5edb68b81135d6c37c76f67" - integrity sha512-2zZFNa6AOpoAcMEgzzV7ilSv2qUecs8gSJbfxVV9D4lbNuwWYW5AV8VEdfKnD8TRPbsC/5Uc4RZ82qhJNYOC1g== +xterm-headless@5.3.0-beta.32: + version "5.3.0-beta.32" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.3.0-beta.32.tgz#fb309a963caf8513c51b12f1562ad901cf3eb884" + integrity sha512-vsZVH4AfvaWHsFABeN9Am7LmRZ5oVc4HEELvkNBZNk8t53R2kaY6VxJuxrccP4W6AJb8P26pLyBOGglzh0I29A== -xterm@5.3.0-beta.28: - version "5.3.0-beta.28" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.28.tgz#1a5176fa6d9f424913a85dd0371d7384d2f882db" - integrity sha512-WA7aX2nE+ptxU4EConXGf0vKwkRisaXNt8ExEw/MRnfDudpZo2J30raJ2SQV2n/xWCGoeVIE5njYmR/hhiGOQA== +xterm@5.3.0-beta.32: + version "5.3.0-beta.32" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.32.tgz#1bc445e87d18c675702339c7eeb0d8ad66f412e6" + integrity sha512-QeK7HZ3SSXHFZvfMSIyVFMhGzh/cesu8cGmFDrsvnS62uDiWOGs/8RCAz3mupFvTZlxRd09IYOXAtqwjM+QPqQ== yallist@^4.0.0: version "4.0.0" diff --git a/yarn.lock b/yarn.lock index e1a14693b68..0ee24456a19 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10781,10 +10781,10 @@ xtend@~2.1.1: dependencies: object-keys "~0.4.0" -xterm-addon-canvas@0.5.0-beta.8: - version "0.5.0-beta.8" - resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.5.0-beta.8.tgz#243d1161cc68441a1d531f25e9c4f1d6b3cbe79d" - integrity sha512-gCqoqFqfc4N8YZLlj3U5+247r3pjLc9ERRHpuBWSJF2GF623LLk4aSjexnEE0M1b7rIDa3xSKo5limHQXx2mWg== +xterm-addon-canvas@0.5.0-beta.9: + version "0.5.0-beta.9" + resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.5.0-beta.9.tgz#d4a9536cf586f78a54527751e03abf6445613886" + integrity sha512-AMyFctHbIWx0aLcACINuZqFFNoz4ndGtIAb4pQguIl9t8r+2otiV11MY4FJmPM0kUzEzoLWzVFJaGxOzykCWuw== xterm-addon-image@0.5.0: version "0.5.0" @@ -10806,20 +10806,20 @@ xterm-addon-unicode11@0.5.0: resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.5.0.tgz#41c0d96acc1e3bb6c6596eee64e163b6bca74be7" integrity sha512-Jm4/g4QiTxiKiTbYICQgC791ubhIZyoIwxAIgOW8z8HWFNY+lwk+dwaKEaEeGBfM48Vk8fklsUW9u/PlenYEBg== -xterm-addon-webgl@0.16.0-beta.7: - version "0.16.0-beta.7" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.7.tgz#d9febd140e2020e05c39e66214dae685769144ed" - integrity sha512-g657v/ah5JSjeTnHL3myCxiqPx0cA1J70kWXCqP4rsb1nPPlXtbq69cH5AsV/mNN5jnXjd6vHPa7CWt3ptOSgQ== +xterm-addon-webgl@0.16.0-beta.12: + version "0.16.0-beta.12" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.12.tgz#c4ee008e3768ae283ed281bddf6913dbc0b99971" + integrity sha512-H1lmX/fPVPZtgLMMevzLQbk2pB2ChZ55mTG3yOfe+dVgyNPw15suM2sL4UGwZUh3GFwT5gXL1rFuq0beQzA0Iw== -xterm-headless@5.3.0-beta.28: - version "5.3.0-beta.28" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.3.0-beta.28.tgz#c0307c7363fe290fc5edb68b81135d6c37c76f67" - integrity sha512-2zZFNa6AOpoAcMEgzzV7ilSv2qUecs8gSJbfxVV9D4lbNuwWYW5AV8VEdfKnD8TRPbsC/5Uc4RZ82qhJNYOC1g== +xterm-headless@5.3.0-beta.32: + version "5.3.0-beta.32" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.3.0-beta.32.tgz#fb309a963caf8513c51b12f1562ad901cf3eb884" + integrity sha512-vsZVH4AfvaWHsFABeN9Am7LmRZ5oVc4HEELvkNBZNk8t53R2kaY6VxJuxrccP4W6AJb8P26pLyBOGglzh0I29A== -xterm@5.3.0-beta.28: - version "5.3.0-beta.28" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.28.tgz#1a5176fa6d9f424913a85dd0371d7384d2f882db" - integrity sha512-WA7aX2nE+ptxU4EConXGf0vKwkRisaXNt8ExEw/MRnfDudpZo2J30raJ2SQV2n/xWCGoeVIE5njYmR/hhiGOQA== +xterm@5.3.0-beta.32: + version "5.3.0-beta.32" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.32.tgz#1bc445e87d18c675702339c7eeb0d8ad66f412e6" + integrity sha512-QeK7HZ3SSXHFZvfMSIyVFMhGzh/cesu8cGmFDrsvnS62uDiWOGs/8RCAz3mupFvTZlxRd09IYOXAtqwjM+QPqQ== y18n@^3.2.1: version "3.2.2" From 3ec4d30288f51505a2385c00b182ccc285d67287 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 11 Aug 2023 13:40:46 -0700 Subject: [PATCH 13/27] Adopt xterm cursorInactiveStyle and reduce type repetition Fixes #190246 --- src/vs/platform/terminal/common/terminal.ts | 1 + .../terminal/browser/xterm/xtermTerminal.ts | 35 +++++++++++++++---- .../contrib/terminal/common/terminal.ts | 7 +--- .../terminal/common/terminalConfiguration.ts | 13 ++++--- 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index f8a98e4aa7d..c647edca009 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -64,6 +64,7 @@ export const enum TerminalSettingId { FontWeightBold = 'terminal.integrated.fontWeightBold', CursorBlinking = 'terminal.integrated.cursorBlinking', CursorStyle = 'terminal.integrated.cursorStyle', + CursorStyleInactive = 'terminal.integrated.cursorStyleInactive', CursorWidth = 'terminal.integrated.cursorWidth', Scrollback = 'terminal.integrated.scrollback', DetectLocale = 'terminal.integrated.detectLocale', diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index f3776f4eab8..1aa541b9a56 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { IBuffer, ITheme, Terminal as RawXtermTerminal, LogLevel as XtermLogLevel } from 'xterm'; +import type { IBuffer, ITerminalOptions, ITheme, Terminal as RawXtermTerminal, LogLevel as XtermLogLevel } from 'xterm'; import type { CanvasAddon as CanvasAddonType } from 'xterm-addon-canvas'; import type { ISearchOptions, SearchAddon as SearchAddonType } from 'xterm-addon-search'; import type { Unicode11Addon as Unicode11AddonType } from 'xterm-addon-unicode11'; @@ -17,7 +17,7 @@ import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/term import { DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IShellIntegration, ITerminalLogService, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { ITerminalFont } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalFont, ITerminalConfiguration } from 'vs/workbench/contrib/terminal/common/terminal'; import { isSafari } from 'vs/base/browser/browser'; import { IMarkTracker, IInternalXtermTerminal, IXtermTerminal, ISuggestController, IXtermColorProvider, XtermTerminalConstants, IXtermAttachToElementOptions, IDetachedXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; import { LogLevel } from 'vs/platform/log/common/log'; @@ -227,7 +227,8 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, ID minimumContrastRatio: config.minimumContrastRatio, tabStopWidth: config.tabStopWidth, cursorBlink: config.cursorBlinking, - cursorStyle: config.cursorStyle === 'line' ? 'bar' : config.cursorStyle, + cursorStyle: vscodeToXtermCursorStyle<'cursorStyle'>(config.cursorStyle), + cursorInactiveStyle: vscodeToXtermCursorStyle(config.cursorStyleInactive), cursorWidth: config.cursorWidth, macOptionIsMeta: config.macOptionIsMeta, macOptionClickForcesSelection: config.macOptionClickForcesSelection, @@ -394,6 +395,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, ID this.raw.options.altClickMovesCursor = config.altClickMovesCursor; this._setCursorBlink(config.cursorBlinking); this._setCursorStyle(config.cursorStyle); + this._setCursorStyleInactive(config.cursorStyleInactive); this._setCursorWidth(config.cursorWidth); this.raw.options.scrollback = config.scrollback; this.raw.options.drawBoldTextInBrightColors = config.drawBoldTextInBrightColors; @@ -651,10 +653,17 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, ID } } - private _setCursorStyle(style: 'block' | 'underline' | 'bar' | 'line'): void { - if (this.raw.options.cursorStyle !== style) { - // 'line' is used instead of bar in VS Code to be consistent with editor.cursorStyle - this.raw.options.cursorStyle = (style === 'line') ? 'bar' : style; + private _setCursorStyle(style: ITerminalConfiguration['cursorStyle']): void { + const mapped = vscodeToXtermCursorStyle<'cursorStyle'>(style); + if (this.raw.options.cursorStyle !== mapped) { + this.raw.options.cursorStyle = mapped; + } + } + + private _setCursorStyleInactive(style: ITerminalConfiguration['cursorStyleInactive']): void { + const mapped = vscodeToXtermCursorStyle(style); + if (this.raw.options.cursorInactiveStyle !== mapped) { + this.raw.options.cursorInactiveStyle = mapped; } } @@ -960,3 +969,15 @@ function vscodeToXtermLogLevel(logLevel: LogLevel): XtermLogLevel { default: return 'off'; } } + +interface ICursorStyleVscodeToXtermMap { + 'cursorStyle': NonNullable; + 'cursorStyleInactive': NonNullable; +} +function vscodeToXtermCursorStyle(style: ITerminalConfiguration[T]): ICursorStyleVscodeToXtermMap[T] { + // 'line' is used instead of bar in VS Code to be consistent with editor.cursorStyle + if (style === 'line') { + return 'bar'; + } + return style as ICursorStyleVscodeToXtermMap[T]; +} diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index da860c0c5ca..3f528b3908c 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -22,12 +22,6 @@ export const TERMINAL_VIEW_ID = 'terminal'; export const TERMINAL_CREATION_COMMANDS = ['workbench.action.terminal.toggleTerminal', 'workbench.action.terminal.new', 'workbench.action.togglePanel', 'workbench.action.terminal.focus']; -export const TerminalCursorStyle = { - BLOCK: 'block', - LINE: 'line', - UNDERLINE: 'underline' -}; - export const TERMINAL_CONFIG_SECTION = 'terminal.integrated'; export const DEFAULT_LETTER_SPACING = 0; @@ -145,6 +139,7 @@ export interface ITerminalConfiguration { rightClickBehavior: 'default' | 'copyPaste' | 'paste' | 'selectWord' | 'nothing'; cursorBlinking: boolean; cursorStyle: 'block' | 'underline' | 'line'; + cursorStyleInactive: 'outline' | 'block' | 'underline' | 'line' | 'none'; cursorWidth: number; drawBoldTextInBrightColors: boolean; fastScrollSensitivity: number; diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index 528abcf5ffe..d92eac5fe3f 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -5,7 +5,7 @@ import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { localize } from 'vs/nls'; -import { DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, DEFAULT_COMMANDS_TO_SKIP_SHELL, SUGGESTIONS_FONT_WEIGHT, MINIMUM_FONT_WEIGHT, MAXIMUM_FONT_WEIGHT, DEFAULT_LOCAL_ECHO_EXCLUDE } from 'vs/workbench/contrib/terminal/common/terminal'; +import { DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, DEFAULT_COMMANDS_TO_SKIP_SHELL, SUGGESTIONS_FONT_WEIGHT, MINIMUM_FONT_WEIGHT, MAXIMUM_FONT_WEIGHT, DEFAULT_LOCAL_ECHO_EXCLUDE } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalLocationString, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -259,9 +259,14 @@ const terminalConfiguration: IConfigurationNode = { default: false }, [TerminalSettingId.CursorStyle]: { - description: localize('terminal.integrated.cursorStyle', "Controls the style of terminal cursor."), - enum: [TerminalCursorStyle.BLOCK, TerminalCursorStyle.LINE, TerminalCursorStyle.UNDERLINE], - default: TerminalCursorStyle.BLOCK + description: localize('terminal.integrated.cursorStyle', "Controls the style of terminal cursor when the terminal is focused."), + enum: ['block', 'line', 'underline'], + default: 'block' + }, + [TerminalSettingId.CursorStyleInactive]: { + description: localize('terminal.integrated.cursorStyleInactive', "Controls the style of terminal cursor when the terminal is not focused."), + enum: ['outline', 'block', 'line', 'underline', 'none'], + default: 'outline' }, [TerminalSettingId.CursorWidth]: { markdownDescription: localize('terminal.integrated.cursorWidth', "Controls the width of the cursor when {0} is set to {1}.", '`#terminal.integrated.cursorStyle#`', '`line`'), From 98512a70f6a8a349ba5b687a892388539290d75d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 11 Aug 2023 13:55:37 -0700 Subject: [PATCH 14/27] only call status if not '' --- src/vs/workbench/services/hover/browser/hoverWidget.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/services/hover/browser/hoverWidget.ts b/src/vs/workbench/services/hover/browser/hoverWidget.ts index b3f39327e36..1dad5ac2042 100644 --- a/src/vs/workbench/services/hover/browser/hoverWidget.ts +++ b/src/vs/workbench/services/hover/browser/hoverWidget.ts @@ -295,7 +295,11 @@ export class HoverWidget extends Widget { public render(container: HTMLElement): void { container.appendChild(this._hoverContainer); - status(getHoverAccessibleViewHint(this._configurationService.getValue('accessibility.verbosity.hover') === true && this._accessibilityService.isScreenReaderOptimized(), this._keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel()) ?? ''); + const accessibleViewHint = getHoverAccessibleViewHint(this._configurationService.getValue('accessibility.verbosity.hover') === true && this._accessibilityService.isScreenReaderOptimized(), this._keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel()); + if (accessibleViewHint) { + + status(accessibleViewHint); + } this.layout(); this.addFocusTrap(); } From 9ce4b05916f0ddc3830945375239e0665ff28a80 Mon Sep 17 00:00:00 2001 From: David Dossett Date: Sat, 12 Aug 2023 07:57:49 -0700 Subject: [PATCH 15/27] Align input part content with list content (#190299) * Remove border top from input * Bring back border, adjust metrics * Remove border top --- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 9 +++++---- src/vs/workbench/contrib/chat/browser/media/chat.css | 5 +---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index d1815071ca9..613701e0e7e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -270,18 +270,19 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const followupsHeight = this.followupsContainer.offsetHeight; const inputPartBorder = 1; - const inputPartPadding = 24; - const inputEditorHeight = Math.min(this._inputEditor.getContentHeight(), height - followupsHeight - inputPartPadding - inputPartBorder, INPUT_EDITOR_MAX_HEIGHT); + const inputPartHorizontalPadding = 40; + const inputPartVerticalPadding = 24; + const inputEditorHeight = Math.min(this._inputEditor.getContentHeight(), height - followupsHeight - inputPartHorizontalPadding - inputPartBorder, INPUT_EDITOR_MAX_HEIGHT); const inputEditorBorder = 2; - const inputPartHeight = followupsHeight + inputEditorHeight + inputPartPadding + inputPartBorder + inputEditorBorder; + const inputPartHeight = followupsHeight + inputEditorHeight + inputPartVerticalPadding + inputPartBorder + inputEditorBorder; const editorBorder = 2; const editorPadding = 8; const executeToolbarWidth = 25; const initialEditorScrollWidth = this._inputEditor.getScrollWidth(); - this._inputEditor.layout({ width: width - inputPartPadding - editorBorder - editorPadding - executeToolbarWidth, height: inputEditorHeight }); + this._inputEditor.layout({ width: width - inputPartHorizontalPadding - editorBorder - editorPadding - executeToolbarWidth, height: inputEditorHeight }); if (allowRecurse && initialEditorScrollWidth < 10) { // This is probably the initial layout. Now that the editor is layed out with its correct width, it should report the correct contentHeight diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 90c3bfe9174..5e65196dfd0 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -183,13 +183,12 @@ display: flex; box-sizing: border-box; cursor: text; - margin: 0px 12px; + margin: 0px 20px; background-color: var(--vscode-input-background); border: 1px solid var(--vscode-input-border, transparent); border-radius: 2px; position: relative; padding: 0 4px; - margin-bottom: 4px; align-items: center; justify-content: space-between; } @@ -297,8 +296,6 @@ padding: 12px 0px; display: flex; flex-direction: column; - border-top: solid 1px var(--vscode-chat-requestBorder); - border-bottom: solid 1px var(--vscode-chat-requestBorder); } .interactive-session-followups { From 1b8729178cd3188de494e9c53b8491d6ad40dd58 Mon Sep 17 00:00:00 2001 From: David Dossett Date: Sat, 12 Aug 2023 07:58:25 -0700 Subject: [PATCH 16/27] Remove background color for chat requests (#190297) Don't use background color for chat requests --- build/lib/stylelint/vscode-known-variables.json | 1 - src/vs/workbench/contrib/chat/browser/media/chat.css | 1 - src/vs/workbench/contrib/chat/common/chatColors.ts | 7 ------- 3 files changed, 9 deletions(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 81fb807a2c4..6f7cd9d1d86 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -36,7 +36,6 @@ "--vscode-charts-purple", "--vscode-charts-red", "--vscode-charts-yellow", - "--vscode-chat-requestBackground", "--vscode-chat-requestBorder", "--vscode-chat-slashCommandBackground", "--vscode-chat-slashCommandForeground", diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 5e65196dfd0..3a350b8da63 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -124,7 +124,6 @@ } .interactive-request { - background-color: var(--vscode-chat-requestBackground); border-bottom: 1px solid var(--vscode-chat-requestBorder); border-top: 1px solid var(--vscode-chat-requestBorder); } diff --git a/src/vs/workbench/contrib/chat/common/chatColors.ts b/src/vs/workbench/contrib/chat/common/chatColors.ts index 94be7ec9916..b7c9ac8337b 100644 --- a/src/vs/workbench/contrib/chat/common/chatColors.ts +++ b/src/vs/workbench/contrib/chat/common/chatColors.ts @@ -7,13 +7,6 @@ import { Color, RGBA } from 'vs/base/common/color'; import { localize } from 'vs/nls'; import { badgeBackground, badgeForeground, registerColor } from 'vs/platform/theme/common/colorRegistry'; - -export const chatRequestBackground = registerColor( - 'chat.requestBackground', - { dark: new Color(new RGBA(255, 255, 255, 0.03)), light: new Color(new RGBA(0, 0, 0, 0.03)), hcDark: null, hcLight: null, }, - localize('chat.requestBackground', 'The background color of a chat request.') -); - export const chatRequestBorder = registerColor( 'chat.requestBorder', { dark: new Color(new RGBA(255, 255, 255, 0.10)), light: new Color(new RGBA(0, 0, 0, 0.10)), hcDark: null, hcLight: null, }, From 2d9cc42045edf3458acbddf3d645bba993f82696 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Sat, 12 Aug 2023 08:03:01 -0700 Subject: [PATCH 17/27] testing: menu contribution points around messages (#190298) - Implements the proposal in #190277 by adding a `contextValue` to TestMessages added to test runs. - Make the `FloatingClickMenu` reusable outside the editor, and uses it to implement a `testing/message/content` contribution point. With this extensions can do things like: ![](https://memes.peet.io/img/23-08-68e2f9db-abc4-4717-9da6-698b002c481c.png) --- extensions/vscode-api-tests/package.json | 1 + src/vs/base/common/marshallingIds.ts | 3 +- .../platform/actions/browser/floatingMenu.ts | 129 ++++++++ src/vs/platform/actions/common/actions.ts | 2 + .../workbench/api/common/extHost.api.impl.ts | 2 +- src/vs/workbench/api/common/extHostTesting.ts | 58 ++-- .../api/common/extHostTypeConverters.ts | 6 +- src/vs/workbench/api/common/extHostTypes.ts | 6 +- .../api/test/browser/extHostTesting.test.ts | 34 ++- src/vs/workbench/browser/codeeditor.ts | 116 +++---- .../parts/editor/editor.contribution.ts | 4 +- .../codeEditor/browser/diffEditorHelper.ts | 4 +- .../debug/browser/debugEditorContribution.ts | 4 +- .../notebook/browser/notebookEditorWidget.ts | 4 +- .../contrib/testing/browser/media/testing.css | 6 + .../testing/browser/testingOutputPeek.ts | 287 ++++++++++-------- .../contrib/testing/common/testTypes.ts | 16 + .../testing/common/testingContextKeys.ts | 4 + .../actions/common/menusExtensionPoint.ts | 10 + .../common/extensionsApiProposals.ts | 1 + ...code.proposed.testMessageContextValue.d.ts | 45 +++ 21 files changed, 493 insertions(+), 249 deletions(-) create mode 100644 src/vs/platform/actions/browser/floatingMenu.ts create mode 100644 src/vscode-dts/vscode.proposed.testMessageContextValue.d.ts diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index aed0d546ce2..5ee95741ff0 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -40,6 +40,7 @@ "envShellEvent", "testCoverage", "testObserver", + "testMessageContextValue", "textSearchProvider", "timeline", "tokenInformation", diff --git a/src/vs/base/common/marshallingIds.ts b/src/vs/base/common/marshallingIds.ts index abd7698ed92..ae02b8901fc 100644 --- a/src/vs/base/common/marshallingIds.ts +++ b/src/vs/base/common/marshallingIds.ts @@ -20,5 +20,6 @@ export const enum MarshalledId { NotebookCellActionContext, NotebookActionContext, TestItemContext, - Date + Date, + TestMessageMenuArgs, } diff --git a/src/vs/platform/actions/browser/floatingMenu.ts b/src/vs/platform/actions/browser/floatingMenu.ts new file mode 100644 index 00000000000..e6840b10e10 --- /dev/null +++ b/src/vs/platform/actions/browser/floatingMenu.ts @@ -0,0 +1,129 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { $, append, clearNode } from 'vs/base/browser/dom'; +import { Widget } from 'vs/base/browser/ui/widget'; +import { IAction } from 'vs/base/common/actions'; +import { Emitter } from 'vs/base/common/event'; +import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { asCssVariable, asCssVariableWithDefault, buttonBackground, buttonForeground, contrastBorder, editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; + +export class FloatingClickWidget extends Widget { + + private readonly _onClick = this._register(new Emitter()); + readonly onClick = this._onClick.event; + + private _domNode: HTMLElement; + + constructor(private label: string) { + super(); + + this._domNode = $('.floating-click-widget'); + this._domNode.style.padding = '6px 11px'; + this._domNode.style.borderRadius = '2px'; + this._domNode.style.cursor = 'pointer'; + this._domNode.style.zIndex = '1'; + } + + getDomNode(): HTMLElement { + return this._domNode; + } + + render() { + clearNode(this._domNode); + this._domNode.style.backgroundColor = asCssVariableWithDefault(buttonBackground, asCssVariable(editorBackground)); + this._domNode.style.color = asCssVariableWithDefault(buttonForeground, asCssVariable(editorForeground)); + this._domNode.style.border = `1px solid ${asCssVariable(contrastBorder)}`; + + append(this._domNode, $('')).textContent = this.label; + + this.onclick(this._domNode, () => this._onClick.fire()); + } +} + +export abstract class AbstractFloatingClickMenu extends Disposable { + private readonly renderEmitter = new Emitter(); + protected readonly onDidRender = this.renderEmitter.event; + private readonly menu: IMenu; + + constructor( + menuId: MenuId, + @IMenuService menuService: IMenuService, + @IContextKeyService contextKeyService: IContextKeyService + ) { + super(); + this.menu = this._register(menuService.createMenu(menuId, contextKeyService)); + } + + /** Should be called in implementation constructors after they initialized */ + protected render() { + const menuDisposables = this._register(new DisposableStore()); + const renderMenuAsFloatingClickBtn = () => { + menuDisposables.clear(); + if (!this.isVisible()) { + return; + } + const actions: IAction[] = []; + createAndFillInActionBarActions(this.menu, { renderShortTitle: true, shouldForwardArgs: true }, actions); + if (actions.length === 0) { + return; + } + // todo@jrieken find a way to handle N actions, like showing a context menu + const [first] = actions; + const widget = this.createWidget(first, menuDisposables); + menuDisposables.add(widget); + menuDisposables.add(widget.onClick(() => first.run(this.getActionArg()))); + widget.render(); + }; + this._register(this.menu.onDidChange(renderMenuAsFloatingClickBtn)); + renderMenuAsFloatingClickBtn(); + } + + protected abstract createWidget(action: IAction, disposables: DisposableStore): FloatingClickWidget; + + protected getActionArg(): unknown { + return undefined; + } + + protected isVisible() { + return true; + } +} + +export class FloatingClickMenu extends AbstractFloatingClickMenu { + + constructor( + private readonly options: { + /** Element the menu should be rendered into. */ + container: HTMLElement; + /** Menu to show. If no actions are present, the button is hidden. */ + menuId: MenuId; + /** Argument provided to the menu action */ + getActionArg: () => void; + }, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IMenuService menuService: IMenuService, + @IContextKeyService contextKeyService: IContextKeyService + ) { + super(options.menuId, menuService, contextKeyService); + this.render(); + } + + protected override createWidget(action: IAction, disposable: DisposableStore): FloatingClickWidget { + const w = this.instantiationService.createInstance(FloatingClickWidget, action.label); + const node = w.getDomNode(); + this.options.container.appendChild(node); + disposable.add(toDisposable(() => this.options.container.removeChild(node))); + return w; + } + + protected override getActionArg(): unknown { + return this.options.getActionArg(); + } +} diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 78ad2e70c2e..d1f216f0971 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -114,6 +114,8 @@ export class MenuId { static readonly StickyScrollContext = new MenuId('StickyScrollContext'); static readonly TestItem = new MenuId('TestItem'); static readonly TestItemGutter = new MenuId('TestItemGutter'); + static readonly TestMessageContext = new MenuId('TestMessageContext'); + static readonly TestMessageContent = new MenuId('TestMessageContent'); static readonly TestPeekElement = new MenuId('TestPeekElement'); static readonly TestPeekTitle = new MenuId('TestPeekTitle'); static readonly TouchBarContext = new MenuId('TouchBarContext'); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 7aee01902ab..233f094a801 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1521,8 +1521,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I LinkedEditingRanges: extHostTypes.LinkedEditingRanges, TestResultState: extHostTypes.TestResultState, TestRunRequest: extHostTypes.TestRunRequest, - TestRunRequest2: extHostTypes.TestRunRequest2, TestMessage: extHostTypes.TestMessage, + TestMessage2: extHostTypes.TestMessage, TestTag: extHostTypes.TestTag, TestRunProfileKind: extHostTypes.TestRunProfileKind, TextSearchCompleteMessageType: TextSearchCompleteMessageType, diff --git a/src/vs/workbench/api/common/extHostTesting.ts b/src/vs/workbench/api/common/extHostTesting.ts index 4227806cb8d..94af4f6b06e 100644 --- a/src/vs/workbench/api/common/extHostTesting.ts +++ b/src/vs/workbench/api/common/extHostTesting.ts @@ -17,7 +17,7 @@ import { MarshalledId } from 'vs/base/common/marshallingIds'; import { deepFreeze } from 'vs/base/common/objects'; import { isDefined } from 'vs/base/common/types'; import { generateUuid } from 'vs/base/common/uuid'; -import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { IExtensionDescription, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ExtHostTestingShape, ILocationDto, MainContext, MainThreadTestingShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; @@ -28,13 +28,15 @@ import { TestRunProfileKind, TestRunRequest } from 'vs/workbench/api/common/extH import { TestCommandId } from 'vs/workbench/contrib/testing/common/constants'; import { TestId, TestIdPathParts, TestPosition } from 'vs/workbench/contrib/testing/common/testId'; import { InvalidTestItemError } from 'vs/workbench/contrib/testing/common/testItemCollection'; -import { AbstractIncrementalTestCollection, CoverageDetails, ICallProfileRunHandler, IFileCoverage, ISerializedTestResults, IStartControllerTests, IStartControllerTestsResult, ITestItem, ITestItemContext, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, TestResultState, TestRunProfileBitset, TestsDiff, TestsDiffOp, isStartControllerTests } from 'vs/workbench/contrib/testing/common/testTypes'; +import { AbstractIncrementalTestCollection, CoverageDetails, ICallProfileRunHandler, IFileCoverage, ISerializedTestResults, IStartControllerTests, IStartControllerTestsResult, ITestErrorMessage, ITestItem, ITestItemContext, ITestMessageMenuArgs, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, TestResultState, TestRunProfileBitset, TestsDiff, TestsDiffOp, isStartControllerTests } from 'vs/workbench/contrib/testing/common/testTypes'; +import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import type * as vscode from 'vscode'; interface ControllerInfo { controller: vscode.TestController; profiles: Map; collection: ExtHostTestItemCollection; + extension: Readonly; } export class ExtHostTesting implements ExtHostTestingShape { @@ -58,14 +60,22 @@ export class ExtHostTesting implements ExtHostTestingShape { commands.registerArgumentProcessor({ processArgument: arg => { - if (arg?.$mid !== MarshalledId.TestItemContext) { - return arg; + switch (arg?.$mid) { + case MarshalledId.TestItemContext: { + const cast = arg as ITestItemContext; + const targetTest = cast.tests[cast.tests.length - 1].item.extId; + const controller = this.controllers.get(TestId.root(targetTest)); + return controller?.collection.tree.get(targetTest)?.actual ?? toItemFromContext(arg); + } + case MarshalledId.TestMessageMenuArgs: { + const { extId, message } = arg as ITestMessageMenuArgs; + return { + test: this.controllers.get(TestId.root(extId))?.collection.tree.get(extId)?.actual, + message: Convert.TestMessage.to(message as ITestErrorMessage.Serialized), + }; + } + default: return arg; } - - const cast = arg as ITestItemContext; - const targetTest = cast.tests[cast.tests.length - 1].item.extId; - const controller = this.controllers.get(TestId.root(targetTest)); - return controller?.collection.tree.get(targetTest)?.actual ?? toItemFromContext(arg); } }); @@ -137,7 +147,7 @@ export class ExtHostTesting implements ExtHostTestingShape { return new TestItemImpl(controllerId, id, label, uri); }, createTestRun: (request, name, persist = true) => { - return this.runTracker.createTestRun(controllerId, collection, request, name, persist); + return this.runTracker.createTestRun(extension, controllerId, collection, request, name, persist); }, invalidateTestResults: items => { if (items === undefined) { @@ -161,7 +171,7 @@ export class ExtHostTesting implements ExtHostTestingShape { proxy.$registerTestController(controllerId, label, !!refreshHandler); disposable.add(toDisposable(() => proxy.$unregisterTestController(controllerId))); - const info: ControllerInfo = { controller, collection, profiles: profiles }; + const info: ControllerInfo = { controller, collection, profiles: profiles, extension }; this.controllers.set(controllerId, info); disposable.add(toDisposable(() => this.controllers.delete(controllerId))); @@ -310,7 +320,7 @@ export class ExtHostTesting implements ExtHostTestingShape { return {}; } - const { collection, profiles } = lookup; + const { collection, profiles, extension } = lookup; const profile = profiles.get(req.profileId); if (!profile) { return {}; @@ -341,6 +351,7 @@ export class ExtHostTesting implements ExtHostTestingShape { const tracker = isStartControllerTests(req) && this.runTracker.prepareForMainThreadTestRun( publicReq, TestRunDto.fromInternal(req, lookup.collection), + extension, token, ); @@ -410,7 +421,12 @@ class TestRunTracker extends Disposable { return this.dto.id; } - constructor(private readonly dto: TestRunDto, private readonly proxy: MainThreadTestingShape, parentToken?: CancellationToken) { + constructor( + private readonly dto: TestRunDto, + private readonly proxy: MainThreadTestingShape, + private readonly extension: Readonly, + parentToken?: CancellationToken, + ) { super(); this.cts = this._register(new CancellationTokenSource(parentToken)); @@ -460,6 +476,10 @@ class TestRunTracker extends Disposable { ? messages.map(Convert.TestMessage.from) : [Convert.TestMessage.from(messages)]; + if (converted.some(c => c.contextValue !== undefined)) { + checkProposedApiEnabled(this.extension, 'testMessageContextValue'); + } + if (test.uri && test.range) { const defaultLocation: ILocationDto = { range: Convert.Range.from(test.range), uri: test.uri }; for (const message of converted) { @@ -606,8 +626,8 @@ export class TestRunCoordinator { * `$startedExtensionTestRun` is not invoked. The run must eventually * be cancelled manually. */ - public prepareForMainThreadTestRun(req: vscode.TestRunRequest, dto: TestRunDto, token: CancellationToken) { - return this.getTracker(req, dto, token); + public prepareForMainThreadTestRun(req: vscode.TestRunRequest, dto: TestRunDto, extension: Readonly, token: CancellationToken) { + return this.getTracker(req, dto, extension, token); } /** @@ -635,7 +655,7 @@ export class TestRunCoordinator { /** * Implements the public `createTestRun` API. */ - public createTestRun(controllerId: string, collection: ExtHostTestItemCollection, request: vscode.TestRunRequest, name: string | undefined, persist: boolean): vscode.TestRun { + public createTestRun(extension: IRelaxedExtensionDescription, controllerId: string, collection: ExtHostTestItemCollection, request: vscode.TestRunRequest, name: string | undefined, persist: boolean): vscode.TestRun { const existing = this.tracked.get(request); if (existing) { return existing.createRun(name); @@ -655,7 +675,7 @@ export class TestRunCoordinator { persist }); - const tracker = this.getTracker(request, dto); + const tracker = this.getTracker(request, dto, extension); tracker.onEnd(() => { this.proxy.$finishedExtensionTestRun(dto.id); tracker.dispose(); @@ -664,8 +684,8 @@ export class TestRunCoordinator { return tracker.createRun(name); } - private getTracker(req: vscode.TestRunRequest, dto: TestRunDto, token?: CancellationToken) { - const tracker = new TestRunTracker(dto, this.proxy, token); + private getTracker(req: vscode.TestRunRequest, dto: TestRunDto, extension: IRelaxedExtensionDescription, token?: CancellationToken) { + const tracker = new TestRunTracker(dto, this.proxy, extension, token); this.tracked.set(req, tracker); tracker.onEnd(() => this.tracked.delete(req)); return tracker; diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 80aeaa6d1f3..37868738d64 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -1799,20 +1799,22 @@ export namespace NotebookRendererScript { } export namespace TestMessage { - export function from(message: vscode.TestMessage): ITestErrorMessage.Serialized { + export function from(message: vscode.TestMessage2): ITestErrorMessage.Serialized { return { message: MarkdownString.fromStrict(message.message) || '', type: TestMessageType.Error, expected: message.expectedOutput, actual: message.actualOutput, + contextValue: message.contextValue, location: message.location && ({ range: Range.from(message.location.range), uri: message.location.uri }), }; } - export function to(item: ITestErrorMessage.Serialized): vscode.TestMessage { + export function to(item: ITestErrorMessage.Serialized): vscode.TestMessage2 { const message = new types.TestMessage(typeof item.message === 'string' ? item.message : MarkdownString.to(item.message)); message.actualOutput = item.actual; message.expectedOutput = item.expected; + message.contextValue = item.contextValue; message.location = item.location ? location.to(item.location) : undefined; return message; } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 98e43702685..723e2e0c500 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3884,15 +3884,13 @@ export class TestRunRequest implements vscode.TestRunRequest { ) { } } -/** Back-compat for proposed API users */ -@es5ClassCompat -export class TestRunRequest2 extends TestRunRequest { } - @es5ClassCompat export class TestMessage implements vscode.TestMessage { public expectedOutput?: string; public actualOutput?: string; public location?: vscode.Location; + /** proposed: */ + public contextValue?: string; public static diff(message: string | vscode.MarkdownString, expected: string, actual: string) { const msg = new TestMessage(message); diff --git a/src/vs/workbench/api/test/browser/extHostTesting.test.ts b/src/vs/workbench/api/test/browser/extHostTesting.test.ts index 92970a9a9cd..085bf16d5ab 100644 --- a/src/vs/workbench/api/test/browser/extHostTesting.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTesting.test.ts @@ -11,6 +11,7 @@ import { Iterable } from 'vs/base/common/iterator'; import { URI } from 'vs/base/common/uri'; import { mockObject, MockObject } from 'vs/base/test/common/mock'; import * as editorRange from 'vs/editor/common/core/range'; +import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { MainThreadTestingShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { TestRunCoordinator, TestRunDto, TestRunProfileImpl } from 'vs/workbench/api/common/extHostTesting'; @@ -594,6 +595,7 @@ suite('ExtHost Testing', () => { let req: TestRunRequest; let dto: TestRunDto; + const ext: IRelaxedExtensionDescription = {} as any; setup(async () => { proxy = mockObject()(); @@ -621,11 +623,11 @@ suite('ExtHost Testing', () => { }); test('tracks a run started from a main thread request', () => { - const tracker = c.prepareForMainThreadTestRun(req, dto, cts.token); + const tracker = c.prepareForMainThreadTestRun(req, dto, ext, cts.token); assert.strictEqual(tracker.hasRunningTasks, false); - const task1 = c.createTestRun('ctrl', single, req, 'run1', true); - const task2 = c.createTestRun('ctrl', single, req, 'run2', true); + const task1 = c.createTestRun(ext, 'ctrl', single, req, 'run1', true); + const task2 = c.createTestRun(ext, 'ctrl', single, req, 'run2', true); assert.strictEqual(proxy.$startedExtensionTestRun.called, false); assert.strictEqual(tracker.hasRunningTasks, true); @@ -646,8 +648,8 @@ suite('ExtHost Testing', () => { test('run cancel force ends after a timeout', () => { const clock = sinon.useFakeTimers(); try { - const tracker = c.prepareForMainThreadTestRun(req, dto, cts.token); - const task = c.createTestRun('ctrl', single, req, 'run1', true); + const tracker = c.prepareForMainThreadTestRun(req, dto, ext, cts.token); + const task = c.createTestRun(ext, 'ctrl', single, req, 'run1', true); const onEnded = sinon.stub(); tracker.onEnd(onEnded); @@ -671,8 +673,8 @@ suite('ExtHost Testing', () => { }); test('run cancel force ends on second cancellation request', () => { - const tracker = c.prepareForMainThreadTestRun(req, dto, cts.token); - const task = c.createTestRun('ctrl', single, req, 'run1', true); + const tracker = c.prepareForMainThreadTestRun(req, dto, ext, cts.token); + const task = c.createTestRun(ext, 'ctrl', single, req, 'run1', true); const onEnded = sinon.stub(); tracker.onEnd(onEnded); @@ -690,7 +692,7 @@ suite('ExtHost Testing', () => { }); test('tracks a run started from an extension request', () => { - const task1 = c.createTestRun('ctrl', single, req, 'hello world', false); + const task1 = c.createTestRun(ext, 'ctrl', single, req, 'hello world', false); const tracker = Iterable.first(c.trackers)!; assert.strictEqual(tracker.hasRunningTasks, true); @@ -706,8 +708,8 @@ suite('ExtHost Testing', () => { }] ]); - const task2 = c.createTestRun('ctrl', single, req, 'run2', true); - const task3Detached = c.createTestRun('ctrl', single, { ...req }, 'task3Detached', true); + const task2 = c.createTestRun(ext, 'ctrl', single, req, 'run2', true); + const task3Detached = c.createTestRun(ext, 'ctrl', single, { ...req }, 'task3Detached', true); task1.end(); assert.strictEqual(proxy.$finishedExtensionTestRun.called, false); @@ -721,7 +723,7 @@ suite('ExtHost Testing', () => { }); test('adds tests to run smartly', () => { - const task1 = c.createTestRun('ctrlId', single, req, 'hello world', false); + const task1 = c.createTestRun(ext, 'ctrlId', single, req, 'hello world', false); const tracker = Iterable.first(c.trackers)!; const expectedArgs: unknown[][] = []; assert.deepStrictEqual(proxy.$addTestsToRun.args, expectedArgs); @@ -758,7 +760,7 @@ suite('ExtHost Testing', () => { const test2 = new TestItemImpl('ctrlId', 'id-d', 'test d', URI.file('/testd.txt')); test1.range = test2.range = new Range(new Position(0, 0), new Position(1, 0)); single.root.children.replace([test1, test2]); - const task = c.createTestRun('ctrlId', single, req, 'hello world', false); + const task = c.createTestRun(ext, 'ctrlId', single, req, 'hello world', false); const message1 = new TestMessage('some message'); message1.location = new Location(URI.file('/a.txt'), new Position(0, 0)); @@ -773,6 +775,7 @@ suite('ExtHost Testing', () => { message: 'some message', type: TestMessageType.Error, expected: undefined, + contextValue: undefined, actual: undefined, location: convert.location.from(message1.location) }] @@ -787,6 +790,7 @@ suite('ExtHost Testing', () => { [{ message: 'some message', type: TestMessageType.Error, + contextValue: undefined, expected: undefined, actual: undefined, location: convert.location.from({ uri: test2.uri!, range: test2.range! }), @@ -795,7 +799,7 @@ suite('ExtHost Testing', () => { }); test('guards calls after runs are ended', () => { - const task = c.createTestRun('ctrl', single, req, 'hello world', false); + const task = c.createTestRun(ext, 'ctrl', single, req, 'hello world', false); task.end(); task.failed(single.root, new TestMessage('some message')); @@ -807,7 +811,7 @@ suite('ExtHost Testing', () => { }); test('excludes tests outside tree or explicitly excluded', () => { - const task = c.createTestRun('ctrlId', single, { + const task = c.createTestRun(ext, 'ctrlId', single, { profile: configuration, include: [single.root.children.get('id-a')!], exclude: [single.root.children.get('id-a')!.children.get('id-aa')!], @@ -835,7 +839,7 @@ suite('ExtHost Testing', () => { const childB = new TestItemImpl('ctrlId', 'id-child', 'child', undefined); testB!.children.replace([childB]); - const task1 = c.createTestRun('ctrl', single, new TestRunRequestImpl(), 'hello world', false); + const task1 = c.createTestRun(ext, 'ctrl', single, new TestRunRequestImpl(), 'hello world', false); const tracker = Iterable.first(c.trackers)!; task1.passed(childA); diff --git a/src/vs/workbench/browser/codeeditor.ts b/src/vs/workbench/browser/codeeditor.ts index 55f1173bc48..33358b10156 100644 --- a/src/vs/workbench/browser/codeeditor.ts +++ b/src/vs/workbench/browser/codeeditor.ts @@ -3,28 +3,25 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Widget } from 'vs/base/browser/ui/widget'; -import { IOverlayWidget, ICodeEditor, IOverlayWidgetPosition, OverlayWidgetPositionPreference, isCodeEditor, isCompositeEditor } from 'vs/editor/browser/editorBrowser'; +import { IAction } from 'vs/base/common/actions'; import { Emitter } from 'vs/base/common/event'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { $, append, clearNode } from 'vs/base/browser/dom'; -import { buttonBackground, buttonForeground, editorBackground, editorForeground, contrastBorder, asCssVariableWithDefault, asCssVariable } from 'vs/platform/theme/common/colorRegistry'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference, isCodeEditor, isCompositeEditor } from 'vs/editor/browser/editorBrowser'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IRange } from 'vs/editor/common/core/range'; import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/cursorEvents'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { IModelDecorationsChangeAccessor, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { TrackedRangeStickiness, IModelDecorationsChangeAccessor } from 'vs/editor/common/model'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { AbstractFloatingClickMenu, FloatingClickWidget } from 'vs/platform/actions/browser/floatingMenu'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; -import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IAction } from 'vs/base/common/actions'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export interface IRangeHighlightDecoration { resource: URI; @@ -134,106 +131,65 @@ export class RangeHighlightDecorations extends Disposable { } } -export class FloatingClickWidget extends Widget implements IOverlayWidget { - - private readonly _onClick = this._register(new Emitter()); - readonly onClick = this._onClick.event; - - private _domNode: HTMLElement; +export class FloatingEditorClickWidget extends FloatingClickWidget implements IOverlayWidget { constructor( private editor: ICodeEditor, - private label: string, + label: string, keyBindingAction: string | null, @IKeybindingService keybindingService: IKeybindingService ) { - super(); - - this._domNode = $('.floating-click-widget'); - this._domNode.style.padding = '6px 11px'; - this._domNode.style.borderRadius = '2px'; - this._domNode.style.cursor = 'pointer'; - this._domNode.style.zIndex = '1'; - - if (keyBindingAction) { - const keybinding = keybindingService.lookupKeybinding(keyBindingAction); - if (keybinding) { - this.label += ` (${keybinding.getLabel()})`; - } - } + super( + keyBindingAction && keybindingService.lookupKeybinding(keyBindingAction) + ? `${label} (${keybindingService.lookupKeybinding(keyBindingAction)!.getLabel()})` + : label + ); } getId(): string { return 'editor.overlayWidget.floatingClickWidget'; } - getDomNode(): HTMLElement { - return this._domNode; - } - getPosition(): IOverlayWidgetPosition { return { preference: OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER }; } - render() { - clearNode(this._domNode); - this._domNode.style.backgroundColor = asCssVariableWithDefault(buttonBackground, asCssVariable(editorBackground)); - this._domNode.style.color = asCssVariableWithDefault(buttonForeground, asCssVariable(editorForeground)); - this._domNode.style.border = `1px solid ${asCssVariable(contrastBorder)}`; - - append(this._domNode, $('')).textContent = this.label; - - this.onclick(this._domNode, e => this._onClick.fire()); - + override render() { + super.render(); this.editor.addOverlayWidget(this); } override dispose(): void { this.editor.removeOverlayWidget(this); - super.dispose(); } + } -export class FloatingClickMenu extends Disposable implements IEditorContribution { - +export class FloatingEditorClickMenu extends AbstractFloatingClickMenu implements IEditorContribution { static readonly ID = 'editor.contrib.floatingClickMenu'; constructor( - editor: ICodeEditor, - @IInstantiationService instantiationService: IInstantiationService, + private readonly editor: ICodeEditor, + @IInstantiationService private readonly instantiationService: IInstantiationService, @IMenuService menuService: IMenuService, @IContextKeyService contextKeyService: IContextKeyService ) { - super(); + super(MenuId.EditorContent, menuService, contextKeyService); + this.render(); + } - // DISABLED for embedded editors. In the future we can use a different MenuId for embedded editors - if (!(editor instanceof EmbeddedCodeEditorWidget)) { - const menu = menuService.createMenu(MenuId.EditorContent, contextKeyService); - const menuDisposables = new DisposableStore(); - const renderMenuAsFloatingClickBtn = () => { - menuDisposables.clear(); - if (!editor.hasModel() || editor.getOption(EditorOption.inDiffEditor)) { - return; - } - const actions: IAction[] = []; - createAndFillInActionBarActions(menu, { renderShortTitle: true, shouldForwardArgs: true }, actions); - if (actions.length === 0) { - return; - } - // todo@jrieken find a way to handle N actions, like showing a context menu - const [first] = actions; - const widget = instantiationService.createInstance(FloatingClickWidget, editor, first.label, first.id); - menuDisposables.add(widget); - menuDisposables.add(widget.onClick(() => first.run(editor.getModel().uri))); - widget.render(); - }; - this._store.add(menu); - this._store.add(menuDisposables); - this._store.add(menu.onDidChange(renderMenuAsFloatingClickBtn)); - renderMenuAsFloatingClickBtn(); - } + protected override createWidget(action: IAction): FloatingClickWidget { + return this.instantiationService.createInstance(FloatingEditorClickWidget, this.editor, action.label, action.id); + } + + protected override isVisible() { + return !(this.editor instanceof EmbeddedCodeEditorWidget) && this.editor?.hasModel() && !this.editor.getOption(EditorOption.inDiffEditor); + } + + protected override getActionArg(): unknown { + return this.editor.getModel()?.uri; } } diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 947557d30f4..a07e77fc9fd 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -54,7 +54,7 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { isMacintosh } from 'vs/base/common/platform'; import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { FloatingClickMenu } from 'vs/workbench/browser/codeeditor'; +import { FloatingEditorClickMenu } from 'vs/workbench/browser/codeeditor'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { EditorAutoSave } from 'vs/workbench/browser/parts/editor/editorAutoSave'; @@ -131,7 +131,7 @@ Registry.as(WorkbenchExtensions.Workbench).regi Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(UntitledTextEditorWorkingCopyEditorHandler, LifecyclePhase.Ready); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DynamicEditorConfigurations, LifecyclePhase.Ready); -registerEditorContribution(FloatingClickMenu.ID, FloatingClickMenu, EditorContributionInstantiation.AfterFirstRender); +registerEditorContribution(FloatingEditorClickMenu.ID, FloatingEditorClickMenu, EditorContributionInstantiation.AfterFirstRender); //#endregion //#region Quick Access diff --git a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts index 26754add9a0..44ba3928008 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts @@ -18,7 +18,7 @@ import { ContextKeyEqualsExpr, ContextKeyExpr } from 'vs/platform/contextkey/com import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; +import { FloatingEditorClickWidget } from 'vs/workbench/browser/codeeditor'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; @@ -47,7 +47,7 @@ class DiffEditorHelperContribution extends Disposable implements IDiffEditorCont /** @description update state */ if (onlyWhiteSpaceChange.read(reader)) { const helperWidget = store.add(this._instantiationService.createInstance( - FloatingClickWidget, + FloatingEditorClickWidget, this._diffEditor.getModifiedEditor(), localize('hintWhitespace', "Show Whitespace Differences"), null diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 56470d34233..06c4fa63988 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -41,7 +41,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; +import { FloatingEditorClickWidget } from 'vs/workbench/browser/codeeditor'; import { DebugHoverWidget, ShowDebugHoverResult } from 'vs/workbench/contrib/debug/browser/debugHover'; import { ExceptionWidget } from 'vs/workbench/contrib/debug/browser/exceptionWidget'; import { CONTEXT_EXCEPTION_WIDGET_VISIBLE, IDebugConfiguration, IDebugEditorContribution, IDebugService, IDebugSession, IExceptionInfo, IExpression, IStackFrame, State } from 'vs/workbench/contrib/debug/common/debug'; @@ -219,7 +219,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { private gutterIsHovered = false; private exceptionWidget: ExceptionWidget | undefined; - private configurationWidget: FloatingClickWidget | undefined; + private configurationWidget: FloatingEditorClickWidget | undefined; private altListener: IDisposable | undefined; private altPressed = false; private oldDecorations = this.editor.createDecorationsCollection(); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 4ccc2ee5de9..c418b358192 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -87,7 +87,7 @@ import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { NotebookPerfMarks } from 'vs/workbench/contrib/notebook/common/notebookPerformance'; import { BaseCellEditorOptions } from 'vs/workbench/contrib/notebook/browser/viewModel/cellEditorOptions'; -import { FloatingClickMenu } from 'vs/workbench/browser/codeeditor'; +import { FloatingEditorClickMenu } from 'vs/workbench/browser/codeeditor'; import { IDimension } from 'vs/editor/common/core/dimension'; import { CellFindMatchModel } from 'vs/workbench/contrib/notebook/browser/contrib/find/findModel'; import { INotebookLoggingService } from 'vs/workbench/contrib/notebook/common/notebookLoggingService'; @@ -107,7 +107,7 @@ export function getDefaultNotebookCreationOptions(): INotebookEditorCreationOpti // We inlined the id to avoid loading comment contrib in tests const skipContributions = [ 'editor.contrib.review', - FloatingClickMenu.ID, + FloatingEditorClickMenu.ID, 'editor.contrib.dirtydiff', 'editor.contrib.testingOutputPeek', 'editor.contrib.testingDecorations', diff --git a/src/vs/workbench/contrib/testing/browser/media/testing.css b/src/vs/workbench/contrib/testing/browser/media/testing.css index 479c6ef2e89..060e138c348 100644 --- a/src/vs/workbench/contrib/testing/browser/media/testing.css +++ b/src/vs/workbench/contrib/testing/browser/media/testing.css @@ -199,6 +199,12 @@ overflow: hidden; } +.test-output-peek-message-container .floating-click-widget { + position: absolute; + right: 20px; + bottom: 10px; +} + .test-output-peek-message-container, .test-output-peek-tree { height: 100%; diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index d0d76ea7e8c..5043900e175 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -27,6 +27,7 @@ import { Iterable } from 'vs/base/common/iterator'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, DisposableStore, IDisposable, IReference, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { MarshalledId } from 'vs/base/common/marshallingIds'; import { count } from 'vs/base/common/strings'; import { ThemeIcon } from 'vs/base/common/themables'; import { isDefined } from 'vs/base/common/types'; @@ -48,6 +49,7 @@ import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/mar import { IPeekViewService, PeekViewWidget, peekViewResultsBackground, peekViewTitleForeground, peekViewTitleInfoForeground } from 'vs/editor/contrib/peekView/browser/peekView'; import { localize } from 'vs/nls'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; +import { FloatingClickMenu } from 'vs/platform/actions/browser/floatingMenu'; import { MenuEntryActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { Action2, IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -88,7 +90,7 @@ import { ITestProfileService } from 'vs/workbench/contrib/testing/common/testPro import { ITaskRawOutput, ITestResult, ITestRunTaskResults, LiveTestResult, TestResultItemChange, TestResultItemChangeReason, maxCountPriority, resultItemParents } from 'vs/workbench/contrib/testing/common/testResult'; import { ITestResultService, ResultChangeEvent } from 'vs/workbench/contrib/testing/common/testResultService'; import { ITestService } from 'vs/workbench/contrib/testing/common/testService'; -import { IRichLocation, ITestErrorMessage, ITestItem, ITestMessage, ITestRunTask, ITestTaskState, TestMessageType, TestResultItem, TestResultState, TestRunProfileBitset, getMarkId } from 'vs/workbench/contrib/testing/common/testTypes'; +import { IRichLocation, ITestErrorMessage, ITestItem, ITestMessage, ITestMessageMenuArgs, ITestRunTask, ITestTaskState, TestMessageType, TestResultItem, TestResultState, TestRunProfileBitset, getMarkId } from 'vs/workbench/contrib/testing/common/testTypes'; import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys'; import { IShowResultOptions, ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener'; import { cmpPriority, isFailedState } from 'vs/workbench/contrib/testing/common/testingStates'; @@ -98,20 +100,30 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic class MessageSubject { public readonly test: ITestItem; public readonly message: ITestMessage; - public readonly messages: ITestMessage[]; public readonly expectedUri: URI; public readonly actualUri: URI; public readonly messageUri: URI; public readonly revealLocation: IRichLocation | undefined; public get isDiffable() { - const message = this.messages[this.messageIndex]; - return message.type === TestMessageType.Error && isDiffable(message); + return this.message.type === TestMessageType.Error && isDiffable(this.message); + } + + public get contextValue() { + return this.message.type === TestMessageType.Error ? this.message.contextValue : undefined; + } + + public get context(): ITestMessageMenuArgs { + return { + $mid: MarshalledId.TestMessageMenuArgs, + extId: this.test.extId, + message: ITestMessage.serialize(this.message), + }; } constructor(public readonly resultId: string, test: TestResultItem, public readonly taskIndex: number, public readonly messageIndex: number) { this.test = test.item; - this.messages = test.tasks[taskIndex].messages; + const messages = test.tasks[taskIndex].messages; this.messageIndex = messageIndex; const parts = { messageIndex, resultId, taskIndex, testExtId: test.item.extId }; @@ -119,7 +131,7 @@ class MessageSubject { this.actualUri = buildTestUri({ ...parts, type: TestUriType.ResultActualOutput }); this.messageUri = buildTestUri({ ...parts, type: TestUriType.ResultMessage }); - const message = this.message = this.messages[this.messageIndex]; + const message = this.message = messages[this.messageIndex]; this.revealLocation = message.location ?? (test.item.uri && test.item.range ? { uri: test.item.uri, range: Range.lift(test.item.range) } : undefined); } } @@ -148,7 +160,7 @@ type InspectSubject = MessageSubject | TaskSubject | TestOutputSubject; const equalsSubject = (a: InspectSubject, b: InspectSubject) => a.resultId === b.resultId && a.taskIndex === b.taskIndex && ( - (a instanceof MessageSubject && b instanceof MessageSubject && a.messageIndex === b.messageIndex) || + (a instanceof MessageSubject && b instanceof MessageSubject && a.message === b.message) || (a instanceof TaskSubject && b instanceof TaskSubject) || (a instanceof TestOutputSubject && b instanceof TestOutputSubject && a.test === b.test) ); @@ -746,8 +758,10 @@ class TestResultsViewContent extends Disposable { private static lastSplitWidth?: number; private readonly didReveal = this._register(new Emitter<{ subject: InspectSubject; preserveFocus: boolean }>()); + private readonly clickMenu = this._register(new MutableDisposable()); private dimension?: dom.Dimension; private splitView!: SplitView; + private messageContainer!: HTMLElement; private contentProviders!: IPeekOutputRenderer[]; private contentProvidersUpdateLimiter = this._register(new Limiter(1)); @@ -764,6 +778,7 @@ class TestResultsViewContent extends Disposable { }, @IInstantiationService private readonly instantiationService: IInstantiationService, @ITextModelService protected readonly modelService: ITextModelService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { super(); } @@ -774,7 +789,7 @@ class TestResultsViewContent extends Disposable { const { historyVisible, showRevealLocationOnMessages } = this.options; const isInPeekView = this.editor !== undefined; - const messageContainer = dom.append(containerElement, dom.$('.test-output-peek-message-container')); + const messageContainer = this.messageContainer = dom.append(containerElement, dom.$('.test-output-peek-message-container')); this.contentProviders = [ this._register(this.instantiationService.createInstance(DiffContentProvider, this.editor, messageContainer)), this._register(this.instantiationService.createInstance(MarkdownTestMessagePeek, messageContainer)), @@ -834,14 +849,30 @@ class TestResultsViewContent extends Disposable { * Shows a message in-place without showing or changing the peek location. * This is mostly used if peeking a message without a location. */ - public async reveal(opts: { subject: InspectSubject; preserveFocus: boolean }) { + public reveal(opts: { subject: InspectSubject; preserveFocus: boolean }) { this.didReveal.fire(opts); - if (!this.current || !equalsSubject(this.current, opts.subject)) { - this.current = opts.subject; - await this.contentProvidersUpdateLimiter.queue(() => Promise.all( - this.contentProviders.map(p => p.update(opts.subject)))); + if (this.current && equalsSubject(this.current, opts.subject)) { + return Promise.resolve(); } + + this.current = opts.subject; + return this.contentProvidersUpdateLimiter.queue(async () => { + await Promise.all(this.contentProviders.map(p => p.update(opts.subject))); + + if (opts.subject instanceof MessageSubject) { + const contextOverlay = this.contextKeyService.createOverlay([[TestingContextKeys.testMessageContext.key, opts.subject.contextValue]]); + this.clickMenu.value = this.instantiationService + .createChild(new ServiceCollection([IContextKeyService, contextOverlay])) + .createInstance(FloatingClickMenu, { + container: this.messageContainer, + menuId: MenuId.TestMessageContent, + getActionArg: () => (opts.subject as MessageSubject).context, + }); + } else { + this.clickMenu.clear(); + } + }); } public onLayoutBody(height: number, width: number) { @@ -1507,11 +1538,15 @@ class TerminalMessagePeek extends Disposable implements IPeekOutputRenderer { } } -const hintMessagePeekHeight = (msg: ITestMessage) => - isDiffable(msg) +const hintMessagePeekHeight = (msg: ITestMessage) => { + const msgHeight = isDiffable(msg) ? Math.max(hintPeekStrHeight(msg.actual), hintPeekStrHeight(msg.expected)) : hintPeekStrHeight(typeof msg.message === 'string' ? msg.message : msg.message.value); + // add 8ish lines for the size of the title and decorations in the peek. + return msgHeight + 8; +}; + const firstLine = (str: string) => { const index = str.indexOf('\n'); return index === -1 ? str : str.slice(0, index); @@ -1519,8 +1554,7 @@ const firstLine = (str: string) => { const isMultiline = (str: string | undefined) => !!str && str.includes('\n'); -// add 5ish lines for the size of the title and decorations in the peek. -const hintPeekStrHeight = (str: string) => Math.min(count(str, '\n') + 5, 24); +const hintPeekStrHeight = (str: string) => Math.min(count(str, '\n'), 24); class SimpleDiffEditorModel extends EditorModel { public readonly original = this._original.object.textEditorModel; @@ -1671,13 +1705,23 @@ class TaskElement implements ITreeElement { class TestMessageElement implements ITreeElement { public readonly type = 'message'; - public readonly context: URI; public readonly id: string; public readonly label: string; public readonly uri: URI; public readonly location?: IRichLocation; public readonly description?: string; public readonly onDidChange = Event.None; + public readonly contextValue?: string; + public readonly message: ITestMessage; + + public get context(): ITestMessageMenuArgs { + return { + $mid: MarshalledId.TestMessageMenuArgs, + extId: this.test.item.extId, + message: ITestMessage.serialize(this.message), + }; + } + constructor( public readonly result: ITestResult, @@ -1685,10 +1729,11 @@ class TestMessageElement implements ITreeElement { public readonly taskIndex: number, public readonly messageIndex: number, ) { - const m = test.tasks[taskIndex].messages[messageIndex]; + const m = this.message = test.tasks[taskIndex].messages[messageIndex]; this.location = m.location; - this.uri = this.context = buildTestUri({ + this.contextValue = m.type === TestMessageType.Error ? m.contextValue : undefined; + this.uri = buildTestUri({ type: TestUriType.ResultMessage, messageIndex, resultId: result.id, @@ -2084,7 +2129,7 @@ class TestRunElementRenderer implements ICompressibleTreeRenderer this.requestReveal.fire(new TaskSubject(element.results.id, element.index)), + )); + } + + if (element instanceof TestResultElement) { + // only show if there are no collapsed test nodes that have more specific choices + if (element.value.tasks.length === 1) { primary.push(new Action( 'testing.outputPeek.showResultOutput', localize('testing.showResultOutput', "Show Result Output"), ThemeIcon.asClassName(Codicon.terminal), undefined, - () => this.requestReveal.fire(new TaskSubject(element.results.id, element.index)), + () => this.requestReveal.fire(new TaskSubject(element.value.id, 0)), )); } - if (element instanceof TestResultElement) { - // only show if there are no collapsed test nodes that have more specific choices - if (element.value.tasks.length === 1) { - primary.push(new Action( - 'testing.outputPeek.showResultOutput', - localize('testing.showResultOutput', "Show Result Output"), - ThemeIcon.asClassName(Codicon.terminal), - undefined, - () => this.requestReveal.fire(new TaskSubject(element.value.id, 0)), - )); - } + primary.push(new Action( + 'testing.outputPeek.reRunLastRun', + localize('testing.reRunLastRun', "Rerun Test Run"), + ThemeIcon.asClassName(icons.testingRunIcon), + undefined, + () => this.commandService.executeCommand('testing.reRunLastRun', element.value.id), + )); + if (capabilities & TestRunProfileBitset.Debug) { primary.push(new Action( - 'testing.outputPeek.reRunLastRun', - localize('testing.reRunLastRun', "Rerun Test Run"), + 'testing.outputPeek.debugLastRun', + localize('testing.debugLastRun', "Debug Test Run"), + ThemeIcon.asClassName(icons.testingDebugIcon), + undefined, + () => this.commandService.executeCommand('testing.debugLastRun', element.value.id), + )); + } + } + + if (element instanceof TestCaseElement) { + const extId = element.test.item.extId; + contextKeys.push(...getTestItemContextOverlay(element.test, capabilities)); + + primary.push(new Action( + 'testing.outputPeek.goToFile', + localize('testing.goToFile', "Go to Source"), + ThemeIcon.asClassName(Codicon.goToFile), + undefined, + () => this.commandService.executeCommand('vscode.revealTest', extId), + )); + + if (element.test.tasks[element.taskIndex].messages.some(m => m.type === TestMessageType.Output)) { + primary.push(new Action( + 'testing.outputPeek.showResultOutput', + localize('testing.showResultOutput', "Show Result Output"), + ThemeIcon.asClassName(Codicon.terminal), + undefined, + () => this.requestReveal.fire(element.outputSubject), + )); + } + + secondary.push(new Action( + 'testing.outputPeek.revealInExplorer', + localize('testing.revealInExplorer', "Reveal in Test Explorer"), + ThemeIcon.asClassName(Codicon.listTree), + undefined, + () => this.commandService.executeCommand('_revealTestInExplorer', extId), + )); + + if (capabilities & TestRunProfileBitset.Run) { + primary.push(new Action( + 'testing.outputPeek.runTest', + localize('run test', 'Run Test'), ThemeIcon.asClassName(icons.testingRunIcon), undefined, - () => this.commandService.executeCommand('testing.reRunLastRun', element.value.id), + () => this.commandService.executeCommand('vscode.runTestsById', TestRunProfileBitset.Run, extId), )); - - if (capabilities & TestRunProfileBitset.Debug) { - primary.push(new Action( - 'testing.outputPeek.debugLastRun', - localize('testing.debugLastRun', "Debug Test Run"), - ThemeIcon.asClassName(icons.testingDebugIcon), - undefined, - () => this.commandService.executeCommand('testing.debugLastRun', element.value.id), - )); - } } - if (element instanceof TestCaseElement) { - const extId = element.test.item.extId; + if (capabilities & TestRunProfileBitset.Debug) { primary.push(new Action( - 'testing.outputPeek.goToFile', - localize('testing.goToFile', "Go to Source"), + 'testing.outputPeek.debugTest', + localize('debug test', 'Debug Test'), + ThemeIcon.asClassName(icons.testingDebugIcon), + undefined, + () => this.commandService.executeCommand('vscode.runTestsById', TestRunProfileBitset.Debug, extId), + )); + } + } + + if (element instanceof TestMessageElement) { + id = MenuId.TestMessageContext; + contextKeys.push([TestingContextKeys.testMessageContext.key, element.contextValue]); + if (this.showRevealLocationOnMessages && element.location) { + primary.push(new Action( + 'testing.outputPeek.goToError', + localize('testing.goToError', "Go to Source"), ThemeIcon.asClassName(Codicon.goToFile), undefined, - () => this.commandService.executeCommand('vscode.revealTest', extId), + () => this.editorService.openEditor({ + resource: element.location!.uri, + options: { + selection: element.location!.range, + preserveFocus: true, + } + }), )); - - if (element.test.tasks[element.taskIndex].messages.some(m => m.type === TestMessageType.Output)) { - primary.push(new Action( - 'testing.outputPeek.showResultOutput', - localize('testing.showResultOutput', "Show Result Output"), - ThemeIcon.asClassName(Codicon.terminal), - undefined, - () => this.requestReveal.fire(element.outputSubject), - )); - } - - secondary.push(new Action( - 'testing.outputPeek.revealInExplorer', - localize('testing.revealInExplorer', "Reveal in Test Explorer"), - ThemeIcon.asClassName(Codicon.listTree), - undefined, - () => this.commandService.executeCommand('_revealTestInExplorer', extId), - )); - - if (capabilities & TestRunProfileBitset.Run) { - primary.push(new Action( - 'testing.outputPeek.runTest', - localize('run test', 'Run Test'), - ThemeIcon.asClassName(icons.testingRunIcon), - undefined, - () => this.commandService.executeCommand('vscode.runTestsById', TestRunProfileBitset.Run, extId), - )); - } - - if (capabilities & TestRunProfileBitset.Debug) { - primary.push(new Action( - 'testing.outputPeek.debugTest', - localize('debug test', 'Debug Test'), - ThemeIcon.asClassName(icons.testingDebugIcon), - undefined, - () => this.commandService.executeCommand('vscode.runTestsById', TestRunProfileBitset.Debug, extId), - )); - } } + } - if (element instanceof TestMessageElement) { - if (this.showRevealLocationOnMessages && element.location) { - primary.push(new Action( - 'testing.outputPeek.goToError', - localize('testing.goToError', "Go to Source"), - ThemeIcon.asClassName(Codicon.goToFile), - undefined, - () => this.editorService.openEditor({ - resource: element.location!.uri, - options: { - selection: element.location!.range, - preserveFocus: true, - } - }), - )); - } - } - - const result = { primary, secondary }; - createAndFillInActionBarActions(menu, { - shouldForwardArgs: true, - }, result, 'inline'); + const contextOverlay = this.contextKeyService.createOverlay(contextKeys); + const result = { primary, secondary }; + const menu = this.menuService.createMenu(id, contextOverlay); + try { + createAndFillInActionBarActions(menu, { arg: element.context }, result, 'inline'); return result; } finally { menu.dispose(); diff --git a/src/vs/workbench/contrib/testing/common/testTypes.ts b/src/vs/workbench/contrib/testing/common/testTypes.ts index 99590868fd0..0b035192993 100644 --- a/src/vs/workbench/contrib/testing/common/testTypes.ts +++ b/src/vs/workbench/contrib/testing/common/testTypes.ts @@ -156,6 +156,7 @@ export interface ITestErrorMessage { type: TestMessageType.Error; expected: string | undefined; actual: string | undefined; + contextValue: string | undefined; location: IRichLocation | undefined; } @@ -165,6 +166,7 @@ export namespace ITestErrorMessage { type: TestMessageType.Error; expected: string | undefined; actual: string | undefined; + contextValue: string | undefined; location: IRichLocation.Serialize | undefined; } @@ -173,6 +175,7 @@ export namespace ITestErrorMessage { type: TestMessageType.Error, expected: message.expected, actual: message.actual, + contextValue: message.contextValue, location: message.location && IRichLocation.serialize(message.location), }); @@ -181,6 +184,7 @@ export namespace ITestErrorMessage { type: TestMessageType.Error, expected: message.expected, actual: message.actual, + contextValue: message.contextValue, location: message.location && IRichLocation.deserialize(message.location), }); } @@ -632,6 +636,18 @@ export interface ITestItemContext { tests: InternalTestItem.Serialized[]; } +/** + * Context for actions taken in the test explorer view. + */ +export interface ITestMessageMenuArgs { + /** Marshalling marker */ + $mid: MarshalledId.TestMessageMenuArgs; + /** Tests ext ID */ + extId: string; + /** Serialized test message */ + message: ITestMessage.Serialized; +} + /** * Request from the ext host or main thread to indicate that tests have * changed. It's assumed that any item upserted *must* have its children diff --git a/src/vs/workbench/contrib/testing/common/testingContextKeys.ts b/src/vs/workbench/contrib/testing/common/testingContextKeys.ts index 05e5536590e..fd8c90d0ce6 100644 --- a/src/vs/workbench/contrib/testing/common/testingContextKeys.ts +++ b/src/vs/workbench/contrib/testing/common/testingContextKeys.ts @@ -58,4 +58,8 @@ export namespace TestingContextKeys { type: 'boolean', description: localize('testing.testItemIsHidden', 'Boolean indicating whether the test item is hidden') }); + export const testMessageContext = new RawContextKey('testMessage', undefined, { + type: 'boolean', + description: localize('testing.testMessage', 'Value set in `testMessage.contextValue`, available in editor/content and testing/message/context') + }); } diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index a6f9d4cb606..e2fb27d6d8b 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -260,6 +260,16 @@ const apiMenus: IAPIMenu[] = [ id: MenuId.TestItemGutter, description: localize('testing.item.gutter.title', "The menu for a gutter decoration for a test item"), }, + { + key: 'testing/message/context', + id: MenuId.TestMessageContext, + description: localize('testing.message.context.title', "A prominent button overlaying editor content where the message is displayed"), + }, + { + key: 'testing/message/content', + id: MenuId.TestMessageContent, + description: localize('testing.message.content.title', "Context menu for the message in the results tree"), + }, { key: 'extension/context', id: MenuId.ExtensionContext, diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 5c4ca2842b0..8f6f09ae49d 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -90,6 +90,7 @@ export const allApiProposals = Object.freeze({ terminalQuickFixProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalQuickFixProvider.d.ts', terminalSelection: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalSelection.d.ts', testCoverage: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testCoverage.d.ts', + testMessageContextValue: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testMessageContextValue.d.ts', testObserver: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testObserver.d.ts', textSearchProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts', timeline: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.timeline.d.ts', diff --git a/src/vscode-dts/vscode.proposed.testMessageContextValue.d.ts b/src/vscode-dts/vscode.proposed.testMessageContextValue.d.ts new file mode 100644 index 00000000000..515a99d0e2b --- /dev/null +++ b/src/vscode-dts/vscode.proposed.testMessageContextValue.d.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/190277 + + export class TestMessage2 extends TestMessage { + + /** + * Context value of the test item. This can be used to contribute message- + * specific actions to the test peek view. The value set here can be found + * in the `testMessage` property of the following `menus` contribution points: + * + * - `testing/message/context` - context menu for the message in the results tree + * - `testing/message/content` - a prominent button overlaying editor content where + * the message is displayed. + * + * For example: + * + * ```json + * "contributes": { + * "menus": { + * "testing/message/content": [ + * { + * "command": "extension.deleteCommentThread", + * "when": "testMessage == canApplyRichDiff" + * } + * ] + * } + * } + * ``` + * + * The command will be called with an object containing: + * - `test`: the {@link TestItem} the message is associated with, *if* it + * is still present in the {@link TestController.items} collection. + * - `message`: the {@link TestMessage} instance. + */ + contextValue?: string; + + // ... + } +} From a029c9576da8ce2fc6a506952ae442638aaf359a Mon Sep 17 00:00:00 2001 From: Tim Hutt Date: Sat, 12 Aug 2023 21:05:00 +0100 Subject: [PATCH 18/27] Support . as a row:column separator in terminal link detector The motivation here is the Sail language compiler which outputs errors conforming to the GNU style https://www.gnu.org/prep/standards/html_node/Errors.html I think they must be the only people in the world actually using the `line0.col0-line1.col1` format. I did have an attempt to capture the ending line/column but it is very difficult with regex, and not that useful anyway so I've opted for the simpler option of just ignoring the `-` part. --- .../terminalContrib/links/browser/terminalLinkParsing.ts | 5 ++++- .../links/test/browser/terminalLinkParsing.test.ts | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing.ts index a94483ab0e3..1e8b5bec80f 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing.ts @@ -71,11 +71,14 @@ function generateLinkSuffixRegex(eolOnly: boolean) { const lineAndColumnRegexClauses = [ // foo:339 // foo:339:12 + // foo:339.12 // foo 339 // foo 339:12 [#140780] + // foo 339.12 // "foo",339 // "foo",339:12 - `(?::| |['"],)${r()}(:${c()})?` + eolSuffix, + // "foo",339.12 + `(?::| |['"],)${r()}([:.]${c()})?` + eolSuffix, // The quotes below are optional [#171652] // "foo", line 339 [#40468] // "foo", line 339, col 12 diff --git a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkParsing.test.ts b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkParsing.test.ts index c32c17d22e1..e7ac28b5efa 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkParsing.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkParsing.test.ts @@ -43,8 +43,10 @@ const testLinks: ITestLink[] = [ { link: 'foo', prefix: undefined, suffix: undefined, hasRow: false, hasCol: false }, { link: 'foo:339', prefix: undefined, suffix: ':339', hasRow: true, hasCol: false }, { link: 'foo:339:12', prefix: undefined, suffix: ':339:12', hasRow: true, hasCol: true }, + { link: 'foo:339.12', prefix: undefined, suffix: ':339.12', hasRow: true, hasCol: true }, { link: 'foo 339', prefix: undefined, suffix: ' 339', hasRow: true, hasCol: false }, { link: 'foo 339:12', prefix: undefined, suffix: ' 339:12', hasRow: true, hasCol: true }, + { link: 'foo 339.12', prefix: undefined, suffix: ' 339.12', hasRow: true, hasCol: true }, // Double quotes { link: '"foo",339', prefix: '"', suffix: '",339', hasRow: true, hasCol: false }, From 945442130e785d3567d46d11bdafb422031f0179 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 13 Aug 2023 10:55:46 -0700 Subject: [PATCH 19/27] Add more test cases for new row.col pattern --- .../links/test/browser/terminalLinkParsing.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkParsing.test.ts b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkParsing.test.ts index e7ac28b5efa..c200c575208 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkParsing.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkParsing.test.ts @@ -51,6 +51,7 @@ const testLinks: ITestLink[] = [ // Double quotes { link: '"foo",339', prefix: '"', suffix: '",339', hasRow: true, hasCol: false }, { link: '"foo",339:12', prefix: '"', suffix: '",339:12', hasRow: true, hasCol: true }, + { link: '"foo",339.12', prefix: '"', suffix: '",339.12', hasRow: true, hasCol: true }, { link: '"foo", line 339', prefix: '"', suffix: '", line 339', hasRow: true, hasCol: false }, { link: '"foo", line 339, col 12', prefix: '"', suffix: '", line 339, col 12', hasRow: true, hasCol: true }, { link: '"foo", line 339, column 12', prefix: '"', suffix: '", line 339, column 12', hasRow: true, hasCol: true }, @@ -69,6 +70,7 @@ const testLinks: ITestLink[] = [ // Single quotes { link: '\'foo\',339', prefix: '\'', suffix: '\',339', hasRow: true, hasCol: false }, { link: '\'foo\',339:12', prefix: '\'', suffix: '\',339:12', hasRow: true, hasCol: true }, + { link: '\'foo\',339.12', prefix: '\'', suffix: '\',339.12', hasRow: true, hasCol: true }, { link: '\'foo\', line 339', prefix: '\'', suffix: '\', line 339', hasRow: true, hasCol: false }, { link: '\'foo\', line 339, col 12', prefix: '\'', suffix: '\', line 339, col 12', hasRow: true, hasCol: true }, { link: '\'foo\', line 339, column 12', prefix: '\'', suffix: '\', line 339, column 12', hasRow: true, hasCol: true }, From b6b13f96709f3685d8688d79d7b44609a52c5f3c Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Sun, 13 Aug 2023 23:37:19 -0700 Subject: [PATCH 20/27] up distro (#190376) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ea14c3b1bf7..78464df1859 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.82.0", - "distro": "b61a76db7be19cb1582c2a9a7157d098e1850562", + "distro": "510a8d740286d6895197995d1edc978ffe23cadf", "author": { "name": "Microsoft Corporation" }, From b7ebbb6990b1d2a04b5cbbd816cfa73f96b1c1d8 Mon Sep 17 00:00:00 2001 From: Tim Hutt Date: Mon, 14 Aug 2023 08:47:40 +0100 Subject: [PATCH 21/27] Update dev container node version (#190346) VSCode requires version Node version 18-20, however 20 doesn't work because it is incompatible with `eslint-plugin-jsdoc@39.3.2`. Fixes #190331 --- .devcontainer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index ad241c22eb6..7c686b12e94 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/devcontainers/typescript-node:16-bullseye +FROM mcr.microsoft.com/devcontainers/typescript-node:18-bookworm ADD install-vscode.sh /root/ RUN /root/install-vscode.sh From 11c5aa237005e807ef98feaa9e3113251302216d Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 14 Aug 2023 11:29:47 +0200 Subject: [PATCH 22/27] Marks editor.experimental.asyncTokenizationVerification as experimental --- src/vs/editor/common/config/editorConfigurationSchema.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/editor/common/config/editorConfigurationSchema.ts b/src/vs/editor/common/config/editorConfigurationSchema.ts index c3ce32a7e1e..b605d1dadf3 100644 --- a/src/vs/editor/common/config/editorConfigurationSchema.ts +++ b/src/vs/editor/common/config/editorConfigurationSchema.ts @@ -110,6 +110,7 @@ const editorConfiguration: IConfigurationNode = { type: 'boolean', default: false, description: nls.localize('editor.experimental.asyncTokenizationVerification', "Controls whether async tokenization should be verified against legacy background tokenization. Might slow down tokenization. For debugging only."), + tags: ['experimental'], }, 'editor.language.brackets': { type: ['array', 'null'], From bf5538618f97ae395714b01bebff0578d59836ac Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 14 Aug 2023 05:37:02 -0700 Subject: [PATCH 23/27] Update src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 --- .../contrib/terminal/browser/media/shellIntegration.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 index 6ce62d540d8..0d79bad8b3a 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 @@ -121,6 +121,7 @@ if (Get-Module -Name PSReadLine) { # Set IsWindows property if ($PSVersionTable.PSVersion -lt "6.0") { + # Windows PowerShell is only available on Windows [Console]::Write("$([char]0x1b)]633;P;IsWindows=$true`a") } else { [Console]::Write("$([char]0x1b)]633;P;IsWindows=$IsWindows`a") From 538698f1acf710a0150c6a28cb2f1466d4af705a Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 14 Aug 2023 14:45:06 +0200 Subject: [PATCH 24/27] Fixes #188470 --- .../widget/workerBasedDocumentDiffProvider.ts | 9 +++++ .../common/diff/standardLinesDiffComputer.ts | 34 +++++++++---------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/vs/editor/browser/widget/workerBasedDocumentDiffProvider.ts b/src/vs/editor/browser/widget/workerBasedDocumentDiffProvider.ts index 8994275abc1..cca1f5ab297 100644 --- a/src/vs/editor/browser/widget/workerBasedDocumentDiffProvider.ts +++ b/src/vs/editor/browser/widget/workerBasedDocumentDiffProvider.ts @@ -41,6 +41,15 @@ export class WorkerBasedDocumentDiffProvider implements IDocumentDiffProvider, I // This significantly speeds up the case when the original file is empty if (original.getLineCount() === 1 && original.getLineMaxColumn(1) === 1) { + if (modified.getLineCount() === 1 && modified.getLineMaxColumn(1) === 1) { + return { + changes: [], + identical: true, + quitEarly: false, + moves: [], + }; + } + return { changes: [ new LineRangeMapping( diff --git a/src/vs/editor/common/diff/standardLinesDiffComputer.ts b/src/vs/editor/common/diff/standardLinesDiffComputer.ts index f9a4b4a4540..b9ef58b85c2 100644 --- a/src/vs/editor/common/diff/standardLinesDiffComputer.ts +++ b/src/vs/editor/common/diff/standardLinesDiffComputer.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { compareBy, findLastIndex, numberComparator, reverseOrder } from 'vs/base/common/arrays'; +import { compareBy, equals, findLastIndex, numberComparator, reverseOrder } from 'vs/base/common/arrays'; import { assertFn, checkAdjacentItems } from 'vs/base/common/assert'; import { CharCode } from 'vs/base/common/charCode'; import { SetMap } from 'vs/base/common/collections'; @@ -22,23 +22,23 @@ export class StandardLinesDiffComputer implements ILinesDiffComputer { private readonly myersDiffingAlgorithm = new MyersDiffAlgorithm(); computeDiff(originalLines: string[], modifiedLines: string[], options: ILinesDiffComputerOptions): LinesDiff { + if (originalLines.length <= 1 && equals(originalLines, modifiedLines, (a, b) => a === b)) { + return new LinesDiff([], [], false); + } + if (originalLines.length === 1 && originalLines[0].length === 0 || modifiedLines.length === 1 && modifiedLines[0].length === 0) { - return { - changes: [ - new LineRangeMapping( - new LineRange(1, originalLines.length + 1), - new LineRange(1, modifiedLines.length + 1), - [ - new RangeMapping( - new Range(1, 1, originalLines.length, originalLines[0].length + 1), - new Range(1, 1, modifiedLines.length, modifiedLines[0].length + 1) - ) - ] - ) - ], - hitTimeout: false, - moves: [], - }; + return new LinesDiff([ + new LineRangeMapping( + new LineRange(1, originalLines.length + 1), + new LineRange(1, modifiedLines.length + 1), + [ + new RangeMapping( + new Range(1, 1, originalLines.length, originalLines[0].length + 1), + new Range(1, 1, modifiedLines.length, modifiedLines[0].length + 1) + ) + ] + ) + ], [], false); } const timeout = options.maxComputationTimeMs === 0 ? InfiniteTimeout.instance : new DateTimeout(options.maxComputationTimeMs); From bda54250c8e5f423c5cb25d99a506dd92d73586c Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 14 Aug 2023 15:45:46 +0200 Subject: [PATCH 25/27] Fixes #190413 --- .../widget/diffEditorWidget2/overviewRulerPart.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditorWidget2/overviewRulerPart.ts b/src/vs/editor/browser/widget/diffEditorWidget2/overviewRulerPart.ts index c051508fd84..8996560b36a 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget2/overviewRulerPart.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget2/overviewRulerPart.ts @@ -119,13 +119,18 @@ export class OverviewRulerPart extends Disposable { .map(r => { const start = vm.coordinatesConverter.convertModelPositionToViewPosition(new Position(r.startLineNumber, 1)); const end = vm.coordinatesConverter.convertModelPositionToViewPosition(new Position(r.endLineNumberExclusive, 1)); - - return new OverviewRulerZone(start.lineNumber, end.lineNumber, 0, color.toString()); + // By computing the lineCount, we won't ask the view model later for the bottom vertical position. + // (The view model will take into account the alignment viewzones, which will give + // modifications and deletetions always the same height.) + const lineCount = end.lineNumber - start.lineNumber; + return new OverviewRulerZone(start.lineNumber, end.lineNumber, lineCount, color.toString()); }); } - originalOverviewRuler?.setZones(createZones((diff || []).map(d => d.lineRangeMapping.originalRange), colors.removeColor, this._editors.original)); - modifiedOverviewRuler?.setZones(createZones((diff || []).map(d => d.lineRangeMapping.modifiedRange), colors.insertColor, this._editors.modified)); + const originalZones = createZones((diff || []).map(d => d.lineRangeMapping.originalRange), colors.removeColor, this._editors.original); + const modifiedZones = createZones((diff || []).map(d => d.lineRangeMapping.modifiedRange), colors.insertColor, this._editors.modified); + originalOverviewRuler?.setZones(originalZones); + modifiedOverviewRuler?.setZones(modifiedZones); })); store.add(autorun(reader => { From 0c6b510904b43bd39629a624e30efe389a3d8fd8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 14 Aug 2023 07:37:43 -0700 Subject: [PATCH 26/27] xterm@5.3.0-beta.39 Fixes #190374 Fixes #190371 --- package.json | 12 +++++------ remote/package.json | 12 +++++------ remote/web/package.json | 8 +++---- remote/web/yarn.lock | 32 +++++++++++++-------------- remote/yarn.lock | 48 ++++++++++++++++++++--------------------- yarn.lock | 48 ++++++++++++++++++++--------------------- 6 files changed, 80 insertions(+), 80 deletions(-) diff --git a/package.json b/package.json index 78464df1859..5ecb6e3e82c 100644 --- a/package.json +++ b/package.json @@ -95,14 +95,14 @@ "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "9.0.0", - "xterm": "5.3.0-beta.32", + "xterm": "5.3.0-beta.39", "xterm-addon-canvas": "0.5.0-beta.9", - "xterm-addon-image": "0.5.0", - "xterm-addon-search": "0.13.0-beta.4", - "xterm-addon-serialize": "0.11.0-beta.6", + "xterm-addon-image": "0.6.0-beta.1", + "xterm-addon-search": "0.13.0-beta.7", + "xterm-addon-serialize": "0.11.0-beta.7", "xterm-addon-unicode11": "0.5.0", - "xterm-addon-webgl": "0.16.0-beta.12", - "xterm-headless": "5.3.0-beta.32", + "xterm-addon-webgl": "0.16.0-beta.13", + "xterm-headless": "5.3.0-beta.39", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/package.json b/remote/package.json index 7f86adaaf61..e2407e66782 100644 --- a/remote/package.json +++ b/remote/package.json @@ -27,14 +27,14 @@ "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "9.0.0", - "xterm": "5.3.0-beta.32", + "xterm": "5.3.0-beta.39", "xterm-addon-canvas": "0.5.0-beta.9", - "xterm-addon-image": "0.5.0", - "xterm-addon-search": "0.13.0-beta.4", - "xterm-addon-serialize": "0.11.0-beta.6", + "xterm-addon-image": "0.6.0-beta.1", + "xterm-addon-search": "0.13.0-beta.7", + "xterm-addon-serialize": "0.11.0-beta.7", "xterm-addon-unicode11": "0.5.0", - "xterm-addon-webgl": "0.16.0-beta.12", - "xterm-headless": "5.3.0-beta.32", + "xterm-addon-webgl": "0.16.0-beta.13", + "xterm-headless": "5.3.0-beta.39", "yauzl": "^2.9.2", "yazl": "^2.4.3" } diff --git a/remote/web/package.json b/remote/web/package.json index 89f528d7d7b..a4bd45b2be5 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -11,11 +11,11 @@ "tas-client-umd": "0.1.8", "vscode-oniguruma": "1.7.0", "vscode-textmate": "9.0.0", - "xterm": "5.3.0-beta.32", + "xterm": "5.3.0-beta.39", "xterm-addon-canvas": "0.5.0-beta.9", - "xterm-addon-image": "0.5.0", - "xterm-addon-search": "0.13.0-beta.4", + "xterm-addon-image": "0.6.0-beta.1", + "xterm-addon-search": "0.13.0-beta.7", "xterm-addon-unicode11": "0.5.0", - "xterm-addon-webgl": "0.16.0-beta.12" + "xterm-addon-webgl": "0.16.0-beta.13" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index c22cc2d061d..9fe8c502181 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -95,27 +95,27 @@ xterm-addon-canvas@0.5.0-beta.9: resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.5.0-beta.9.tgz#d4a9536cf586f78a54527751e03abf6445613886" integrity sha512-AMyFctHbIWx0aLcACINuZqFFNoz4ndGtIAb4pQguIl9t8r+2otiV11MY4FJmPM0kUzEzoLWzVFJaGxOzykCWuw== -xterm-addon-image@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.5.0.tgz#3c9bb332a3de55ab200dbefd3411e3b0d985314f" - integrity sha512-bWXUBeDzhisYh0clVKx4JgQrZjpn+/QRMRwNsfnRpjCMhgmZ+SL3Bivktd7q03O4uKMMcAOe6bSmppwP9/um0Q== +xterm-addon-image@0.6.0-beta.1: + version "0.6.0-beta.1" + resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.6.0-beta.1.tgz#058c171a4866f1cde7608e3b00050476b55ece4c" + integrity sha512-vLxUEo/KAsEaRhMshJ4uxnfc/Si8YfT3kilaK5WuiJflVODzQjytzZrl2iLeZaWKiqQp0NR6AEhuXlYASK675A== -xterm-addon-search@0.13.0-beta.4: - version "0.13.0-beta.4" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.13.0-beta.4.tgz#a9817600cc0a71dee524d0f025e88e013f87f14a" - integrity sha512-A9Ps7iqGVUSphLCVrGYrfua1ADjtm9ff5CLs8cpiZnMK45X1FTIrXxEWvFccoiwtmZINEDJPGvbfIIqu8ZkXyg== +xterm-addon-search@0.13.0-beta.7: + version "0.13.0-beta.7" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.13.0-beta.7.tgz#2f9bf9d8380c4c40f021c47dc583aa32bea8796e" + integrity sha512-Dnuws0ThaqzxRDo8TjkSb5WlMYycONVDSr/irx2pk2I4vC5RuSpOifmD46ALlryNSDkMasjfCLjHPILHsFDCfw== xterm-addon-unicode11@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.5.0.tgz#41c0d96acc1e3bb6c6596eee64e163b6bca74be7" integrity sha512-Jm4/g4QiTxiKiTbYICQgC791ubhIZyoIwxAIgOW8z8HWFNY+lwk+dwaKEaEeGBfM48Vk8fklsUW9u/PlenYEBg== -xterm-addon-webgl@0.16.0-beta.12: - version "0.16.0-beta.12" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.12.tgz#c4ee008e3768ae283ed281bddf6913dbc0b99971" - integrity sha512-H1lmX/fPVPZtgLMMevzLQbk2pB2ChZ55mTG3yOfe+dVgyNPw15suM2sL4UGwZUh3GFwT5gXL1rFuq0beQzA0Iw== +xterm-addon-webgl@0.16.0-beta.13: + version "0.16.0-beta.13" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.13.tgz#ffacd22b8d86ca7f9ba903a64bc85654523955fb" + integrity sha512-1avJBoAKl+0dOjRK0pvu2im6gaXyP/6oJcEBQXCTz3V8YlgcG/jom8A6FEQSEDGZ/4dnyDvnLbsg3+aLZIr6gw== -xterm@5.3.0-beta.32: - version "5.3.0-beta.32" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.32.tgz#1bc445e87d18c675702339c7eeb0d8ad66f412e6" - integrity sha512-QeK7HZ3SSXHFZvfMSIyVFMhGzh/cesu8cGmFDrsvnS62uDiWOGs/8RCAz3mupFvTZlxRd09IYOXAtqwjM+QPqQ== +xterm@5.3.0-beta.39: + version "5.3.0-beta.39" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.39.tgz#b64da3fd1b374fd535db61294a49c16453dbb77e" + integrity sha512-Z1r0ijTgad5MjFoz8qZ+2Qc3bgjG6kYY/kZI3DTA1pibArHeOMDYk9+IeCb9adF74zvZurjytRfXKJgX6M9hwg== diff --git a/remote/yarn.lock b/remote/yarn.lock index 0f5a02945bb..8ad9cc2907f 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -904,40 +904,40 @@ xterm-addon-canvas@0.5.0-beta.9: resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.5.0-beta.9.tgz#d4a9536cf586f78a54527751e03abf6445613886" integrity sha512-AMyFctHbIWx0aLcACINuZqFFNoz4ndGtIAb4pQguIl9t8r+2otiV11MY4FJmPM0kUzEzoLWzVFJaGxOzykCWuw== -xterm-addon-image@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.5.0.tgz#3c9bb332a3de55ab200dbefd3411e3b0d985314f" - integrity sha512-bWXUBeDzhisYh0clVKx4JgQrZjpn+/QRMRwNsfnRpjCMhgmZ+SL3Bivktd7q03O4uKMMcAOe6bSmppwP9/um0Q== +xterm-addon-image@0.6.0-beta.1: + version "0.6.0-beta.1" + resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.6.0-beta.1.tgz#058c171a4866f1cde7608e3b00050476b55ece4c" + integrity sha512-vLxUEo/KAsEaRhMshJ4uxnfc/Si8YfT3kilaK5WuiJflVODzQjytzZrl2iLeZaWKiqQp0NR6AEhuXlYASK675A== -xterm-addon-search@0.13.0-beta.4: - version "0.13.0-beta.4" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.13.0-beta.4.tgz#a9817600cc0a71dee524d0f025e88e013f87f14a" - integrity sha512-A9Ps7iqGVUSphLCVrGYrfua1ADjtm9ff5CLs8cpiZnMK45X1FTIrXxEWvFccoiwtmZINEDJPGvbfIIqu8ZkXyg== +xterm-addon-search@0.13.0-beta.7: + version "0.13.0-beta.7" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.13.0-beta.7.tgz#2f9bf9d8380c4c40f021c47dc583aa32bea8796e" + integrity sha512-Dnuws0ThaqzxRDo8TjkSb5WlMYycONVDSr/irx2pk2I4vC5RuSpOifmD46ALlryNSDkMasjfCLjHPILHsFDCfw== -xterm-addon-serialize@0.11.0-beta.6: - version "0.11.0-beta.6" - resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.11.0-beta.6.tgz#2533a2f6bf41327cfcc51a92fca79404499cffe2" - integrity sha512-wXx3j9Nf+JOxoBW8iZElEPVEBD6LRDlnrGhfe4oSEa+wDJX+8iORcIKH06mNBlwSApajawrDhpcugwAhjvSOHg== +xterm-addon-serialize@0.11.0-beta.7: + version "0.11.0-beta.7" + resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.11.0-beta.7.tgz#934a48a4490c2ca94a5cfcdd646fb0813b4e56d0" + integrity sha512-TwYsEHq6TflY0vmq3yAI2VKNqzEAZ0gCm3TThDMPrPQiv4LcewQSeGu9RujEqF9RAhujhceaM3GajBxySAWyqQ== xterm-addon-unicode11@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.5.0.tgz#41c0d96acc1e3bb6c6596eee64e163b6bca74be7" integrity sha512-Jm4/g4QiTxiKiTbYICQgC791ubhIZyoIwxAIgOW8z8HWFNY+lwk+dwaKEaEeGBfM48Vk8fklsUW9u/PlenYEBg== -xterm-addon-webgl@0.16.0-beta.12: - version "0.16.0-beta.12" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.12.tgz#c4ee008e3768ae283ed281bddf6913dbc0b99971" - integrity sha512-H1lmX/fPVPZtgLMMevzLQbk2pB2ChZ55mTG3yOfe+dVgyNPw15suM2sL4UGwZUh3GFwT5gXL1rFuq0beQzA0Iw== +xterm-addon-webgl@0.16.0-beta.13: + version "0.16.0-beta.13" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.13.tgz#ffacd22b8d86ca7f9ba903a64bc85654523955fb" + integrity sha512-1avJBoAKl+0dOjRK0pvu2im6gaXyP/6oJcEBQXCTz3V8YlgcG/jom8A6FEQSEDGZ/4dnyDvnLbsg3+aLZIr6gw== -xterm-headless@5.3.0-beta.32: - version "5.3.0-beta.32" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.3.0-beta.32.tgz#fb309a963caf8513c51b12f1562ad901cf3eb884" - integrity sha512-vsZVH4AfvaWHsFABeN9Am7LmRZ5oVc4HEELvkNBZNk8t53R2kaY6VxJuxrccP4W6AJb8P26pLyBOGglzh0I29A== +xterm-headless@5.3.0-beta.39: + version "5.3.0-beta.39" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.3.0-beta.39.tgz#84a509dbe1dc9b2229b5274ae3430ce968986346" + integrity sha512-Q4vfqAaYoWh7/T4HearlLlKkn9vjN5EugsNyuPd6hq+Ug1FwJIJYj+I9vY67+Rqxr24XGnrQdXfvvEas29KXsw== -xterm@5.3.0-beta.32: - version "5.3.0-beta.32" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.32.tgz#1bc445e87d18c675702339c7eeb0d8ad66f412e6" - integrity sha512-QeK7HZ3SSXHFZvfMSIyVFMhGzh/cesu8cGmFDrsvnS62uDiWOGs/8RCAz3mupFvTZlxRd09IYOXAtqwjM+QPqQ== +xterm@5.3.0-beta.39: + version "5.3.0-beta.39" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.39.tgz#b64da3fd1b374fd535db61294a49c16453dbb77e" + integrity sha512-Z1r0ijTgad5MjFoz8qZ+2Qc3bgjG6kYY/kZI3DTA1pibArHeOMDYk9+IeCb9adF74zvZurjytRfXKJgX6M9hwg== yallist@^4.0.0: version "4.0.0" diff --git a/yarn.lock b/yarn.lock index 0ee24456a19..e06f0699b66 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10786,40 +10786,40 @@ xterm-addon-canvas@0.5.0-beta.9: resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.5.0-beta.9.tgz#d4a9536cf586f78a54527751e03abf6445613886" integrity sha512-AMyFctHbIWx0aLcACINuZqFFNoz4ndGtIAb4pQguIl9t8r+2otiV11MY4FJmPM0kUzEzoLWzVFJaGxOzykCWuw== -xterm-addon-image@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.5.0.tgz#3c9bb332a3de55ab200dbefd3411e3b0d985314f" - integrity sha512-bWXUBeDzhisYh0clVKx4JgQrZjpn+/QRMRwNsfnRpjCMhgmZ+SL3Bivktd7q03O4uKMMcAOe6bSmppwP9/um0Q== +xterm-addon-image@0.6.0-beta.1: + version "0.6.0-beta.1" + resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.6.0-beta.1.tgz#058c171a4866f1cde7608e3b00050476b55ece4c" + integrity sha512-vLxUEo/KAsEaRhMshJ4uxnfc/Si8YfT3kilaK5WuiJflVODzQjytzZrl2iLeZaWKiqQp0NR6AEhuXlYASK675A== -xterm-addon-search@0.13.0-beta.4: - version "0.13.0-beta.4" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.13.0-beta.4.tgz#a9817600cc0a71dee524d0f025e88e013f87f14a" - integrity sha512-A9Ps7iqGVUSphLCVrGYrfua1ADjtm9ff5CLs8cpiZnMK45X1FTIrXxEWvFccoiwtmZINEDJPGvbfIIqu8ZkXyg== +xterm-addon-search@0.13.0-beta.7: + version "0.13.0-beta.7" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.13.0-beta.7.tgz#2f9bf9d8380c4c40f021c47dc583aa32bea8796e" + integrity sha512-Dnuws0ThaqzxRDo8TjkSb5WlMYycONVDSr/irx2pk2I4vC5RuSpOifmD46ALlryNSDkMasjfCLjHPILHsFDCfw== -xterm-addon-serialize@0.11.0-beta.6: - version "0.11.0-beta.6" - resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.11.0-beta.6.tgz#2533a2f6bf41327cfcc51a92fca79404499cffe2" - integrity sha512-wXx3j9Nf+JOxoBW8iZElEPVEBD6LRDlnrGhfe4oSEa+wDJX+8iORcIKH06mNBlwSApajawrDhpcugwAhjvSOHg== +xterm-addon-serialize@0.11.0-beta.7: + version "0.11.0-beta.7" + resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.11.0-beta.7.tgz#934a48a4490c2ca94a5cfcdd646fb0813b4e56d0" + integrity sha512-TwYsEHq6TflY0vmq3yAI2VKNqzEAZ0gCm3TThDMPrPQiv4LcewQSeGu9RujEqF9RAhujhceaM3GajBxySAWyqQ== xterm-addon-unicode11@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.5.0.tgz#41c0d96acc1e3bb6c6596eee64e163b6bca74be7" integrity sha512-Jm4/g4QiTxiKiTbYICQgC791ubhIZyoIwxAIgOW8z8HWFNY+lwk+dwaKEaEeGBfM48Vk8fklsUW9u/PlenYEBg== -xterm-addon-webgl@0.16.0-beta.12: - version "0.16.0-beta.12" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.12.tgz#c4ee008e3768ae283ed281bddf6913dbc0b99971" - integrity sha512-H1lmX/fPVPZtgLMMevzLQbk2pB2ChZ55mTG3yOfe+dVgyNPw15suM2sL4UGwZUh3GFwT5gXL1rFuq0beQzA0Iw== +xterm-addon-webgl@0.16.0-beta.13: + version "0.16.0-beta.13" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.13.tgz#ffacd22b8d86ca7f9ba903a64bc85654523955fb" + integrity sha512-1avJBoAKl+0dOjRK0pvu2im6gaXyP/6oJcEBQXCTz3V8YlgcG/jom8A6FEQSEDGZ/4dnyDvnLbsg3+aLZIr6gw== -xterm-headless@5.3.0-beta.32: - version "5.3.0-beta.32" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.3.0-beta.32.tgz#fb309a963caf8513c51b12f1562ad901cf3eb884" - integrity sha512-vsZVH4AfvaWHsFABeN9Am7LmRZ5oVc4HEELvkNBZNk8t53R2kaY6VxJuxrccP4W6AJb8P26pLyBOGglzh0I29A== +xterm-headless@5.3.0-beta.39: + version "5.3.0-beta.39" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.3.0-beta.39.tgz#84a509dbe1dc9b2229b5274ae3430ce968986346" + integrity sha512-Q4vfqAaYoWh7/T4HearlLlKkn9vjN5EugsNyuPd6hq+Ug1FwJIJYj+I9vY67+Rqxr24XGnrQdXfvvEas29KXsw== -xterm@5.3.0-beta.32: - version "5.3.0-beta.32" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.32.tgz#1bc445e87d18c675702339c7eeb0d8ad66f412e6" - integrity sha512-QeK7HZ3SSXHFZvfMSIyVFMhGzh/cesu8cGmFDrsvnS62uDiWOGs/8RCAz3mupFvTZlxRd09IYOXAtqwjM+QPqQ== +xterm@5.3.0-beta.39: + version "5.3.0-beta.39" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.39.tgz#b64da3fd1b374fd535db61294a49c16453dbb77e" + integrity sha512-Z1r0ijTgad5MjFoz8qZ+2Qc3bgjG6kYY/kZI3DTA1pibArHeOMDYk9+IeCb9adF74zvZurjytRfXKJgX6M9hwg== y18n@^3.2.1: version "3.2.2" From 0ea34aed3c0eff24a7d3aa4b28b239d0c1e4d88e Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 14 Aug 2023 17:01:20 +0200 Subject: [PATCH 27/27] Capture the errors that tunnel providers throw (#190282) and show those errors to the user if the port forward was user initiated. --- src/vs/platform/tunnel/common/tunnel.ts | 40 ++++++++++--------- .../tunnel/node/sharedProcessTunnelService.ts | 2 +- src/vs/platform/tunnel/node/tunnelService.ts | 4 +- .../webview/common/webviewPortMapping.ts | 6 ++- .../api/browser/mainThreadTunnelService.ts | 34 +++++++++------- .../workbench/api/common/extHost.protocol.ts | 2 +- .../api/common/extHostTunnelService.ts | 5 ++- src/vs/workbench/browser/web.main.ts | 4 ++ .../contrib/remote/browser/remoteExplorer.ts | 6 +-- .../contrib/remote/browser/tunnelFactory.ts | 5 ++- .../contrib/remote/browser/tunnelView.ts | 10 +++-- src/vs/workbench/electron-sandbox/window.ts | 11 ++--- .../remote/common/remoteExplorerService.ts | 4 +- .../services/remote/common/tunnelModel.ts | 11 +++-- .../services/tunnel/browser/tunnelService.ts | 2 +- .../tunnel/electron-sandbox/tunnelService.ts | 2 +- 16 files changed, 88 insertions(+), 60 deletions(-) diff --git a/src/vs/platform/tunnel/common/tunnel.ts b/src/vs/platform/tunnel/common/tunnel.ts index 7ce224a4b98..0622e624c15 100644 --- a/src/vs/platform/tunnel/common/tunnel.ts +++ b/src/vs/platform/tunnel/common/tunnel.ts @@ -61,7 +61,7 @@ export interface TunnelProviderFeatures { } export interface ITunnelProvider { - forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise | undefined; + forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise | undefined; } export function isTunnelProvider(addressOrTunnelProvider: IAddressProvider | ITunnelProvider): addressOrTunnelProvider is ITunnelProvider { @@ -114,7 +114,7 @@ export interface ITunnel { export interface ISharedTunnelsService { readonly _serviceBrand: undefined; - openTunnel(authority: string, addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localHost: string, localPort?: number, elevateIfNeeded?: boolean, privacy?: string, protocol?: string): Promise | undefined; + openTunnel(authority: string, addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localHost: string, localPort?: number, elevateIfNeeded?: boolean, privacy?: string, protocol?: string): Promise | undefined; } export interface ITunnelService { @@ -130,8 +130,8 @@ export interface ITunnelService { readonly onAddedTunnelProvider: Event; canTunnel(uri: URI): boolean; - openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localHost?: string, localPort?: number, elevateIfNeeded?: boolean, privacy?: string, protocol?: string): Promise | undefined; - getExistingTunnel(remoteHost: string, remotePort: number): Promise; + openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localHost?: string, localPort?: number, elevateIfNeeded?: boolean, privacy?: string, protocol?: string): Promise | undefined; + getExistingTunnel(remoteHost: string, remotePort: number): Promise; setEnvironmentTunnel(remoteHost: string, remotePort: number, localAddress: string, privacy: string, protocol: string): void; closeTunnel(remoteHost: string, remotePort: number): Promise; setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable; @@ -205,7 +205,7 @@ export abstract class AbstractTunnelService implements ITunnelService { public onTunnelClosed: Event<{ host: string; port: number }> = this._onTunnelClosed.event; private _onAddedTunnelProvider: Emitter = new Emitter(); public onAddedTunnelProvider: Event = this._onAddedTunnelProvider.event; - protected readonly _tunnels = new Map }>>(); + protected readonly _tunnels = new Map }>>(); protected _tunnelProvider: ITunnelProvider | undefined; protected _canElevate: boolean = false; private _privacyOptions: TunnelPrivacy[] = []; @@ -275,7 +275,7 @@ export abstract class AbstractTunnelService implements ITunnelService { const portArray = Array.from(portMap.values()); for (const x of portArray) { const tunnelValue = await x.value; - if (tunnelValue) { + if (tunnelValue && (typeof tunnelValue !== 'string')) { tunnels.push(tunnelValue); } } @@ -286,7 +286,7 @@ export abstract class AbstractTunnelService implements ITunnelService { async dispose(): Promise { for (const portMap of this._tunnels.values()) { for (const { value } of portMap.values()) { - await value.then(tunnel => tunnel?.dispose()); + await value.then(tunnel => typeof tunnel !== 'string' ? tunnel?.dispose() : undefined); } portMap.clear(); } @@ -304,7 +304,7 @@ export abstract class AbstractTunnelService implements ITunnelService { })); } - async getExistingTunnel(remoteHost: string, remotePort: number): Promise { + async getExistingTunnel(remoteHost: string, remotePort: number): Promise { if (isAllInterfaces(remoteHost) || isLocalhost(remoteHost)) { remoteHost = LOCALHOST_ADDRESSES[0]; } @@ -317,7 +317,7 @@ export abstract class AbstractTunnelService implements ITunnelService { return undefined; } - openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localHost?: string, localPort?: number, elevateIfNeeded: boolean = false, privacy?: string, protocol?: string): Promise | undefined { + openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localHost?: string, localPort?: number, elevateIfNeeded: boolean = false, privacy?: string, protocol?: string): Promise | undefined { this.logService.trace(`ForwardedPorts: (TunnelService) openTunnel request for ${remoteHost}:${remotePort} on local port ${localPort}.`); const addressOrTunnelProvider = this._tunnelProvider ?? addressProvider; if (!addressOrTunnelProvider) { @@ -346,8 +346,12 @@ export abstract class AbstractTunnelService implements ITunnelService { return resolvedTunnel.then(tunnel => { if (!tunnel) { this.logService.trace('ForwardedPorts: (TunnelService) New tunnel is undefined.'); - this.removeEmptyTunnelFromMap(remoteHost!, remotePort); + this.removeEmptyOrErrorTunnelFromMap(remoteHost!, remotePort); return undefined; + } else if (typeof tunnel === 'string') { + this.logService.trace('ForwardedPorts: (TunnelService) The tunnel provider returned an error when creating the tunnel.'); + this.removeEmptyOrErrorTunnelFromMap(remoteHost!, remotePort); + return tunnel; } this.logService.trace('ForwardedPorts: (TunnelService) New tunnel established.'); const newTunnel = this.makeTunnel(tunnel); @@ -384,11 +388,11 @@ export abstract class AbstractTunnelService implements ITunnelService { }; } - private async tryDisposeTunnel(remoteHost: string, remotePort: number, tunnel: { refcount: number; readonly value: Promise }): Promise { + private async tryDisposeTunnel(remoteHost: string, remotePort: number, tunnel: { refcount: number; readonly value: Promise }): Promise { if (tunnel.refcount <= 0) { this.logService.trace(`ForwardedPorts: (TunnelService) Tunnel is being disposed ${remoteHost}:${remotePort}.`); const disposePromise: Promise = tunnel.value.then(async (tunnel) => { - if (tunnel) { + if (tunnel && (typeof tunnel !== 'string')) { await tunnel.dispose(true); this._onTunnelClosed.fire({ host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }); } @@ -410,19 +414,19 @@ export abstract class AbstractTunnelService implements ITunnelService { } } - protected addTunnelToMap(remoteHost: string, remotePort: number, tunnel: Promise) { + protected addTunnelToMap(remoteHost: string, remotePort: number, tunnel: Promise) { if (!this._tunnels.has(remoteHost)) { this._tunnels.set(remoteHost, new Map()); } this._tunnels.get(remoteHost)!.set(remotePort, { refcount: 1, value: tunnel }); } - private async removeEmptyTunnelFromMap(remoteHost: string, remotePort: number) { + private async removeEmptyOrErrorTunnelFromMap(remoteHost: string, remotePort: number) { const hostMap = this._tunnels.get(remoteHost); if (hostMap) { const tunnel = hostMap.get(remotePort); const tunnelResult = tunnel ? await tunnel.value : undefined; - if (!tunnelResult) { + if (!tunnelResult || (typeof tunnelResult === 'string')) { hostMap.delete(remotePort); } if (hostMap.size === 0) { @@ -431,7 +435,7 @@ export abstract class AbstractTunnelService implements ITunnelService { } } - protected getTunnelFromMap(remoteHost: string, remotePort: number): { refcount: number; readonly value: Promise } | undefined { + protected getTunnelFromMap(remoteHost: string, remotePort: number): { refcount: number; readonly value: Promise } | undefined { const hosts = [remoteHost]; // Order matters. We want the original host to be first. if (isLocalhost(remoteHost)) { @@ -459,9 +463,9 @@ export abstract class AbstractTunnelService implements ITunnelService { public abstract isPortPrivileged(port: number): boolean; - protected abstract retainOrCreateTunnel(addressProvider: IAddressProvider | ITunnelProvider, remoteHost: string, remotePort: number, localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise | undefined; + protected abstract retainOrCreateTunnel(addressProvider: IAddressProvider | ITunnelProvider, remoteHost: string, remotePort: number, localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise | undefined; - protected createWithProvider(tunnelProvider: ITunnelProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise | undefined { + protected createWithProvider(tunnelProvider: ITunnelProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise | undefined { this.logService.trace(`ForwardedPorts: (TunnelService) Creating tunnel with provider ${remoteHost}:${remotePort} on local port ${localPort}.`); const key = remotePort; this._factoryInProgress.add(key); diff --git a/src/vs/platform/tunnel/node/sharedProcessTunnelService.ts b/src/vs/platform/tunnel/node/sharedProcessTunnelService.ts index 54369f21b3a..4dbb881b3c1 100644 --- a/src/vs/platform/tunnel/node/sharedProcessTunnelService.ts +++ b/src/vs/platform/tunnel/node/sharedProcessTunnelService.ts @@ -75,7 +75,7 @@ export class SharedProcessTunnelService extends Disposable implements ISharedPro const tunnelData = new TunnelData(); const tunnel = await Promise.resolve(this._tunnelService.openTunnel(authority, tunnelData, tunnelRemoteHost, tunnelRemotePort, tunnelLocalHost, tunnelLocalPort, elevateIfNeeded)); - if (!tunnel) { + if (!tunnel || (typeof tunnel === 'string')) { this._logService.info(`[SharedProcessTunnelService] Could not create a tunnel to ${tunnelRemoteHost}:${tunnelRemotePort} (remote).`); tunnelData.dispose(); throw new Error(`Could not create tunnel`); diff --git a/src/vs/platform/tunnel/node/tunnelService.ts b/src/vs/platform/tunnel/node/tunnelService.ts index 8493fbc2fdf..50eb82abc61 100644 --- a/src/vs/platform/tunnel/node/tunnelService.ts +++ b/src/vs/platform/tunnel/node/tunnelService.ts @@ -168,7 +168,7 @@ export class BaseTunnelService extends AbstractTunnelService { return isPortPrivileged(port, this.defaultTunnelHost, OS, os.release()); } - protected retainOrCreateTunnel(addressOrTunnelProvider: IAddressProvider | ITunnelProvider, remoteHost: string, remotePort: number, localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise | undefined { + protected retainOrCreateTunnel(addressOrTunnelProvider: IAddressProvider | ITunnelProvider, remoteHost: string, remotePort: number, localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise | undefined { const existing = this.getTunnelFromMap(remoteHost, remotePort); if (existing) { ++existing.refcount; @@ -223,7 +223,7 @@ export class SharedTunnelsService extends Disposable implements ISharedTunnelsSe super(); } - async openTunnel(authority: string, addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localHost: string, localPort?: number, elevateIfNeeded?: boolean, privacy?: string, protocol?: string): Promise { + async openTunnel(authority: string, addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localHost: string, localPort?: number, elevateIfNeeded?: boolean, privacy?: string, protocol?: string): Promise { this.logService.trace(`ForwardedPorts: (SharedTunnelService) openTunnel request for ${remoteHost}:${remotePort} on local port ${localPort}.`); if (!this._tunnelServices.has(authority)) { const tunnelService = new TunnelService(this.remoteSocketFactoryService, this.logService, this.signService, this.productService, this.configurationService); diff --git a/src/vs/platform/webview/common/webviewPortMapping.ts b/src/vs/platform/webview/common/webviewPortMapping.ts index ee1014f7cce..b47339d28fa 100644 --- a/src/vs/platform/webview/common/webviewPortMapping.ts +++ b/src/vs/platform/webview/common/webviewPortMapping.ts @@ -72,7 +72,11 @@ export class WebviewPortMappingManager implements IDisposable { if (existing) { return existing; } - const tunnel = await this.tunnelService.openTunnel({ getAddress: async () => remoteAuthority }, undefined, remotePort); + const tunnelOrError = await this.tunnelService.openTunnel({ getAddress: async () => remoteAuthority }, undefined, remotePort); + let tunnel: RemoteTunnel | undefined; + if (typeof tunnelOrError === 'string') { + tunnel = undefined; + } if (tunnel) { this._tunnels.set(remotePort, tunnel); } diff --git a/src/vs/workbench/api/browser/mainThreadTunnelService.ts b/src/vs/workbench/api/browser/mainThreadTunnelService.ts index 1f70a619c0c..d41e861572b 100644 --- a/src/vs/workbench/api/browser/mainThreadTunnelService.ts +++ b/src/vs/workbench/api/browser/mainThreadTunnelService.ts @@ -109,19 +109,19 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun }, elevateIfNeeded: false }); - if (tunnel) { - if (!this.elevateionRetry - && (tunnelOptions.localAddressPort !== undefined) - && (tunnel.tunnelLocalPort !== undefined) - && this.tunnelService.isPortPrivileged(tunnelOptions.localAddressPort) - && (tunnel.tunnelLocalPort !== tunnelOptions.localAddressPort) - && this.tunnelService.canElevate) { - - this.elevationPrompt(tunnelOptions, tunnel, source); - } - return TunnelDtoConverter.fromServiceTunnel(tunnel); + if (!tunnel || (typeof tunnel === 'string')) { + return undefined; } - return undefined; + if (!this.elevateionRetry + && (tunnelOptions.localAddressPort !== undefined) + && (tunnel.tunnelLocalPort !== undefined) + && this.tunnelService.isPortPrivileged(tunnelOptions.localAddressPort) + && (tunnel.tunnelLocalPort !== tunnelOptions.localAddressPort) + && this.tunnelService.canElevate) { + + this.elevationPrompt(tunnelOptions, tunnel, source); + } + return TunnelDtoConverter.fromServiceTunnel(tunnel); } private async elevationPrompt(tunnelOptions: TunnelOptions, tunnel: RemoteTunnel, source: string) { @@ -170,11 +170,15 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun const tunnelProvider: ITunnelProvider = { forwardPort: (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => { const forward = this._proxy.$forwardPort(tunnelOptions, tunnelCreationOptions); - return forward.then(tunnel => { - this.logService.trace(`ForwardedPorts: (MainThreadTunnelService) New tunnel established by tunnel provider: ${tunnel?.remoteAddress.host}:${tunnel?.remoteAddress.port}`); - if (!tunnel) { + return forward.then(tunnelOrError => { + if (!tunnelOrError) { return undefined; + } else if (typeof tunnelOrError === 'string') { + return tunnelOrError; } + const tunnel = tunnelOrError; + this.logService.trace(`ForwardedPorts: (MainThreadTunnelService) New tunnel established by tunnel provider: ${tunnel?.remoteAddress.host}:${tunnel?.remoteAddress.port}`); + return { tunnelRemotePort: tunnel.remoteAddress.port, tunnelRemoteHost: tunnel.remoteAddress.host, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 0314fe1b5b0..02864ebf389 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2445,7 +2445,7 @@ export interface TunnelDto { export interface ExtHostTunnelServiceShape { - $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise; + $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise; $closeTunnel(remote: { host: string; port: number }, silent?: boolean): Promise; $onDidTunnelsChange(): Promise; $registerCandidateFinder(enable: boolean): Promise; diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index 6611152a260..34fed87a677 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -227,7 +227,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe this._onDidChangeTunnels.fire(); } - async $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise { + async $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise { if (this._forwardPortProvider) { try { this.logService.trace('ForwardedPorts: (ExtHostTunnelService) Getting tunnel from provider.'); @@ -254,6 +254,9 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe } } catch (e) { this.logService.trace('ForwardedPorts: (ExtHostTunnelService) tunnel provider error'); + if (e instanceof Error) { + return e.message; + } } } return undefined; diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index c14c64c48c9..925403791af 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -211,6 +211,10 @@ export class BrowserMain extends Disposable { protocol: tunnelOptions.protocol === TunnelProtocol.Https ? tunnelOptions.protocol : TunnelProtocol.Http })); + if (typeof tunnel === 'string') { + throw new Error(tunnel); + } + return new class extends DisposableTunnel implements ITunnel { declare localAddress: string; }({ diff --git a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts index e5ffe893a2a..fef42ab467a 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts @@ -412,7 +412,7 @@ class OnAutoForwardedAction extends Disposable { elevateIfNeeded: true, source: AutoTunnelSource }); - if (!newTunnel) { + if (!newTunnel || (typeof newTunnel === 'string')) { return; } this.lastNotification?.close(); @@ -493,7 +493,7 @@ class OutputAutomaticPortForwarding extends Disposable { return; } const forwarded = await this.remoteExplorerService.forward({ remote: localUrl, source: AutoTunnelSource }, attributes ?? null); - if (forwarded) { + if (forwarded && (typeof forwarded !== 'string')) { this.notifier.doAction([forwarded]); } })); @@ -633,7 +633,7 @@ class ProcAutomaticPortForwarding extends Disposable { this.logService.trace(`ForwardedPorts: (ProcForwarding) Port ${value.port} has been notified`); this.notifiedOnly.add(address); } - if (forwarded) { + if (forwarded && (typeof forwarded !== 'string')) { allTunnels.push(forwarded); } } diff --git a/src/vs/workbench/contrib/remote/browser/tunnelFactory.ts b/src/vs/workbench/contrib/remote/browser/tunnelFactory.ts index 85fcbfc0637..ce1c98ab11f 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelFactory.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelFactory.ts @@ -48,7 +48,7 @@ export class TunnelFactoryContribution extends Disposable implements IWorkbenchC } this._register(tunnelService.setTunnelProvider({ - forwardPort: async (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise => { + forwardPort: async (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise => { let tunnelPromise: Promise | undefined; try { tunnelPromise = tunnelFactory(tunnelOptions, tunnelCreationOptions); @@ -64,6 +64,9 @@ export class TunnelFactoryContribution extends Disposable implements IWorkbenchC tunnel = await tunnelPromise; } catch (e) { logService.trace('tunnelFactory: tunnel provider promise error'); + if (e instanceof Error) { + return e.message; + } return undefined; } const localAddress = tunnel.localAddress.startsWith('http') ? tunnel.localAddress : `http://${tunnel.localAddress}`; diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 65270cb1815..037e25ff458 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -1149,9 +1149,11 @@ export namespace ForwardPortAction { return null; } - function error(notificationService: INotificationService, tunnel: RemoteTunnel | void, host: string, port: number) { - if (!tunnel) { + function error(notificationService: INotificationService, tunnelOrError: RemoteTunnel | string | void, host: string, port: number) { + if (!tunnelOrError) { notificationService.warn(nls.localize('remote.tunnel.forwardError', "Unable to forward {0}:{1}. The host may not be available or that remote port may already be forwarded", host, port)); + } else if (typeof tunnelOrError === 'string') { + notificationService.warn(nls.localize('remote.tunnel.forwardErrorProvided', "Unable to forward {0}:{1}. {2}", host, port, tunnelOrError)); } } @@ -1168,7 +1170,7 @@ export namespace ForwardPortAction { remoteExplorerService.forward({ remote: { host: parsed.host, port: parsed.port }, elevateIfNeeded: true - }).then(tunnel => error(notificationService, tunnel, parsed!.host, parsed!.port)); + }).then(tunnelOrError => error(notificationService, tunnelOrError, parsed!.host, parsed!.port)); } }, validationMessage: (value) => validateInput(remoteExplorerService, tunnelService, value, tunnelService.canElevate), @@ -1477,7 +1479,7 @@ namespace ChangeLocalPortAction { elevateIfNeeded: true, source: tunnelItem.source }); - if (newForward && newForward.tunnelLocalPort !== numberValue) { + if (newForward && (typeof newForward !== 'string') && newForward.tunnelLocalPort !== numberValue) { notificationService.warn(nls.localize('remote.tunnel.changeLocalPortNumber', "The local port {0} is not available. Port number {1} has been used instead", value, newForward.tunnelLocalPort ?? newForward.localAddress)); } } diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 719780f0cef..5b625c9c856 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -902,15 +902,16 @@ export class NativeWindow extends Disposable { } } : undefined; let tunnel = await this.tunnelService.getExistingTunnel(portMappingRequest.address, portMappingRequest.port); - if (!tunnel) { + if (!tunnel || (typeof tunnel === 'string')) { tunnel = await this.tunnelService.openTunnel(addressProvider, portMappingRequest.address, portMappingRequest.port); } - if (tunnel) { - const addressAsUri = URI.parse(tunnel.localAddress); - const resolved = addressAsUri.scheme.startsWith(uri.scheme) ? addressAsUri : uri.with({ authority: tunnel.localAddress }); + if (tunnel && (typeof tunnel !== 'string')) { + const constTunnel = tunnel; + const addressAsUri = URI.parse(constTunnel.localAddress); + const resolved = addressAsUri.scheme.startsWith(uri.scheme) ? addressAsUri : uri.with({ authority: constTunnel.localAddress }); return { resolved, - dispose: () => tunnel?.dispose(), + dispose: () => constTunnel.dispose(), }; } } diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index 76162f95825..acd6f4d6b92 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -65,7 +65,7 @@ export interface IRemoteExplorerService { onDidChangeEditable: Event<{ tunnel: ITunnelItem; editId: TunnelEditId } | undefined>; setEditable(tunnelItem: ITunnelItem | undefined, editId: TunnelEditId, data: IEditableData | null): void; getEditableData(tunnelItem: ITunnelItem | undefined, editId?: TunnelEditId): IEditableData | undefined; - forward(tunnelProperties: TunnelProperties, attributes?: Attributes | null): Promise; + forward(tunnelProperties: TunnelProperties, attributes?: Attributes | null): Promise; close(remote: { host: string; port: number }, reason: TunnelCloseReason): Promise; setTunnelInformation(tunnelInformation: TunnelInformation | undefined): void; setCandidateFilter(filter: ((candidates: CandidatePort[]) => Promise) | undefined): IDisposable; @@ -118,7 +118,7 @@ class RemoteExplorerService implements IRemoteExplorerService { return this._tunnelModel; } - forward(tunnelProperties: TunnelProperties, attributes?: Attributes | null): Promise { + forward(tunnelProperties: TunnelProperties, attributes?: Attributes | null): Promise { return this.tunnelModel.forward(tunnelProperties, attributes); } diff --git a/src/vs/workbench/services/remote/common/tunnelModel.ts b/src/vs/workbench/services/remote/common/tunnelModel.ts index c0995869581..f901f0a1081 100644 --- a/src/vs/workbench/services/remote/common/tunnelModel.ts +++ b/src/vs/workbench/services/remote/common/tunnelModel.ts @@ -618,7 +618,7 @@ export class TunnelModel extends Disposable { return this.dialogService.info(mismatchString); } - async forward(tunnelProperties: TunnelProperties, attributes?: Attributes | null): Promise { + async forward(tunnelProperties: TunnelProperties, attributes?: Attributes | null): Promise { await this.extensionService.activateByEvent(ACTIVATION_EVENT); const existingTunnel = mapHasAddressLocalhostOrAllInterfaces(this.forwarded, tunnelProperties.remote.host, tunnelProperties.remote.port); @@ -627,7 +627,7 @@ export class TunnelModel extends Disposable { ? (await this.getAttributes([tunnelProperties.remote]))?.get(tunnelProperties.remote.port) : undefined); const localPort = (tunnelProperties.local !== undefined) ? tunnelProperties.local : tunnelProperties.remote.port; - + let noTunnelValue: string | undefined; if (!existingTunnel) { const authority = this.environmentService.remoteAuthority; const addressProvider: IAddressProvider | undefined = authority ? { @@ -639,7 +639,10 @@ export class TunnelModel extends Disposable { tunnelProperties = this.mergeCachedAndUnrestoredProperties(key, tunnelProperties); const tunnel = await this.tunnelService.openTunnel(addressProvider, tunnelProperties.remote.host, tunnelProperties.remote.port, undefined, localPort, (!tunnelProperties.elevateIfNeeded) ? attributes?.elevateIfNeeded : tunnelProperties.elevateIfNeeded, tunnelProperties.privacy, attributes?.protocol); - if (tunnel && tunnel.localAddress) { + if (typeof tunnel === 'string') { + // There was an error while creating the tunnel. + noTunnelValue = tunnel; + } else if (tunnel && tunnel.localAddress) { const matchingCandidate = mapHasAddressLocalhostOrAllInterfaces(this._candidates ?? new Map(), tunnelProperties.remote.host, tunnelProperties.remote.port); const protocol = (tunnel.protocol ? ((tunnel.protocol === TunnelProtocol.Https) ? TunnelProtocol.Https : TunnelProtocol.Http) @@ -672,7 +675,7 @@ export class TunnelModel extends Disposable { return this.mergeAttributesIntoExistingTunnel(existingTunnel, tunnelProperties, attributes); } - return undefined; + return noTunnelValue; } private mergeCachedAndUnrestoredProperties(key: string, tunnelProperties: TunnelProperties): TunnelProperties { diff --git a/src/vs/workbench/services/tunnel/browser/tunnelService.ts b/src/vs/workbench/services/tunnel/browser/tunnelService.ts index 74c874e0bfd..6a0cf2515b7 100644 --- a/src/vs/workbench/services/tunnel/browser/tunnelService.ts +++ b/src/vs/workbench/services/tunnel/browser/tunnelService.ts @@ -24,7 +24,7 @@ export class TunnelService extends AbstractTunnelService { return false; } - protected retainOrCreateTunnel(tunnelProvider: IAddressProvider | ITunnelProvider, remoteHost: string, remotePort: number, _localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise | undefined { + protected retainOrCreateTunnel(tunnelProvider: IAddressProvider | ITunnelProvider, remoteHost: string, remotePort: number, _localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise | undefined { const existing = this.getTunnelFromMap(remoteHost, remotePort); if (existing) { ++existing.refcount; diff --git a/src/vs/workbench/services/tunnel/electron-sandbox/tunnelService.ts b/src/vs/workbench/services/tunnel/electron-sandbox/tunnelService.ts index edd218d3a9c..17948851797 100644 --- a/src/vs/workbench/services/tunnel/electron-sandbox/tunnelService.ts +++ b/src/vs/workbench/services/tunnel/electron-sandbox/tunnelService.ts @@ -79,7 +79,7 @@ export class TunnelService extends AbstractTunnelService { return isPortPrivileged(port, this.defaultTunnelHost, OS, this._nativeWorkbenchEnvironmentService.os.release); } - protected retainOrCreateTunnel(addressOrTunnelProvider: IAddressProvider | ITunnelProvider, remoteHost: string, remotePort: number, localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise | undefined { + protected retainOrCreateTunnel(addressOrTunnelProvider: IAddressProvider | ITunnelProvider, remoteHost: string, remotePort: number, localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise | undefined { const existing = this.getTunnelFromMap(remoteHost, remotePort); if (existing) { ++existing.refcount;