From cd02ed00bd52eee1e34435b1875e48e05901a78f Mon Sep 17 00:00:00 2001 From: Sebastian Pahnke Date: Tue, 14 Mar 2023 10:23:36 +0100 Subject: [PATCH 01/59] Add `monaco.editor.registerEditorOpener` method to be able to intercept editor open operations --- .../standalone/browser/standaloneEditor.ts | 34 +++++++++++++++++++ src/vs/monaco.d.ts | 12 +++++++ 2 files changed, 46 insertions(+) diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 826b9f3f0ffb0..a493344ea5f26 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -38,6 +38,9 @@ import { LineRangeMapping, RangeMapping } from 'vs/editor/common/diff/linesDiffC import { LineRange } from 'vs/editor/common/core/lineRange'; import { EditorZoom } from 'vs/editor/common/config/editorZoom'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IRange } from 'vs/editor/common/core/range'; +import { IPosition } from 'vs/editor/common/core/position'; +import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; /** * Create a new editor under `domElement`. @@ -456,6 +459,36 @@ export function registerLinkOpener(opener: ILinkOpener): IDisposable { }); } +export interface ICodeEditorOpener { + openCodeEditor(source: ICodeEditor, resource: URI, selectionOrPosition?: IRange | IPosition): boolean | Promise; +} + +/** + * Registers a handler that is called when a resource other than the current model should be opened in the editor (e.g. "go to definition"). + * The handler callback should return `true` if the request was handled and `false` otherwise. + * + * Returns a disposable that can unregister the opener again. + */ +export function registerEditorOpener(opener: ICodeEditorOpener): IDisposable { + const codeEditorService = StandaloneServices.get(ICodeEditorService); + return codeEditorService.registerCodeEditorOpenHandler(async (input: ITextResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean) => { + if (!source) { + return null; + } + const selection = input.options?.selection; + let selectionOrPosition: IRange | IPosition | undefined; + if (selection && typeof selection.endLineNumber === 'number' && typeof selection.endColumn === 'number') { + selectionOrPosition = selection; + } else if (selection) { + selectionOrPosition = { lineNumber: selection.startLineNumber, column: selection.startColumn }; + } + if (await opener.openCodeEditor(source, input.resource, selectionOrPosition)) { + return source; // return source editor to indicate that this handler has successfully handled the opening + } + return null; // fallback to other registered handlers + }); +} + /** * @internal */ @@ -499,6 +532,7 @@ export function createMonacoEditorAPI(): typeof monaco.editor { registerCommand: registerCommand, registerLinkOpener: registerLinkOpener, + registerEditorOpener: registerEditorOpener, // enums AccessibilitySupport: standaloneEnums.AccessibilitySupport, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 82437f899728e..973b62e0fa660 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1109,6 +1109,18 @@ declare namespace monaco.editor { */ export function registerLinkOpener(opener: ILinkOpener): IDisposable; + export interface ICodeEditorOpener { + openCodeEditor(source: ICodeEditor, resource: Uri, selectionOrPosition?: IRange | IPosition): boolean | Promise; + } + + /** + * Registers a handler that is called when a resource other than the current model should be opened in the editor (e.g. "go to definition"). + * The handler callback should return `true` if the request was handled and `false` otherwise. + * + * Returns a disposable that can unregister the opener again. + */ + export function registerEditorOpener(opener: ICodeEditorOpener): IDisposable; + export type BuiltinTheme = 'vs' | 'vs-dark' | 'hc-black' | 'hc-light'; export interface IStandaloneThemeData { From d84852dd5c4cd9db67c3e083039659b1b7110f7a Mon Sep 17 00:00:00 2001 From: Sebastian Pahnke Date: Wed, 5 Apr 2023 13:43:16 +0200 Subject: [PATCH 02/59] Add documentation --- .../editor/standalone/browser/standaloneEditor.ts | 13 +++++++++++++ src/vs/monaco.d.ts | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index a493344ea5f26..56bda1f9ac9f8 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -459,7 +459,18 @@ export function registerLinkOpener(opener: ILinkOpener): IDisposable { }); } +/** + * Represents an object that can handle editor open operations (e.g. when "go to definition" is called + * with a resource other than the current model). + */ export interface ICodeEditorOpener { + /** + * Callback that is invoked when a resource other than the current model should be opened (e.g. when "go to definition" is called). + * The callback should return `true` if the request was handled and `false` otherwise. + * @param source The code editor instance that initiated the request. + * @param resource The URI of the resource that should be opened. + * @param selectionOrPosition An optional position or selection inside the model corresponding to `resource` that can be used to set the cursor. + */ openCodeEditor(source: ICodeEditor, resource: URI, selectionOrPosition?: IRange | IPosition): boolean | Promise; } @@ -468,6 +479,8 @@ export interface ICodeEditorOpener { * The handler callback should return `true` if the request was handled and `false` otherwise. * * Returns a disposable that can unregister the opener again. + * + * If no handler is registered the default behavior is to do nothing for models other than the currently attached one. */ export function registerEditorOpener(opener: ICodeEditorOpener): IDisposable { const codeEditorService = StandaloneServices.get(ICodeEditorService); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 973b62e0fa660..616795c6ed7c8 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1109,7 +1109,18 @@ declare namespace monaco.editor { */ export function registerLinkOpener(opener: ILinkOpener): IDisposable; + /** + * Represents an object that can handle editor open operations (e.g. when "go to definition" is called + * with a resource other than the current model). + */ export interface ICodeEditorOpener { + /** + * Callback that is invoked when a resource other than the current model should be opened (e.g. when "go to definition" is called). + * The callback should return `true` if the request was handled and `false` otherwise. + * @param source The code editor instance that initiated the request. + * @param resource The Uri of the resource that should be opened. + * @param selectionOrPosition An optional position or selection inside the model corresponding to `resource` that can be used to set the cursor. + */ openCodeEditor(source: ICodeEditor, resource: Uri, selectionOrPosition?: IRange | IPosition): boolean | Promise; } @@ -1118,6 +1129,8 @@ declare namespace monaco.editor { * The handler callback should return `true` if the request was handled and `false` otherwise. * * Returns a disposable that can unregister the opener again. + * + * If no handler is registered the default behavior is to do nothing for models other than the currently attached one. */ export function registerEditorOpener(opener: ICodeEditorOpener): IDisposable; From 7e8064edad6d9658a295e5d37efebdebc9a77b6d Mon Sep 17 00:00:00 2001 From: weartist Date: Mon, 10 Apr 2023 18:49:40 +0800 Subject: [PATCH 03/59] fix #179337 Task icon for terminal tab entry does not update with task label when terminal is reused --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 70d75777da40b..ef24696f885a3 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1414,6 +1414,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._initialCwd = value; this._cwd = this._initialCwd; this._setTitle(this.title, TitleEventSource.Config); + this._icon = this._shellLaunchConfig.attachPersistentProcess?.icon || this._shellLaunchConfig.icon; + this._onIconChanged.fire({ instance: this, userInitiated: false }); break; case ProcessPropertyType.Title: this._setTitle(value ?? '', TitleEventSource.Process); From 07c971358c5aeec77d91b7c816a433b9d25337e6 Mon Sep 17 00:00:00 2001 From: Jacob Jaeggli <13710580+jjaeggli@users.noreply.github.com> Date: Tue, 11 Apr 2023 13:51:55 -0700 Subject: [PATCH 04/59] A11y help dialog uses semantic markup --- src/vs/editor/common/standaloneStrings.ts | 1 + .../accessibilityHelp/accessibilityHelp.ts | 53 ++++++++++--------- .../browser/accessibility/accessibility.ts | 53 +++++++++---------- 3 files changed, 54 insertions(+), 53 deletions(-) diff --git a/src/vs/editor/common/standaloneStrings.ts b/src/vs/editor/common/standaloneStrings.ts index b24c8b7e6a3f7..11cc856661bdc 100644 --- a/src/vs/editor/common/standaloneStrings.ts +++ b/src/vs/editor/common/standaloneStrings.ts @@ -29,6 +29,7 @@ export namespace AccessibilityHelpNLS { export const openDocWinLinux = nls.localize("openDocWinLinux", "Press Control+H now to open a browser window with more information related to editor accessibility."); export const outroMsg = nls.localize("outroMsg", "You can dismiss this tooltip and return to the editor by pressing Escape or Shift+Escape."); export const showAccessibilityHelpAction = nls.localize("showAccessibilityHelpAction", "Show Accessibility Help"); + export const accessibilityHelpTitle = nls.localize('accessibilityHelpTitle', "Accessibility Help"); } export namespace InspectTokensNLS { diff --git a/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts b/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts index 0a4f69a2f8e45..75268dce65c49 100644 --- a/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts +++ b/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts @@ -4,9 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./accessibilityHelp'; -import * as dom from 'vs/base/browser/dom'; +import { $, addStandardDisposableListener, append, clearNode, } from 'vs/base/browser/dom'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; -import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { Widget } from 'vs/base/browser/ui/widget'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -119,11 +118,17 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { this._domNode.setClassName('accessibilityHelpWidget'); this._domNode.setDisplay('none'); this._domNode.setAttribute('role', 'dialog'); + this._domNode.setAttribute('aria-modal', 'true'); this._domNode.setAttribute('aria-hidden', 'true'); + const heading = append(this._domNode.domNode, $('h1', undefined, AccessibilityHelpNLS.accessibilityHelpTitle)); + heading.id = 'help-dialog-heading'; + this._domNode.setAttribute('aria-labelledby', heading.id); + this._contentDomNode = createFastDomNode(document.createElement('div')); - this._contentDomNode.setAttribute('role', 'document'); + this._contentDomNode.domNode.id = 'help-dialog-content'; this._domNode.appendChild(this._contentDomNode); + this._contentDomNode.setAttribute('aria-describedby', this._contentDomNode.domNode.id); this._isVisible = false; @@ -134,7 +139,7 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { })); // Intentionally not configurable! - this._register(dom.addStandardDisposableListener(this._contentDomNode.domNode, 'keydown', (e) => { + this._register(addStandardDisposableListener(this._contentDomNode.domNode, 'keydown', (e) => { if (!this._isVisible) { return; } @@ -146,7 +151,7 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { accessibilitySupport: 'on' }); - dom.clearNode(this._contentDomNode.domNode); + clearNode(this._contentDomNode.domNode); this._buildContent(); this._contentDomNode.domNode.focus(); @@ -203,7 +208,7 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { this._layout(); this._domNode.setDisplay('block'); this._domNode.setAttribute('aria-hidden', 'false'); - this._contentDomNode.domNode.tabIndex = 0; + this._contentDomNode.domNode.tabIndex = -1; this._buildContent(); this._contentDomNode.domNode.focus(); } @@ -217,6 +222,7 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { } private _buildContent() { + const contentDomNode = this._contentDomNode.domNode; const options = this._editor.getOptions(); const selections = this._editor.getSelections(); @@ -231,22 +237,24 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { } } - let text = getSelectionLabel(selections, charactersSelected); + append(contentDomNode, $('p', undefined, getSelectionLabel(selections, charactersSelected))); + const top = append(contentDomNode, $('p')); if (options.get(EditorOption.inDiffEditor)) { if (options.get(EditorOption.readOnly)) { - text += AccessibilityHelpNLS.readonlyDiffEditor; + top.textContent = AccessibilityHelpNLS.readonlyDiffEditor; } else { - text += AccessibilityHelpNLS.editableDiffEditor; + top.textContent = AccessibilityHelpNLS.editableDiffEditor; } } else { if (options.get(EditorOption.readOnly)) { - text += AccessibilityHelpNLS.readonlyEditor; + top.textContent = AccessibilityHelpNLS.readonlyEditor; } else { - text += AccessibilityHelpNLS.editableEditor; + top.textContent = AccessibilityHelpNLS.editableEditor; } } + const instructions = append(contentDomNode, $('ul')); const turnOnMessage = ( platform.isMacintosh ? AccessibilityHelpNLS.changeConfigToOnMac @@ -254,22 +262,20 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { ); switch (options.get(EditorOption.accessibilitySupport)) { case AccessibilitySupport.Unknown: - text += '\n\n - ' + turnOnMessage; + append(instructions, $('li', undefined, turnOnMessage)); break; case AccessibilitySupport.Enabled: - text += '\n\n - ' + AccessibilityHelpNLS.auto_on; + append(instructions, $('li', undefined, AccessibilityHelpNLS.auto_on)); break; case AccessibilitySupport.Disabled: - text += '\n\n - ' + AccessibilityHelpNLS.auto_off; - text += ' ' + turnOnMessage; + append(instructions, $('li', undefined, AccessibilityHelpNLS.auto_off, turnOnMessage)); break; } - if (options.get(EditorOption.tabFocusMode)) { - text += '\n\n - ' + this._descriptionForCommand(ToggleTabFocusModeAction.ID, AccessibilityHelpNLS.tabFocusModeOnMsg, AccessibilityHelpNLS.tabFocusModeOnMsgNoKb); + append(instructions, $('li', undefined, this._descriptionForCommand(ToggleTabFocusModeAction.ID, AccessibilityHelpNLS.tabFocusModeOnMsg, AccessibilityHelpNLS.tabFocusModeOnMsgNoKb))); } else { - text += '\n\n - ' + this._descriptionForCommand(ToggleTabFocusModeAction.ID, AccessibilityHelpNLS.tabFocusModeOffMsg, AccessibilityHelpNLS.tabFocusModeOffMsgNoKb); + append(instructions, $('li', undefined, this._descriptionForCommand(ToggleTabFocusModeAction.ID, AccessibilityHelpNLS.tabFocusModeOffMsg, AccessibilityHelpNLS.tabFocusModeOffMsgNoKb))); } const openDocMessage = ( @@ -278,13 +284,8 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { : AccessibilityHelpNLS.openDocWinLinux ); - text += '\n\n - ' + openDocMessage; - - text += '\n\n' + AccessibilityHelpNLS.outroMsg; - - this._contentDomNode.domNode.appendChild(renderFormattedText(text)); - // Per https://www.w3.org/TR/wai-aria/roles#document, Authors SHOULD provide a title or label for documents - this._contentDomNode.domNode.setAttribute('aria-label', text); + append(instructions, $('li', undefined, openDocMessage)); + append(contentDomNode, $('p', undefined, AccessibilityHelpNLS.outroMsg)); } public hide(): void { @@ -296,7 +297,7 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { this._domNode.setDisplay('none'); this._domNode.setAttribute('aria-hidden', 'true'); this._contentDomNode.domNode.tabIndex = -1; - dom.clearNode(this._contentDomNode.domNode); + clearNode(this._contentDomNode.domNode); this._editor.focus(); } diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts index 5a76a91f6eb8a..3b92f16ae8a78 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts @@ -5,9 +5,8 @@ import 'vs/css!./accessibility'; import * as nls from 'vs/nls'; -import * as dom from 'vs/base/browser/dom'; +import { $, append, addStandardDisposableListener, clearNode } from 'vs/base/browser/dom'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; -import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { Widget } from 'vs/base/browser/ui/widget'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -72,7 +71,7 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { private static readonly ID = 'editor.contrib.accessibilityHelpWidget'; private static readonly WIDTH = 500; - private static readonly HEIGHT = 300; + private static readonly HEIGHT = 320; private _editor: ICodeEditor; private _domNode: FastDomNode; @@ -98,11 +97,17 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { this._domNode.setHeight(AccessibilityHelpWidget.HEIGHT); this._domNode.setDisplay('none'); this._domNode.setAttribute('role', 'dialog'); + this._domNode.setAttribute('aria-modal', 'true'); this._domNode.setAttribute('aria-hidden', 'true'); + const heading = append(this._domNode.domNode, $('h1', undefined, nls.localize('accessibilityHelpTitle', "Accessibility Help"))); + heading.id = 'help-dialog-heading'; + this._domNode.setAttribute('aria-labelledby', heading.id); + this._contentDomNode = createFastDomNode(document.createElement('div')); - this._contentDomNode.setAttribute('role', 'document'); + this._contentDomNode.domNode.id = 'help-dialog-content'; this._domNode.appendChild(this._contentDomNode); + this._domNode.setAttribute('aria-describedby', this._contentDomNode.domNode.id); this._isVisible = false; @@ -113,7 +118,7 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { })); // Intentionally not configurable! - this._register(dom.addStandardDisposableListener(this._contentDomNode.domNode, 'keydown', (e) => { + this._register(addStandardDisposableListener(this._contentDomNode.domNode, 'keydown', (e) => { if (!this._isVisible) { return; } @@ -172,7 +177,7 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { this._layout(); this._domNode.setDisplay('block'); this._domNode.setAttribute('aria-hidden', 'false'); - this._contentDomNode.domNode.tabIndex = 0; + this._contentDomNode.domNode.tabIndex = -1; this._buildContent(); this._contentDomNode.domNode.focus(); } @@ -186,10 +191,11 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { } private _buildContent() { + const contentDomNode = this._contentDomNode.domNode; const options = this._editor.getOptions(); - let text = nls.localize('introMsg', "Thank you for trying out VS Code's accessibility options."); - text += '\n\n' + nls.localize('status', "Status:"); + append(contentDomNode, $('p', undefined, nls.localize('introMsg', "Thank you for trying out VS Code's accessibility options."))); + append(contentDomNode, $('p', undefined, nls.localize('status', "Status:"))); const configuredValue = this._configurationService.getValue('editor').accessibilitySupport; const actualValue = options.get(EditorOption.accessibilitySupport); @@ -200,28 +206,27 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { : nls.localize('changeConfigToOnWinLinux', "To configure the editor to be permanently optimized for usage with a Screen Reader press Control+E now.") ); + const instructions = append(contentDomNode, $('ul')); switch (configuredValue) { case 'auto': switch (actualValue) { case AccessibilitySupport.Unknown: // Should never happen in VS Code - text += '\n\n - ' + nls.localize('auto_unknown', "The editor is configured to use platform APIs to detect when a Screen Reader is attached, but the current runtime does not support this."); + append(instructions, $('li', undefined, nls.localize('auto_unknown', "The editor is configured to use platform APIs to detect when a Screen Reader is attached, but the current runtime does not support this."))); break; case AccessibilitySupport.Enabled: - text += '\n\n - ' + nls.localize('auto_on', "The editor has automatically detected a Screen Reader is attached."); + append(instructions, $('li', undefined, nls.localize('auto_on', "The editor has automatically detected a Screen Reader is attached."))); break; case AccessibilitySupport.Disabled: - text += '\n\n - ' + nls.localize('auto_off', "The editor is configured to automatically detect when a Screen Reader is attached, which is not the case at this time."); - text += ' ' + emergencyTurnOnMessage; + append(instructions, $('li', undefined, nls.localize('auto_off', "The editor is configured to automatically detect when a Screen Reader is attached, which is not the case at this time."), ' ' + emergencyTurnOnMessage)); break; } break; case 'on': - text += '\n\n - ' + nls.localize('configuredOn', "The editor is configured to be permanently optimized for usage with a Screen Reader - you can change this via the command `Toggle Screen Reader Accessibility Mode` or by editing the setting `editor.accessibilitySupport`"); + append(instructions, $('li', undefined, nls.localize('configuredOn', "The editor is configured to be permanently optimized for usage with a Screen Reader - you can change this via the command `Toggle Screen Reader Accessibility Mode` or by editing the setting `editor.accessibilitySupport`"))); break; case 'off': - text += '\n\n - ' + nls.localize('configuredOff', "The editor is configured to never be optimized for usage with a Screen Reader."); - text += ' ' + emergencyTurnOnMessage; + append(instructions, $('li', undefined, nls.localize('configuredOff', "The editor is configured to never be optimized for usage with a Screen Reader.", ' ' + emergencyTurnOnMessage))); break; } @@ -231,24 +236,18 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { const NLS_TAB_FOCUS_MODE_OFF_NO_KB = nls.localize('tabFocusModeOffMsgNoKb', "Pressing Tab in the current editor will insert the tab character. The command {0} is currently not triggerable by a keybinding."); if (TabFocus.getTabFocusMode(TabFocusContext.Editor)) { - text += '\n\n - ' + this._descriptionForCommand(ToggleTabFocusModeAction.ID, NLS_TAB_FOCUS_MODE_ON, NLS_TAB_FOCUS_MODE_ON_NO_KB); + append(instructions, $('li', undefined, this._descriptionForCommand(ToggleTabFocusModeAction.ID, NLS_TAB_FOCUS_MODE_ON, NLS_TAB_FOCUS_MODE_ON_NO_KB))); } else { - text += '\n\n - ' + this._descriptionForCommand(ToggleTabFocusModeAction.ID, NLS_TAB_FOCUS_MODE_OFF, NLS_TAB_FOCUS_MODE_OFF_NO_KB); + append(instructions, $('li', undefined, this._descriptionForCommand(ToggleTabFocusModeAction.ID, NLS_TAB_FOCUS_MODE_OFF, NLS_TAB_FOCUS_MODE_OFF_NO_KB))); } - const openDocMessage = ( + append(contentDomNode, ( platform.isMacintosh ? nls.localize('openDocMac', "Press Command+H now to open a browser window with more VS Code information related to Accessibility.") : nls.localize('openDocWinLinux', "Press Control+H now to open a browser window with more VS Code information related to Accessibility.") - ); - - text += '\n\n' + openDocMessage; - - text += '\n\n' + nls.localize('outroMsg', "You can dismiss this tooltip and return to the editor by pressing Escape or Shift+Escape."); + )); - this._contentDomNode.domNode.appendChild(renderFormattedText(text)); - // Per https://www.w3.org/TR/wai-aria/roles#document, Authors SHOULD provide a title or label for documents - this._contentDomNode.domNode.setAttribute('aria-label', text); + append(contentDomNode, $('p', undefined, nls.localize('outroMsg', "You can dismiss this tooltip and return to the editor by pressing Escape or Shift+Escape."))); } public hide(): void { @@ -260,7 +259,7 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { this._domNode.setDisplay('none'); this._domNode.setAttribute('aria-hidden', 'true'); this._contentDomNode.domNode.tabIndex = -1; - dom.clearNode(this._contentDomNode.domNode); + clearNode(this._contentDomNode.domNode); this._editor.focus(); } From ee2a960bb7fab9d0bc3e2bf69aaa3a79a5cb309c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 12 Apr 2023 01:39:21 +0200 Subject: [PATCH 05/59] remember the current account provider (#179728) --- .../browser/userDataSyncWorkbenchService.ts | 74 +++++++++++-------- .../userDataSync/common/userDataSync.ts | 1 - 2 files changed, 42 insertions(+), 33 deletions(-) diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index 6c76709eac77e..1a38706ec3ef8 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -63,6 +63,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat _serviceBrand: any; private static DONOT_USE_WORKBENCH_SESSION_STORAGE_KEY = 'userDataSyncAccount.donotUseWorkbenchSession'; + private static CACHED_AUTHENTICATION_PROVIDER_KEY = 'userDataSyncAccountProvider'; private static CACHED_SESSION_STORAGE_KEY = 'userDataSyncAccountPreference'; get enabled() { return !!this.userDataSyncStoreManagementService.userDataSyncStore; } @@ -221,6 +222,9 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat this._all = allAccounts; const current = this.current; + if (current) { + this.currentAuthenticationProviderId = current.authenticationProviderId; + } await this.updateToken(current); this.updateAccountStatus(current ? AccountStatus.Available : AccountStatus.Unavailable); } @@ -293,22 +297,6 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat throw new Error(localize('no account', "No account available")); } - await this.turnOnUsingCurrentAccount(); - } - - async turnOnUsingCurrentAccount(): Promise { - if (this.userDataSyncEnablementService.isEnabled()) { - return; - } - - if (this.userDataSyncService.status !== SyncStatus.Idle) { - throw new Error('Cannot turn on sync while syncing'); - } - - if (this.accountStatus !== AccountStatus.Available) { - throw new Error(localize('no account', "No account available")); - } - const turnOnSyncCancellationToken = this.turnOnSyncCancellationToken = new CancellationTokenSource(); const disposable = isWeb ? Disposable.None : this.lifecycleService.onBeforeShutdown(e => e.veto((async () => { const { confirmed } = await this.dialogService.confirm({ @@ -335,6 +323,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat await this.synchroniseUserDataSyncStoreType(); } + this.currentAuthenticationProviderId = this.current?.authenticationProviderId; this.notificationService.info(localize('sync turned on', "{0} is turned on", SYNC_TITLE)); } @@ -485,7 +474,13 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } async signIn(): Promise { - await this.pick(); + const currentAuthenticationProviderId = this.currentAuthenticationProviderId; + const authenticationProvider = currentAuthenticationProviderId ? this.authenticationProviders.find(p => p.id === currentAuthenticationProviderId) : undefined; + if (authenticationProvider) { + await this.doSignIn(authenticationProvider); + } else { + await this.pick(); + } } private async pick(): Promise { @@ -493,20 +488,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat if (!result) { return false; } - let sessionId: string, accountName: string, accountId: string, authenticationProviderId: string; - if (isAuthenticationProvider(result)) { - const session = await this.authenticationService.createSession(result.id, result.scopes); - sessionId = session.id; - accountName = session.account.label; - accountId = session.account.id; - authenticationProviderId = result.id; - } else { - sessionId = result.sessionId; - accountName = result.accountName; - accountId = result.accountId; - authenticationProviderId = result.authenticationProviderId; - } - await this.switch(sessionId, accountName, accountId, authenticationProviderId); + await this.doSignIn(result); return true; } @@ -580,7 +562,16 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat return quickPickItems; } - private async switch(sessionId: string, accountName: string, accountId: string, authenticationProviderId: string): Promise { + private async doSignIn(accountOrAuthProvider: UserDataSyncAccount | IAuthenticationProvider): Promise { + let sessionId: string, accountName: string; + if (isAuthenticationProvider(accountOrAuthProvider)) { + const session = await this.authenticationService.createSession(accountOrAuthProvider.id, accountOrAuthProvider.scopes); + sessionId = session.id; + accountName = session.account.label; + } else { + sessionId = accountOrAuthProvider.sessionId; + accountName = accountOrAuthProvider.accountName; + } const currentAccount = this.current; if (this.userDataSyncEnablementService.isEnabled() && (currentAccount && currentAccount.accountName !== accountName)) { // accounts are switched while sync is enabled. @@ -620,6 +611,25 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } } + private _cachedCurrentAuthenticationProviderId: string | undefined | null = null; + private get currentAuthenticationProviderId(): string | undefined { + if (this._cachedCurrentAuthenticationProviderId === null) { + this._cachedCurrentAuthenticationProviderId = this.storageService.get(UserDataSyncWorkbenchService.CACHED_AUTHENTICATION_PROVIDER_KEY, StorageScope.APPLICATION); + } + return this._cachedCurrentAuthenticationProviderId; + } + + private set currentAuthenticationProviderId(currentAuthenticationProviderId: string | undefined) { + if (this._cachedCurrentAuthenticationProviderId !== currentAuthenticationProviderId) { + this._cachedCurrentAuthenticationProviderId = currentAuthenticationProviderId; + if (currentAuthenticationProviderId === undefined) { + this.storageService.remove(UserDataSyncWorkbenchService.CACHED_AUTHENTICATION_PROVIDER_KEY, StorageScope.APPLICATION); + } else { + this.storageService.store(UserDataSyncWorkbenchService.CACHED_AUTHENTICATION_PROVIDER_KEY, currentAuthenticationProviderId, StorageScope.APPLICATION, StorageTarget.MACHINE); + } + } + } + private _cachedCurrentSessionId: string | undefined | null = null; private get currentSessionId(): string | undefined { if (this._cachedCurrentSessionId === null) { diff --git a/src/vs/workbench/services/userDataSync/common/userDataSync.ts b/src/vs/workbench/services/userDataSync/common/userDataSync.ts index 245b2b09664ee..2b412374d3e11 100644 --- a/src/vs/workbench/services/userDataSync/common/userDataSync.ts +++ b/src/vs/workbench/services/userDataSync/common/userDataSync.ts @@ -33,7 +33,6 @@ export interface IUserDataSyncWorkbenchService { readonly onDidChangeAccountStatus: Event; turnOn(): Promise; - turnOnUsingCurrentAccount(): Promise; turnoff(everyWhere: boolean): Promise; signIn(): Promise; From 2387e49c891489382e5a1db940bb7a2107bb65ea Mon Sep 17 00:00:00 2001 From: Jacob Jaeggli <13710580+jjaeggli@users.noreply.github.com> Date: Tue, 11 Apr 2023 16:55:08 -0700 Subject: [PATCH 06/59] Reapply document role to content element --- .../standalone/browser/accessibilityHelp/accessibilityHelp.ts | 3 ++- .../contrib/codeEditor/browser/accessibility/accessibility.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts b/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts index 75268dce65c49..f433eb184f188 100644 --- a/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts +++ b/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts @@ -126,6 +126,7 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { this._domNode.setAttribute('aria-labelledby', heading.id); this._contentDomNode = createFastDomNode(document.createElement('div')); + this._contentDomNode.setAttribute('role', 'document'); this._contentDomNode.domNode.id = 'help-dialog-content'; this._domNode.appendChild(this._contentDomNode); this._contentDomNode.setAttribute('aria-describedby', this._contentDomNode.domNode.id); @@ -208,7 +209,7 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { this._layout(); this._domNode.setDisplay('block'); this._domNode.setAttribute('aria-hidden', 'false'); - this._contentDomNode.domNode.tabIndex = -1; + this._contentDomNode.domNode.tabIndex = 0; this._buildContent(); this._contentDomNode.domNode.focus(); } diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts index 3b92f16ae8a78..30fa15e372ff0 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts @@ -105,6 +105,7 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { this._domNode.setAttribute('aria-labelledby', heading.id); this._contentDomNode = createFastDomNode(document.createElement('div')); + this._contentDomNode.setAttribute('role', 'document'); this._contentDomNode.domNode.id = 'help-dialog-content'; this._domNode.appendChild(this._contentDomNode); this._domNode.setAttribute('aria-describedby', this._contentDomNode.domNode.id); @@ -177,7 +178,7 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { this._layout(); this._domNode.setDisplay('block'); this._domNode.setAttribute('aria-hidden', 'false'); - this._contentDomNode.domNode.tabIndex = -1; + this._contentDomNode.domNode.tabIndex = 0; this._buildContent(); this._contentDomNode.domNode.focus(); } From e5512217bcfb2b644fe7ad6c465fe0b45d23265b Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 11 Apr 2023 18:43:34 -0700 Subject: [PATCH 07/59] Use inline progress widget for long running copy pastes (#179730) Extracts the inline progress widget for use with the copy paste too --- .../copyPaste/browser/copyPasteController.ts | 126 ++++++++++-------- .../browser/dropIntoEditorContribution.ts | 19 +-- .../dropIntoEditor/browser/postDropWidget.ts | 1 - .../browser/inlineProgress.ts} | 47 ++++--- .../browser/inlineProgressWidget.css} | 10 +- src/vs/editor/editor.all.ts | 1 + 6 files changed, 114 insertions(+), 90 deletions(-) rename src/vs/editor/contrib/{dropIntoEditor/browser/dropProgressWidget.ts => inlineProgress/browser/inlineProgress.ts} (77%) rename src/vs/editor/contrib/{dropIntoEditor/browser/dropProgressWidget.css => inlineProgress/browser/inlineProgressWidget.css} (74%) diff --git a/src/vs/editor/contrib/copyPaste/browser/copyPasteController.ts b/src/vs/editor/contrib/copyPaste/browser/copyPasteController.ts index f6fe1d00ed5c5..df8b67912d369 100644 --- a/src/vs/editor/contrib/copyPaste/browser/copyPasteController.ts +++ b/src/vs/editor/contrib/copyPaste/browser/copyPasteController.ts @@ -7,7 +7,7 @@ import { DataTransfers } from 'vs/base/browser/dnd'; import { addDisposableListener } from 'vs/base/browser/dom'; import { CancelablePromise, createCancelablePromise, raceCancellation } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { createStringDataTransferItem, UriList, VSDataTransfer } from 'vs/base/common/dataTransfer'; +import { UriList, VSDataTransfer, createStringDataTransferItem } from 'vs/base/common/dataTransfer'; import { Disposable } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; import { Schemas } from 'vs/base/common/network'; @@ -23,11 +23,12 @@ import { DocumentPasteEdit, DocumentPasteEditProvider, WorkspaceEdit } from 'vs/ import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from 'vs/editor/contrib/editorState/browser/editorState'; +import { InlineProgressManager } from 'vs/editor/contrib/inlineProgress/browser/inlineProgress'; import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser'; import { localize } from 'vs/nls'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; const vscodeClipboardMime = 'application/vnd.code.copyMetadata'; @@ -51,13 +52,18 @@ export class CopyPasteController extends Disposable implements IEditorContributi readonly dataTransferPromise: CancelablePromise; }; + private operationIdPool = 0; + private _currentOperation?: { readonly id: number; readonly promise: CancelablePromise }; + + private readonly _pasteProgressManager: InlineProgressManager; + constructor( editor: ICodeEditor, + @IInstantiationService instantiationService: IInstantiationService, @IBulkEditService private readonly _bulkEditService: IBulkEditService, @IClipboardService private readonly _clipboardService: IClipboardService, @IConfigurationService private readonly _configurationService: IConfigurationService, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, - @IProgressService private readonly _progressService: IProgressService, ) { super(); @@ -67,6 +73,8 @@ export class CopyPasteController extends Disposable implements IEditorContributi this._register(addDisposableListener(container, 'copy', e => this.handleCopy(e))); this._register(addDisposableListener(container, 'cut', e => this.handleCopy(e))); this._register(addDisposableListener(container, 'paste', e => this.handlePaste(e), true)); + + this._pasteProgressManager = this._register(new InlineProgressManager('pasteIntoEditor', editor, instantiationService)); } private arePasteActionsEnabled(model: ITextModel): boolean { @@ -145,6 +153,10 @@ export class CopyPasteController extends Disposable implements IEditorContributi return; } + const operationId = this.operationIdPool++; + this._currentOperation?.promise.cancel(); + this._pasteProgressManager.clear(); + const selections = this._editor.getSelections(); if (!selections?.length || !this._editor.hasModel()) { return; @@ -169,69 +181,77 @@ export class CopyPasteController extends Disposable implements IEditorContributi e.preventDefault(); e.stopImmediatePropagation(); - const tokenSource = new EditorStateCancellationTokenSource(this._editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection); - try { - const dataTransfer = toVSDataTransfer(e.clipboardData); - if (metadata?.id && this._currentClipboardItem?.handle === metadata.id) { - const toMergeDataTransfer = await this._currentClipboardItem.dataTransferPromise; - if (tokenSource.token.isCancellationRequested) { - return; - } + const p = createCancelablePromise(async (token) => { + const editor = this._editor; + if (!editor.hasModel()) { + return; + } - toMergeDataTransfer.forEach((value, key) => { - dataTransfer.replace(key, value); + const tokenSource = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection, undefined, token); + try { + this._pasteProgressManager.setAtPosition(selections[0].getEndPosition(), localize('pasteIntoEditorProgress', "Running paste handlers. Click to cancel"), { + cancel: () => tokenSource.cancel() }); - } - if (!dataTransfer.has(Mimes.uriList)) { - const resources = await this._clipboardService.readResources(); - if (tokenSource.token.isCancellationRequested) { - return; + const dataTransfer = toVSDataTransfer(e.clipboardData!); + + if (metadata?.id && this._currentClipboardItem?.handle === metadata.id) { + const toMergeDataTransfer = await this._currentClipboardItem.dataTransferPromise; + if (tokenSource.token.isCancellationRequested) { + return; + } + + toMergeDataTransfer.forEach((value, key) => { + dataTransfer.replace(key, value); + }); } - if (resources.length) { - dataTransfer.append(Mimes.uriList, createStringDataTransferItem(UriList.create(resources))); + if (!dataTransfer.has(Mimes.uriList)) { + const resources = await this._clipboardService.readResources(); + if (tokenSource.token.isCancellationRequested) { + return; + } + + if (resources.length) { + dataTransfer.append(Mimes.uriList, createStringDataTransferItem(UriList.create(resources))); + } } - } - dataTransfer.delete(vscodeClipboardMime); + dataTransfer.delete(vscodeClipboardMime); - const providerEdit = await this._progressService.withProgress({ - location: ProgressLocation.Notification, - delay: 750, - title: localize('pasteProgressTitle', "Running paste handlers..."), - cancellable: true, - }, () => { - return this.getProviderPasteEdit(providers, dataTransfer, model, selections, tokenSource.token); - }, () => { - return tokenSource.cancel(); - }); + const providerEdit = await this.getProviderPasteEdit(providers, dataTransfer, model, selections, tokenSource.token); + if (tokenSource.token.isCancellationRequested) { + return; + } - if (tokenSource.token.isCancellationRequested) { - return; - } + if (providerEdit) { + const snippet = typeof providerEdit.insertText === 'string' ? SnippetParser.escape(providerEdit.insertText) : providerEdit.insertText.snippet; + const combinedWorkspaceEdit: WorkspaceEdit = { + edits: [ + new ResourceTextEdit(model.uri, { + range: Selection.liftSelection(editor.getSelection()), + text: snippet, + insertAsSnippet: true, + }), + ...(providerEdit.additionalEdit?.edits ?? []) + ] + }; + await this._bulkEditService.apply(combinedWorkspaceEdit, { editor }); + return; + } - if (providerEdit) { - const snippet = typeof providerEdit.insertText === 'string' ? SnippetParser.escape(providerEdit.insertText) : providerEdit.insertText.snippet; - const combinedWorkspaceEdit: WorkspaceEdit = { - edits: [ - new ResourceTextEdit(model.uri, { - range: Selection.liftSelection(this._editor.getSelection()), - text: snippet, - insertAsSnippet: true, - }), - ...(providerEdit.additionalEdit?.edits ?? []) - ] - }; - await this._bulkEditService.apply(combinedWorkspaceEdit, { editor: this._editor }); - return; + await this.applyDefaultPasteHandler(dataTransfer, metadata, tokenSource.token); + } finally { + tokenSource.dispose(); + if (this._currentOperation?.id === operationId) { + this._pasteProgressManager.clear(); + this._currentOperation = undefined; + } } + }); - await this.applyDefaultPasteHandler(dataTransfer, metadata, tokenSource.token); - } finally { - tokenSource.dispose(); - } + this._currentOperation = { id: operationId, promise: p }; } private getProviderPasteEdit(providers: DocumentPasteEditProvider[], dataTransfer: VSDataTransfer, model: ITextModel, selections: Selection[], token: CancellationToken): Promise { diff --git a/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts b/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts index 538f5e95fa53a..5a2d5a0904ab6 100644 --- a/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts +++ b/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts @@ -18,7 +18,6 @@ import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { DocumentOnDropEdit, WorkspaceEdit } from 'vs/editor/common/languages'; import { TrackedRangeStickiness } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { DropProgressManager as DropProgressWidgetManager } from 'vs/editor/contrib/dropIntoEditor/browser/dropProgressWidget'; import { PostDropWidgetManager } from 'vs/editor/contrib/dropIntoEditor/browser/postDropWidget'; import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from 'vs/editor/contrib/editorState/browser/editorState'; import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser'; @@ -26,6 +25,8 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { registerDefaultDropProviders } from './defaultOnDropProviders'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { InlineProgressManager } from 'vs/editor/contrib/inlineProgress/browser/inlineProgress'; +import { localize } from 'vs/nls'; export class DropIntoEditorController extends Disposable implements IEditorContribution { @@ -35,7 +36,7 @@ export class DropIntoEditorController extends Disposable implements IEditorContr private operationIdPool = 0; private _currentOperation?: { readonly id: number; readonly promise: CancelablePromise }; - private readonly _dropProgressWidgetManager: DropProgressWidgetManager; + private readonly _dropProgressManager: InlineProgressManager; private readonly _postDropWidgetManager: PostDropWidgetManager; constructor( @@ -47,7 +48,7 @@ export class DropIntoEditorController extends Disposable implements IEditorContr ) { super(); - this._dropProgressWidgetManager = this._register(new DropProgressWidgetManager(editor, instantiationService)); + this._dropProgressManager = this._register(new InlineProgressManager('dropIntoEditor', editor, instantiationService)); this._postDropWidgetManager = this._register(new PostDropWidgetManager(editor, instantiationService)); this._register(editor.onDropIntoEditor(e => this.onDropIntoEditor(editor, e.position, e.event))); @@ -61,17 +62,17 @@ export class DropIntoEditorController extends Disposable implements IEditorContr } this._currentOperation?.promise.cancel(); - this._dropProgressWidgetManager.clear(); + this._dropProgressManager.clear(); editor.focus(); editor.setPosition(position); - const id = this.operationIdPool++; + const operationId = this.operationIdPool++; const p = createCancelablePromise(async (token) => { const tokenSource = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value, undefined, token); - this._dropProgressWidgetManager.setAtPosition(position, { + this._dropProgressManager.setAtPosition(position, localize('dropIntoEditorProgress', "Running drop handlers. Click to cancel"), { cancel: () => tokenSource.cancel() }); @@ -110,14 +111,14 @@ export class DropIntoEditorController extends Disposable implements IEditorContr } finally { tokenSource.dispose(); - if (this._currentOperation?.id === id) { - this._dropProgressWidgetManager.clear(); + if (this._currentOperation?.id === operationId) { + this._dropProgressManager.clear(); this._currentOperation = undefined; } } }); - this._currentOperation = { id, promise: p }; + this._currentOperation = { id: operationId, promise: p }; } private async extractDataTransferData(dragEvent: DragEvent): Promise { diff --git a/src/vs/editor/contrib/dropIntoEditor/browser/postDropWidget.ts b/src/vs/editor/contrib/dropIntoEditor/browser/postDropWidget.ts index 0e17255193d82..872dfa2c37c4b 100644 --- a/src/vs/editor/contrib/dropIntoEditor/browser/postDropWidget.ts +++ b/src/vs/editor/contrib/dropIntoEditor/browser/postDropWidget.ts @@ -7,7 +7,6 @@ import * as dom from 'vs/base/browser/dom'; import { Button } from 'vs/base/browser/ui/button/button'; import { toAction } from 'vs/base/common/actions'; import { Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import 'vs/css!./dropProgressWidget'; import 'vs/css!./postDropWidget'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { Range } from 'vs/editor/common/core/range'; diff --git a/src/vs/editor/contrib/dropIntoEditor/browser/dropProgressWidget.ts b/src/vs/editor/contrib/inlineProgress/browser/inlineProgress.ts similarity index 77% rename from src/vs/editor/contrib/dropIntoEditor/browser/dropProgressWidget.ts rename to src/vs/editor/contrib/inlineProgress/browser/inlineProgress.ts index c6367d4111a23..f337f9e7d6d59 100644 --- a/src/vs/editor/contrib/dropIntoEditor/browser/dropProgressWidget.ts +++ b/src/vs/editor/contrib/inlineProgress/browser/inlineProgress.ts @@ -9,7 +9,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { noBreakWhitespace } from 'vs/base/common/strings'; import { ThemeIcon } from 'vs/base/common/themables'; -import 'vs/css!./dropProgressWidget'; +import 'vs/css!./inlineProgressWidget'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IPosition } from 'vs/editor/common/core/position'; @@ -17,26 +17,22 @@ import { Range } from 'vs/editor/common/core/range'; import { IEditorDecorationsCollection } from 'vs/editor/common/editorCommon'; import { TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -const dropIntoEditorProgress = ModelDecorationOptions.register({ - description: 'drop-into-editor-progress', +const inlineProgressDecoration = ModelDecorationOptions.register({ + description: 'inline-progress-widget', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, showIfCollapsed: true, after: { content: noBreakWhitespace, - inlineClassName: 'drop-into-editor-progress-decoration', + inlineClassName: 'inline-editor-progress-decoration', inlineClassNameAffectsLetterSpacing: true, } }); -interface DropProgressDelegate { - cancel(): void; -} -class InlineDropProgressWidget extends Disposable implements IContentWidget { - private static readonly ID = 'editor.widget.dropProgressWidget'; +class InlineProgressWidget extends Disposable implements IContentWidget { + private static readonly baseId = 'editor.widget.inlineProgressWidget'; allowEditorOverflow = false; suppressMouseDown = true; @@ -44,22 +40,24 @@ class InlineDropProgressWidget extends Disposable implements IContentWidget { private domNode!: HTMLElement; constructor( + private readonly typeId: string, private readonly editor: ICodeEditor, private readonly range: Range, - private readonly delegate: DropProgressDelegate, + title: string, + private readonly delegate: InlineProgressDelegate, ) { super(); - this.create(); + this.create(title); this.editor.addContentWidget(this); this.editor.layoutContentWidget(this); } - private create(): void { - this.domNode = dom.$('.inline-drop-progress-widget'); + private create(title: string): void { + this.domNode = dom.$('.inline-progress-widget'); this.domNode.role = 'button'; - this.domNode.title = localize('dropIntoEditorProgress', "Running drop handlers. Click to cancel"); + this.domNode.title = title; const iconElement = dom.$('span.icon'); this.domNode.append(iconElement); @@ -85,7 +83,7 @@ class InlineDropProgressWidget extends Disposable implements IContentWidget { } getId(): string { - return InlineDropProgressWidget.ID; + return InlineProgressWidget.baseId + '.' + this.typeId; } getDomNode(): HTMLElement { @@ -105,16 +103,21 @@ class InlineDropProgressWidget extends Disposable implements IContentWidget { } } -export class DropProgressManager extends Disposable { +interface InlineProgressDelegate { + cancel(): void; +} + +export class InlineProgressManager extends Disposable { /** Delay before showing the progress widget */ private readonly _showDelay = 500; // ms private readonly _showPromise = this._register(new MutableDisposable()); private readonly _currentDecorations: IEditorDecorationsCollection; - private readonly _currentWidget = new MutableDisposable(); + private readonly _currentWidget = new MutableDisposable(); constructor( + readonly id: string, private readonly _editor: ICodeEditor, @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { @@ -123,18 +126,18 @@ export class DropProgressManager extends Disposable { this._currentDecorations = _editor.createDecorationsCollection(); } - public setAtPosition(position: IPosition, delegate: DropProgressDelegate) { + public setAtPosition(position: IPosition, title: string, delegate: InlineProgressDelegate) { this.clear(); this._showPromise.value = disposableTimeout(() => { - const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column); + const range = Range.fromPositions(position); const decorationIds = this._currentDecorations.set([{ range: range, - options: dropIntoEditorProgress, + options: inlineProgressDecoration, }]); if (decorationIds.length > 0) { - this._currentWidget.value = this._instantiationService.createInstance(InlineDropProgressWidget, this._editor, range, delegate); + this._currentWidget.value = this._instantiationService.createInstance(InlineProgressWidget, this.id, this._editor, range, title, delegate); } }, this._showDelay); } diff --git a/src/vs/editor/contrib/dropIntoEditor/browser/dropProgressWidget.css b/src/vs/editor/contrib/inlineProgress/browser/inlineProgressWidget.css similarity index 74% rename from src/vs/editor/contrib/dropIntoEditor/browser/dropProgressWidget.css rename to src/vs/editor/contrib/inlineProgress/browser/inlineProgressWidget.css index f33beb406e8df..105c60d52b5a1 100644 --- a/src/vs/editor/contrib/dropIntoEditor/browser/dropProgressWidget.css +++ b/src/vs/editor/contrib/inlineProgress/browser/inlineProgressWidget.css @@ -3,27 +3,27 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.drop-into-editor-progress-decoration { +.inline-editor-progress-decoration { display: inline-block; width: 1em; height: 1em; } -.inline-drop-progress-widget { +.inline-progress-widget { display: flex !important; justify-content: center; align-items: center; } -.inline-drop-progress-widget .icon { +.inline-progress-widget .icon { font-size: 80% !important; } -.inline-drop-progress-widget:hover .icon { +.inline-progress-widget:hover .icon { font-size: 90% !important; animation: none; } -.inline-drop-progress-widget:hover .icon::before { +.inline-progress-widget:hover .icon::before { content: "\ea76"; /* codicon-x */ } diff --git a/src/vs/editor/editor.all.ts b/src/vs/editor/editor.all.ts index 38a7a98a345d5..457a7b1f0084b 100644 --- a/src/vs/editor/editor.all.ts +++ b/src/vs/editor/editor.all.ts @@ -27,6 +27,7 @@ import 'vs/editor/contrib/fontZoom/browser/fontZoom'; import 'vs/editor/contrib/format/browser/formatActions'; import 'vs/editor/contrib/documentSymbols/browser/documentSymbols'; import 'vs/editor/contrib/inlineCompletions/browser/ghostText.contribution'; +import 'vs/editor/contrib/inlineProgress/browser/inlineProgress'; import 'vs/editor/contrib/gotoSymbol/browser/goToCommands'; import 'vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition'; import 'vs/editor/contrib/gotoError/browser/gotoError'; From a1eb9e2b48736c3f79697fb6618528d094318a93 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 12 Apr 2023 09:48:29 +0200 Subject: [PATCH 08/59] Git - implement branch protection provider (#179752) * Branch protection using settings is working * Revert extension api changes * Refactor code --- extensions/git/src/actionButton.ts | 4 +- extensions/git/src/api/git.d.ts | 5 +++ extensions/git/src/branchProtection.ts | 50 +++++++++++++++++++++++ extensions/git/src/commands.ts | 2 +- extensions/git/src/model.ts | 36 +++++++++++++++-- extensions/git/src/repository.ts | 55 ++++++++++++++++---------- extensions/git/src/statusbar.ts | 1 + 7 files changed, 126 insertions(+), 27 deletions(-) create mode 100644 extensions/git/src/branchProtection.ts diff --git a/extensions/git/src/actionButton.ts b/extensions/git/src/actionButton.ts index 4b07d2a768450..6a4bd06174ae6 100644 --- a/extensions/git/src/actionButton.ts +++ b/extensions/git/src/actionButton.ts @@ -51,6 +51,7 @@ export class ActionButtonCommand { repository.onDidRunGitStatus(this.onDidRunGitStatus, this, this.disposables); repository.onDidChangeOperations(this.onDidChangeOperations, this, this.disposables); + this.disposables.push(repository.onDidChangeBranchProtection(() => this._onDidChange.fire())); this.disposables.push(postCommitCommandCenter.onDidChange(() => this._onDidChange.fire())); const root = Uri.file(repository.root); @@ -61,8 +62,7 @@ export class ActionButtonCommand { this.onDidChangeSmartCommitSettings(); } - if (e.affectsConfiguration('git.branchProtection', root) || - e.affectsConfiguration('git.branchProtectionPrompt', root) || + if (e.affectsConfiguration('git.branchProtectionPrompt', root) || e.affectsConfiguration('git.postCommitCommand', root) || e.affectsConfiguration('git.rememberPostCommitCommand', root) || e.affectsConfiguration('git.showActionButton', root)) { diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index 0b428fa73c1af..c3b6b76111e95 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -274,6 +274,11 @@ export interface PushErrorHandler { handlePushError(repository: Repository, remote: Remote, refspec: string, error: Error & { gitErrorCode: GitErrorCodes }): Promise; } +export interface BranchProtectionProvider { + onDidChangeBranchProtection: Event; + provideBranchProtection(): Map; +} + export type APIState = 'uninitialized' | 'initialized'; export interface PublishEvent { diff --git a/extensions/git/src/branchProtection.ts b/extensions/git/src/branchProtection.ts new file mode 100644 index 0000000000000..0aece314f4545 --- /dev/null +++ b/extensions/git/src/branchProtection.ts @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, Event, EventEmitter, Uri, workspace } from 'vscode'; +import { BranchProtectionProvider } from './api/git'; +import { dispose, filterEvent } from './util'; + +export interface IBranchProtectionProviderRegistry { + readonly onDidChangeBranchProtectionProviders: Event; + + getBranchProtectionProviders(root: Uri): BranchProtectionProvider[]; + registerBranchProtectionProvider(root: Uri, provider: BranchProtectionProvider): Disposable; +} + +export class GitBranchProtectionProvider implements BranchProtectionProvider { + + private readonly _onDidChangeBranchProtection = new EventEmitter(); + onDidChangeBranchProtection = this._onDidChangeBranchProtection.event; + + private branchProtection = new Map<'', string[]>(); + private disposables: Disposable[] = []; + + constructor(private readonly repositoryRoot: Uri) { + const onDidChangeBranchProtectionEvent = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.branchProtection', repositoryRoot)); + onDidChangeBranchProtectionEvent(this.updateBranchProtection, this, this.disposables); + this.updateBranchProtection(); + } + + provideBranchProtection(): Map { + return this.branchProtection; + } + + private updateBranchProtection(): void { + const scopedConfig = workspace.getConfiguration('git', this.repositoryRoot); + const branchProtectionConfig = scopedConfig.get('branchProtection') ?? []; + const branchProtectionValues = Array.isArray(branchProtectionConfig) ? branchProtectionConfig : [branchProtectionConfig]; + + this.branchProtection.set('', branchProtectionValues + .map(bp => typeof bp === 'string' ? bp.trim() : '') + .filter(bp => bp !== '')); + + this._onDidChangeBranchProtection.fire(this.repositoryRoot); + } + + dispose(): void { + this.disposables = dispose(this.disposables); + } +} diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 859c522022994..a697c0be6fdb1 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -22,7 +22,7 @@ import { pickRemoteSource } from './remoteSource'; class CheckoutItem implements QuickPickItem { protected get shortCommit(): string { return (this.ref.commit || '').substr(0, 8); } - get label(): string { return `${this.repository.isBranchProtected(this.ref.name ?? '') ? '$(lock)' : '$(git-branch)'} ${this.ref.name || this.shortCommit}`; } + get label(): string { return `${this.repository.isBranchProtected(this.ref) ? '$(lock)' : '$(git-branch)'} ${this.ref.name || this.shortCommit}`; } get description(): string { return this.shortCommit; } get refName(): string | undefined { return this.ref.name; } diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 863bacdfb1b8a..fc6a1e1dd9e70 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -12,12 +12,13 @@ import { Git } from './git'; import * as path from 'path'; import * as fs from 'fs'; import { fromGitUri } from './uri'; -import { APIState as State, CredentialsProvider, PushErrorHandler, PublishEvent, RemoteSourcePublisher, PostCommitCommandsProvider } from './api/git'; +import { APIState as State, CredentialsProvider, PushErrorHandler, PublishEvent, RemoteSourcePublisher, PostCommitCommandsProvider, BranchProtectionProvider } from './api/git'; import { Askpass } from './askpass'; import { IPushErrorHandlerRegistry } from './pushError'; import { ApiRepository } from './api/api1'; import { IRemoteSourcePublisherRegistry } from './remotePublisher'; import { IPostCommitCommandsProviderRegistry } from './postCommitCommands'; +import { IBranchProtectionProviderRegistry } from './branchProtection'; class RepositoryPick implements QuickPickItem { @memoize get label(): string { @@ -91,7 +92,7 @@ interface OpenRepository extends Disposable { repository: Repository; } -export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommandsProviderRegistry, IPushErrorHandlerRegistry { +export class Model implements IBranchProtectionProviderRegistry, IRemoteSourcePublisherRegistry, IPostCommitCommandsProviderRegistry, IPushErrorHandlerRegistry { private _onDidOpenRepository = new EventEmitter(); readonly onDidOpenRepository: Event = this._onDidOpenRepository.event; @@ -151,6 +152,11 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand private _onDidChangePostCommitCommandsProviders = new EventEmitter(); readonly onDidChangePostCommitCommandsProviders = this._onDidChangePostCommitCommandsProviders.event; + private branchProtectionProviders = new Map>(); + + private _onDidChangeBranchProtectionProviders = new EventEmitter(); + readonly onDidChangeBranchProtectionProviders = this._onDidChangeBranchProtectionProviders.event; + private pushErrorHandlers = new Set(); private _unsafeRepositories = new UnsafeRepositoryMap(); @@ -476,7 +482,7 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand // Open repository const dotGit = await this.git.getRepositoryDotGit(repositoryRoot); - const repository = new Repository(this.git.open(repositoryRoot, dotGit, this.logger), this, this, this, this.globalState, this.logger, this.telemetryReporter); + const repository = new Repository(this.git.open(repositoryRoot, dotGit, this.logger), this, this, this, this, this.globalState, this.logger, this.telemetryReporter); this.open(repository); repository.status(); // do not await this, we want SCM to know about the repo asap @@ -760,6 +766,30 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand return [...this.remoteSourcePublishers.values()]; } + registerBranchProtectionProvider(root: Uri, provider: BranchProtectionProvider): Disposable { + const providerDisposables: Disposable[] = []; + + this.branchProtectionProviders.set(root, (this.branchProtectionProviders.get(root) ?? new Set()).add(provider)); + providerDisposables.push(provider.onDidChangeBranchProtection(uri => this._onDidChangeBranchProtectionProviders.fire(uri))); + + this._onDidChangeBranchProtectionProviders.fire(root); + + return toDisposable(() => { + const providers = this.branchProtectionProviders.get(root); + + if (providers && providers.has(provider)) { + providers.delete(provider); + this.branchProtectionProviders.set(root, providers); + } + + dispose(providerDisposables); + }); + } + + getBranchProtectionProviders(root: Uri): BranchProtectionProvider[] { + return [...(this.branchProtectionProviders.get(root) ?? new Set()).values()]; + } + registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable { this.postCommitCommandsProviders.add(provider); this._onDidChangePostCommitCommandsProviders.fire(); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index a783a0758b146..2c6d4b377557e 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -22,6 +22,7 @@ import { IRemoteSourcePublisherRegistry } from './remotePublisher'; import { ActionButtonCommand } from './actionButton'; import { IPostCommitCommandsProviderRegistry, CommitCommandsCenter } from './postCommitCommands'; import { Operation, OperationKind, OperationManager, OperationResult } from './operation'; +import { GitBranchProtectionProvider, IBranchProtectionProviderRegistry } from './branchProtection'; const timeout = (millis: number) => new Promise(c => setTimeout(c, millis)); @@ -624,6 +625,9 @@ export class Repository implements Disposable { private _onDidRunOperation = new EventEmitter(); readonly onDidRunOperation: Event = this._onDidRunOperation.event; + private _onDidChangeBranchProtection = new EventEmitter(); + readonly onDidChangeBranchProtection: Event = this._onDidChangeBranchProtection.event; + @memoize get onDidChangeOperations(): Event { return anyEvent(this.onRunOperation as Event, this.onDidRunOperation as Event); @@ -740,7 +744,7 @@ export class Repository implements Disposable { private isRepositoryHuge: false | { limit: number } = false; private didWarnAboutLimit = false; - private isBranchProtectedMatcher: picomatch.Matcher | undefined; + private branchProtection = new Map(); private commitCommandCenter: CommitCommandsCenter; private resourceCommandResolver = new ResourceCommandResolver(this); private updateModelStateCancellationTokenSource: CancellationTokenSource | undefined; @@ -751,6 +755,7 @@ export class Repository implements Disposable { private pushErrorHandlerRegistry: IPushErrorHandlerRegistry, remoteSourcePublisherRegistry: IRemoteSourcePublisherRegistry, postCommitCommandsProviderRegistry: IPostCommitCommandsProviderRegistry, + private readonly branchProtectionProviderRegistry: IBranchProtectionProviderRegistry, globalState: Memento, private readonly logger: LogOutputChannel, private telemetryReporter: TelemetryReporter @@ -816,8 +821,7 @@ export class Repository implements Disposable { }, undefined, this.disposables); filterEvent(workspace.onDidChangeConfiguration, e => - e.affectsConfiguration('git.branchProtection', root) - || e.affectsConfiguration('git.branchSortOrder', root) + e.affectsConfiguration('git.branchSortOrder', root) || e.affectsConfiguration('git.untrackedChanges', root) || e.affectsConfiguration('git.ignoreSubmodules', root) || e.affectsConfiguration('git.openDiffOnClick', root) @@ -862,9 +866,10 @@ export class Repository implements Disposable { } }, null, this.disposables); - const onDidChangeBranchProtection = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.branchProtection', root)); - onDidChangeBranchProtection(this.updateBranchProtectionMatcher, this, this.disposables); - this.updateBranchProtectionMatcher(); + // Default branch protection provider + const onBranchProtectionProviderChanged = filterEvent(this.branchProtectionProviderRegistry.onDidChangeBranchProtectionProviders, e => pathEquals(e.fsPath, root.fsPath)); + this.disposables.push(onBranchProtectionProviderChanged(root => this.updateBranchProtectionMatchers(root))); + this.disposables.push(this.branchProtectionProviderRegistry.registerBranchProtectionProvider(root, new GitBranchProtectionProvider(root))); const statusBar = new StatusBarCommands(this, remoteSourcePublisherRegistry); this.disposables.push(statusBar); @@ -2358,20 +2363,14 @@ export class Repository implements Disposable { } } - private updateBranchProtectionMatcher(): void { - const scopedConfig = workspace.getConfiguration('git', Uri.file(this.repository.root)); - const branchProtectionConfig = scopedConfig.get('branchProtection') ?? []; - const branchProtectionValues = Array.isArray(branchProtectionConfig) ? branchProtectionConfig : [branchProtectionConfig]; - - const branchProtectionGlobs = branchProtectionValues - .map(bp => typeof bp === 'string' ? bp.trim() : '') - .filter(bp => bp !== ''); - - if (branchProtectionGlobs.length === 0) { - this.isBranchProtectedMatcher = undefined; - } else { - this.isBranchProtectedMatcher = picomatch(branchProtectionGlobs); + private updateBranchProtectionMatchers(root: Uri): void { + for (const provider of this.branchProtectionProviderRegistry.getBranchProtectionProviders(root)) { + for (const [remote, branches] of provider.provideBranchProtection().entries()) { + this.branchProtection.set(remote, branches.length !== 0 ? picomatch(branches) : undefined); + } } + + this._onDidChangeBranchProtection.fire(); } private optimisticUpdateEnabled(): boolean { @@ -2411,8 +2410,22 @@ export class Repository implements Disposable { return true; } - public isBranchProtected(name = this.HEAD?.name ?? ''): boolean { - return this.isBranchProtectedMatcher ? this.isBranchProtectedMatcher(name) : false; + public isBranchProtected(branch = this.HEAD): boolean { + if (branch?.name) { + // Default branch protection (settings) + const defaultBranchProtectionMatcher = this.branchProtection.get(''); + if (defaultBranchProtectionMatcher && defaultBranchProtectionMatcher(branch.name)) { + return true; + } + + if (branch.upstream?.remote) { + // Branch protection (contributed) + const remoteBranchProtectionMatcher = this.branchProtection.get(branch.upstream.remote); + return remoteBranchProtectionMatcher ? remoteBranchProtectionMatcher(branch.name) : false; + } + } + + return false; } dispose(): void { diff --git a/extensions/git/src/statusbar.ts b/extensions/git/src/statusbar.ts index bb2abe2ee26be..e58096442f2f2 100644 --- a/extensions/git/src/statusbar.ts +++ b/extensions/git/src/statusbar.ts @@ -38,6 +38,7 @@ class CheckoutStatusBar { repository.onDidChangeOperations(this.onDidChangeOperations, this, this.disposables); repository.onDidRunGitStatus(this._onDidChange.fire, this._onDidChange, this.disposables); + repository.onDidChangeBranchProtection(this._onDidChange.fire, this._onDidChange, this.disposables); } get command(): Command | undefined { From 34003bce95fa19478db9a4198f1e945cc57d4f62 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 12 Apr 2023 14:35:15 +0200 Subject: [PATCH 09/59] activityBar.activeBorder at workbench.colorCustomizations has no effect. #179229 (#179767) --- .../browser/parts/activitybar/activitybarActions.ts | 11 ++++++++++- .../parts/activitybar/media/activityaction.css | 4 ---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index 3d63db14fbd5e..62bd9a354bf90 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -20,7 +20,7 @@ import { IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platf import { ActivityAction, ActivityActionViewItem, IActivityActionViewItemOptions, IActivityHoverOptions, ICompositeBar, ICompositeBarColors, ToggleCompositeBadgeAction, ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { IActivity } from 'vs/workbench/common/activity'; -import { ACTIVITY_BAR_ACTIVE_FOCUS_BORDER, ACTIVITY_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme'; +import { ACTIVITY_BAR_ACTIVE_FOCUS_BORDER, ACTIVITY_BAR_ACTIVE_BACKGROUND, ACTIVITY_BAR_ACTIVE_BORDER } from 'vs/workbench/common/theme'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; @@ -628,6 +628,15 @@ registerAction2( registerThemingParticipant((theme, collector) => { + const activityBarActiveBorderColor = theme.getColor(ACTIVITY_BAR_ACTIVE_BORDER); + if (activityBarActiveBorderColor) { + collector.addRule(` + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before { + border-left-color: ${activityBarActiveBorderColor}; + } + `); + } + const activityBarActiveFocusBorderColor = theme.getColor(ACTIVITY_BAR_ACTIVE_FOCUS_BORDER); if (activityBarActiveFocusBorderColor) { collector.addRule(` diff --git a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css index 89808c0be0e78..a63412b56cb95 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css @@ -96,10 +96,6 @@ background-color: var(--vscode-activityBar-foreground) !important; } -.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before { - border-left-color: var(--vscode-activityBar-activeBorder); -} - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before, .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus .active-item-indicator:before { content: ""; From d77e53a2b313af16d7c223a184e4e62f371dbdba Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 12 Apr 2023 14:39:51 +0200 Subject: [PATCH 10/59] Update shellscript grammar (#179758) --- extensions/shellscript/cgmanifest.json | 4 +- .../syntaxes/shell-unix-bash.tmLanguage.json | 312 +++--------------- .../test/colorize-results/test-173216_sh.json | 86 ++--- .../test/colorize-results/test-173336_sh.json | 116 +++---- .../test/colorize-results/test_sh.json | 166 +++++----- 5 files changed, 226 insertions(+), 458 deletions(-) diff --git a/extensions/shellscript/cgmanifest.json b/extensions/shellscript/cgmanifest.json index 1b54ec84ee5d5..87be4976392e7 100644 --- a/extensions/shellscript/cgmanifest.json +++ b/extensions/shellscript/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "jeff-hykin/better-shell-syntax", "repositoryUrl": "https://github.com/jeff-hykin/better-shell-syntax", - "commitHash": "f0becead09678ee025faef6bc062b33fab60274b" + "commitHash": "1bad17d8badf6283125aaa7c31be06ba64146a0f" } }, "license": "MIT", - "version": "1.5.0" + "version": "1.5.4" } ], "version": 1 diff --git a/extensions/shellscript/syntaxes/shell-unix-bash.tmLanguage.json b/extensions/shellscript/syntaxes/shell-unix-bash.tmLanguage.json index 463517c92ee44..e132b9e5699cb 100644 --- a/extensions/shellscript/syntaxes/shell-unix-bash.tmLanguage.json +++ b/extensions/shellscript/syntaxes/shell-unix-bash.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/jeff-hykin/better-shell-syntax/commit/f0becead09678ee025faef6bc062b33fab60274b", + "version": "https://github.com/jeff-hykin/better-shell-syntax/commit/1bad17d8badf6283125aaa7c31be06ba64146a0f", "name": "Shell Script", "scopeName": "source.shell", "patterns": [ @@ -67,7 +67,7 @@ ] }, "argument": { - "begin": "[ \\t]++(?!(?:%|&|\\||\\(|\\[|#|\\n|$|;))", + "begin": "[ \\t]++(?!(?:&|\\||\\(|\\[|#|\\n|$|;))", "end": "(?= |\\t|;|\\||&|$|\\n|\\)|\\`)", "beginCaptures": {}, "endCaptures": {}, @@ -221,13 +221,13 @@ ] }, "basic_command_name": { - "match": "(?!(?:!|%|&|\\||\\(|\\)|\\{|\\[|<|>|#|\\n|$|;|[ \\t]))(?!foreach\\b(?!\\/)|select\\b(?!\\/)|repeat\\b(?!\\/)|until\\b(?!\\/)|while\\b(?!\\/)|case\\b(?!\\/)|done\\b(?!\\/)|elif\\b(?!\\/)|else\\b(?!\\/)|esac\\b(?!\\/)|then\\b(?!\\/)|for\\b(?!\\/)|end\\b(?!\\/)|in\\b(?!\\/)|fi\\b(?!\\/)|do\\b(?!\\/)|if\\b(?!\\/))(?:((?<=^|;|&|[ \\t])(?:export|declare|typeset|local|readonly)(?=[ \\t]|;|&|$))|((?!\"|'|\\\\\\n?$)[^!'\" \\t\\n\\r]+?))(?:(?= |\\t)|(?=;|\\||&|\\n|\\)|\\`|\\{|\\}|[ \\t]*#|\\])(?|#|\\n|$|;|[ \\t]))(?!foreach\\b(?!\\/)|select\\b(?!\\/)|repeat\\b(?!\\/)|until\\b(?!\\/)|while\\b(?!\\/)|case\\b(?!\\/)|done\\b(?!\\/)|elif\\b(?!\\/)|else\\b(?!\\/)|esac\\b(?!\\/)|then\\b(?!\\/)|for\\b(?!\\/)|end\\b(?!\\/)|in\\b(?!\\/)|fi\\b(?!\\/)|do\\b(?!\\/)|if\\b(?!\\/))(?:((?<=^|;|&|[ \\t])(?:export|declare|typeset|local|readonly)(?=[ \\t]|;|&|$))|((?!\"|'|\\\\\\n?$)[^!'\" \\t\\n\\r]+?))(?:(?= |\\t)|(?=;|\\||&|\\n|\\)|\\`|\\{|\\}|[ \\t]*#|\\])(?|#|\\n|$|;|[ \\t]))(?!foreach\\b(?!\\/)|select\\b(?!\\/)|repeat\\b(?!\\/)|until\\b(?!\\/)|while\\b(?!\\/)|case\\b(?!\\/)|done\\b(?!\\/)|elif\\b(?!\\/)|else\\b(?!\\/)|esac\\b(?!\\/)|then\\b(?!\\/)|for\\b(?!\\/)|end\\b(?!\\/)|in\\b(?!\\/)|fi\\b(?!\\/)|do\\b(?!\\/)|if\\b(?!\\/))(?!\\\\\\n?$)", + "begin": "[ \\t]*+(?!(?:!|&|\\||\\(|\\)|\\{|\\[|<|>|#|\\n|$|;|[ \\t]))(?!foreach\\b(?!\\/)|select\\b(?!\\/)|repeat\\b(?!\\/)|until\\b(?!\\/)|while\\b(?!\\/)|case\\b(?!\\/)|done\\b(?!\\/)|elif\\b(?!\\/)|else\\b(?!\\/)|esac\\b(?!\\/)|then\\b(?!\\/)|for\\b(?!\\/)|end\\b(?!\\/)|in\\b(?!\\/)|fi\\b(?!\\/)|do\\b(?!\\/)|if\\b(?!\\/))(?!\\\\\\n?$)", "end": "(?=;|\\||&|\\n|\\)|\\`|\\{|\\}|[ \\t]*#|\\])(?)", "captures": { "1": { - "patterns": [ - { - "begin": "(?=.)", - "end": "$", - "beginCaptures": {}, - "endCaptures": {}, - "patterns": [ - { - "match": "(\\G0[xX])([0-9a-fA-F](?:[0-9a-fA-F]|((?<=[0-9a-fA-F])_(?=[0-9a-fA-F])))*)?((?:(?<=[0-9a-fA-F])\\.|\\.(?=[0-9a-fA-F])))([0-9a-fA-F](?:[0-9a-fA-F]|((?<=[0-9a-fA-F])_(?=[0-9a-fA-F])))*)?(?:(?|#|\\n|$|;|[ \\t])))", + "begin": "[ \\t]++(-)((?!(?:!|&|\\||\\(|\\)|\\{|\\[|<|>|#|\\n|$|;|[ \\t])))", "end": "(?:(?=[ \\t])|(?=;|\\||&|\\n|\\)|\\`|\\{|\\}|[ \\t]*#|\\])(?|#|\\n|$|;|[ \\t]))(?!foreach\\b(?!\\/)|select\\b(?!\\/)|repeat\\b(?!\\/)|until\\b(?!\\/)|while\\b(?!\\/)|case\\b(?!\\/)|done\\b(?!\\/)|elif\\b(?!\\/)|else\\b(?!\\/)|esac\\b(?!\\/)|then\\b(?!\\/)|for\\b(?!\\/)|end\\b(?!\\/)|in\\b(?!\\/)|fi\\b(?!\\/)|do\\b(?!\\/)|if\\b(?!\\/))(?!\\\\\\n?$)" + "match": "[ \\t]*+(?!(?:!|&|\\||\\(|\\)|\\{|\\[|<|>|#|\\n|$|;|[ \\t]))(?!foreach\\b(?!\\/)|select\\b(?!\\/)|repeat\\b(?!\\/)|until\\b(?!\\/)|while\\b(?!\\/)|case\\b(?!\\/)|done\\b(?!\\/)|elif\\b(?!\\/)|else\\b(?!\\/)|esac\\b(?!\\/)|then\\b(?!\\/)|for\\b(?!\\/)|end\\b(?!\\/)|in\\b(?!\\/)|fi\\b(?!\\/)|do\\b(?!\\/)|if\\b(?!\\/))(?!\\\\\\n?$)" }, "start_of_double_quoted_command_name": { - "match": "(?!(?:!|%|&|\\||\\(|\\)|\\{|\\[|<|>|#|\\n|$|;|[ \\t]))(?:[ \\t]*+([^ \t\n'&;<>\\(\\)\\$`\\\\\"\\|]+(?!>)))?(?:(?:\\$\")|\")", + "match": "(?!(?:!|&|\\||\\(|\\)|\\{|\\[|<|>|#|\\n|$|;|[ \\t]))(?:[ \\t]*+([^ \t\n'&;<>\\(\\)\\$`\\\\\"\\|]+(?!>)))?(?:(?:\\$\")|\")", "captures": { "1": { - "name": "entity.name.command.shell", + "name": "entity.name.function.call.shell entity.name.command.shell", "patterns": [ { "match": "\\*", @@ -1973,13 +1741,13 @@ ] } }, - "name": "meta.statement.command.name.quoted.shell string.quoted.double.shell punctuation.definition.string.begin.shell entity.name.command.shell" + "name": "meta.statement.command.name.quoted.shell string.quoted.double.shell punctuation.definition.string.begin.shell entity.name.function.call.shell entity.name.command.shell" }, "start_of_single_quoted_command_name": { - "match": "(?!(?:!|%|&|\\||\\(|\\)|\\{|\\[|<|>|#|\\n|$|;|[ \\t]))(?:[ \\t]*+([^ \t\n'&;<>\\(\\)\\$`\\\\\"\\|]+(?!>)))?(?:(?:\\$')|')", + "match": "(?!(?:!|&|\\||\\(|\\)|\\{|\\[|<|>|#|\\n|$|;|[ \\t]))(?:[ \\t]*+([^ \t\n'&;<>\\(\\)\\$`\\\\\"\\|]+(?!>)))?(?:(?:\\$')|')", "captures": { "1": { - "name": "entity.name.command.shell", + "name": "entity.name.function.call.shell entity.name.command.shell", "patterns": [ { "match": "\\*", @@ -2002,7 +1770,7 @@ ] } }, - "name": "meta.statement.command.name.quoted.shell string.quoted.single.shell punctuation.definition.string.begin.shell entity.name.command.shell" + "name": "meta.statement.command.name.quoted.shell string.quoted.single.shell punctuation.definition.string.begin.shell entity.name.function.call.shell entity.name.command.shell" }, "string": { "patterns": [ diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test-173216_sh.json b/extensions/vscode-colorize-tests/test/colorize-results/test-173216_sh.json index 08df18f6b4a1e..a19adbcf18afa 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test-173216_sh.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test-173216_sh.json @@ -225,44 +225,44 @@ }, { "c": "'", - "t": "source.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell meta.statement.command.name.quoted.shell string.quoted.single.shell punctuation.definition.string.begin.shell entity.name.command.shell", + "t": "source.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell meta.statement.command.name.quoted.shell string.quoted.single.shell punctuation.definition.string.begin.shell entity.name.function.call.shell entity.name.command.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", - "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", - "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { "c": "Apple Juice", - "t": "source.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell meta.statement.command.name.continuation string.quoted.single entity.name.command", + "t": "source.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell meta.statement.command.name.continuation string.quoted.single entity.name.function.call entity.name.command", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", - "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", - "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { "c": "'", - "t": "source.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell string.quoted.single.shell punctuation.definition.string.end.shell entity.name.command.shell", + "t": "source.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell string.quoted.single.shell punctuation.definition.string.end.shell entity.name.function.call.shell entity.name.command.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", - "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", - "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { @@ -365,44 +365,44 @@ }, { "c": "'", - "t": "source.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell meta.statement.command.name.quoted.shell string.quoted.single.shell punctuation.definition.string.begin.shell entity.name.command.shell", + "t": "source.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell meta.statement.command.name.quoted.shell string.quoted.single.shell punctuation.definition.string.begin.shell entity.name.function.call.shell entity.name.command.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", - "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", - "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { "c": "Orange Juice", - "t": "source.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell meta.statement.command.name.continuation string.quoted.single entity.name.command", + "t": "source.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell meta.statement.command.name.continuation string.quoted.single entity.name.function.call entity.name.command", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", - "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", - "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { "c": "'", - "t": "source.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell string.quoted.single.shell punctuation.definition.string.end.shell entity.name.command.shell", + "t": "source.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell string.quoted.single.shell punctuation.definition.string.end.shell entity.name.function.call.shell entity.name.command.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", - "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", - "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { @@ -449,7 +449,7 @@ }, { "c": "echo", - "t": "source.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.command.shell support.function.builtin.shell", + "t": "source.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.function.call.shell entity.name.command.shell support.function.builtin.shell", "r": { "dark_plus": "support.function: #DCDCAA", "light_plus": "support.function: #795E26", diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test-173336_sh.json b/extensions/vscode-colorize-tests/test/colorize-results/test-173336_sh.json index 65fefcd51f552..0a1300285e863 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test-173336_sh.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test-173336_sh.json @@ -463,49 +463,49 @@ }, { "c": "\"", - "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell meta.statement.command.name.quoted.shell string.quoted.double.shell punctuation.definition.string.begin.shell entity.name.command.shell", + "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell meta.statement.command.name.quoted.shell string.quoted.double.shell punctuation.definition.string.begin.shell entity.name.function.call.shell entity.name.command.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", - "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", - "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { "c": "$", - "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell meta.statement.command.name.continuation string.quoted.double entity.name.command punctuation.definition.variable.shell", + "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell meta.statement.command.name.continuation string.quoted.double entity.name.function.call entity.name.command punctuation.definition.variable.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", - "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", - "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { "c": "{", - "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell meta.statement.command.name.continuation string.quoted.double entity.name.command punctuation.section.bracket.curly.variable.begin.shell punctuation.definition.variable.shell", + "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell meta.statement.command.name.continuation string.quoted.double entity.name.function.call entity.name.command punctuation.section.bracket.curly.variable.begin.shell punctuation.definition.variable.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", - "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", - "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { "c": "cmd", - "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell meta.statement.command.name.continuation string.quoted.double entity.name.command meta.parameter-expansion variable.other.normal.shell", + "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell meta.statement.command.name.continuation string.quoted.double entity.name.function.call entity.name.command meta.parameter-expansion variable.other.normal.shell", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -519,72 +519,72 @@ }, { "c": "[", - "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell meta.statement.command.name.continuation string.quoted.double entity.name.command meta.parameter-expansion punctuation.section.array.shell", + "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell meta.statement.command.name.continuation string.quoted.double entity.name.function.call entity.name.command meta.parameter-expansion punctuation.section.array.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", - "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", - "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { "c": "@", - "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell meta.statement.command.name.continuation string.quoted.double entity.name.command meta.parameter-expansion", + "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell meta.statement.command.name.continuation string.quoted.double entity.name.function.call entity.name.command meta.parameter-expansion", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", - "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", - "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { "c": "]", - "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell meta.statement.command.name.continuation string.quoted.double entity.name.command meta.parameter-expansion punctuation.section.array.shell", + "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell meta.statement.command.name.continuation string.quoted.double entity.name.function.call entity.name.command meta.parameter-expansion punctuation.section.array.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", - "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", - "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { "c": "}", - "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell meta.statement.command.name.continuation string.quoted.double entity.name.command punctuation.section.bracket.curly.variable.end.shell punctuation.definition.variable.shell", + "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell meta.statement.command.name.continuation string.quoted.double entity.name.function.call entity.name.command punctuation.section.bracket.curly.variable.end.shell punctuation.definition.variable.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", - "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", - "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { "c": "\"", - "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell string.quoted.double.shell punctuation.definition.string.end.shell entity.name.command.shell", + "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell string.quoted.double.shell punctuation.definition.string.end.shell entity.name.function.call.shell entity.name.command.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", - "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", - "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { @@ -603,7 +603,7 @@ }, { "c": "printf", - "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.command.shell support.function.builtin.shell", + "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.function.call.shell entity.name.command.shell support.function.builtin.shell", "r": { "dark_plus": "support.function: #DCDCAA", "light_plus": "support.function: #795E26", diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_sh.json b/extensions/vscode-colorize-tests/test/colorize-results/test_sh.json index c8b7d26b661e9..ff14f5bf99b5c 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_sh.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_sh.json @@ -603,7 +603,7 @@ }, { "c": "echo", - "t": "source.shell meta.scope.if-block.shell meta.function.shell meta.function.body.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.command.shell support.function.builtin.shell", + "t": "source.shell meta.scope.if-block.shell meta.function.shell meta.function.body.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.function.call.shell entity.name.command.shell support.function.builtin.shell", "r": { "dark_plus": "support.function: #DCDCAA", "light_plus": "support.function: #795E26", @@ -729,7 +729,7 @@ }, { "c": "echo", - "t": "source.shell meta.scope.if-block.shell meta.function.shell meta.function.body.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.command.shell support.function.builtin.shell", + "t": "source.shell meta.scope.if-block.shell meta.function.shell meta.function.body.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.function.call.shell entity.name.command.shell support.function.builtin.shell", "r": { "dark_plus": "support.function: #DCDCAA", "light_plus": "support.function: #795E26", @@ -1023,16 +1023,16 @@ }, { "c": "dirname", - "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.expression.assignment.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.command.shell", + "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.expression.assignment.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.function.call.shell entity.name.command.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", - "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", - "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { @@ -1065,16 +1065,16 @@ }, { "c": "dirname", - "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.expression.assignment.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.argument.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.command.shell", + "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.expression.assignment.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.argument.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.function.call.shell entity.name.command.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", - "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", - "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { @@ -1107,16 +1107,16 @@ }, { "c": "realpath", - "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.expression.assignment.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.argument.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.argument.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.command.shell", + "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.expression.assignment.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.argument.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.argument.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.function.call.shell entity.name.command.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", - "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", - "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { @@ -1303,16 +1303,16 @@ }, { "c": "dirname", - "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.expression.assignment.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.command.shell", + "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.expression.assignment.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.function.call.shell entity.name.command.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", - "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", - "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { @@ -1345,16 +1345,16 @@ }, { "c": "dirname", - "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.expression.assignment.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.argument.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.command.shell", + "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.expression.assignment.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.argument.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.function.call.shell entity.name.command.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", - "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", - "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { @@ -1387,16 +1387,16 @@ }, { "c": "readlink", - "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.expression.assignment.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.argument.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.argument.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.command.shell", + "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.expression.assignment.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.argument.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.argument.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.function.call.shell entity.name.command.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", - "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", - "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { @@ -1583,16 +1583,16 @@ }, { "c": "xcode-select", - "t": "source.shell meta.statement.shell meta.expression.assignment.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.command.shell", + "t": "source.shell meta.statement.shell meta.expression.assignment.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.function.call.shell entity.name.command.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", - "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", - "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { @@ -1695,16 +1695,16 @@ }, { "c": "xcrun", - "t": "source.shell meta.statement.shell meta.expression.assignment.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.command.shell", + "t": "source.shell meta.statement.shell meta.expression.assignment.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.function.call.shell entity.name.command.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", - "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", - "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { @@ -1863,16 +1863,16 @@ }, { "c": "cat", - "t": "source.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.command.shell", + "t": "source.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.function.call.shell entity.name.command.shell", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_plus_experimental": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_plus_experimental": "default: #3B3B3B" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { @@ -2129,7 +2129,7 @@ }, { "c": "cd", - "t": "source.shell meta.function.shell meta.function.body.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.command.shell support.function.builtin.shell", + "t": "source.shell meta.function.shell meta.function.body.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.function.call.shell entity.name.command.shell support.function.builtin.shell", "r": { "dark_plus": "support.function: #DCDCAA", "light_plus": "support.function: #795E26", @@ -2241,7 +2241,7 @@ }, { "c": "test", - "t": "source.shell meta.function.shell meta.function.body.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.command.shell support.function.builtin.shell", + "t": "source.shell meta.function.shell meta.function.body.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.function.call.shell entity.name.command.shell support.function.builtin.shell", "r": { "dark_plus": "support.function: #DCDCAA", "light_plus": "support.function: #795E26", @@ -2367,16 +2367,16 @@ }, { "c": "./scripts/npm.sh", - "t": "source.shell meta.function.shell meta.function.body.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.command.shell", + "t": "source.shell meta.function.shell meta.function.body.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.function.call.shell entity.name.command.shell", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_plus_experimental": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_plus_experimental": "default: #3B3B3B" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { @@ -2885,7 +2885,7 @@ }, { "c": "exec", - "t": "source.shell meta.function.shell meta.function.body.shell meta.scope.if-block.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.command.shell support.function.builtin.shell", + "t": "source.shell meta.function.shell meta.function.body.shell meta.scope.if-block.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.function.call.shell entity.name.command.shell support.function.builtin.shell", "r": { "dark_plus": "support.function: #DCDCAA", "light_plus": "support.function: #795E26", @@ -3067,7 +3067,7 @@ }, { "c": "exec", - "t": "source.shell meta.function.shell meta.function.body.shell meta.scope.if-block.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.command.shell support.function.builtin.shell", + "t": "source.shell meta.function.shell meta.function.body.shell meta.scope.if-block.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.function.call.shell entity.name.command.shell support.function.builtin.shell", "r": { "dark_plus": "support.function: #DCDCAA", "light_plus": "support.function: #795E26", @@ -3249,16 +3249,16 @@ }, { "c": "code", - "t": "source.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.command.shell", + "t": "source.shell meta.statement.shell meta.statement.command.shell meta.statement.command.name.shell entity.name.function.call.shell entity.name.command.shell", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_plus_experimental": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_plus_experimental": "default: #3B3B3B" + "hc_black": "entity.name.function: #DCDCAA", + "dark_plus_experimental": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_plus_experimental": "entity.name.function: #795E26" } }, { From 0e8b1c8e09e2e0b5787a6b60797e3bacb446822f Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 12 Apr 2023 15:33:55 +0200 Subject: [PATCH 11/59] Confusing Bash/Shell syntax highlighting (#179775) Fixes #179749 --- .../theme-defaults/themes/dark_plus.json | 5 +- .../theme-defaults/themes/light_plus.json | 5 +- .../test/colorize-results/test-173216_sh.json | 16 ++-- .../test/colorize-results/test_sh.json | 80 +++++++++---------- 4 files changed, 56 insertions(+), 50 deletions(-) diff --git a/extensions/theme-defaults/themes/dark_plus.json b/extensions/theme-defaults/themes/dark_plus.json index 318309948127c..fc2fd7d7f32e4 100644 --- a/extensions/theme-defaults/themes/dark_plus.json +++ b/extensions/theme-defaults/themes/dark_plus.json @@ -173,7 +173,10 @@ } }, { - "scope": "constant.character", + "scope": [ + "constant.character", + "constant.other.option" + ], "settings": { "foreground": "#569cd6" } diff --git a/extensions/theme-defaults/themes/light_plus.json b/extensions/theme-defaults/themes/light_plus.json index 9a0d0d6dea48f..d24599e6feb14 100644 --- a/extensions/theme-defaults/themes/light_plus.json +++ b/extensions/theme-defaults/themes/light_plus.json @@ -174,7 +174,10 @@ } }, { - "scope": "constant.character", + "scope": [ + "constant.character", + "constant.other.option" + ], "settings": { "foreground": "#0000ff" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test-173216_sh.json b/extensions/vscode-colorize-tests/test/colorize-results/test-173216_sh.json index a19adbcf18afa..a166e7d96f42b 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test-173216_sh.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test-173216_sh.json @@ -59,28 +59,28 @@ "c": "-", "t": "source.shell meta.statement.shell meta.statement.command.shell string.unquoted.argument.shell constant.other.option.dash.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.option: #569CD6", + "light_plus": "constant.other.option: #0000FF", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", + "dark_plus_experimental": "constant.other.option: #569CD6", "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "light_plus_experimental": "constant.other.option: #0000FF" } }, { "c": "A", "t": "source.shell meta.statement.shell meta.statement.command.shell string.unquoted.argument constant.other.option", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.option: #569CD6", + "light_plus": "constant.other.option: #0000FF", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", + "dark_plus_experimental": "constant.other.option: #569CD6", "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "light_plus_experimental": "constant.other.option: #0000FF" } }, { diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_sh.json b/extensions/vscode-colorize-tests/test/colorize-results/test_sh.json index ff14f5bf99b5c..0a0f0b4d05071 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_sh.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_sh.json @@ -1417,28 +1417,28 @@ "c": "-", "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.expression.assignment.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.argument.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.argument.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell string.unquoted.argument.shell constant.other.option.dash.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.option: #569CD6", + "light_plus": "constant.other.option: #0000FF", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", + "dark_plus_experimental": "constant.other.option: #569CD6", "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "light_plus_experimental": "constant.other.option: #0000FF" } }, { "c": "f", "t": "source.shell meta.scope.if-block.shell meta.statement.shell meta.expression.assignment.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.argument.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell meta.argument.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell string.unquoted.argument constant.other.option", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.option: #569CD6", + "light_plus": "constant.other.option: #0000FF", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", + "dark_plus_experimental": "constant.other.option: #569CD6", "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "light_plus_experimental": "constant.other.option: #0000FF" } }, { @@ -1613,28 +1613,28 @@ "c": "-", "t": "source.shell meta.statement.shell meta.expression.assignment.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell string.unquoted.argument.shell constant.other.option.dash.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.option: #569CD6", + "light_plus": "constant.other.option: #0000FF", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", + "dark_plus_experimental": "constant.other.option: #569CD6", "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "light_plus_experimental": "constant.other.option: #0000FF" } }, { "c": "print-path", "t": "source.shell meta.statement.shell meta.expression.assignment.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell string.unquoted.argument constant.other.option", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.option: #569CD6", + "light_plus": "constant.other.option: #0000FF", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", + "dark_plus_experimental": "constant.other.option: #569CD6", "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "light_plus_experimental": "constant.other.option: #0000FF" } }, { @@ -1725,28 +1725,28 @@ "c": "-", "t": "source.shell meta.statement.shell meta.expression.assignment.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell string.unquoted.argument.shell constant.other.option.dash.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.option: #569CD6", + "light_plus": "constant.other.option: #0000FF", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", + "dark_plus_experimental": "constant.other.option: #569CD6", "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "light_plus_experimental": "constant.other.option: #0000FF" } }, { "c": "sdk", "t": "source.shell meta.statement.shell meta.expression.assignment.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell string.unquoted.argument constant.other.option", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.option: #569CD6", + "light_plus": "constant.other.option: #0000FF", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", + "dark_plus_experimental": "constant.other.option: #569CD6", "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "light_plus_experimental": "constant.other.option: #0000FF" } }, { @@ -1795,28 +1795,28 @@ "c": "-", "t": "source.shell meta.statement.shell meta.expression.assignment.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell string.unquoted.argument.shell constant.other.option.dash.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.option: #569CD6", + "light_plus": "constant.other.option: #0000FF", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", + "dark_plus_experimental": "constant.other.option: #569CD6", "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "light_plus_experimental": "constant.other.option: #0000FF" } }, { "c": "find", "t": "source.shell meta.statement.shell meta.expression.assignment.shell string.interpolated.dollar.shell meta.statement.shell meta.statement.command.shell string.unquoted.argument constant.other.option", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.option: #569CD6", + "light_plus": "constant.other.option: #0000FF", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", + "dark_plus_experimental": "constant.other.option: #569CD6", "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "light_plus_experimental": "constant.other.option: #0000FF" } }, { @@ -2271,28 +2271,28 @@ "c": "-", "t": "source.shell meta.function.shell meta.function.body.shell meta.statement.shell meta.statement.command.shell string.unquoted.argument.shell constant.other.option.dash.shell", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.option: #569CD6", + "light_plus": "constant.other.option: #0000FF", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", + "dark_plus_experimental": "constant.other.option: #569CD6", "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "light_plus_experimental": "constant.other.option: #0000FF" } }, { "c": "d", "t": "source.shell meta.function.shell meta.function.body.shell meta.statement.shell meta.statement.command.shell string.unquoted.argument constant.other.option", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.option: #569CD6", + "light_plus": "constant.other.option: #0000FF", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178", - "dark_plus_experimental": "string: #CE9178", + "dark_plus_experimental": "constant.other.option: #569CD6", "hc_light": "string: #0F4A85", - "light_plus_experimental": "string: #A31515" + "light_plus_experimental": "constant.other.option: #0000FF" } }, { From 381c3ab0d8872f0e3c12b02bf0011b91d1dc6af7 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 12 Apr 2023 15:49:20 +0200 Subject: [PATCH 12/59] support to preview simple file creation (#179771) * support to preview simple file creation * fix height --- .../browser/interactiveEditor.css | 20 ++- .../browser/interactiveEditorController.ts | 122 ++++++++++++++---- .../browser/interactiveEditorWidget.ts | 85 +++++++++--- 3 files changed, 180 insertions(+), 47 deletions(-) diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditor.css b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditor.css index fc50fcd5b7057..7c337cec8bb2a 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditor.css +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditor.css @@ -137,7 +137,7 @@ display: none; } -.monaco-editor .interactive-editor.preview .preview { +.monaco-editor .interactive-editor.preview .previewDiff { display: inherit; padding: 6px; border: 1px solid var(--vscode-interactiveEditor-border); @@ -147,6 +147,24 @@ margin: 0 2px 6px 2px; } +.monaco-editor .interactive-editor.preview .previewCreateTitle { + padding-top: 6px; +} + +.monaco-editor .interactive-editor.preview .previewCreate { + display: inherit; + padding: 6px; + border: 1px solid var(--vscode-interactiveEditor-border); + border-radius: 2px; + margin: 0 2px 6px 2px; +} + +.monaco-editor .interactive-editor .previewDiff.hidden, +.monaco-editor .interactive-editor .previewCreate.hidden, +.monaco-editor .interactive-editor .previewCreateTitle.hidden { + display: none; +} + /* decoration styles */ .monaco-editor .interactive-editor-lines-deleted-range-inline { diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts index 7a53ece2f6912..cdf5e2f91fa50 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts @@ -12,7 +12,7 @@ import { IEditorContribution, IEditorDecorationsCollection, ScrollType } from 'v import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IInteractiveEditorResponse, IInteractiveEditorService, CTX_INTERACTIVE_EDITOR_HAS_ACTIVE_REQUEST, IInteractiveEditorRequest, IInteractiveEditorSession, IInteractiveEditorSlashCommand, IInteractiveEditorSessionProvider, InteractiveEditorResponseFeedbackKind, IInteractiveEditorEditResponse, CTX_INTERACTIVE_EDITOR_LAST_EDIT_TYPE as CTX_INTERACTIVE_EDITOR_LAST_EDIT_KIND, CTX_INTERACTIVE_EDITOR_LAST_FEEDBACK as CTX_INTERACTIVE_EDITOR_LAST_FEEDBACK_KIND, CTX_INTERACTIVE_EDITOR_INLNE_DIFF, CTX_INTERACTIVE_EDITOR_HAS_RESPONSE } from 'vs/workbench/contrib/interactiveEditor/common/interactiveEditor'; +import { IInteractiveEditorResponse, IInteractiveEditorService, CTX_INTERACTIVE_EDITOR_HAS_ACTIVE_REQUEST, IInteractiveEditorRequest, IInteractiveEditorSession, IInteractiveEditorSlashCommand, IInteractiveEditorSessionProvider, InteractiveEditorResponseFeedbackKind, IInteractiveEditorEditResponse, CTX_INTERACTIVE_EDITOR_LAST_EDIT_TYPE as CTX_INTERACTIVE_EDITOR_LAST_EDIT_KIND, CTX_INTERACTIVE_EDITOR_LAST_FEEDBACK as CTX_INTERACTIVE_EDITOR_LAST_FEEDBACK_KIND, CTX_INTERACTIVE_EDITOR_INLNE_DIFF, CTX_INTERACTIVE_EDITOR_HAS_RESPONSE, IInteractiveEditorBulkEditResponse } from 'vs/workbench/contrib/interactiveEditor/common/interactiveEditor'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Iterable } from 'vs/base/common/iterator'; import { ICursorStateComputer, IModelDecorationOptions, IModelDeltaDecoration, ITextModel, IValidEditOperation } from 'vs/editor/common/model'; @@ -26,21 +26,23 @@ import { ILogService } from 'vs/platform/log/common/log'; import { StopWatch } from 'vs/base/common/stopwatch'; import { LRUCache } from 'vs/base/common/map'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; +import { IBulkEditService, ResourceEdit, ResourceFileEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { IInteractiveSessionWidgetService } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionWidget'; import { IViewsService } from 'vs/workbench/common/views'; import { IInteractiveSessionContributionService } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionContributionService'; import { InteractiveSessionViewPane } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionSidebar'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { CompletionContext, CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemProvider, CompletionList, ProviderResult } from 'vs/editor/common/languages'; +import { CompletionContext, CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemProvider, CompletionList, ProviderResult, TextEdit } from 'vs/editor/common/languages'; import { LanguageSelector } from 'vs/editor/common/languageSelector'; import { IInteractiveSessionService } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { splitLines } from 'vs/base/common/strings'; import { InteractiveEditorZoneWidget } from 'vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; - +import { URI } from 'vs/base/common/uri'; +import { isEqual } from 'vs/base/common/resources'; +import { decodeBase64 } from 'vs/base/common/buffer'; type Exchange = { req: IInteractiveEditorRequest; res: IInteractiveEditorResponse }; @@ -152,6 +154,60 @@ class InlineDiffDecorations { } } +class UIEditResponse { + + readonly localEdits: TextEdit[] = []; + readonly singleCreateFileEdit: { uri: URI; edits: TextEdit[] } | undefined; + readonly workspaceEdits: ResourceEdit[] | undefined; + + constructor(uri: URI, readonly raw: IInteractiveEditorBulkEditResponse | IInteractiveEditorEditResponse) { + if (raw.type === 'editorEdit') { + // + this.localEdits = raw.edits; + this.singleCreateFileEdit = undefined; + this.workspaceEdits = undefined; + + } else { + // + const edits = ResourceEdit.convert(raw.edits); + + let isComplexEdit = false; + + for (const edit of edits) { + if (edit instanceof ResourceFileEdit) { + if (!isComplexEdit && edit.newResource && !edit.oldResource) { + // file create + if (this.singleCreateFileEdit) { + isComplexEdit = true; + this.singleCreateFileEdit = undefined; + } else { + this.singleCreateFileEdit = { uri: edit.newResource, edits: [] }; + if (edit.options.contentsBase64) { + const newText = decodeBase64(edit.options.contentsBase64).toString(); + this.singleCreateFileEdit.edits.push({ range: new Range(1, 1, 1, 1), text: newText }); + } + } + } + } else if (edit instanceof ResourceTextEdit) { + // + if (isEqual(edit.resource, uri)) { + this.localEdits.push(edit.textEdit); + } else if (isEqual(this.singleCreateFileEdit?.uri, edit.resource)) { + this.singleCreateFileEdit!.edits.push(edit.textEdit); + } else { + isComplexEdit = true; + } + } + } + + if (isComplexEdit) { + this.workspaceEdits = edits; + } + + } + } +} + class LastEditorState { constructor( @@ -159,7 +215,7 @@ class LastEditorState { readonly modelVersionId: number, readonly provider: IInteractiveEditorSessionProvider, readonly session: IInteractiveEditorSession, - readonly response: IInteractiveEditorEditResponse, + readonly response: UIEditResponse, ) { } } @@ -443,28 +499,29 @@ export class InteractiveEditorController implements IEditorContribution { continue; } - if (reply.type === 'bulkEdit') { - this._logService.info('[IE] performaing a BULK EDIT, exiting interactive editor', provider.debugName); - this._bulkEditService.apply(reply.edits, { editor: this._editor, label: localize('ie', "{0}", input), showPreview: true }); - // todo@jrieken preview bulk edit? - // todo@jrieken keep interactive editor? - break; - } + + this._recorder.addExchange(session, request, reply); if (reply.type === 'message') { this._logService.info('[IE] received a MESSAGE, continuing outside editor', provider.debugName); this._instaService.invokeFunction(showMessageResponse, request.prompt, reply.message.value); - continue; } - this._ctxLastEditKind.set(reply.edits.length === 1 ? 'simple' : ''); - this._recorder.addExchange(session, request, reply); + const editResponse = new UIEditResponse(textModel.uri, reply); + + if (editResponse.workspaceEdits) { + this._bulkEditService.apply(editResponse.workspaceEdits, { editor: this._editor, label: localize('ie', "{0}", input), showPreview: true }); + // todo@jrieken keep interactive editor? + break; + } + + this._ctxLastEditKind.set(editResponse.localEdits.length === 1 ? 'simple' : ''); // inline diff inlineDiffDecorations.clear(); - this._lastEditState = new LastEditorState(textModel, textModel.getAlternativeVersionId(), provider, session, reply); + this._lastEditState = new LastEditorState(textModel, textModel.getAlternativeVersionId(), provider, session, editResponse); // use whole range from reply if (reply.wholeRange) { @@ -476,13 +533,16 @@ export class InteractiveEditorController implements IEditorContribution { if (editMode === 'preview') { // only preview changes - this._zone.widget.updateToolbar(true); - this._zone.widget.preview(textModel, reply.edits); + if (editResponse.localEdits.length > 0) { + this._zone.widget.showEditsPreview(textModel, editResponse.localEdits); + } else { + this._zone.widget.hideEditsPreview(); + } } else { // make edits more minimal - const moreMinimalEdits = (await this._editorWorkerService.computeHumanReadableDiff(textModel.uri, reply.edits)); - this._logService.trace('[IE] edits from PROVIDER and after making them MORE MINIMAL', provider.debugName, reply.edits, moreMinimalEdits); + const moreMinimalEdits = (await this._editorWorkerService.computeHumanReadableDiff(textModel.uri, editResponse.localEdits)); + this._logService.trace('[IE] edits from PROVIDER and after making them MORE MINIMAL', provider.debugName, editResponse.localEdits, moreMinimalEdits); try { ignoreModelChanges = true; @@ -499,7 +559,7 @@ export class InteractiveEditorController implements IEditorContribution { this._editor.pushUndoStop(); this._editor.executeEdits( 'interactive-editor', - (moreMinimalEdits ?? reply.edits).map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)), + (moreMinimalEdits ?? editResponse.localEdits).map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)), cursorStateComputerAndInlineDiffCollection ); this._editor.pushUndoStop(); @@ -513,7 +573,7 @@ export class InteractiveEditorController implements IEditorContribution { // line count const lineSet = new Set(); let addRemoveCount = 0; - for (const edit of moreMinimalEdits ?? reply.edits) { + for (const edit of moreMinimalEdits ?? editResponse.localEdits) { const len2 = splitLines(edit.text).length - 1; @@ -532,12 +592,20 @@ export class InteractiveEditorController implements IEditorContribution { } const linesChanged = addRemoveCount + lineSet.size; - this._zone.widget.updateToolbar(true); this._zone.widget.updateMessage(linesChanged === 1 ? localize('lines.1', "Generated reply and changed 1 line.") : localize('lines.N', "Generated reply and changed {0} lines.", linesChanged) ); } + + this._zone.widget.updateToolbar(true); + + if (editResponse.singleCreateFileEdit) { + this._zone.widget.showCreatePreview(editResponse.singleCreateFileEdit.uri, editResponse.singleCreateFileEdit.edits); + } else { + this._zone.widget.hideCreatePreview(); + } + placeholder = reply.placeholder ?? session.placeholder ?? ''; data.rounds += round + '|'; @@ -626,15 +694,15 @@ export class InteractiveEditorController implements IEditorContribution { while (model.getAlternativeVersionId() !== modelVersionId) { model.undo(); } - this._lastEditState.provider.handleInteractiveEditorResponseFeedback?.(this._lastEditState.session, this._lastEditState.response, InteractiveEditorResponseFeedbackKind.Undone); - return this._lastEditState.response.edits[0].text; + this._lastEditState.provider.handleInteractiveEditorResponseFeedback?.(this._lastEditState.session, this._lastEditState.response.raw, InteractiveEditorResponseFeedbackKind.Undone); + return this._lastEditState.response.localEdits[0].text; } } feedbackLast(helpful: boolean) { if (this._lastEditState) { const kind = helpful ? InteractiveEditorResponseFeedbackKind.Helpful : InteractiveEditorResponseFeedbackKind.Unhelpful; - this._lastEditState.provider.handleInteractiveEditorResponseFeedback?.(this._lastEditState.session, this._lastEditState.response, kind); + this._lastEditState.provider.handleInteractiveEditorResponseFeedback?.(this._lastEditState.session, this._lastEditState.response.raw, kind); this._ctxLastFeedbackKind.set(helpful ? 'helpful' : 'unhelpful'); this._zone.widget.updateMessage('Thank you for your feedback!', undefined, 1250); } @@ -645,7 +713,7 @@ export class InteractiveEditorController implements IEditorContribution { const { model, modelVersionId, response } = this._lastEditState; if (model.getAlternativeVersionId() === modelVersionId) { model.pushStackElement(); - const edits = response.edits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)); + const edits = response.localEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)); model.pushEditOperations(null, edits, () => null); model.pushStackElement(); return true; diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts index 20ba724546676..70ddda4595772 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts @@ -17,7 +17,7 @@ import { assertType } from 'vs/base/common/types'; import { CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_FIRST, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_LAST, CTX_INTERACTIVE_EDITOR_EMPTY, CTX_INTERACTIVE_EDITOR_OUTER_CURSOR_POSITION, CTX_INTERACTIVE_EDITOR_VISIBLE, MENU_INTERACTIVE_EDITOR_WIDGET, MENU_INTERACTIVE_EDITOR_WIDGET_STATUS } from 'vs/workbench/contrib/interactiveEditor/common/interactiveEditor'; import { ITextModel } from 'vs/editor/common/model'; import { Dimension, addDisposableListener, getTotalHeight, getTotalWidth, h, reset } from 'vs/base/browser/dom'; -import { Emitter, Event } from 'vs/base/common/event'; +import { Event, MicrotaskEmitter } from 'vs/base/common/event'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; @@ -35,6 +35,8 @@ import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActio import { TextEdit } from 'vs/editor/common/languages'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { ILanguageSelection } from 'vs/editor/common/languages/language'; +import { ResourceLabel } from 'vs/workbench/browser/labels'; +import { FileKind } from 'vs/platform/files/common/files'; const _commonEditorOptions: IEditorConstructionOptions = { padding: { top: 3, bottom: 2 }, @@ -86,6 +88,7 @@ const _inputEditorOptions: IEditorConstructionOptions = { const _previewEditorEditorOptions: IDiffEditorConstructionOptions = { ..._commonEditorOptions, + readOnly: true, wordWrap: 'off', enableSplitViewResizing: true, isInEmbeddedEditor: true, @@ -116,7 +119,9 @@ class InteractiveEditorWidget { ]), ]), h('div.progress@progress'), - h('div.preview@preview'), + h('div.previewDiff@previewDiff'), + h('div.previewCreateTitle.show-file-icons@previewCreateTitle'), + h('div.previewCreate@previewCreate'), h('div.status@status', [ h('div.actions.hidden@statusToolbar'), h('div.label@statusLabel'), @@ -126,7 +131,6 @@ class InteractiveEditorWidget { private readonly _store = new DisposableStore(); private readonly _historyStore = new DisposableStore(); - private readonly _previewModel = this._store.add(new MutableDisposable()); readonly inputEditor: ICodeEditor; private readonly _inputModel: ITextModel; @@ -134,9 +138,14 @@ class InteractiveEditorWidget { private readonly _progressBar: ProgressBar; - private readonly _previewEditor: EmbeddedDiffEditorWidget; + private readonly _previewDiffEditor: EmbeddedDiffEditorWidget; + private readonly _previewDiffModel = this._store.add(new MutableDisposable()); - private readonly _onDidChangeHeight = new Emitter(); + private readonly _previewCreateTitle: ResourceLabel; + private readonly _previewCreateEditor: ICodeEditor; + private readonly _previewCreateModel = this._store.add(new MutableDisposable()); + + private readonly _onDidChangeHeight = new MicrotaskEmitter(); readonly onDidChangeHeight: Event = Event.filter(this._onDidChangeHeight.event, _ => !this._isLayouting); private _lastDim: Dimension | undefined; @@ -213,10 +222,12 @@ class InteractiveEditorWidget { this._historyStore.add(statusToolbar); - // preview editor + // preview editors + this._previewDiffEditor = this._store.add(_instantiationService.createInstance(EmbeddedDiffEditorWidget, this._elements.previewDiff, _previewEditorEditorOptions, parentEditor)); + + this._previewCreateTitle = this._store.add(_instantiationService.createInstance(ResourceLabel, this._elements.previewCreateTitle, { supportIcons: true })); + this._previewCreateEditor = this._store.add(_instantiationService.createInstance(EmbeddedCodeEditorWidget, this._elements.previewCreate, _previewEditorEditorOptions, codeEditorWidgetOptions, parentEditor)); - this._previewEditor = _instantiationService.createInstance(EmbeddedDiffEditorWidget, this._elements.preview, _previewEditorEditorOptions, parentEditor); - this._store.add(this._previewEditor); } dispose(): void { @@ -239,9 +250,13 @@ class InteractiveEditorWidget { this.inputEditor.layout(new Dimension(innerEditorWidth, this.inputEditor.getContentHeight())); this._elements.placeholder.style.width = `${innerEditorWidth /* input-padding*/}px`; - const previewDim = new Dimension(dim.width, Math.min(300, Math.max(0, this._previewEditor.getContentHeight()))); - this._previewEditor.layout(previewDim); - this._elements.preview.style.height = `${previewDim.height}px`; + const previewDiffDim = new Dimension(dim.width, Math.min(300, Math.max(0, this._previewDiffEditor.getContentHeight()))); + this._previewDiffEditor.layout(previewDiffDim); + this._elements.previewDiff.style.height = `${previewDiffDim.height}px`; + + const previewCreateDim = new Dimension(dim.width, Math.min(300, Math.max(0, this._previewCreateEditor.getContentHeight()))); + this._previewCreateEditor.layout(previewCreateDim); + this._elements.previewCreate.style.height = `${previewCreateDim.height}px`; } } finally { this._isLayouting = false; @@ -251,8 +266,10 @@ class InteractiveEditorWidget { getHeight(): number { const base = getTotalHeight(this._elements.progress) + getTotalHeight(this._elements.status); const editorHeight = this.inputEditor.getContentHeight() + 12 /* padding and border */; - const previewHeight = this._previewEditor.getModel() ? 12 + Math.min(300, Math.max(0, this._previewEditor.getContentHeight())) : 0; - return base + editorHeight + previewHeight + 18 /* padding */ + 8 /*shadow*/; + const previewDiffHeight = this._previewDiffEditor.getModel().modified ? 12 + Math.min(300, Math.max(0, this._previewDiffEditor.getContentHeight())) : 0; + const previewCreateTitleHeight = getTotalHeight(this._elements.previewCreateTitle); + const previewCreateHeight = this._previewCreateEditor.getModel() ? 18 + Math.min(300, Math.max(0, this._previewCreateEditor.getContentHeight())) : 0; + return base + editorHeight + previewDiffHeight + previewCreateTitleHeight + previewCreateHeight + 18 /* padding */ + 8 /*shadow*/; } updateProgress(show: boolean) { @@ -381,9 +398,8 @@ class InteractiveEditorWidget { this._ctxInputEmpty.reset(); reset(this._elements.statusLabel); this._elements.statusToolbar.classList.add('hidden'); - this._previewEditor.setModel(null); - this._previewModel.clear(); - this._elements.root.classList.remove('preview'); + this.hideCreatePreview(); + this.hideEditsPreview(); this._onDidChangeHeight.fire(); } @@ -393,8 +409,9 @@ class InteractiveEditorWidget { // --- preview - preview(actualModel: ITextModel, edits: TextEdit[]) { + showEditsPreview(actualModel: ITextModel, edits: TextEdit[]) { this._elements.root.classList.add('preview'); + this._elements.previewDiff.classList.remove('hidden'); const pad = 3; const unionRange = (ranges: IRange[]) => ranges.reduce((p, c) => Range.plusRange(p, c)); @@ -417,12 +434,42 @@ class InteractiveEditorWidget { const original = this._modelService.createModel(originalValue, languageSelection, baseModel.uri.with({ scheme: 'vscode', query: 'original' }), true); const modified = this._modelService.createModel(modifiedValue, languageSelection, baseModel.uri.with({ scheme: 'vscode', query: 'modified' }), true); - this._previewModel.value = toDisposable(() => { + this._previewDiffModel.value = toDisposable(() => { original.dispose(); modified.dispose(); }); - this._previewEditor.setModel({ original, modified }); + this._previewDiffEditor.setModel({ original, modified }); + this._onDidChangeHeight.fire(); + } + + hideEditsPreview() { + this._elements.root.classList.remove('preview'); + this._elements.previewDiff.classList.add('hidden'); + this._previewDiffEditor.setModel(null); + this._previewDiffModel.clear(); + this._onDidChangeHeight.fire(); + } + + showCreatePreview(uri: URI, edits: TextEdit[]): void { + this._elements.root.classList.add('preview'); + this._elements.previewCreateTitle.classList.remove('hidden'); + this._elements.previewCreate.classList.remove('hidden'); + + this._previewCreateTitle.element.setFile(uri, { fileKind: FileKind.FILE }); + + const model = this._modelService.createModel('', null, undefined, true); + model.applyEdits(edits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text))); + this._previewCreateModel.value = model; + this._previewCreateEditor.setModel(model); + this._onDidChangeHeight.fire(); + } + + hideCreatePreview() { + this._elements.previewCreateTitle.classList.add('hidden'); + this._elements.previewCreate.classList.add('hidden'); + this._previewCreateEditor.setModel(null); + this._previewCreateTitle.element.clear(); this._onDidChangeHeight.fire(); } } From f76e264679fc952294486aa86e4cd70ac089ff79 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 12 Apr 2023 16:12:36 +0200 Subject: [PATCH 13/59] support settings sync signing in via embedder (#179769) --- src/vs/workbench/browser/web.api.ts | 18 ++++++++++- .../services/userData/browser/userDataInit.ts | 9 ++++-- .../browser/userDataSyncWorkbenchService.ts | 30 +++++++++++-------- .../webUserDataSyncEnablementService.ts | 3 -- 4 files changed, 41 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/browser/web.api.ts b/src/vs/workbench/browser/web.api.ts index 21415c69b9533..b99bc694fb14a 100644 --- a/src/vs/workbench/browser/web.api.ts +++ b/src/vs/workbench/browser/web.api.ts @@ -709,7 +709,23 @@ export interface ISettingsSyncOptions { /** * Handler is being called when the user changes Settings Sync enablement. */ - enablementHandler?(enablement: boolean): void; + enablementHandler?(enablement: boolean, authenticationProvider: string): void; + + /** + * Authentication provider + */ + readonly authenticationProvider?: { + /** + * Unique identifier of the authentication provider. + */ + readonly id: string; + + /** + * Called when the user wants to signin to Settings Sync using the given authentication provider. + * The returned promise should resolve to the authentication session id. + */ + signIn(): Promise; + }; } export interface IDevelopmentOptions { diff --git a/src/vs/workbench/services/userData/browser/userDataInit.ts b/src/vs/workbench/services/userData/browser/userDataInit.ts index 308deacf6a367..cbb271ccd23c7 100644 --- a/src/vs/workbench/services/userData/browser/userDataInit.ts +++ b/src/vs/workbench/services/userData/browser/userDataInit.ts @@ -9,7 +9,6 @@ import { GlobalStateInitializer, UserDataSyncStoreTypeSynchronizer } from 'vs/pl import { KeybindingsInitializer } from 'vs/platform/userDataSync/common/keybindingsSync'; import { SettingsInitializer } from 'vs/platform/userDataSync/common/settingsSync'; import { SnippetsInitializer } from 'vs/platform/userDataSync/common/snippetsSync'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IFileService } from 'vs/platform/files/common/files'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; @@ -38,6 +37,7 @@ import { IExtensionStorageService } from 'vs/platform/extensionManagement/common import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { TasksInitializer } from 'vs/platform/userDataSync/common/tasksSync'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; export const IUserDataInitializationService = createDecorator('IUserDataInitializationService'); export interface IUserDataInitializationService { @@ -59,7 +59,7 @@ export class UserDataInitializationService implements IUserDataInitializationSer private globalStateUserData: IUserData | null = null; constructor( - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IBrowserWorkbenchEnvironmentService private readonly environmentService: IBrowserWorkbenchEnvironmentService, @ICredentialsService private readonly credentialsService: ICredentialsService, @IUserDataSyncStoreManagementService private readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService, @IFileService private readonly fileService: IFileService, @@ -97,6 +97,11 @@ export class UserDataInitializationService implements IUserDataInitializationSer return; } + if (!this.environmentService.options?.settingsSyncOptions?.enabled) { + this.logService.trace(`Skipping initializing user data as settings sync is disabled`); + return; + } + let authenticationSession; try { authenticationSession = await getCurrentAuthenticationSessionInfo(this.credentialsService, this.productService); diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index 1a38706ec3ef8..93b2dbbc5f818 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -18,7 +18,6 @@ import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { localize } from 'vs/nls'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -39,6 +38,7 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { isDiffEditorInput } from 'vs/workbench/common/editor'; +import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; type AccountQuickPickItem = { label: string; authenticationProvider: IAuthenticationProvider; account?: UserDataSyncAccount; description?: string }; @@ -103,7 +103,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat @ILogService private readonly logService: ILogService, @IProductService private readonly productService: IProductService, @IExtensionService private readonly extensionService: IExtensionService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IBrowserWorkbenchEnvironmentService private readonly environmentService: IBrowserWorkbenchEnvironmentService, @ICredentialsService private readonly credentialsService: ICredentialsService, @INotificationService private readonly notificationService: INotificationService, @IProgressService private readonly progressService: IProgressService, @@ -324,15 +324,22 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } this.currentAuthenticationProviderId = this.current?.authenticationProviderId; + if (this.environmentService.options?.settingsSyncOptions?.enablementHandler && this.currentAuthenticationProviderId) { + this.environmentService.options.settingsSyncOptions.enablementHandler(true, this.currentAuthenticationProviderId); + } + this.notificationService.info(localize('sync turned on', "{0} is turned on", SYNC_TITLE)); } async turnoff(everywhere: boolean): Promise { if (this.userDataSyncEnablementService.isEnabled()) { - return this.userDataAutoSyncService.turnOff(everywhere); + await this.userDataAutoSyncService.turnOff(everywhere); + if (this.environmentService.options?.settingsSyncOptions?.enablementHandler && this.currentAuthenticationProviderId) { + this.environmentService.options.settingsSyncOptions.enablementHandler(false, this.currentAuthenticationProviderId); + } } if (this.turnOnSyncCancellationToken) { - return this.turnOnSyncCancellationToken.cancel(); + this.turnOnSyncCancellationToken.cancel(); } } @@ -563,18 +570,15 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } private async doSignIn(accountOrAuthProvider: UserDataSyncAccount | IAuthenticationProvider): Promise { - let sessionId: string, accountName: string; + let sessionId: string; if (isAuthenticationProvider(accountOrAuthProvider)) { - const session = await this.authenticationService.createSession(accountOrAuthProvider.id, accountOrAuthProvider.scopes); - sessionId = session.id; - accountName = session.account.label; + if (this.environmentService.options?.settingsSyncOptions?.authenticationProvider?.id === accountOrAuthProvider.id) { + sessionId = await this.environmentService.options?.settingsSyncOptions?.authenticationProvider?.signIn(); + } else { + sessionId = (await this.authenticationService.createSession(accountOrAuthProvider.id, accountOrAuthProvider.scopes)).id; + } } else { sessionId = accountOrAuthProvider.sessionId; - accountName = accountOrAuthProvider.accountName; - } - const currentAccount = this.current; - if (this.userDataSyncEnablementService.isEnabled() && (currentAccount && currentAccount.accountName !== accountName)) { - // accounts are switched while sync is enabled. } this.currentSessionId = sessionId; await this.update(); diff --git a/src/vs/workbench/services/userDataSync/browser/webUserDataSyncEnablementService.ts b/src/vs/workbench/services/userDataSync/browser/webUserDataSyncEnablementService.ts index a05ae03d23d1c..9b230c592cb4f 100644 --- a/src/vs/workbench/services/userDataSync/browser/webUserDataSyncEnablementService.ts +++ b/src/vs/workbench/services/userDataSync/browser/webUserDataSyncEnablementService.ts @@ -35,9 +35,6 @@ export class WebUserDataSyncEnablementService extends UserDataSyncEnablementServ if (this.enabled !== enabled) { this.enabled = enabled; super.setEnablement(enabled); - if (this.workbenchEnvironmentService.options?.settingsSyncOptions?.enablementHandler) { - this.workbenchEnvironmentService.options.settingsSyncOptions.enablementHandler(this.enabled); - } } } From 1c2a464475f80b6884ff034e8a0c17348047e8dd Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 12 Apr 2023 07:36:00 -0700 Subject: [PATCH 14/59] fix #179717 --- src/vs/workbench/contrib/markers/browser/messages.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/markers/browser/messages.ts b/src/vs/workbench/contrib/markers/browser/messages.ts index ed66fa20c78bb..7ff0582f02ffe 100644 --- a/src/vs/workbench/contrib/markers/browser/messages.ts +++ b/src/vs/workbench/contrib/markers/browser/messages.ts @@ -57,17 +57,17 @@ export default class Messages { const relatedInformationMessage = marker.relatedInformation.length ? nls.localize('problems.tree.aria.label.marker.relatedInformation', " This problem has references to {0} locations.", marker.relatedInformation.length) : ''; switch (marker.marker.severity) { case MarkerSeverity.Error: - return marker.marker.source ? nls.localize('problems.tree.aria.label.error.marker', "Error generated by {0}: {1} at line {2} and character {3}.{4}", marker.marker.source, marker.marker.message, marker.marker.startLineNumber, marker.marker.startColumn, relatedInformationMessage) + return marker.marker.source ? nls.localize('problems.tree.aria.label.error.marker', "Error: {0} at line {1} and character {2}.{3} generated by {4}", marker.marker.message, marker.marker.startLineNumber, marker.marker.startColumn, relatedInformationMessage, marker.marker.source) : nls.localize('problems.tree.aria.label.error.marker.nosource', "Error: {0} at line {1} and character {2}.{3}", marker.marker.message, marker.marker.startLineNumber, marker.marker.startColumn, relatedInformationMessage); case MarkerSeverity.Warning: - return marker.marker.source ? nls.localize('problems.tree.aria.label.warning.marker', "Warning generated by {0}: {1} at line {2} and character {3}.{4}", marker.marker.source, marker.marker.message, marker.marker.startLineNumber, marker.marker.startColumn, relatedInformationMessage) + return marker.marker.source ? nls.localize('problems.tree.aria.label.warning.marker', "Warning: {0} at line {1} and character {2}.{3} generated by {4}", marker.marker.message, marker.marker.startLineNumber, marker.marker.startColumn, relatedInformationMessage, marker.marker.source) : nls.localize('problems.tree.aria.label.warning.marker.nosource', "Warning: {0} at line {1} and character {2}.{3}", marker.marker.message, marker.marker.startLineNumber, marker.marker.startColumn, relatedInformationMessage, relatedInformationMessage); case MarkerSeverity.Info: - return marker.marker.source ? nls.localize('problems.tree.aria.label.info.marker', "Info generated by {0}: {1} at line {2} and character {3}.{4}", marker.marker.source, marker.marker.message, marker.marker.startLineNumber, marker.marker.startColumn, relatedInformationMessage) + return marker.marker.source ? nls.localize('problems.tree.aria.label.info.marker', "Info: {0} at line {1} and character {2}.{3} generated by {4}", marker.marker.message, marker.marker.startLineNumber, marker.marker.startColumn, relatedInformationMessage, marker.marker.source) : nls.localize('problems.tree.aria.label.info.marker.nosource', "Info: {0} at line {1} and character {2}.{3}", marker.marker.message, marker.marker.startLineNumber, marker.marker.startColumn, relatedInformationMessage); default: - return marker.marker.source ? nls.localize('problems.tree.aria.label.marker', "Problem generated by {0}: {1} at line {2} and character {3}.{4}", marker.marker.source, marker.marker.message, marker.marker.startLineNumber, marker.marker.startColumn, relatedInformationMessage) + return marker.marker.source ? nls.localize('problems.tree.aria.label.marker', "Problem: {0} at line {1} and character {2}.{3} generated by {4}", marker.marker.source, marker.marker.message, marker.marker.startLineNumber, marker.marker.startColumn, relatedInformationMessage, marker.marker.source) : nls.localize('problems.tree.aria.label.marker.nosource', "Problem: {0} at line {1} and character {2}.{3}", marker.marker.message, marker.marker.startLineNumber, marker.marker.startColumn, relatedInformationMessage); } }; From cc8733af747f6094dcd8cdf6445d8d1b8f7b9ab0 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 12 Apr 2023 16:55:54 +0200 Subject: [PATCH 15/59] comments API issue when `addComment` called too early (#179786) Fixes #176498 --- src/vs/workbench/contrib/comments/browser/commentsController.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index 75481165d7fa0..fab32d698f1f4 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -810,6 +810,7 @@ export class CommentController implements IEditorContribution { public addCommentAtLine(range: Range | undefined, e: IEditorMouseEvent | undefined): Promise { const newCommentInfos = this._commentingRangeDecorator.getMatchedCommentAction(range); if (!newCommentInfos.length || !this.editor?.hasModel()) { + this._addInProgress = false; return Promise.resolve(); } From bb7570f4f894cb48fc86dbbcd13777209668d804 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 12 Apr 2023 17:42:51 +0200 Subject: [PATCH 16/59] GitHub - branch protection provider (#179789) * Initial implementation * Update default setting state --- extensions/git/src/api/api1.ts | 6 +- extensions/git/src/api/git.d.ts | 1 + extensions/git/src/model.ts | 1 + extensions/git/src/repository.ts | 2 + extensions/github/package.json | 7 +- extensions/github/package.nls.json | 1 + extensions/github/src/branchProtection.ts | 160 ++++++++++++++++++++++ extensions/github/src/extension.ts | 2 + extensions/github/src/typings/git.d.ts | 6 + 9 files changed, 184 insertions(+), 2 deletions(-) create mode 100644 extensions/github/src/branchProtection.ts diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index faa70b84949de..e1fcc022aa67c 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -5,7 +5,7 @@ import { Model } from '../model'; import { Repository as BaseRepository, Resource } from '../repository'; -import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider, RefQuery } from './git'; +import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider, RefQuery, BranchProtectionProvider } from './git'; import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands, CancellationToken } from 'vscode'; import { combinedDisposable, mapEvent } from '../util'; import { toGitUri } from '../uri'; @@ -333,6 +333,10 @@ export class ApiImpl implements API { return this._model.registerPushErrorHandler(handler); } + registerBranchProtectionProvider(root: Uri, provider: BranchProtectionProvider): Disposable { + return this._model.registerBranchProtectionProvider(root, provider); + } + constructor(private _model: Model) { } } diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index c3b6b76111e95..6a20b9017f0a3 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -305,6 +305,7 @@ export interface API { registerCredentialsProvider(provider: CredentialsProvider): Disposable; registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable; registerPushErrorHandler(handler: PushErrorHandler): Disposable; + registerBranchProtectionProvider(root: Uri, provider: BranchProtectionProvider): Disposable; } export interface GitExtension { diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index fc6a1e1dd9e70..2624ffb412602 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -780,6 +780,7 @@ export class Model implements IBranchProtectionProviderRegistry, IRemoteSourcePu if (providers && providers.has(provider)) { providers.delete(provider); this.branchProtectionProviders.set(root, providers); + this._onDidChangeBranchProtectionProviders.fire(root); } dispose(providerDisposables); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 2c6d4b377557e..75e7c9097883a 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -2364,6 +2364,8 @@ export class Repository implements Disposable { } private updateBranchProtectionMatchers(root: Uri): void { + this.branchProtection.clear(); + for (const provider of this.branchProtectionProviderRegistry.getBranchProtectionProviders(root)) { for (const [remote, branches] of provider.provideBranchProtection().entries()) { this.branchProtection.set(remote, branches.length !== 0 ? picomatch(branches) : undefined); diff --git a/extensions/github/package.json b/extensions/github/package.json index f9e5cf40ef469..90f6d96c111c9 100644 --- a/extensions/github/package.json +++ b/extensions/github/package.json @@ -125,12 +125,17 @@ "group": "0_vscode@0" } ] - }, "configuration": [ { "title": "GitHub", "properties": { + "github.branchProtection": { + "type": "boolean", + "scope": "resource", + "default": false, + "description": "%config.branchProtection%" + }, "github.gitAuthentication": { "type": "boolean", "scope": "resource", diff --git a/extensions/github/package.nls.json b/extensions/github/package.nls.json index 1e0ac702bb457..dc9c7bf887c0c 100644 --- a/extensions/github/package.nls.json +++ b/extensions/github/package.nls.json @@ -1,6 +1,7 @@ { "displayName": "GitHub", "description": "GitHub features for VS Code", + "config.branchProtection": "Controls whether to query branch protection information for GitHub repositories", "config.gitAuthentication": "Controls whether to enable automatic GitHub authentication for git commands within VS Code.", "config.gitProtocol": "Controls which protocol is used to clone a GitHub repository", "welcome.publishFolder": { diff --git a/extensions/github/src/branchProtection.ts b/extensions/github/src/branchProtection.ts new file mode 100644 index 0000000000000..05dee5465a9c7 --- /dev/null +++ b/extensions/github/src/branchProtection.ts @@ -0,0 +1,160 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { EventEmitter, Uri, workspace } from 'vscode'; +import { getOctokit } from './auth'; +import { API, BranchProtectionProvider, Repository } from './typings/git'; +import { DisposableStore, getRepositoryFromUrl } from './util'; + +export class GithubBranchProtectionProviderManager { + + private readonly disposables = new DisposableStore(); + private readonly providerDisposables = new DisposableStore(); + + private _enabled = false; + private set enabled(enabled: boolean) { + if (this._enabled === enabled) { + return; + } + + if (enabled) { + for (const repository of this.gitAPI.repositories) { + this.providerDisposables.add(this.gitAPI.registerBranchProtectionProvider(repository.rootUri, new GithubBranchProtectionProvider(repository))); + } + } else { + this.providerDisposables.dispose(); + } + + this._enabled = enabled; + } + + constructor(private gitAPI: API) { + this.disposables.add(this.gitAPI.onDidOpenRepository(repository => { + if (this._enabled) { + this.providerDisposables.add(gitAPI.registerBranchProtectionProvider(repository.rootUri, new GithubBranchProtectionProvider(repository))); + } + })); + + this.disposables.add(workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('github.branchProtection')) { + this.updateEnablement(); + } + })); + + this.updateEnablement(); + } + + private updateEnablement(): void { + const config = workspace.getConfiguration('github', null); + this.enabled = config.get('branchProtection', true) === true; + } + + dispose(): void { + this.enabled = false; + this.disposables.dispose(); + } + +} + +export class GithubBranchProtectionProvider implements BranchProtectionProvider { + private readonly _onDidChangeBranchProtection = new EventEmitter(); + onDidChangeBranchProtection = this._onDidChangeBranchProtection.event; + + private branchProtection = new Map(); + + constructor(private readonly repository: Repository) { + repository.status() + .then(() => this.initializeBranchProtection()); + } + + provideBranchProtection(): Map { + return this.branchProtection; + } + + private async initializeBranchProtection(): Promise { + // Branch protection (HEAD) + await this.updateHEADBranchProtection(); + + // Branch protection (remotes) + await this.updateBranchProtection(); + } + + private async updateHEADBranchProtection(): Promise { + try { + const HEAD = this.repository.state.HEAD; + + if (!HEAD?.name || !HEAD?.upstream?.remote) { + return; + } + + const remoteName = HEAD.upstream.remote; + const remote = this.repository.state.remotes.find(r => r.name === remoteName); + + if (!remote) { + return; + } + + const repository = getRepositoryFromUrl(remote.pushUrl ?? remote.fetchUrl ?? ''); + + if (!repository) { + return; + } + + const octokit = await getOctokit(); + const response = await octokit.repos.getBranch({ ...repository, branch: HEAD.name }); + + if (!response.data.protected) { + return; + } + + this.branchProtection.set(remote.name, [HEAD.name]); + this._onDidChangeBranchProtection.fire(this.repository.rootUri); + } catch { + // todo@lszomoru - add logging + } + } + + private async updateBranchProtection(): Promise { + try { + let branchProtectionUpdated = false; + + for (const remote of this.repository.state.remotes) { + const repository = getRepositoryFromUrl(remote.pushUrl ?? remote.fetchUrl ?? ''); + + if (!repository) { + continue; + } + + const octokit = await getOctokit(); + + let page = 1; + const protectedBranches: string[] = []; + + while (true) { + const response = await octokit.repos.listBranches({ ...repository, protected: true, per_page: 100, page }); + + if (response.data.length === 0) { + break; + } + + protectedBranches.push(...response.data.map(b => b.name)); + page++; + } + + if (protectedBranches.length > 0) { + this.branchProtection.set(remote.name, protectedBranches); + branchProtectionUpdated = true; + } + } + + if (branchProtectionUpdated) { + this._onDidChangeBranchProtection.fire(this.repository.rootUri); + } + } catch { + // todo@lszomoru - add logging + } + } + +} diff --git a/extensions/github/src/extension.ts b/extensions/github/src/extension.ts index a3a84b033dd86..b58e23683f029 100644 --- a/extensions/github/src/extension.ts +++ b/extensions/github/src/extension.ts @@ -12,6 +12,7 @@ import { DisposableStore, repositoryHasGitHubRemote } from './util'; import { GithubPushErrorHandler } from './pushErrorHandler'; import { GitBaseExtension } from './typings/git-base'; import { GithubRemoteSourcePublisher } from './remoteSourcePublisher'; +import { GithubBranchProtectionProviderManager } from './branchProtection'; export function activate(context: ExtensionContext): void { context.subscriptions.push(initializeGitBaseExtension()); @@ -77,6 +78,7 @@ function initializeGitExtension(): Disposable { disposables.add(registerCommands(gitAPI)); disposables.add(new GithubCredentialProviderManager(gitAPI)); + disposables.add(new GithubBranchProtectionProviderManager(gitAPI)); disposables.add(gitAPI.registerPushErrorHandler(new GithubPushErrorHandler())); disposables.add(gitAPI.registerRemoteSourcePublisher(new GithubRemoteSourcePublisher(gitAPI))); setGitHubContext(gitAPI, disposables); diff --git a/extensions/github/src/typings/git.d.ts b/extensions/github/src/typings/git.d.ts index 2e10affa1546e..5a84e952abbdd 100644 --- a/extensions/github/src/typings/git.d.ts +++ b/extensions/github/src/typings/git.d.ts @@ -268,6 +268,11 @@ export interface PushErrorHandler { handlePushError(repository: Repository, remote: Remote, refspec: string, error: Error & { gitErrorCode: GitErrorCodes }): Promise; } +export interface BranchProtectionProvider { + onDidChangeBranchProtection: Event; + provideBranchProtection(): Map; +} + export type APIState = 'uninitialized' | 'initialized'; export interface PublishEvent { @@ -294,6 +299,7 @@ export interface API { registerCredentialsProvider(provider: CredentialsProvider): Disposable; registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable; registerPushErrorHandler(handler: PushErrorHandler): Disposable; + registerBranchProtectionProvider(root: Uri, provider: BranchProtectionProvider): Disposable; } export interface GitExtension { From 2d8ff25c85013ec2b0d4094c1740bee7c477c050 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 12 Apr 2023 08:51:29 -0700 Subject: [PATCH 17/59] cli: add streams to rpc, generic 'spawn' command (#179732) * cli: apply improvements from integrated wsl branch * cli: add streams to rpc, generic 'spawn' command For the "exec server" concept, fyi @aeschli. * update clippy and apply fixes * fix unused imports :( --- cli/Cargo.lock | 5 +- cli/Cargo.toml | 3 +- cli/src/commands/tunnels.rs | 4 +- cli/src/commands/update.rs | 4 +- cli/src/commands/version.rs | 2 +- cli/src/json_rpc.rs | 15 +- cli/src/log.rs | 10 +- cli/src/msgpack_rpc.rs | 65 +++++++-- cli/src/rpc.rs | 218 ++++++++++++++++++++++++++++- cli/src/self_update.rs | 8 +- cli/src/tunnels/code_server.rs | 18 +-- cli/src/tunnels/control_server.rs | 171 ++++++++++++++++------ cli/src/tunnels/paths.rs | 2 +- cli/src/tunnels/protocol.rs | 14 ++ cli/src/tunnels/service_windows.rs | 2 +- cli/src/tunnels/socket_signal.rs | 6 + cli/src/tunnels/wsl_server.rs | 9 +- cli/src/update_service.rs | 41 ++++-- cli/src/util/command.rs | 72 ++++++---- cli/src/util/errors.rs | 50 ++----- cli/src/util/http.rs | 5 +- cli/src/util/prereqs.rs | 24 ++-- cli/src/util/sync.rs | 8 +- 23 files changed, 572 insertions(+), 184 deletions(-) diff --git a/cli/Cargo.lock b/cli/Cargo.lock index 45d0f478db13d..198430cee9216 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -146,9 +146,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.2.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cache-padded" @@ -230,6 +230,7 @@ dependencies = [ "async-trait", "atty", "base64", + "bytes", "cfg-if", "chrono", "clap", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index c7b6c3746a652..97affef0a64a7 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -17,7 +17,7 @@ clap = { version = "3.0", features = ["derive", "env"] } open = { version = "2.1.0" } reqwest = { version = "0.11.9", default-features = false, features = ["json", "stream", "native-tls"] } tokio = { version = "1.24.2", features = ["full"] } -tokio-util = { version = "0.7", features = ["compat"] } +tokio-util = { version = "0.7", features = ["compat", "codec"] } flate2 = { version = "1.0.22" } zip = { version = "0.5.13", default-features = false, features = ["time", "deflate"] } regex = { version = "1.5.5" } @@ -54,6 +54,7 @@ thiserror = "1.0" cfg-if = "1.0.0" pin-project = "1.0" console = "0.15" +bytes = "1.4" [build-dependencies] serde = { version = "1.0" } diff --git a/cli/src/commands/tunnels.rs b/cli/src/commands/tunnels.rs index 7c3771fe644cb..8a390930f9bca 100644 --- a/cli/src/commands/tunnels.rs +++ b/cli/src/commands/tunnels.rs @@ -190,7 +190,7 @@ pub async fn rename(ctx: CommandContext, rename_args: TunnelRenameArgs) -> Resul let auth = Auth::new(&ctx.paths, ctx.log.clone()); let mut dt = dev_tunnels::DevTunnels::new(&ctx.log, auth, &ctx.paths); dt.rename_tunnel(&rename_args.name).await?; - ctx.log.result(&format!( + ctx.log.result(format!( "Successfully renamed this gateway to {}", &rename_args.name )); @@ -287,7 +287,7 @@ pub async fn prune(ctx: CommandContext) -> Result { .filter(|s| s.get_running_pid().is_none()) .try_for_each(|s| { ctx.log - .result(&format!("Deleted {}", s.server_dir.display())); + .result(format!("Deleted {}", s.server_dir.display())); s.delete() }) .map_err(AnyError::from)?; diff --git a/cli/src/commands/update.rs b/cli/src/commands/update.rs index 80a57b12bb136..0d7321a814fdd 100644 --- a/cli/src/commands/update.rs +++ b/cli/src/commands/update.rs @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +use std::sync::Arc; + use indicatif::ProgressBar; use crate::{ @@ -17,7 +19,7 @@ use super::{args::StandaloneUpdateArgs, CommandContext}; pub async fn update(ctx: CommandContext, args: StandaloneUpdateArgs) -> Result { let update_service = UpdateService::new( ctx.log.clone(), - ReqwestSimpleHttp::with_client(ctx.http.clone()), + Arc::new(ReqwestSimpleHttp::with_client(ctx.http.clone())), ); let update_service = SelfUpdate::new(&update_service)?; diff --git a/cli/src/commands/version.rs b/cli/src/commands/version.rs index c0d44fa143898..e80fa481c7b15 100644 --- a/cli/src/commands/version.rs +++ b/cli/src/commands/version.rs @@ -58,5 +58,5 @@ pub async fn show(ctx: CommandContext) -> Result { } fn print_now_using(log: &log::Logger, version: &RequestedVersion, path: &Path) { - log.result(&format!("Now using {} from {}", version, path.display())); + log.result(format!("Now using {} from {}", version, path.display())); } diff --git a/cli/src/json_rpc.rs b/cli/src/json_rpc.rs index 083c431654222..57baac01c5e41 100644 --- a/cli/src/json_rpc.rs +++ b/cli/src/json_rpc.rs @@ -50,7 +50,7 @@ pub async fn start_json_rpc( mut msg_rx: impl Receivable>, mut shutdown_rx: Barrier, ) -> io::Result> { - let (write_tx, mut write_rx) = mpsc::unbounded_channel::>(); + let (write_tx, mut write_rx) = mpsc::channel::>(8); let mut read = BufReader::new(read); let mut read_buf = String::new(); @@ -84,7 +84,18 @@ pub async fn start_json_rpc( let write_tx = write_tx.clone(); tokio::spawn(async move { if let Some(v) = fut.await { - write_tx.send(v).ok(); + let _ = write_tx.send(v).await; + } + }); + }, + MaybeSync::Stream((dto, fut)) => { + if let Some(dto) = dto { + dispatcher.register_stream(write_tx.clone(), dto).await; + } + let write_tx = write_tx.clone(); + tokio::spawn(async move { + if let Some(v) = fut.await { + let _ = write_tx.send(v).await; } }); } diff --git a/cli/src/log.rs b/cli/src/log.rs index 7162432062d7c..1bce766a8718c 100644 --- a/cli/src/log.rs +++ b/cli/src/log.rs @@ -27,21 +27,19 @@ pub fn next_counter() -> u32 { // Log level #[derive(clap::ArgEnum, PartialEq, Eq, PartialOrd, Clone, Copy, Debug, Serialize, Deserialize)] +#[derive(Default)] pub enum Level { Trace = 0, Debug, - Info, + #[default] + Info, Warn, Error, Critical, Off, } -impl Default for Level { - fn default() -> Self { - Level::Info - } -} + impl fmt::Display for Level { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/cli/src/msgpack_rpc.rs b/cli/src/msgpack_rpc.rs index de46e738da820..18d5d0b9d2b5c 100644 --- a/cli/src/msgpack_rpc.rs +++ b/cli/src/msgpack_rpc.rs @@ -8,6 +8,7 @@ use tokio::{ pin, sync::mpsc, }; +use tokio_util::codec::Decoder; use crate::{ rpc::{self, MaybeSync, Serialization}, @@ -38,7 +39,6 @@ pub fn new_msgpack_rpc() -> rpc::RpcBuilder { rpc::RpcBuilder::new(MsgPackSerializer {}) } -#[allow(clippy::read_zero_byte_vec)] // false positive pub async fn start_msgpack_rpc( dispatcher: rpc::RpcDispatcher, read: impl AsyncRead + Unpin, @@ -46,34 +46,45 @@ pub async fn start_msgpack_rpc( mut msg_rx: impl Receivable>, mut shutdown_rx: Barrier, ) -> io::Result> { - let (write_tx, mut write_rx) = mpsc::unbounded_channel::>(); + let (write_tx, mut write_rx) = mpsc::channel::>(8); let mut read = BufReader::new(read); - let mut decode_buf = vec![]; + let mut decoder = U32PrefixedCodec {}; + let mut decoder_buf = bytes::BytesMut::new(); let shutdown_fut = shutdown_rx.wait(); pin!(shutdown_fut); loop { tokio::select! { - u = read.read_u32() => { - let msg_length = u? as usize; - decode_buf.resize(msg_length, 0); - tokio::select! { - r = read.read_exact(&mut decode_buf) => match dispatcher.dispatch(&decode_buf[..r?]) { + r = read.read_buf(&mut decoder_buf) => { + r?; + + while let Some(frame) = decoder.decode(&mut decoder_buf)? { + match dispatcher.dispatch(&frame) { MaybeSync::Sync(Some(v)) => { - write_tx.send(v).ok(); + let _ = write_tx.send(v).await; }, MaybeSync::Sync(None) => continue, MaybeSync::Future(fut) => { let write_tx = write_tx.clone(); tokio::spawn(async move { if let Some(v) = fut.await { - write_tx.send(v).ok(); + let _ = write_tx.send(v).await; } }); } - }, - r = &mut shutdown_fut => return Ok(r.ok()), + MaybeSync::Stream((stream, fut)) => { + if let Some(stream) = stream { + dispatcher.register_stream(write_tx.clone(), stream).await; + } + let write_tx = write_tx.clone(); + tokio::spawn(async move { + if let Some(v) = fut.await { + let _ = write_tx.send(v).await; + } + }); + } + } }; }, Some(m) = write_rx.recv() => { @@ -88,3 +99,33 @@ pub async fn start_msgpack_rpc( write.flush().await?; } } + +/// Reader that reads length-prefixed msgpack messages in a cancellation-safe +/// way using Tokio's codecs. +pub struct U32PrefixedCodec {} + +const U32_SIZE: usize = 4; + +impl tokio_util::codec::Decoder for U32PrefixedCodec { + type Item = Vec; + type Error = io::Error; + + fn decode(&mut self, src: &mut bytes::BytesMut) -> Result, Self::Error> { + if src.len() < 4 { + src.reserve(U32_SIZE - src.len()); + return Ok(None); + } + + let mut be_bytes = [0; U32_SIZE]; + be_bytes.copy_from_slice(&src[..U32_SIZE]); + let required_len = U32_SIZE + (u32::from_be_bytes(be_bytes) as usize); + if src.len() < required_len { + src.reserve(required_len - src.len()); + return Ok(None); + } + + let msg = src[U32_SIZE..].to_vec(); + src.resize(0, 0); + Ok(Some(msg)) + } +} diff --git a/cli/src/rpc.rs b/cli/src/rpc.rs index b5e5b53ee69f0..f3c68321590ba 100644 --- a/cli/src/rpc.rs +++ b/cli/src/rpc.rs @@ -15,17 +15,26 @@ use std::{ use crate::log; use futures::{future::BoxFuture, Future, FutureExt}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use tokio::sync::{mpsc, oneshot}; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt, DuplexStream, WriteHalf}, + sync::{mpsc, oneshot}, +}; use crate::util::errors::AnyError; pub type SyncMethod = Arc, &[u8]) -> Option>>; pub type AsyncMethod = Arc, &[u8]) -> BoxFuture<'static, Option>>>; +pub type Duplex = Arc< + dyn Send + + Sync + + Fn(Option, &[u8]) -> (Option, BoxFuture<'static, Option>>), +>; pub enum Method { Sync(SyncMethod), Async(AsyncMethod), + Duplex(Duplex), } /// Serialization is given to the RpcBuilder and defines how data gets serialized @@ -81,6 +90,12 @@ pub struct RpcMethodBuilder { calls: Arc>>, } +#[derive(Serialize)] +struct DuplexStreamStarted { + pub for_request_id: u32, + pub stream_id: u32, +} + impl RpcMethodBuilder { /// Registers a synchronous rpc call that returns its result directly. pub fn register_sync(&mut self, method_name: &'static str, callback: F) @@ -179,14 +194,105 @@ impl RpcMethodBuilder { ); } + /// Registers an async rpc call that returns a Future containing a duplex + /// stream that should be handled by the client. + pub fn register_duplex(&mut self, method_name: &'static str, callback: F) + where + P: DeserializeOwned + Send + 'static, + R: Serialize + Send + Sync + 'static, + Fut: Future> + Send, + F: (Fn(DuplexStream, P, Arc) -> Fut) + Clone + Send + Sync + 'static, + { + let serial = self.serializer.clone(); + let context = self.context.clone(); + self.methods.insert( + method_name, + Method::Duplex(Arc::new(move |id, body| { + let param = match serial.deserialize::>(body) { + Ok(p) => p, + Err(err) => { + return ( + None, + future::ready(id.map(|id| { + serial.serialize(&ErrorResponse { + id, + error: ResponseError { + code: 0, + message: format!("{:?}", err), + }, + }) + })) + .boxed(), + ); + } + }; + + let callback = callback.clone(); + let serial = serial.clone(); + let context = context.clone(); + let stream_id = next_message_id(); + let (client, server) = tokio::io::duplex(8192); + + let fut = async move { + match callback(server, param.params, context).await { + Ok(r) => id.map(|id| serial.serialize(&SuccessResponse { id, result: r })), + Err(err) => id.map(|id| { + serial.serialize(&ErrorResponse { + id, + error: ResponseError { + code: -1, + message: format!("{:?}", err), + }, + }) + }), + } + }; + + ( + Some(StreamDto { + req_id: id.unwrap_or(0), + stream_id, + duplex: client, + }), + fut.boxed(), + ) + })), + ); + } + /// Builds into a usable, sync rpc dispatcher. - pub fn build(self, log: log::Logger) -> RpcDispatcher { + pub fn build(mut self, log: log::Logger) -> RpcDispatcher { + let streams: Arc>>> = + Arc::new(tokio::sync::Mutex::new(HashMap::new())); + + let s1 = streams.clone(); + self.register_async(METHOD_STREAM_ENDED, move |m: StreamEndedParams, _| { + let s1 = s1.clone(); + async move { + s1.lock().await.remove(&m.stream); + Ok(()) + } + }); + + let s2 = streams.clone(); + self.register_async(METHOD_STREAM_DATA, move |m: StreamDataIncomingParams, _| { + let s2 = s2.clone(); + async move { + let mut lock = s2.lock().await; + if let Some(stream) = lock.get_mut(&m.stream) { + let _ = stream.write_all(&m.segment).await; + } + Ok(()) + } + }); + RpcDispatcher { log, context: self.context, calls: self.calls, serializer: self.serializer, methods: Arc::new(self.methods), + streams, } } } @@ -281,6 +387,7 @@ pub struct RpcDispatcher { serializer: Arc, methods: Arc>, calls: Arc>>, + streams: Arc>>>, } static MESSAGE_ID_COUNTER: AtomicU32 = AtomicU32::new(0); @@ -310,6 +417,7 @@ impl RpcDispatcher { match method { Some(Method::Sync(callback)) => MaybeSync::Sync(callback(id, body)), Some(Method::Async(callback)) => MaybeSync::Future(callback(id, body)), + Some(Method::Duplex(callback)) => MaybeSync::Stream(callback(id, body)), None => MaybeSync::Sync(id.map(|id| { self.serializer.serialize(&ErrorResponse { id, @@ -333,11 +441,91 @@ impl RpcDispatcher { } } + /// Registers a stream call returned from dispatch(). + pub async fn register_stream( + &self, + write_tx: mpsc::Sender> + Send>, + dto: StreamDto, + ) { + let stream_id = dto.stream_id; + let for_request_id = dto.req_id; + let (mut read, write) = tokio::io::split(dto.duplex); + let serial = self.serializer.clone(); + + self.streams.lock().await.insert(dto.stream_id, write); + + tokio::spawn(async move { + let r = write_tx + .send( + serial + .serialize(&FullRequest { + id: None, + method: METHOD_STREAM_STARTED, + params: DuplexStreamStarted { + stream_id, + for_request_id, + }, + }) + .into(), + ) + .await; + + if r.is_err() { + return; + } + + let mut buf = Vec::with_capacity(4096); + loop { + match read.read_buf(&mut buf).await { + Ok(0) | Err(_) => break, + Ok(n) => { + let r = write_tx + .send( + serial + .serialize(&FullRequest { + id: None, + method: METHOD_STREAM_DATA, + params: StreamDataParams { + segment: &buf[..n], + stream: stream_id, + }, + }) + .into(), + ) + .await; + + if r.is_err() { + return; + } + + buf.truncate(0); + } + } + } + + let _ = write_tx + .send( + serial + .serialize(&FullRequest { + id: None, + method: METHOD_STREAM_ENDED, + params: StreamEndedParams { stream: stream_id }, + }) + .into(), + ) + .await; + }); + } + pub fn context(&self) -> Arc { self.context.clone() } } +const METHOD_STREAM_STARTED: &str = "stream_started"; +const METHOD_STREAM_DATA: &str = "stream_data"; +const METHOD_STREAM_ENDED: &str = "stream_ended"; + trait AssertIsSync: Sync {} impl AssertIsSync for RpcDispatcher {} @@ -349,6 +537,25 @@ struct PartialIncoming { pub error: Option, } +#[derive(Deserialize)] +struct StreamDataIncomingParams { + #[serde(with = "serde_bytes")] + pub segment: Vec, + pub stream: u32, +} + +#[derive(Serialize, Deserialize)] +struct StreamDataParams<'a> { + #[serde(with = "serde_bytes")] + pub segment: &'a [u8], + pub stream: u32, +} + +#[derive(Serialize, Deserialize)] +struct StreamEndedParams { + pub stream: u32, +} + #[derive(Serialize)] pub struct FullRequest, P> { pub id: Option, @@ -384,7 +591,14 @@ enum Outcome { Error(ResponseError), } +pub struct StreamDto { + stream_id: u32, + req_id: u32, + duplex: DuplexStream, +} + pub enum MaybeSync { + Stream((Option, BoxFuture<'static, Option>>)), Future(BoxFuture<'static, Option>>), Sync(Option>), } diff --git a/cli/src/self_update.rs b/cli/src/self_update.rs index 62228a5b3d15b..33201a345e356 100644 --- a/cli/src/self_update.rs +++ b/cli/src/self_update.rs @@ -86,8 +86,8 @@ impl<'a> SelfUpdate<'a> { // Try to rename the old CLI to the tempdir, where it can get cleaned up by the // OS later. However, this can fail if the tempdir is on a different drive // than the installation dir. In this case just rename it to ".old". - if fs::rename(&target_path, &tempdir.path().join("old-code-cli")).is_err() { - fs::rename(&target_path, &target_path.with_extension(".old")) + if fs::rename(&target_path, tempdir.path().join("old-code-cli")).is_err() { + fs::rename(&target_path, target_path.with_extension(".old")) .map_err(|e| wrap(e, "failed to rename old CLI"))?; } @@ -132,7 +132,7 @@ fn copy_updated_cli_to_path(unzipped_content: &Path, staging_path: &Path) -> Res let archive_file = unzipped_files[0] .as_ref() .map_err(|e| wrap(e, "error listing update files"))?; - fs::copy(&archive_file.path(), staging_path) + fs::copy(archive_file.path(), staging_path) .map_err(|e| wrap(e, "error copying to staging file"))?; Ok(()) } @@ -140,7 +140,7 @@ fn copy_updated_cli_to_path(unzipped_content: &Path, staging_path: &Path) -> Res #[cfg(target_os = "windows")] fn copy_file_metadata(from: &Path, to: &Path) -> Result<(), std::io::Error> { let permissions = from.metadata()?.permissions(); - fs::set_permissions(&to, permissions)?; + fs::set_permissions(to, permissions)?; Ok(()) } diff --git a/cli/src/tunnels/code_server.rs b/cli/src/tunnels/code_server.rs index 1f9eb4ea85c7d..677bbfc2546ef 100644 --- a/cli/src/tunnels/code_server.rs +++ b/cli/src/tunnels/code_server.rs @@ -16,7 +16,7 @@ use crate::util::command::{capture_command, kill_tree}; use crate::util::errors::{ wrap, AnyError, ExtensionInstallFailed, MissingEntrypointError, WrappedError, }; -use crate::util::http::{self, SimpleHttp}; +use crate::util::http::{self, BoxedHttp}; use crate::util::io::SilentCopyProgress; use crate::util::machine::process_exists; use crate::{debug, info, log, span, spanf, trace, warning}; @@ -176,7 +176,7 @@ impl ServerParamsRaw { pub async fn resolve( self, log: &log::Logger, - http: impl SimpleHttp + Send + Sync + 'static, + http: BoxedHttp, ) -> Result { Ok(ResolvedServerParams { release: self.get_or_fetch_commit_id(log, http).await?, @@ -187,7 +187,7 @@ impl ServerParamsRaw { async fn get_or_fetch_commit_id( &self, log: &log::Logger, - http: impl SimpleHttp + Send + Sync + 'static, + http: BoxedHttp, ) -> Result { let target = match self.headless { true => TargetKind::Server, @@ -287,7 +287,7 @@ async fn install_server_if_needed( log: &log::Logger, paths: &ServerPaths, release: &Release, - http: impl SimpleHttp + Send + Sync + 'static, + http: BoxedHttp, existing_archive_path: Option, ) -> Result<(), AnyError> { if paths.executable.exists() { @@ -321,7 +321,7 @@ async fn download_server( path: &Path, release: &Release, log: &log::Logger, - http: impl SimpleHttp + Send + Sync + 'static, + http: BoxedHttp, ) -> Result { let response = UpdateService::new(log.clone(), http) .get_download_stream(release) @@ -403,20 +403,20 @@ async fn do_extension_install_on_running_server( } } -pub struct ServerBuilder<'a, Http: SimpleHttp + Send + Sync + Clone> { +pub struct ServerBuilder<'a> { logger: &'a log::Logger, server_params: &'a ResolvedServerParams, last_used: LastUsedServers<'a>, server_paths: ServerPaths, - http: Http, + http: BoxedHttp, } -impl<'a, Http: SimpleHttp + Send + Sync + Clone + 'static> ServerBuilder<'a, Http> { +impl<'a> ServerBuilder<'a> { pub fn new( logger: &'a log::Logger, server_params: &'a ResolvedServerParams, launcher_paths: &'a LauncherPaths, - http: Http, + http: BoxedHttp, ) -> Self { Self { logger, diff --git a/cli/src/tunnels/control_server.rs b/cli/src/tunnels/control_server.rs index 8c2d1a76d1ab8..bf8ce00380f77 100644 --- a/cli/src/tunnels/control_server.rs +++ b/cli/src/tunnels/control_server.rs @@ -5,6 +5,7 @@ use crate::async_pipe::get_socket_rw_stream; use crate::constants::CONTROL_PORT; use crate::log; +use crate::msgpack_rpc::U32PrefixedCodec; use crate::rpc::{MaybeSync, RpcBuilder, RpcDispatcher, Serialization}; use crate::self_update::SelfUpdate; use crate::state::LauncherPaths; @@ -12,7 +13,8 @@ use crate::tunnels::protocol::HttpRequestParams; use crate::tunnels::socket_signal::CloseReason; use crate::update_service::{Platform, UpdateService}; use crate::util::errors::{ - wrap, AnyError, InvalidRpcDataError, MismatchedLaunchModeError, NoAttachedServerError, + wrap, AnyError, CodeError, InvalidRpcDataError, MismatchedLaunchModeError, + NoAttachedServerError, }; use crate::util::http::{ DelegatedHttpRequest, DelegatedSimpleHttp, FallbackSimpleHttp, ReqwestSimpleHttp, @@ -24,11 +26,14 @@ use crate::util::sync::{new_barrier, Barrier}; use opentelemetry::trace::SpanKind; use opentelemetry::KeyValue; use std::collections::HashMap; +use std::process::Stdio; +use tokio::pin; +use tokio_util::codec::Decoder; use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering}; use std::sync::Arc; use std::time::Instant; -use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, BufReader}; +use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, BufReader, DuplexStream}; use tokio::sync::{mpsc, Mutex}; use super::code_server::{ @@ -40,8 +45,8 @@ use super::port_forwarder::{PortForwarding, PortForwardingProcessor}; use super::protocol::{ CallServerHttpParams, CallServerHttpResult, ClientRequestMethod, EmptyObject, ForwardParams, ForwardResult, GetHostnameResponse, HttpBodyParams, HttpHeadersParams, ServeParams, ServerLog, - ServerMessageParams, ToClientRequest, UnforwardParams, UpdateParams, UpdateResult, - VersionParams, + ServerMessageParams, SpawnParams, SpawnResult, ToClientRequest, UnforwardParams, UpdateParams, + UpdateResult, VersionParams, }; use super::server_bridge::ServerBridge; use super::server_multiplexer::ServerMultiplexer; @@ -73,7 +78,7 @@ struct HandlerContext { /// install platform for the VS Code server platform: Platform, /// http client to make download/update requests - http: FallbackSimpleHttp, + http: Arc, /// requests being served by the client http_requests: HttpRequestsMap, } @@ -196,7 +201,7 @@ pub async fn serve( ], ); cx.span().end(); - }); + }); } } } @@ -247,7 +252,10 @@ async fn process_socket( server_bridges: server_bridges.clone(), port_forwarding, platform, - http: FallbackSimpleHttp::new(ReqwestSimpleHttp::new(), http_delegated), + http: Arc::new(FallbackSimpleHttp::new( + ReqwestSimpleHttp::new(), + http_delegated, + )), http_requests: http_requests.clone(), }); @@ -276,6 +284,9 @@ async fn process_socket( rpc.register_async("unforward", |p: UnforwardParams, c| async move { handle_unforward(&c.log, &c.port_forwarding, p).await }); + rpc.register_duplex("spawn", |stream, p: SpawnParams, c| async move { + handle_spawn(&c.log, stream, p).await + }); rpc.register_sync("httpheaders", |p: HttpHeadersParams, c| { if let Some(req) = c.http_requests.lock().unwrap().get(&p.req_id) { req.initial_response(p.status_code, p.headers); @@ -393,20 +404,20 @@ async fn handle_socket_read( rx_counter: Arc, rpc: &RpcDispatcher, ) -> Result<(), std::io::Error> { - let mut socket_reader = BufReader::new(readhalf); - let mut decode_buf = vec![]; + let mut readhalf = BufReader::new(readhalf); + let mut decoder = U32PrefixedCodec {}; + let mut decoder_buf = bytes::BytesMut::new(); loop { - let read = read_next( - &mut socket_reader, - &rx_counter, - &mut closer, - &mut decode_buf, - ) - .await; + let read_len = tokio::select! { + r = readhalf.read_buf(&mut decoder_buf) => r, + _ = closer.wait() => Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "eof")), + }?; + + rx_counter.fetch_add(read_len, Ordering::Relaxed); - match read { - Ok(len) => match rpc.dispatch(&decode_buf[..len]) { + while let Some(frame) = decoder.decode(&mut decoder_buf)? { + match rpc.dispatch(&frame) { MaybeSync::Sync(Some(v)) => { if socket_tx.send(SocketSignal::Send(v)).await.is_err() { return Ok(()); @@ -421,34 +432,22 @@ async fn handle_socket_read( } }); } - }, - Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(()), - Err(e) => return Err(e), + MaybeSync::Stream((stream, fut)) => { + if let Some(stream) = stream { + rpc.register_stream(socket_tx.clone(), stream).await; + } + let socket_tx = socket_tx.clone(); + tokio::spawn(async move { + if let Some(v) = fut.await { + socket_tx.send(SocketSignal::Send(v)).await.ok(); + } + }); + } + } } } } -/// Reads and handles the next data packet. Returns the next packet to dispatch, -/// or an error (including EOF). -async fn read_next( - socket_reader: &mut BufReader, - rx_counter: &Arc, - closer: &mut Barrier<()>, - decode_buf: &mut Vec, -) -> Result { - let msg_length = tokio::select! { - u = socket_reader.read_u32() => u? as usize, - _ = closer.wait() => return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "eof")), - }; - decode_buf.resize(msg_length, 0); - rx_counter.fetch_add(msg_length + 4 /* u32 */, Ordering::Relaxed); - - tokio::select! { - r = socket_reader.read_exact(decode_buf) => r, - _ = closer.wait() => Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "eof")), - } -} - #[derive(Clone)] struct ServerOutputSink { tx: mpsc::Sender, @@ -487,7 +486,9 @@ async fn handle_serve( }; let resolved = if params.use_local_download { - params_raw.resolve(&c.log, c.http.delegated()).await + params_raw + .resolve(&c.log, Arc::new(c.http.delegated())) + .await } else { params_raw.resolve(&c.log, c.http.clone()).await }?; @@ -518,7 +519,7 @@ async fn handle_serve( &install_log, &resolved, &c.launcher_paths, - c.http.delegated(), + Arc::new(c.http.delegated()), ); do_setup!(sb) } else { @@ -606,7 +607,7 @@ fn handle_prune(paths: &LauncherPaths) -> Result, AnyError> { } async fn handle_update( - http: &FallbackSimpleHttp, + http: &Arc, log: &log::Logger, did_update: &AtomicBool, params: &UpdateParams, @@ -732,3 +733,83 @@ async fn handle_call_server_http( .to_vec(), }) } + +async fn handle_spawn( + log: &log::Logger, + mut duplex: DuplexStream, + params: SpawnParams, +) -> Result { + debug!( + log, + "requested to spawn {} with args {:?}", params.command, params.args + ); + + let mut p = tokio::process::Command::new(¶ms.command) + .args(¶ms.args) + .envs(¶ms.env) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .map_err(CodeError::ProcessSpawnFailed)?; + + let mut stdout = p.stdout.take().unwrap(); + let mut stderr = p.stderr.take().unwrap(); + let mut stdin = p.stdin.take().unwrap(); + let (tx, mut rx) = mpsc::channel(4); + + macro_rules! copy_stream_to { + ($target:expr) => { + let tx = tx.clone(); + tokio::spawn(async move { + let mut buf = vec![0; 4096]; + loop { + let n = match $target.read(&mut buf).await { + Ok(0) | Err(_) => return, + Ok(n) => n, + }; + if !tx.send(buf[..n].to_vec()).await.is_ok() { + return; + } + } + }); + }; + } + + copy_stream_to!(stdout); + copy_stream_to!(stderr); + + let mut stdin_buf = vec![0; 4096]; + let closed = p.wait(); + pin!(closed); + + loop { + tokio::select! { + Ok(n) = duplex.read(&mut stdin_buf) => { + let _ = stdin.write_all(&stdin_buf[..n]).await; + }, + Some(m) = rx.recv() => { + let _ = duplex.write_all(&m).await; + }, + r = &mut closed => { + let r = match r { + Ok(e) => SpawnResult { + message: e.to_string(), + exit_code: e.code().unwrap_or(-1), + }, + Err(e) => SpawnResult { + message: e.to_string(), + exit_code: -1, + }, + }; + + debug!( + log, + "spawned command {} exited with code {}", params.command, r.exit_code + ); + + return Ok(r) + }, + } + } +} diff --git a/cli/src/tunnels/paths.rs b/cli/src/tunnels/paths.rs index 3c47b2575d73c..cdf6cef6f51da 100644 --- a/cli/src/tunnels/paths.rs +++ b/cli/src/tunnels/paths.rs @@ -68,7 +68,7 @@ impl ServerPaths { // VS Code Server pid pub fn write_pid(&self, pid: u32) -> Result<(), WrappedError> { - write(&self.pidfile, &format!("{}", pid)).map_err(|e| { + write(&self.pidfile, format!("{}", pid)).map_err(|e| { wrap( e, format!("error writing process id into {}", self.pidfile.display()), diff --git a/cli/src/tunnels/protocol.rs b/cli/src/tunnels/protocol.rs index ef033415d935f..89f9c3acb2807 100644 --- a/cli/src/tunnels/protocol.rs +++ b/cli/src/tunnels/protocol.rs @@ -158,6 +158,20 @@ impl Default for VersionParams { } } +#[derive(Deserialize)] +pub struct SpawnParams { + pub command: String, + pub args: Vec, + #[serde(default)] + pub env: HashMap, +} + +#[derive(Serialize)] +pub struct SpawnResult { + pub message: String, + pub exit_code: i32, +} + pub mod singleton { use crate::log; use serde::{Deserialize, Serialize}; diff --git a/cli/src/tunnels/service_windows.rs b/cli/src/tunnels/service_windows.rs index d230d2e454f92..e557499364c19 100644 --- a/cli/src/tunnels/service_windows.rs +++ b/cli/src/tunnels/service_windows.rs @@ -59,7 +59,7 @@ impl CliServiceManager for WindowsService { }; for arg in args { - add_arg(*arg); + add_arg(arg); } add_arg("--log-to-file"); diff --git a/cli/src/tunnels/socket_signal.rs b/cli/src/tunnels/socket_signal.rs index c625593a21aaf..a3d3b08a5d41c 100644 --- a/cli/src/tunnels/socket_signal.rs +++ b/cli/src/tunnels/socket_signal.rs @@ -22,6 +22,12 @@ pub enum SocketSignal { CloseWith(CloseReason), } +impl From> for SocketSignal { + fn from(v: Vec) -> Self { + SocketSignal::Send(v) + } +} + impl SocketSignal { pub fn from_message(msg: &T) -> Self where diff --git a/cli/src/tunnels/wsl_server.rs b/cli/src/tunnels/wsl_server.rs index b6250c8247f79..69d7eb943898c 100644 --- a/cli/src/tunnels/wsl_server.rs +++ b/cli/src/tunnels/wsl_server.rs @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +use std::sync::Arc; + use tokio::sync::mpsc; use crate::{ @@ -139,7 +141,12 @@ async fn handle_serve( }, }; - let sb = ServerBuilder::new(&c.log, &resolved, &c.launcher_paths, c.http.clone()); + let sb = ServerBuilder::new( + &c.log, + &resolved, + &c.launcher_paths, + Arc::new(c.http.clone()), + ); let code_server = match sb.get_running().await? { Some(AnyCodeServer::Socket(s)) => s, Some(_) => return Err(MismatchedLaunchModeError().into()), diff --git a/cli/src/update_service.rs b/cli/src/update_service.rs index 9dcfc0f510749..e56d378180483 100644 --- a/cli/src/update_service.rs +++ b/cli/src/update_service.rs @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -use std::path::Path; +use std::{fmt, path::Path}; use serde::Deserialize; @@ -11,19 +11,20 @@ use crate::{ constants::VSCODE_CLI_UPDATE_ENDPOINT, debug, log, options, spanf, util::{ - errors::{AnyError, UnsupportedPlatformError, UpdatesNotConfigured, WrappedError}, - http::{SimpleHttp, SimpleResponse}, + errors::{AnyError, CodeError, UpdatesNotConfigured, WrappedError}, + http::{BoxedHttp, SimpleResponse}, io::ReportCopyProgress, }, }; /// Implementation of the VS Code Update service for use in the CLI. pub struct UpdateService { - client: Box, + client: BoxedHttp, log: log::Logger, } /// Describes a specific release, can be created manually or returned from the update service. +#[derive(Clone, Eq, PartialEq)] pub struct Release { pub name: String, pub platform: Platform, @@ -53,11 +54,8 @@ fn quality_download_segment(quality: options::Quality) -> &'static str { } impl UpdateService { - pub fn new(log: log::Logger, http: impl SimpleHttp + Send + Sync + 'static) -> Self { - UpdateService { - client: Box::new(http), - log, - } + pub fn new(log: log::Logger, http: BoxedHttp) -> Self { + UpdateService { client: http, log } } pub async fn get_release_by_semver_version( @@ -71,7 +69,7 @@ impl UpdateService { VSCODE_CLI_UPDATE_ENDPOINT.ok_or_else(UpdatesNotConfigured::no_url)?; let download_segment = target .download_segment(platform) - .ok_or(UnsupportedPlatformError())?; + .ok_or_else(|| CodeError::UnsupportedPlatform(platform.to_string()))?; let download_url = format!( "{}/api/versions/{}/{}/{}", update_endpoint, @@ -113,7 +111,7 @@ impl UpdateService { VSCODE_CLI_UPDATE_ENDPOINT.ok_or_else(UpdatesNotConfigured::no_url)?; let download_segment = target .download_segment(platform) - .ok_or(UnsupportedPlatformError())?; + .ok_or_else(|| CodeError::UnsupportedPlatform(platform.to_string()))?; let download_url = format!( "{}/api/latest/{}/{}", update_endpoint, @@ -150,7 +148,7 @@ impl UpdateService { let download_segment = release .target .download_segment(release.platform) - .ok_or(UnsupportedPlatformError())?; + .ok_or_else(|| CodeError::UnsupportedPlatform(release.platform.to_string()))?; let download_url = format!( "{}/commit:{}/{}/{}", @@ -208,7 +206,7 @@ impl TargetKind { } } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Platform { LinuxAlpineX64, LinuxAlpineARM64, @@ -306,3 +304,20 @@ impl Platform { } } } + +impl fmt::Display for Platform { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match self { + Platform::LinuxAlpineARM64 => "LinuxAlpineARM64", + Platform::LinuxAlpineX64 => "LinuxAlpineX64", + Platform::LinuxX64 => "LinuxX64", + Platform::LinuxARM64 => "LinuxARM64", + Platform::LinuxARM32 => "LinuxARM32", + Platform::DarwinX64 => "DarwinX64", + Platform::DarwinARM64 => "DarwinARM64", + Platform::WindowsX64 => "WindowsX64", + Platform::WindowsX86 => "WindowsX86", + Platform::WindowsARM64 => "WindowsARM64", + }) + } +} diff --git a/cli/src/util/command.rs b/cli/src/util/command.rs index c0434b10647a8..ad1f3a1d13ef6 100644 --- a/cli/src/util/command.rs +++ b/cli/src/util/command.rs @@ -2,29 +2,47 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -use super::errors::{wrap, AnyError, CommandFailed, WrappedError}; -use std::{borrow::Cow, ffi::OsStr, process::Stdio}; +use super::errors::CodeError; +use std::{ + borrow::Cow, + ffi::OsStr, + process::{Output, Stdio}, +}; use tokio::process::Command; pub async fn capture_command_and_check_status( command_str: impl AsRef, args: &[impl AsRef], -) -> Result { +) -> Result { let output = capture_command(&command_str, args).await?; + check_output_status(output, || { + format!( + "{} {}", + command_str.as_ref().to_string_lossy(), + args.iter() + .map(|a| a.as_ref().to_string_lossy()) + .collect::>>() + .join(" ") + ) + }) +} + +pub fn check_output_status( + output: Output, + cmd_str: impl FnOnce() -> String, +) -> Result { if !output.status.success() { - return Err(CommandFailed { - command: format!( - "{} {}", - command_str.as_ref().to_string_lossy(), - args.iter() - .map(|a| a.as_ref().to_string_lossy()) - .collect::>>() - .join(" ") - ), - output, - } - .into()); + return Err(CodeError::CommandFailed { + command: cmd_str(), + code: output.status.code().unwrap_or(-1), + output: String::from_utf8_lossy(if output.stderr.is_empty() { + &output.stdout + } else { + &output.stderr + }) + .into(), + }); } Ok(output) @@ -33,7 +51,7 @@ pub async fn capture_command_and_check_status( pub async fn capture_command( command_str: A, args: I, -) -> Result +) -> Result where A: AsRef, I: IntoIterator, @@ -45,27 +63,23 @@ where .stdout(Stdio::piped()) .output() .await - .map_err(|e| { - wrap( - e, - format!( - "failed to execute command '{}'", - command_str.as_ref().to_string_lossy() - ), - ) + .map_err(|e| CodeError::CommandFailed { + command: command_str.as_ref().to_string_lossy().to_string(), + code: -1, + output: e.to_string(), }) } /// Kills and processes and all of its children. #[cfg(target_os = "windows")] -pub async fn kill_tree(process_id: u32) -> Result<(), WrappedError> { +pub async fn kill_tree(process_id: u32) -> Result<(), CodeError> { capture_command("taskkill", &["/t", "/pid", &process_id.to_string()]).await?; Ok(()) } /// Kills and processes and all of its children. #[cfg(not(target_os = "windows"))] -pub async fn kill_tree(process_id: u32) -> Result<(), WrappedError> { +pub async fn kill_tree(process_id: u32) -> Result<(), CodeError> { use futures::future::join_all; use tokio::io::{AsyncBufReadExt, BufReader}; @@ -82,7 +96,11 @@ pub async fn kill_tree(process_id: u32) -> Result<(), WrappedError> { .stdin(Stdio::null()) .stdout(Stdio::piped()) .spawn() - .map_err(|e| wrap(e, "error enumerating process tree"))?; + .map_err(|e| CodeError::CommandFailed { + command: format!("pgrep -P {}", parent_id), + code: -1, + output: e.to_string(), + })?; let mut kill_futures = vec![tokio::spawn( async move { kill_single_pid(parent_id).await }, diff --git a/cli/src/util/errors.rs b/cli/src/util/errors.rs index 0ad128d7cf094..b5ca10660466b 100644 --- a/cli/src/util/errors.rs +++ b/cli/src/util/errors.rs @@ -258,18 +258,6 @@ impl std::fmt::Display for RefreshTokenNotAvailableError { } } -#[derive(Debug)] -pub struct UnsupportedPlatformError(); - -impl std::fmt::Display for UnsupportedPlatformError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "This operation is not supported on your current platform" - ) - } -} - #[derive(Debug)] pub struct NoInstallInUserProvidedPath(pub String); @@ -419,28 +407,6 @@ impl std::fmt::Display for OAuthError { } } -#[derive(Debug)] -pub struct CommandFailed { - pub output: std::process::Output, - pub command: String, -} - -impl std::fmt::Display for CommandFailed { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "Failed to run command \"{}\" (code {}): {}", - self.command, - self.output.status, - String::from_utf8_lossy(if self.output.stderr.is_empty() { - &self.output.stdout - } else { - &self.output.stderr - }) - ) - } -} - // Makes an "AnyError" enum that contains any of the given errors, in the form // `enum AnyError { FooError(FooError) }` (when given `makeAnyError!(FooError)`). // Useful to easily deal with application error types without making tons of "From" @@ -500,6 +466,20 @@ pub enum CodeError { #[cfg(windows)] #[error("could not get windows app lock: {0:?}")] AppLockFailed(std::io::Error), + #[error("failed to run command \"{command}\" (code {code}): {output}")] + CommandFailed { + command: String, + code: i32, + output: String, + }, + + #[error("platform not currently supported: {0}")] + UnsupportedPlatform(String), + #[error("This machine not meet {name}'s prerequisites, expected either...: {bullets}")] + PrerequisitesFailed { name: &'static str, bullets: String }, + + #[error("failed to spawn process: {0:?}")] + ProcessSpawnFailed(std::io::Error) } makeAnyError!( @@ -518,7 +498,6 @@ makeAnyError!( ExtensionInstallFailed, MismatchedLaunchModeError, NoAttachedServerError, - UnsupportedPlatformError, RefreshTokenNotAvailableError, NoInstallInUserProvidedPath, UserCancelledInstallation, @@ -530,7 +509,6 @@ makeAnyError!( UpdatesNotConfigured, CorruptDownload, MissingHomeDirectory, - CommandFailed, OAuthError, InvalidRpcDataError, CodeError diff --git a/cli/src/util/http.rs b/cli/src/util/http.rs index 16681c0759655..953dba678c386 100644 --- a/cli/src/util/http.rs +++ b/cli/src/util/http.rs @@ -16,7 +16,7 @@ use hyper::{ HeaderMap, StatusCode, }; use serde::de::DeserializeOwned; -use std::{io, pin::Pin, str::FromStr, task::Poll}; +use std::{io, pin::Pin, str::FromStr, sync::Arc, task::Poll}; use tokio::{ fs, io::{AsyncRead, AsyncReadExt}, @@ -116,6 +116,8 @@ pub trait SimpleHttp { ) -> Result; } +pub type BoxedHttp = Arc; + // Implementation of SimpleHttp that uses a reqwest client. #[derive(Clone)] pub struct ReqwestSimpleHttp { @@ -324,7 +326,6 @@ impl AsyncRead for DelegatedReader { /// Simple http implementation that falls back to delegated http if /// making a direct reqwest fails. -#[derive(Clone)] pub struct FallbackSimpleHttp { native: ReqwestSimpleHttp, delegated: DelegatedSimpleHttp, diff --git a/cli/src/util/prereqs.rs b/cli/src/util/prereqs.rs index 5e5c6db7c151a..d8cbd1b91ddf3 100644 --- a/cli/src/util/prereqs.rs +++ b/cli/src/util/prereqs.rs @@ -7,13 +7,12 @@ use std::cmp::Ordering; use super::command::capture_command; use crate::constants::QUALITYLESS_SERVER_NAME; use crate::update_service::Platform; -use crate::util::errors::SetupError; use lazy_static::lazy_static; use regex::bytes::Regex as BinRegex; use regex::Regex; use tokio::fs; -use super::errors::AnyError; +use super::errors::CodeError; lazy_static! { static ref LDCONFIG_STDC_RE: Regex = Regex::new(r"libstdc\+\+.* => (.+)").unwrap(); @@ -41,19 +40,18 @@ impl PreReqChecker { } #[cfg(not(target_os = "linux"))] - pub async fn verify(&self) -> Result { - use crate::constants::QUALITYLESS_PRODUCT_NAME; + pub async fn verify(&self) -> Result { Platform::env_default().ok_or_else(|| { - SetupError(format!( - "{} is not supported on this platform", - QUALITYLESS_PRODUCT_NAME + CodeError::UnsupportedPlatform(format!( + "{} {}", + std::env::consts::OS, + std::env::consts::ARCH )) - .into() }) } #[cfg(target_os = "linux")] - pub async fn verify(&self) -> Result { + pub async fn verify(&self) -> Result { let (is_nixos, gnu_a, gnu_b, or_musl) = tokio::join!( check_is_nixos(), check_glibc_version(), @@ -96,10 +94,10 @@ impl PreReqChecker { .collect::>() .join("\n"); - Err(AnyError::from(SetupError(format!( - "This machine not meet {}'s prerequisites, expected either...\n{}", - QUALITYLESS_SERVER_NAME, bullets, - )))) + Err(CodeError::PrerequisitesFailed { + bullets, + name: QUALITYLESS_SERVER_NAME, + }) } } diff --git a/cli/src/util/sync.rs b/cli/src/util/sync.rs index 8b653cd2d535c..2b506bd54e3f2 100644 --- a/cli/src/util/sync.rs +++ b/cli/src/util/sync.rs @@ -4,9 +4,11 @@ *--------------------------------------------------------------------------------------------*/ use async_trait::async_trait; use std::{marker::PhantomData, sync::Arc}; -use tokio::sync::{ - broadcast, mpsc, - watch::{self, error::RecvError}, +use tokio::{ + sync::{ + broadcast, mpsc, + watch::{self, error::RecvError}, + }, }; #[derive(Clone)] From 9d2c16793884adbf89799e0c889e6408c2af0a81 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 12 Apr 2023 18:11:54 +0200 Subject: [PATCH 18/59] apply should accept workspace edit --- .../browser/interactiveEditorActions.ts | 4 +-- .../browser/interactiveEditorController.ts | 32 ++++++++++++------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts index 284777bfe53b6..3cf3a42c26c81 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts @@ -406,8 +406,8 @@ export class ApplyPreviewEdits extends AbstractInteractiveEditorAction { }); } - override runInteractiveEditorCommand(_accessor: ServicesAccessor, ctrl: InteractiveEditorController): void { - ctrl.applyChanges(); + override async runInteractiveEditorCommand(_accessor: ServicesAccessor, ctrl: InteractiveEditorController): Promise { + await ctrl.applyChanges(); ctrl.cancelSession(); } } diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts index cdf5e2f91fa50..7c47e7588015a 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts @@ -159,8 +159,9 @@ class UIEditResponse { readonly localEdits: TextEdit[] = []; readonly singleCreateFileEdit: { uri: URI; edits: TextEdit[] } | undefined; readonly workspaceEdits: ResourceEdit[] | undefined; + readonly workspaceEditsIncludeLocalEdits: boolean = false; - constructor(uri: URI, readonly raw: IInteractiveEditorBulkEditResponse | IInteractiveEditorEditResponse) { + constructor(localUri: URI, readonly raw: IInteractiveEditorBulkEditResponse | IInteractiveEditorEditResponse) { if (raw.type === 'editorEdit') { // this.localEdits = raw.edits; @@ -170,6 +171,7 @@ class UIEditResponse { } else { // const edits = ResourceEdit.convert(raw.edits); + this.workspaceEdits = edits; let isComplexEdit = false; @@ -190,8 +192,10 @@ class UIEditResponse { } } else if (edit instanceof ResourceTextEdit) { // - if (isEqual(edit.resource, uri)) { + if (isEqual(edit.resource, localUri)) { this.localEdits.push(edit.textEdit); + this.workspaceEditsIncludeLocalEdits = true; + } else if (isEqual(this.singleCreateFileEdit?.uri, edit.resource)) { this.singleCreateFileEdit!.edits.push(edit.textEdit); } else { @@ -201,9 +205,8 @@ class UIEditResponse { } if (isComplexEdit) { - this.workspaceEdits = edits; + this.singleCreateFileEdit = undefined; } - } } } @@ -510,7 +513,7 @@ export class InteractiveEditorController implements IEditorContribution { const editResponse = new UIEditResponse(textModel.uri, reply); - if (editResponse.workspaceEdits) { + if (editResponse.workspaceEdits && (!editResponse.singleCreateFileEdit || editMode === 'direct')) { this._bulkEditService.apply(editResponse.workspaceEdits, { editor: this._editor, label: localize('ie', "{0}", input), showPreview: true }); // todo@jrieken keep interactive editor? break; @@ -708,15 +711,22 @@ export class InteractiveEditorController implements IEditorContribution { } } - applyChanges() { + async applyChanges() { if (this._lastEditState) { const { model, modelVersionId, response } = this._lastEditState; - if (model.getAlternativeVersionId() === modelVersionId) { - model.pushStackElement(); - const edits = response.localEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)); - model.pushEditOperations(null, edits, () => null); - model.pushStackElement(); + + if (response.workspaceEdits) { + await this._bulkEditService.apply(response.workspaceEdits); return true; + + } else if (!response.workspaceEditsIncludeLocalEdits) { + if (model.getAlternativeVersionId() === modelVersionId) { + model.pushStackElement(); + const edits = response.localEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)); + model.pushEditOperations(null, edits, () => null); + model.pushStackElement(); + return true; + } } } return false; From dad1246f70b3b7438abf7babf604042f81448ca5 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 12 Apr 2023 18:30:38 +0200 Subject: [PATCH 19/59] adjust width based on showing preview or not --- .../browser/interactiveEditorWidget.ts | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts index 70ddda4595772..f1cf3ef3b8dc1 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts @@ -472,6 +472,11 @@ class InteractiveEditorWidget { this._previewCreateTitle.element.clear(); this._onDidChangeHeight.fire(); } + + showsAnyPreview() { + return !this._elements.previewDiff.classList.contains('hidden') || + !this._elements.previewCreate.classList.contains('hidden'); + } } export class InteractiveEditorZoneWidget extends ZoneWidget { @@ -524,19 +529,20 @@ export class InteractiveEditorZoneWidget extends ZoneWidget { container.appendChild(this.widget.domNode); } - protected override _onWidth(widthInPixel: number): void { - if (this._dimension) { - this._doLayout(this._dimension.height, widthInPixel); - } - } + // protected override _onWidth(_widthInPixel: number): void { + // if (this._dimension) { + // this._doLayout(this._dimension.height); + // } + // } - protected override _doLayout(heightInPixel: number, widthInPixel: number): void { + protected override _doLayout(heightInPixel: number): void { const info = this.editor.getLayoutInfo(); const spaceLeft = info.lineNumbersWidth + info.glyphMarginWidth + info.decorationsWidth; const spaceRight = info.minimap.minimapWidth + info.verticalScrollbarWidth; - const width = Math.min(640, info.contentWidth - (info.glyphMarginWidth + info.decorationsWidth)); + const maxWidth = !this.widget.showsAnyPreview() ? 640 : Number.MAX_SAFE_INTEGER; + const width = Math.min(maxWidth, info.contentWidth - (info.glyphMarginWidth + info.decorationsWidth)); this._dimension = new Dimension(width, heightInPixel); this.widget.domNode.style.marginLeft = `${spaceLeft}px`; this.widget.domNode.style.marginRight = `${spaceRight}px`; @@ -550,6 +556,9 @@ export class InteractiveEditorZoneWidget extends ZoneWidget { } protected override _relayout() { + if (this._dimension) { + this._doLayout(this._dimension.height); + } super._relayout(this._computeHeightInLines()); } From 4180bbd21e9308f67771a6f703d66b209d106dda Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 12 Apr 2023 09:33:29 -0700 Subject: [PATCH 20/59] cli: fix macos build (#179794) --- cli/src/tunnels/service_macos.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/src/tunnels/service_macos.rs b/cli/src/tunnels/service_macos.rs index 7344f34b0ac7c..dcc676ffce533 100644 --- a/cli/src/tunnels/service_macos.rs +++ b/cli/src/tunnels/service_macos.rs @@ -17,7 +17,7 @@ use crate::{ state::LauncherPaths, util::{ command::capture_command_and_check_status, - errors::{wrap, AnyError, MissingHomeDirectory}, + errors::{wrap, AnyError, CodeError, MissingHomeDirectory}, }, }; @@ -81,8 +81,8 @@ impl ServiceManager for LaunchdService { match capture_command_and_check_status("launchctl", &["stop", &get_service_label()]).await { Ok(_) => {} // status 3 == "no such process" - Err(AnyError::CommandFailed(e)) if e.output.status.code() == Some(3) => {} - Err(e) => return Err(e), + Err(CodeError::CommandFailed { code, .. }) if code == 3 => {} + Err(e) => return Err(wrap(e, "error stopping service").into()), }; info!(self.log, "Successfully stopped service..."); From c53e658dc50968c20a7eab17e49e7038b3694dbb Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Wed, 12 Apr 2023 10:33:08 -0700 Subject: [PATCH 21/59] Restructure notebook search to group by cell (#178593) --- .../contrib/find/findMatchDecorationModel.ts | 1 - .../contrib/search/browser/replaceService.ts | 6 +- .../browser/searchActionsRemoveReplace.ts | 4 +- .../contrib/search/browser/searchModel.ts | 561 ++++++++++++------ .../search/browser/searchNotebookHelpers.ts | 131 ++-- .../search/browser/searchResultsView.ts | 6 +- .../contrib/search/browser/searchView.ts | 30 +- .../contrib/search/browser/searchWidget.ts | 2 +- .../browser/searchNotebookHelpers.test.ts | 4 +- .../search/test/browser/searchResult.test.ts | 66 ++- .../searchEditor/browser/searchEditor.ts | 16 +- .../searchEditor/browser/searchEditorInput.ts | 6 + .../browser/searchEditorSerialization.ts | 35 +- .../services/search/common/queryBuilder.ts | 30 +- .../services/search/common/search.ts | 5 +- 15 files changed, 594 insertions(+), 309 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts index 31e53a8c0f700..fe5d013a6a2b3 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts @@ -107,7 +107,6 @@ export class FindMatchDecorationModel extends Disposable { this._currentMatchCellDecorations = this._notebookEditor.deltaCellDecorations(this._currentMatchCellDecorations, []); } - public setAllFindMatchesDecorations(cellFindMatches: CellFindMatchWithIndex[]) { this._notebookEditor.changeModelDecorations((accessor) => { diff --git a/src/vs/workbench/contrib/search/browser/replaceService.ts b/src/vs/workbench/contrib/search/browser/replaceService.ts index 1d1e46802eda3..d45d1f289ed42 100644 --- a/src/vs/workbench/contrib/search/browser/replaceService.ts +++ b/src/vs/workbench/contrib/search/browser/replaceService.ts @@ -11,7 +11,7 @@ import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IModelService } from 'vs/editor/common/services/model'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { Match, FileMatch, FileMatchOrMatch, ISearchWorkbenchService, NotebookMatch } from 'vs/workbench/contrib/search/browser/searchModel'; +import { Match, FileMatch, FileMatchOrMatch, ISearchWorkbenchService, MatchInNotebook } from 'vs/workbench/contrib/search/browser/searchModel'; import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -198,10 +198,10 @@ export class ReplaceService implements IReplaceService { const edits: ResourceTextEdit[] = []; if (arg instanceof Match) { - if (arg instanceof NotebookMatch) { + if (arg instanceof MatchInNotebook) { if (!arg.isWebviewMatch()) { // only apply edits if it's not a webview match, since webview matches are read-only - const match = arg; + const match = arg; edits.push(this.createEdit(match, match.replaceString, match.cell.uri)); } } else { diff --git a/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts b/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts index c9e36f7d55e11..82c0a9b1a8ee0 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts @@ -13,7 +13,7 @@ import { searchRemoveIcon, searchReplaceIcon } from 'vs/workbench/contrib/search import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; -import { arrayContainsElementOrParent, FileMatch, FolderMatch, Match, NotebookMatch, RenderableMatch, SearchResult } from 'vs/workbench/contrib/search/browser/searchModel'; +import { arrayContainsElementOrParent, FileMatch, FolderMatch, Match, MatchInNotebook, RenderableMatch, SearchResult } from 'vs/workbench/contrib/search/browser/searchModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ISearchConfiguration, ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; @@ -312,7 +312,7 @@ function performReplace(accessor: ServicesAccessor, if (nextFocusElement instanceof Match) { const useReplacePreview = configurationService.getValue().search.useReplacePreview; - if (!useReplacePreview || hasToOpenFile(accessor, nextFocusElement) || nextFocusElement instanceof NotebookMatch) { + if (!useReplacePreview || hasToOpenFile(accessor, nextFocusElement) || nextFocusElement instanceof MatchInNotebook) { viewlet?.open(nextFocusElement, true); } else { accessor.get(IReplaceService).openReplacePreview(nextFocusElement, true); diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index f194626986d16..8961a7bb0479c 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -33,12 +33,12 @@ import { minimapFindMatch, overviewRulerFindMatchForeground } from 'vs/platform/ import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { FindMatchDecorationModel } from 'vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel'; -import { CellEditState, CellFindMatchWithIndex } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, CellFindMatchWithIndex, CellWebviewFindMatch, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; -import { notebookEditorMatchesToTextSearchResults, NotebookMatchInfo, NotebookTextSearchMatch } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; +import { ICellMatch, IFileMatchWithCells, contentMatchesToTextSearchMatches, isIFileMatchWithCells, webviewMatchesToTextSearchMatches } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; import { ReplacePattern } from 'vs/workbench/services/search/common/replace'; import { IFileMatch, IPatternInfo, ISearchComplete, ISearchConfigurationProperties, ISearchProgressItem, ISearchRange, ISearchService, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchPreviewOptions, ITextSearchResult, ITextSearchStats, OneLineRange, QueryType, resultIsMatch, SearchCompletionExitCode, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { addContextToEditorMatches, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; @@ -180,43 +180,127 @@ export class Match { } } -export class NotebookMatch extends Match { - constructor(_parent: FileMatch, _fullPreviewLines: string[], _fullPreviewRange: ISearchRange, _documentRange: ISearchRange, private _notebookMatchInfo: NotebookMatchInfo) { - super(_parent, _fullPreviewLines, _fullPreviewRange, _documentRange); +export class CellMatch { + private _contentMatches: Map; + private _webviewMatches: Map; + private _context: Map; - this._id = this._parent.id() + '>' + this._notebookMatchInfo.cellIndex + '_' + this.notebookMatchTypeString() + this.getRangeString() + this._range + this.getMatchString(); + constructor( + private readonly _parent: FileMatch, + private readonly _cell: ICellViewModel, + private readonly _cellIndex: number, + ) { + + this._contentMatches = new Map(); + this._webviewMatches = new Map(); + this._context = new Map(); } - private notebookMatchTypeString(): string { - return this.isWebviewMatch() ? 'webview' : 'content'; + get context(): Map { + return new Map(this._context); } - private getRangeString(): string { - return `[${this._notebookMatchInfo.matchStartIndex},${this._notebookMatchInfo.matchStartIndex}]`; + matches() { + return [...this._contentMatches.values(), ... this._webviewMatches.values()]; } - public isWebviewMatch() { - return this._notebookMatchInfo.webviewMatchInfo !== undefined; + get contentMatches(): MatchInNotebook[] { + return Array.from(this._contentMatches.values()); } - get cellIndex() { - return this._notebookMatchInfo.cellIndex; + get webviewMatches(): MatchInNotebook[] { + return Array.from(this._webviewMatches.values()); + } + + remove(matches: MatchInNotebook | MatchInNotebook[]): void { + if (!Array.isArray(matches)) { + matches = [matches]; + } + for (const match of matches) { + this._contentMatches.delete(match.id()); + this._webviewMatches.delete(match.id()); + } + } + + addContentMatches(textSearchMatches: ITextSearchMatch[]) { + const contentMatches = textSearchMatchesToNotebookMatches(textSearchMatches, this); + contentMatches.forEach((match) => { + this._contentMatches.set(match.id(), match); + }); + this.addContext(textSearchMatches); + } + + public addContext(textSearchMatches: ITextSearchMatch[]) { + this.cell.resolveTextModel().then((textModel) => { + const textResultsWithContext = addContextToEditorMatches(textSearchMatches, textModel, this.parent.parent().query!); + const contexts = textResultsWithContext.filter((result => !resultIsMatch(result)) as ((a: any) => a is ITextSearchContext)); + contexts.map(context => ({ ...context, lineNumber: context.lineNumber + 1 })) + .forEach((context) => { this._context.set(context.lineNumber, context.text); }); + }); + } + + addWebviewMatches(textSearchMatches: ITextSearchMatch[]) { + const webviewMatches = textSearchMatchesToNotebookMatches(textSearchMatches, this); + webviewMatches.forEach((match) => { + this._webviewMatches.set(match.id(), match); + }); + // TODO: add webview results to context + } + + + get parent(): FileMatch { + return this._parent; + } + + get id(): string { + return this._cell.id; + } + + get cellIndex(): number { + return this._cellIndex; + } + + get cell(): ICellViewModel { + return this._cell; } - get matchStartIndex() { - return this._notebookMatchInfo.matchStartIndex; +} + +export class MatchInNotebook extends Match { + private _webviewIndex: number | undefined; + + constructor(private readonly _cellParent: CellMatch, _fullPreviewLines: string[], _fullPreviewRange: ISearchRange, _documentRange: ISearchRange, webviewIndex?: number) { + super(_cellParent.parent, _fullPreviewLines, _fullPreviewRange, _documentRange); + this._id = this._parent.id() + '>' + this._cellParent.cellIndex + (webviewIndex ? '_' + webviewIndex : '') + '_' + this.notebookMatchTypeString() + this._range + this.getMatchString(); + this._webviewIndex = webviewIndex; } - get matchEndIndex() { - return this._notebookMatchInfo.matchEndIndex; + override parent(): FileMatch { // visible parent in search tree + return this._cellParent.parent; + } + + get cellParent(): CellMatch { + return this._cellParent; + } + + private notebookMatchTypeString(): string { + return this.isWebviewMatch() ? 'webview' : 'content'; + } + + public isWebviewMatch() { + return this._webviewIndex !== undefined; + } + + get cellIndex() { + return this._cellParent.cellIndex; } get webviewIndex() { - return this._notebookMatchInfo.webviewMatchInfo?.index; + return this._webviewIndex; } get cell() { - return this._notebookMatchInfo.cell; + return this._cellParent.cell; } } @@ -256,7 +340,7 @@ export class FileMatch extends Disposable implements IFileMatch { return (selected ? FileMatch._CURRENT_FIND_MATCH : FileMatch._FIND_MATCH); } - private _onChange = this._register(new Emitter<{ didRemove?: boolean; forceUpdateModel?: boolean }>()); + protected _onChange = this._register(new Emitter<{ didRemove?: boolean; forceUpdateModel?: boolean }>()); readonly onChange: Event<{ didRemove?: boolean; forceUpdateModel?: boolean }> = this._onChange.event; private _onDispose = this._register(new Emitter()); @@ -265,24 +349,38 @@ export class FileMatch extends Disposable implements IFileMatch { private _resource: URI; private _fileStat?: IFileStatWithPartialMetadata; private _model: ITextModel | null = null; - private _notebookEditorWidget: NotebookEditorWidget | null = null; private _modelListener: IDisposable | null = null; - private _editorWidgetListener: IDisposable | null = null; - private _matches: Map; - private _removedMatches: Set; + private _textMatches: Map; + private _cellMatches: Map; + + private _removedTextMatches: Set; private _selectedMatch: Match | null = null; private _name: Lazy; private _updateScheduler: RunOnceScheduler; - private _notebookUpdateScheduler: RunOnceScheduler; private _modelDecorations: string[] = []; - private _findMatchDecorationModel: FindMatchDecorationModel | undefined; private _context: Map = new Map(); + public get context(): Map { return new Map(this._context); } + public get cellContext(): Map> { + const cellContext = new Map>(); + this._cellMatches.forEach(cellMatch => { + cellContext.set(cellMatch.id, cellMatch.context); + }); + return cellContext; + } + + // #region notebook fields + private _notebookEditorWidget: NotebookEditorWidget | null = null; + private _editorWidgetListener: IDisposable | null = null; + private _notebookUpdateScheduler: RunOnceScheduler; + private _findMatchDecorationModel: FindMatchDecorationModel | undefined; + // #endregion + constructor( private _query: IPatternInfo, private _previewOptions: ITextSearchPreviewOptions | undefined, @@ -298,38 +396,74 @@ export class FileMatch extends Disposable implements IFileMatch { ) { super(); this._resource = this.rawMatch.resource; - this._matches = new Map(); - this._removedMatches = new Set(); + this._textMatches = new Map(); + this._removedTextMatches = new Set(); this._updateScheduler = new RunOnceScheduler(this.updateMatchesForModel.bind(this), 250); - this._notebookUpdateScheduler = new RunOnceScheduler(this.updateMatchesForEditorWidget.bind(this), 250); this._name = new Lazy(() => labelService.getUriBasenameLabel(this.resource)); + this._cellMatches = new Map(); + this._notebookUpdateScheduler = new RunOnceScheduler(this.updateMatchesForEditorWidget.bind(this), 250); this.createMatches(); } + addWebviewMatchesToCell(cellID: string, webviewMatches: ITextSearchMatch[]) { + const cellMatch = this.getCellMatch(cellID); + if (cellMatch !== undefined) { + cellMatch.addWebviewMatches(webviewMatches); + } + } + + addContentMatchesToCell(cellID: string, contentMatches: ITextSearchMatch[]) { + const cellMatch = this.getCellMatch(cellID); + if (cellMatch !== undefined) { + cellMatch.addContentMatches(contentMatches); + } + } + + getCellMatch(cellID: string): CellMatch | undefined { + return this._cellMatches.get(cellID); + } + + addCellMatch(rawCell: ICellMatch) { + const cellMatch = new CellMatch(this, rawCell.cell, rawCell.index); + this._cellMatches.set(cellMatch.id, cellMatch); + this.addWebviewMatchesToCell(rawCell.cell.id, rawCell.webviewResults); + this.addContentMatchesToCell(rawCell.cell.id, rawCell.contentResults); + } + get closestRoot(): FolderMatchWorkspaceRoot | null { return this._closestRoot; } hasWebviewMatches(): boolean { - return this.matches().some(m => m instanceof NotebookMatch && m.isWebviewMatch()); + return this.matches().some(m => m instanceof MatchInNotebook && m.isWebviewMatch()); } - private async createMatches(): Promise { + + createMatches(): void { const model = this.modelService.getModel(this._resource); - const experimentalNotebooksEnabled = this.configurationService.getValue('search').experimental.notebookSearch; - const notebookEditorWidgetBorrow = experimentalNotebooksEnabled ? this.notebookEditorService.retrieveExistingWidgetFromURI(this._resource) : undefined; - if (notebookEditorWidgetBorrow?.value) { - await this.bindNotebookEditorWidget(notebookEditorWidgetBorrow.value); - } else if (model) { + if (model) { this.bindModel(model); this.updateMatchesForModel(); } else { - this.rawMatch.results! - .filter(resultIsMatch) - .forEach(rawMatch => { - textSearchResultToMatches(rawMatch, this) - .forEach(m => this.add(m)); - }); + const experimentalNotebooksEnabled = this.configurationService.getValue('search').experimental.notebookSearch; + const notebookEditorWidgetBorrow = experimentalNotebooksEnabled ? this.notebookEditorService.retrieveExistingWidgetFromURI(this.resource) : undefined; + + if (notebookEditorWidgetBorrow?.value) { + this.bindNotebookEditorWidget(notebookEditorWidgetBorrow.value); + } + if (this.rawMatch.results) { + this.rawMatch.results + .filter(resultIsMatch) + .forEach(rawMatch => { + textSearchResultToMatches(rawMatch, this) + .forEach(m => this.add(m)); + }); + } + if (isIFileMatchWithCells(this.rawMatch)) { + this.rawMatch.cellResults?.forEach(cell => this.addCellMatch(cell)); + this.setNotebookFindMatchDecorationsUsingCellMatches(this.cellMatches()); + this._onChange.fire({ forceUpdateModel: true }); + } this.addContext(this.rawMatch.results); } } @@ -360,52 +494,13 @@ export class FileMatch extends Disposable implements IFileMatch { } } - async bindNotebookEditorWidget(widget: NotebookEditorWidget) { - - if (this._notebookEditorWidget === widget) { - // ensure that the matches are up to date, but everything else should be configured already. - // TODO: try to reduce calls that occur when the notebook hasn't loaded yet - await this.updateMatchesForEditorWidget(); - return; - } - - this._notebookEditorWidget = widget; - - this._editorWidgetListener = this._notebookEditorWidget.textModel?.onDidChangeContent((e) => { - if (!e.rawEvents.some(event => event.kind === NotebookCellsChangeType.ChangeCellContent || event.kind === NotebookCellsChangeType.ModelChange)) { - return; - } - this._notebookUpdateScheduler.schedule(); - }) ?? null; - await this.updateMatchesForEditorWidget(); - } - - unbindNotebookEditorWidget(widget?: NotebookEditorWidget) { - if (widget && this._notebookEditorWidget !== widget) { - return; - } - - if (this._notebookEditorWidget) { - this._notebookUpdateScheduler.cancel(); - this._findMatchDecorationModel?.dispose(); - this._findMatchDecorationModel = undefined; - this._editorWidgetListener?.dispose(); - } - - if (this._findMatchDecorationModel) { - this._findMatchDecorationModel?.dispose(); - this._findMatchDecorationModel = undefined; - } - this._notebookEditorWidget = null; - } - private updateMatchesForModel(): void { // this is called from a timeout and might fire // after the model has been disposed if (!this._model) { return; } - this._matches = new Map(); + this._textMatches = new Map(); const wordSeparators = this._query.isWordMatch && this._query.wordSeparators ? this._query.wordSeparators : null; const matches = this._model @@ -414,33 +509,9 @@ export class FileMatch extends Disposable implements IFileMatch { this.updateMatches(matches, true, this._model); } - private async updateMatchesForEditorWidget(): Promise { - if (!this._notebookEditorWidget) { - return; - } - - this._findMatchDecorationModel?.dispose(); - this._findMatchDecorationModel = new FindMatchDecorationModel(this._notebookEditorWidget); - - this._matches = new Map(); - - const wordSeparators = this._query.isWordMatch && this._query.wordSeparators ? this._query.wordSeparators : null; - const allMatches = await this._notebookEditorWidget - .find(this._query.pattern, { - regex: this._query.isRegExp, - wholeWord: this._query.isWordMatch, - caseSensitive: this._query.isCaseSensitive, - wordSeparators: wordSeparators ?? undefined, - includeMarkupInput: this._query.notebookInfo?.isInNotebookMarkdownInput, - includeMarkupPreview: !this._query.notebookInfo?.isInNotebookMarkdownInput, - includeCodeInput: this._query.notebookInfo?.isInNotebookCellInput, - includeOutput: this._query.notebookInfo?.isInNotebookCellOutput, - }, CancellationToken.None, false, true); - this.updateNotebookMatches(allMatches, true); - } - private async updatesMatchesForLineAfterReplace(lineNumber: number, modelChange: boolean): Promise { + protected async updatesMatchesForLineAfterReplace(lineNumber: number, modelChange: boolean): Promise { if (!this._model) { return; } @@ -450,37 +521,23 @@ export class FileMatch extends Disposable implements IFileMatch { endLineNumber: lineNumber, endColumn: this._model.getLineMaxColumn(lineNumber) }; - const oldMatches = Array.from(this._matches.values()).filter(match => match.range().startLineNumber === lineNumber); - oldMatches.forEach(match => this._matches.delete(match.id())); + const oldMatches = Array.from(this._textMatches.values()).filter(match => match.range().startLineNumber === lineNumber); + oldMatches.forEach(match => this._textMatches.delete(match.id())); const wordSeparators = this._query.isWordMatch && this._query.wordSeparators ? this._query.wordSeparators : null; const matches = this._model.findMatches(this._query.pattern, range, !!this._query.isRegExp, !!this._query.isCaseSensitive, wordSeparators, false, this._maxResults ?? Number.MAX_SAFE_INTEGER); this.updateMatches(matches, modelChange, this._model); - await this.updateMatchesForEditorWidget(); + + // await this.updateMatchesForEditorWidget(); } - private updateNotebookMatches(matches: CellFindMatchWithIndex[], modelChange: boolean): void { - const textSearchResults = notebookEditorMatchesToTextSearchResults(matches, this._previewOptions); - textSearchResults.forEach(textSearchResult => { - textSearchResultToNotebookMatches(textSearchResult, this).forEach(match => { - if (!this._removedMatches.has(match.id())) { - this.add(match); - if (this.isMatchSelected(match)) { - this._selectedMatch = match; - } - } - }); - }); - this._findMatchDecorationModel?.setAllFindMatchesDecorations(matches); - this._onChange.fire({ forceUpdateModel: modelChange }); - } private updateMatches(matches: FindMatch[], modelChange: boolean, model: ITextModel): void { const textSearchResults = editorMatchesToTextSearchResults(matches, model, this._previewOptions); textSearchResults.forEach(textSearchResult => { textSearchResultToMatches(textSearchResult, this).forEach(match => { - if (!this._removedMatches.has(match.id())) { + if (!this._removedTextMatches.has(match.id())) { this.add(match); if (this.isMatchSelected(match)) { this._selectedMatch = match; @@ -525,7 +582,16 @@ export class FileMatch extends Disposable implements IFileMatch { } matches(): Match[] { - return Array.from(this._matches.values()); + const cellMatches: MatchInNotebook[] = Array.from(this._cellMatches.values()).flatMap((e) => e.matches()); + return [...this._textMatches.values(), ...cellMatches]; + } + + textMatches(): Match[] { + return Array.from(this._textMatches.values()); + } + + cellMatches(): CellMatch[] { + return Array.from(this._cellMatches.values()); } remove(matches: Match | Match[]): void { @@ -535,7 +601,7 @@ export class FileMatch extends Disposable implements IFileMatch { for (const match of matches) { this.removeMatch(match); - this._removedMatches.add(match.id()); + this._removedTextMatches.add(match.id()); } this._onChange.fire({ didRemove: true }); @@ -551,7 +617,7 @@ export class FileMatch extends Disposable implements IFileMatch { setSelectedMatch(match: Match | null): void { if (match) { - if (!this._matches.has(match.id())) { + if (!this._textMatches.has(match.id())) { return; } if (this.isMatchSelected(match)) { @@ -586,25 +652,39 @@ export class FileMatch extends Disposable implements IFileMatch { addContext(results: ITextSearchResult[] | undefined) { if (!results) { return; } - results - .filter((result => !resultIsMatch(result)) as ((a: any) => a is ITextSearchContext)) - .forEach(context => this._context.set(context.lineNumber, context.text)); + const contexts = results + .filter((result => + !resultIsMatch(result)) as ((a: any) => a is ITextSearchContext)); + + return contexts.forEach(context => this._context.set(context.lineNumber, context.text)); } add(match: Match, trigger?: boolean) { - this._matches.set(match.id(), match); + this._textMatches.set(match.id(), match); if (trigger) { this._onChange.fire({ forceUpdateModel: true }); } } private removeMatch(match: Match) { - this._matches.delete(match.id()); + + if (match instanceof MatchInNotebook) { + match.cellParent.remove(match); + if (match.cellParent.matches().length === 0) { + this._cellMatches.delete(match.cellParent.id); + } + } else { + this._textMatches.delete(match.id()); + } if (this.isMatchSelected(match)) { this.setSelectedMatch(null); + this._findMatchDecorationModel?.clearCurrentFindMatchDecoration(); } else { this.updateHighlights(); } + if (match instanceof MatchInNotebook) { + this.setNotebookFindMatchDecorationsUsingCellMatches(this.cellMatches()); + } } async resolveFileStat(fileService: IFileService): Promise { @@ -627,12 +707,111 @@ export class FileMatch extends Disposable implements IFileMatch { super.dispose(); } - public async showMatch(match: NotebookMatch) { + // #region strictly notebook methods + bindNotebookEditorWidget(widget: NotebookEditorWidget) { + if (this._notebookEditorWidget === widget) { + return; + } + + this._notebookEditorWidget = widget; + + this._editorWidgetListener = this._notebookEditorWidget.textModel?.onDidChangeContent((e) => { + if (!e.rawEvents.some(event => event.kind === NotebookCellsChangeType.ChangeCellContent || event.kind === NotebookCellsChangeType.ModelChange)) { + return; + } + this._notebookUpdateScheduler.schedule(); + }) ?? null; + + this._findMatchDecorationModel?.dispose(); + this._findMatchDecorationModel = new FindMatchDecorationModel(this._notebookEditorWidget); + } + + unbindNotebookEditorWidget(widget?: NotebookEditorWidget) { + if (widget && this._notebookEditorWidget !== widget) { + return; + } + + if (this._notebookEditorWidget) { + this._notebookUpdateScheduler.cancel(); + this._findMatchDecorationModel?.dispose(); + this._findMatchDecorationModel = undefined; + this._editorWidgetListener?.dispose(); + } + + if (this._findMatchDecorationModel) { + this._findMatchDecorationModel?.dispose(); + this._findMatchDecorationModel = undefined; + } + this._notebookEditorWidget = null; + } + private updateNotebookMatches(matches: CellFindMatchWithIndex[], modelChange: boolean): void { + matches.forEach(match => { + let cell = this._cellMatches.get(match.cell.id); + if (!cell) { + cell = new CellMatch(this, match.cell, match.index); + } + cell.addContentMatches(contentMatchesToTextSearchMatches(match.contentMatches, match.cell)); + cell.addWebviewMatches(webviewMatchesToTextSearchMatches(match.webviewMatches)); + this._cellMatches.set(cell.id, cell); + + }); + + this._findMatchDecorationModel?.setAllFindMatchesDecorations(matches); + this._onChange.fire({ forceUpdateModel: modelChange }); + } + + private setNotebookFindMatchDecorationsUsingCellMatches(cells: CellMatch[]): void { + if (!this._findMatchDecorationModel) { + return; + } + const cellFindMatch: CellFindMatchWithIndex[] = cells.map((cell) => { + const webviewMatches: CellWebviewFindMatch[] = cell.webviewMatches.map(match => { + return { + index: match.webviewIndex, + }; + }); + const findMatches: FindMatch[] = cell.contentMatches.map(match => { + return new FindMatch(match.range(), [match.text()]); + }); + return { + cell: cell.cell, + index: cell.cellIndex, + contentMatches: findMatches, + webviewMatches: webviewMatches + }; + }); + this._findMatchDecorationModel.setAllFindMatchesDecorations(cellFindMatch); + } + async updateMatchesForEditorWidget(): Promise { + if (!this._notebookEditorWidget) { + return; + } + + this._textMatches = new Map(); + + const wordSeparators = this._query.isWordMatch && this._query.wordSeparators ? this._query.wordSeparators : null; + const allMatches = await this._notebookEditorWidget + .find(this._query.pattern, { + regex: this._query.isRegExp, + wholeWord: this._query.isWordMatch, + caseSensitive: this._query.isCaseSensitive, + wordSeparators: wordSeparators ?? undefined, + includeMarkupInput: this._query.notebookInfo?.isInNotebookMarkdownInput, + includeMarkupPreview: !this._query.notebookInfo?.isInNotebookMarkdownInput, + includeCodeInput: this._query.notebookInfo?.isInNotebookCellInput, + includeOutput: this._query.notebookInfo?.isInNotebookCellOutput, + }, CancellationToken.None, false, true); + + this.updateNotebookMatches(allMatches, true); + } + + public async showMatch(match: MatchInNotebook) { const offset = await this.highlightCurrentFindMatchDecoration(match); + this.setSelectedMatch(match); this.revealCellRange(match, offset); } - private async highlightCurrentFindMatchDecoration(match: NotebookMatch): Promise { + private async highlightCurrentFindMatchDecoration(match: MatchInNotebook): Promise { if (!this._findMatchDecorationModel) { return null; } @@ -643,7 +822,7 @@ export class FileMatch extends Disposable implements IFileMatch { } } - private revealCellRange(match: NotebookMatch, outputOffset: number | null) { + private revealCellRange(match: MatchInNotebook, outputOffset: number | null) { if (!this._notebookEditorWidget) { return; } @@ -658,6 +837,8 @@ export class FileMatch extends Disposable implements IFileMatch { this._notebookEditorWidget.revealRangeInCenterIfOutsideViewportAsync(match.cell, match.range()); } } + + //#endregion } export interface IChangeEvent { @@ -757,7 +938,8 @@ export class FolderMatch extends Disposable { const fileMatch = this._fileMatches.get(resource); if (fileMatch) { - await fileMatch.bindNotebookEditorWidget(editor); + fileMatch.bindNotebookEditorWidget(editor); + await fileMatch.updateMatchesForEditorWidget(); } else { const folderMatches = this.folderMatchesIterator(); for (const elem of folderMatches) { @@ -897,6 +1079,20 @@ export class FolderMatch extends Disposable { textSearchResultToMatches(m, existingFileMatch) .forEach(m => existingFileMatch.add(m)); }); + + // add cell matches + if (isIFileMatchWithCells(rawFileMatch)) { + rawFileMatch.cellResults?.forEach(rawCellMatch => { + const existingCellMatch = existingFileMatch.getCellMatch(rawCellMatch.cell.id); + if (existingCellMatch) { + existingCellMatch.addContentMatches(rawCellMatch.contentResults); + existingCellMatch.addContentMatches(rawCellMatch.webviewResults); + } else { + existingFileMatch.addCellMatch(rawCellMatch); + } + }); + } + updated.push(existingFileMatch); existingFileMatch.addContext(rawFileMatch.results); @@ -1064,7 +1260,7 @@ export class FolderMatchWorkspaceRoot extends FolderMatchWithResource { @IInstantiationService instantiationService: IInstantiationService, @ILabelService labelService: ILabelService, @IUriIdentityService uriIdentityService: IUriIdentityService, - @ILogService private readonly _logService: ILogService, + @ILogService private readonly _logService: ILogService ) { super(_resource, _id, _index, _query, _parent, _searchModel, null, replaceService, instantiationService, labelService, uriIdentityService); } @@ -1078,7 +1274,16 @@ export class FolderMatchWorkspaceRoot extends FolderMatchWithResource { } private createFileMatch(query: IPatternInfo, previewOptions: ITextSearchPreviewOptions | undefined, maxResults: number | undefined, parent: FolderMatch, rawFileMatch: IFileMatch, closestRoot: FolderMatchWorkspaceRoot | null,): FileMatch { - const fileMatch = this.instantiationService.createInstance(FileMatch, query, previewOptions, maxResults, parent, rawFileMatch, closestRoot); + const fileMatch = + this.instantiationService.createInstance( + FileMatch, + query, + previewOptions, + maxResults, + parent, + rawFileMatch, + closestRoot + ); parent.doAddFile(fileMatch); const disposable = fileMatch.onChange(({ didRemove }) => parent.onFileChange(fileMatch, didRemove)); fileMatch.onDispose(() => disposable.dispose()); @@ -1141,13 +1346,20 @@ export class FolderMatchNoRoot extends FolderMatch { @IReplaceService replaceService: IReplaceService, @IInstantiationService instantiationService: IInstantiationService, @ILabelService labelService: ILabelService, - @IUriIdentityService uriIdentityService: IUriIdentityService + @IUriIdentityService uriIdentityService: IUriIdentityService, + ) { super(null, _id, _index, _query, _parent, _searchModel, null, replaceService, instantiationService, labelService, uriIdentityService); } createAndConfigureFileMatch(rawFileMatch: IFileMatch): FileMatch { - const fileMatch = this.instantiationService.createInstance(FileMatch, this._query.contentPattern, this._query.previewOptions, this._query.maxResults, this, rawFileMatch, null); + const fileMatch = this.instantiationService.createInstance( + FileMatch, + this._query.contentPattern, + this._query.previewOptions, + this._query.maxResults, + this, rawFileMatch, + null); this.doAddFile(fileMatch); const disposable = fileMatch.onChange(({ didRemove }) => this.onFileChange(fileMatch, didRemove)); fileMatch.onDispose(() => disposable.dispose()); @@ -1220,7 +1432,7 @@ export function searchMatchComparer(elementA: RenderableMatch, elementB: Rendera } } - if (elementA instanceof NotebookMatch && elementB instanceof NotebookMatch) { + if (elementA instanceof MatchInNotebook && elementB instanceof MatchInNotebook) { return compareNotebookPos(elementA, elementB); } @@ -1231,19 +1443,13 @@ export function searchMatchComparer(elementA: RenderableMatch, elementB: Rendera return 0; } -export function compareNotebookPos(match1: NotebookMatch, match2: NotebookMatch): number { +export function compareNotebookPos(match1: MatchInNotebook, match2: MatchInNotebook): number { if (match1.cellIndex === match2.cellIndex) { if (match1.webviewIndex !== undefined && match2.webviewIndex !== undefined) { return match1.webviewIndex - match2.webviewIndex; } else if (match1.webviewIndex === undefined && match2.webviewIndex === undefined) { - if (match1.matchStartIndex === match2.matchStartIndex) { - return match1.matchEndIndex - match2.matchEndIndex; - } else if (match1.matchStartIndex < match2.matchStartIndex) { - return -1; - } else { - return 1; - } + return Range.compareRangesUsingStarts(match1.range(), match2.range()); } else { // webview matches should always be after content matches if (match1.webviewIndex !== undefined) { @@ -1741,8 +1947,8 @@ export class SearchModel extends Disposable { return this._searchResult; } - private async getLocalNotebookResults(query: ITextQuery, token: CancellationToken): Promise<{ results: ResourceMap; limitHit: boolean }> { - const localResults = new ResourceMap(uri => this.uriIdentityService.extUri.getComparisonKey(uri)); + private async getLocalNotebookResults(query: ITextQuery, token: CancellationToken): Promise<{ results: ResourceMap; limitHit: boolean }> { + const localResults = new ResourceMap(uri => this.uriIdentityService.extUri.getComparisonKey(uri)); let limitHit = false; if (query.type === QueryType.Text) { @@ -1771,7 +1977,20 @@ export class SearchModel extends Disposable { limitHit = true; matches = matches.slice(0, askMax - 1); } - const fileMatch = { resource: widget.viewModel.uri, results: notebookEditorMatchesToTextSearchResults(matches, query.previewOptions) }; + const cellResults: ICellMatch[] = matches.map(match => { + const contentResults = contentMatchesToTextSearchMatches(match.contentMatches, match.cell); + const webviewResults = webviewMatchesToTextSearchMatches(match.webviewMatches); + return { + cell: match.cell, + index: match.index, + contentResults: contentResults, + webviewResults: webviewResults, + }; + }); + + const fileMatch: IFileMatchWithCells = { + resource: widget.viewModel.uri, cellResults: cellResults + }; localResults.set(widget.viewModel.uri, fileMatch); } else { localResults.set(widget.viewModel.uri, null); @@ -2077,19 +2296,25 @@ function textSearchResultToMatches(rawMatch: ITextSearchMatch, fileMatch: FileMa } } -function textSearchResultToNotebookMatches(rawMatch: NotebookTextSearchMatch, fileMatch: FileMatch): NotebookMatch[] { - const previewLines = rawMatch.preview.text.split('\n'); - if (Array.isArray(rawMatch.ranges)) { - - return rawMatch.ranges.map((r, i) => { - const previewRange: ISearchRange = (rawMatch.preview.matches)[i]; - return new NotebookMatch(fileMatch, previewLines, previewRange, r, rawMatch.notebookMatchInfo); - }); - } else { - const previewRange = rawMatch.preview.matches; - const match = new NotebookMatch(fileMatch, previewLines, previewRange, rawMatch.ranges, rawMatch.notebookMatchInfo); - return [match]; - } +// text search to notebook matches + +export function textSearchMatchesToNotebookMatches(textSearchMatches: ITextSearchMatch[], cell: CellMatch): MatchInNotebook[] { + const notebookMatches: MatchInNotebook[] = []; + textSearchMatches.map((textSearchMatch) => { + const previewLines = textSearchMatch.preview.text.split('\n'); + if (Array.isArray(textSearchMatch.ranges)) { + textSearchMatch.ranges.forEach((r, i) => { + const previewRange: ISearchRange = (textSearchMatch.preview.matches)[i]; + const match = new MatchInNotebook(cell, previewLines, previewRange, r, textSearchMatch.webviewIndex); + notebookMatches.push(match); + }); + } else { + const previewRange = textSearchMatch.preview.matches; + const match = new MatchInNotebook(cell, previewLines, previewRange, textSearchMatch.ranges, textSearchMatch.webviewIndex); + notebookMatches.push(match); + } + }); + return notebookMatches; } export function arrayContainsElementOrParent(element: RenderableMatch, testArray: RenderableMatch[]): boolean { diff --git a/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts b/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts index 84c48e8a41279..cefeda6ce1d90 100644 --- a/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts +++ b/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts @@ -4,98 +4,73 @@ *--------------------------------------------------------------------------------------------*/ import { FindMatch } from 'vs/editor/common/model'; -import { CellFindMatchWithIndex, ICellViewModel, CellWebviewFindMatch } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; - -import { ISearchRange, ITextSearchPreviewOptions, TextSearchMatch } from 'vs/workbench/services/search/common/search'; +import { CellWebviewFindMatch, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { IFileMatch, ITextSearchMatch, TextSearchMatch } from 'vs/workbench/services/search/common/search'; import { Range } from 'vs/editor/common/core/range'; -export interface NotebookMatchInfo { - cellIndex: number; - matchStartIndex: number; - matchEndIndex: number; - cell: ICellViewModel; - webviewMatchInfo?: { - index: number; - }; +export interface IFileMatchWithCells extends IFileMatch { + cellResults: ICellMatch[]; } -interface CellFindMatchInfoForTextModel { - notebookMatchInfo: NotebookMatchInfo; - matches: FindMatch[] | CellWebviewFindMatch; +export interface ICellMatch { + cell: ICellViewModel; + index: number; + contentResults: ITextSearchMatch[]; + webviewResults: ITextSearchMatch[]; } - -export class NotebookTextSearchMatch extends TextSearchMatch { - constructor(text: string, range: ISearchRange | ISearchRange[], public notebookMatchInfo: NotebookMatchInfo, previewOptions?: ITextSearchPreviewOptions) { - super(text, range, previewOptions); - } +export function isIFileMatchWithCells(object: IFileMatch): object is IFileMatchWithCells { + return 'cellResults' in object; } -function notebookEditorMatchToTextSearchResult(cellInfo: CellFindMatchInfoForTextModel, previewOptions?: ITextSearchPreviewOptions): NotebookTextSearchMatch | undefined { - const matches = cellInfo.matches; +// to text search results - if (Array.isArray(matches)) { - if (matches.length > 0) { - const lineTexts: string[] = []; - const firstLine = matches[0].range.startLineNumber; - const lastLine = matches[matches.length - 1].range.endLineNumber; - for (let i = firstLine; i <= lastLine; i++) { - lineTexts.push(cellInfo.notebookMatchInfo.cell.textBuffer.getLineContent(i)); - } - - return new NotebookTextSearchMatch( - lineTexts.join('\n') + '\n', - matches.map(m => new Range(m.range.startLineNumber - 1, m.range.startColumn - 1, m.range.endLineNumber - 1, m.range.endColumn - 1)), - cellInfo.notebookMatchInfo, - previewOptions); - } - } - else { - // TODO: this is a placeholder for webview matches - const searchPreviewInfo = matches.searchPreviewInfo ?? { - line: '', range: { start: 0, end: 0 } - }; - - return new NotebookTextSearchMatch( - searchPreviewInfo.line, - new Range(0, searchPreviewInfo.range.start, 0, searchPreviewInfo.range.end), - cellInfo.notebookMatchInfo, - previewOptions); - } - return undefined; -} -export function notebookEditorMatchesToTextSearchResults(cellFindMatches: CellFindMatchWithIndex[], previewOptions?: ITextSearchPreviewOptions): NotebookTextSearchMatch[] { +export function contentMatchesToTextSearchMatches(contentMatches: FindMatch[], cell: ICellViewModel): ITextSearchMatch[] { let previousEndLine = -1; - const groupedMatches: CellFindMatchInfoForTextModel[] = []; - let currentMatches: FindMatch[] = []; - let startIndexOfCurrentMatches = 0; - + const contextGroupings: FindMatch[][] = []; + let currentContextGrouping: FindMatch[] = []; - cellFindMatches.forEach((cellFindMatch) => { - const cellIndex = cellFindMatch.index; - cellFindMatch.contentMatches.forEach((match, index) => { - if (match.range.startLineNumber !== previousEndLine) { - if (currentMatches.length > 0) { - groupedMatches.push({ matches: [...currentMatches], notebookMatchInfo: { cellIndex, matchStartIndex: startIndexOfCurrentMatches, matchEndIndex: index, cell: cellFindMatch.cell } }); - currentMatches = []; - } - startIndexOfCurrentMatches = cellIndex + 1; + contentMatches.forEach((match) => { + if (match.range.startLineNumber !== previousEndLine) { + if (currentContextGrouping.length > 0) { + contextGroupings.push([...currentContextGrouping]); + currentContextGrouping = []; } + } - currentMatches.push(match); - previousEndLine = match.range.endLineNumber; - }); + currentContextGrouping.push(match); + previousEndLine = match.range.endLineNumber; + }); - if (currentMatches.length > 0) { - groupedMatches.push({ matches: [...currentMatches], notebookMatchInfo: { cellIndex, matchStartIndex: startIndexOfCurrentMatches, matchEndIndex: cellFindMatch.contentMatches.length - 1, cell: cellFindMatch.cell } }); - currentMatches = []; - } + if (currentContextGrouping.length > 0) { + contextGroupings.push([...currentContextGrouping]); + } - cellFindMatch.webviewMatches.forEach((match, index) => { - groupedMatches.push({ matches: match, notebookMatchInfo: { cellIndex, matchStartIndex: index, matchEndIndex: index, cell: cellFindMatch.cell, webviewMatchInfo: { index: match.index } } }); - }); + const textSearchResults = contextGroupings.map((grouping) => { + const lineTexts: string[] = []; + const firstLine = grouping[0].range.startLineNumber; + const lastLine = grouping[grouping.length - 1].range.endLineNumber; + for (let i = firstLine; i <= lastLine; i++) { + lineTexts.push(cell.textBuffer.getLineContent(i)); + } + return new TextSearchMatch( + lineTexts.join('\n') + '\n', + grouping.map(m => new Range(m.range.startLineNumber - 1, m.range.startColumn - 1, m.range.endLineNumber - 1, m.range.endColumn - 1)), + ); }); - return groupedMatches.map(sameLineMatches => { - return notebookEditorMatchToTextSearchResult(sameLineMatches, previewOptions); - }).filter((elem): elem is NotebookTextSearchMatch => !!elem); + return textSearchResults; +} + +export function webviewMatchesToTextSearchMatches(webviewMatches: CellWebviewFindMatch[]): ITextSearchMatch[] { + return webviewMatches + .map(rawMatch => + (rawMatch.searchPreviewInfo) ? + new TextSearchMatch( + rawMatch.searchPreviewInfo.line, + new Range(0, rawMatch.searchPreviewInfo.range.start, 0, rawMatch.searchPreviewInfo.range.end), + undefined, + rawMatch.index) : undefined + ).filter((e): e is ITextSearchMatch => !!e); } + + diff --git a/src/vs/workbench/contrib/search/browser/searchResultsView.ts b/src/vs/workbench/contrib/search/browser/searchResultsView.ts index 910ed817280af..06479dcff3f7e 100644 --- a/src/vs/workbench/contrib/search/browser/searchResultsView.ts +++ b/src/vs/workbench/contrib/search/browser/searchResultsView.ts @@ -18,7 +18,7 @@ import { ISearchConfigurationProperties } from 'vs/workbench/services/search/com import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; -import { FileMatch, Match, RenderableMatch, SearchModel, FolderMatch, FolderMatchNoRoot, FolderMatchWorkspaceRoot, NotebookMatch } from 'vs/workbench/contrib/search/browser/searchModel'; +import { FileMatch, Match, RenderableMatch, SearchModel, FolderMatch, FolderMatchNoRoot, FolderMatchWorkspaceRoot, MatchInNotebook } from 'vs/workbench/contrib/search/browser/searchModel'; import { isEqual } from 'vs/base/common/resources'; import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; @@ -322,7 +322,7 @@ export class MatchRenderer extends Disposable implements ICompressibleTreeRender renderElement(node: ITreeNode, index: number, templateData: IMatchTemplate): void { const match = node.element; const preview = match.preview(); - const replace = this.searchModel.isReplaceActive() && !!this.searchModel.replaceString && !(match instanceof NotebookMatch && match.isWebviewMatch()); + const replace = this.searchModel.isReplaceActive() && !!this.searchModel.replaceString && !(match instanceof MatchInNotebook && match.isWebviewMatch()); templateData.before.textContent = preview.before; templateData.match.textContent = preview.inside; @@ -331,7 +331,7 @@ export class MatchRenderer extends Disposable implements ICompressibleTreeRender templateData.after.textContent = preview.after; templateData.parent.title = (preview.before + (replace ? match.replaceString : preview.inside) + preview.after).trim().substr(0, 999); - IsEditableItemKey.bindTo(templateData.contextKeyService).set(!(match instanceof NotebookMatch && match.isWebviewMatch())); + IsEditableItemKey.bindTo(templateData.contextKeyService).set(!(match instanceof MatchInNotebook && match.isWebviewMatch())); const numLines = match.range().endLineNumber - match.range().startLineNumber; const extraLinesStr = numLines > 0 ? `+${numLines}` : ''; diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 6e266d4a8abd6..5953f4fbed725 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -71,7 +71,7 @@ import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; import { getOutOfWorkspaceEditorResources, SearchStateKey, SearchUIState } from 'vs/workbench/contrib/search/common/search'; import { ISearchHistoryService, ISearchHistoryValues } from 'vs/workbench/contrib/search/common/searchHistoryService'; -import { FileMatch, FileMatchOrMatch, FolderMatch, FolderMatchWithResource, IChangeEvent, ISearchWorkbenchService, Match, NotebookMatch, RenderableMatch, searchMatchComparer, SearchModel, SearchResult } from 'vs/workbench/contrib/search/browser/searchModel'; +import { FileMatch, FileMatchOrMatch, FolderMatch, FolderMatchWithResource, IChangeEvent, ISearchWorkbenchService, Match, MatchInNotebook, RenderableMatch, searchMatchComparer, SearchModel, SearchResult } from 'vs/workbench/contrib/search/browser/searchModel'; import { createEditorFromSearchResult } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IPreferencesService, ISettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences'; @@ -500,7 +500,7 @@ export class SearchView extends ViewPane { this._register(this.searchWidget.onSearchSubmit(options => this.triggerQueryChange(options))); this._register(this.searchWidget.onSearchCancel(({ focus }) => this.cancelSearch(focus))); this._register(this.searchWidget.searchInput.onDidOptionChange(() => this.triggerQueryChange())); - this._register(this.searchWidget.getFilters().onDidChange(() => this.triggerQueryChange())); + this._register(this.searchWidget.getNotebookFilters().onDidChange(() => this.triggerQueryChange())); const updateHasPatternKey = () => this.hasSearchPatternKey.set(this.searchWidget.searchInput ? (this.searchWidget.searchInput.getValue().length > 0) : false); updateHasPatternKey(); @@ -852,7 +852,7 @@ export class SearchView extends ViewPane { } // we don't need to check experimental flag here because NotebookMatches only exist when the flag is enabled - const editable = (!(focus instanceof NotebookMatch)) || !focus.isWebviewMatch(); + const editable = (!(focus instanceof MatchInNotebook)) || !focus.isWebviewMatch(); this.isEditableItem.set(editable); })); @@ -1409,9 +1409,9 @@ export class SearchView extends ViewPane { } const isRegex = this.searchWidget.searchInput.getRegex(); - const isInNotebookMarkdownInput = this.searchWidget.getFilters().markupInput; - const isInNotebookCellInput = this.searchWidget.getFilters().codeInput; - const isInNotebookCellOutput = this.searchWidget.getFilters().codeOutput; + const isInNotebookMarkdownInput = this.searchWidget.getNotebookFilters().markupInput; + const isInNotebookCellInput = this.searchWidget.getNotebookFilters().codeInput; + const isInNotebookCellOutput = this.searchWidget.getNotebookFilters().codeOutput; const isWholeWords = this.searchWidget.searchInput.getWholeWords(); const isCaseSensitive = this.searchWidget.searchInput.getCaseSensitive(); @@ -1813,7 +1813,7 @@ export class SearchView extends ViewPane { // Since untitled files are already open, then untitled notebooks should return NotebookMatch results. // notebookMatch are only created when search.experimental.notebookSearch is enabled, so this should never return true if experimental flag is disabled. - return match instanceof NotebookMatch || (uri.scheme !== network.Schemas.untitled && this.notebookService.getContributedNotebookTypes(uri).length > 0); + return match instanceof MatchInNotebook || (uri.scheme !== network.Schemas.untitled && this.notebookService.getContributedNotebookTypes(uri).length > 0); } private onFocus(lineMatch: Match, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { @@ -1856,24 +1856,26 @@ export class SearchView extends ViewPane { } if (editor instanceof NotebookEditor) { + const elemParent = element.parent() as FileMatch; const experimentalNotebooksEnabled = this.configurationService.getValue('search').experimental.notebookSearch; if (experimentalNotebooksEnabled) { if (element instanceof Match) { - if (element instanceof NotebookMatch) { + if (element instanceof MatchInNotebook) { element.parent().showMatch(element); } else { const editorWidget = editor.getControl(); if (editorWidget) { // Ensure that the editor widget is binded. If if is, then this should return immediately. // Otherwise, it will bind the widget. - await element.parent().bindNotebookEditorWidget(editorWidget); + await elemParent.bindNotebookEditorWidget(editorWidget); + await elemParent.updateMatchesForEditorWidget(); const matchIndex = oldParentMatches.findIndex(e => e.id() === element.id()); const matches = element.parent().matches(); const match = matchIndex >= matches.length ? matches[matches.length - 1] : matches[matchIndex]; - if (match instanceof NotebookMatch) { - element.parent().showMatch(match); + if (match instanceof MatchInNotebook) { + elemParent.showMatch(match); } if (!this.tree.getFocus().includes(match) || !this.tree.getSelection().includes(match)) { @@ -2006,9 +2008,9 @@ export class SearchView extends ViewPane { const isCaseSensitive = this.searchWidget.searchInput.getCaseSensitive(); const contentPattern = this.searchWidget.searchInput.getValue(); - const isInNotebookCellInput = this.searchWidget.getFilters().codeInput; - const isInNotebookCellOutput = this.searchWidget.getFilters().codeOutput; - const isInNotebookMarkdownInput = this.searchWidget.getFilters().markupInput; + const isInNotebookCellInput = this.searchWidget.getNotebookFilters().codeInput; + const isInNotebookCellOutput = this.searchWidget.getNotebookFilters().codeOutput; + const isInNotebookMarkdownInput = this.searchWidget.getNotebookFilters().markupInput; this.viewletState['query.contentPattern'] = contentPattern; this.viewletState['query.regex'] = isRegex; diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index f648d7dcc83a4..b099e4627ca0a 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -229,7 +229,7 @@ export class SearchWidget extends Widget { return editors.some(editor => editor instanceof NotebookEditorInput); } - getFilters() { + getNotebookFilters() { return this._notebookFilters; } diff --git a/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts b/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts index 96c3c4b59f800..7bd66dfc9a6bc 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts @@ -6,10 +6,10 @@ import * as assert from 'assert'; import { Range } from 'vs/editor/common/core/range'; import { FindMatch, IReadonlyTextBuffer } from 'vs/editor/common/model'; -import { notebookEditorMatchesToTextSearchResults } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; import { ISearchRange } from 'vs/workbench/services/search/common/search'; import { CellFindMatchWithIndex, ICellViewModel, CellWebviewFindMatch } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { contentMatchesToTextSearchMatches } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; suite('searchNotebookHelpers', () => { setup(() => { @@ -54,7 +54,7 @@ suite('searchNotebookHelpers', () => { webviewMatches: [] }; - const results = notebookEditorMatchesToTextSearchResults([cellFindMatchWithIndex]); + const results = contentMatchesToTextSearchMatches(cellFindMatchWithIndex.contentMatches, cell); assert.strictEqual(results.length, 1); assert.strictEqual(results[0].preview.text, 'test\n'); assertRangesEqual(results[0].preview.matches, [new Range(0, 0, 0, 1)]); diff --git a/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts index 8ba2465a21a56..3c9f814ae0eaf 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { Match, FileMatch, SearchResult, SearchModel, FolderMatch } from 'vs/workbench/contrib/search/browser/searchModel'; +import { Match, FileMatch, SearchResult, SearchModel, FolderMatch, CellMatch } from 'vs/workbench/contrib/search/browser/searchModel'; import { URI } from 'vs/base/common/uri'; import { IFileMatch, TextSearchMatch, OneLineRange, ITextSearchMatch, QueryType } from 'vs/workbench/services/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -29,9 +29,9 @@ import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/se import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { TestEditorGroupsService } from 'vs/workbench/test/browser/workbenchTestServices'; import { NotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl'; -import { NotebookTextSearchMatch } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; -import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICellMatch, IFileMatchWithCells } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; const lineOneRange = new OneLineRange(1, 0, 1); @@ -225,33 +225,40 @@ suite('SearchResult', () => { test('Adding multiple raw notebook matches', function () { const testObject = aSearchResult(); - const modelTarget = instantiationService.spy(IModelService, 'getModel'); - const cell = { cellKind: CellKind.Code } as ICellViewModel; + const cell1 = { cellKind: CellKind.Code } as ICellViewModel; + const cell2 = { cellKind: CellKind.Code } as ICellViewModel; + + sinon.stub(CellMatch.prototype, 'addContext'); const target = [ - aRawMatch('/1', - new NotebookTextSearchMatch('preview 1', new OneLineRange(1, 1, 4), { - cellIndex: 0, - matchStartIndex: 0, - matchEndIndex: 1, - cell, - }), - new NotebookTextSearchMatch('preview 1', new OneLineRange(1, 4, 11), { - cellIndex: 0, - matchStartIndex: 0, - matchEndIndex: 1, - cell, - })), - aRawMatch('/2', - new NotebookTextSearchMatch('preview 2', lineOneRange, { - cellIndex: 0, - matchStartIndex: 0, - matchEndIndex: 1, - cell, - }))]; + aRawFileMatchWithCells('/1', + { + cell: cell1, + index: 0, + contentResults: [ + new TextSearchMatch('preview 1', new OneLineRange(1, 1, 4)), + ], + webviewResults: [ + new TextSearchMatch('preview 1', new OneLineRange(1, 4, 11)), + new TextSearchMatch('preview 2', lineOneRange) + ] + },), + aRawFileMatchWithCells('/2', + { + cell: cell2, + index: 0, + contentResults: [ + new TextSearchMatch('preview 1', new OneLineRange(1, 1, 4)), + ], + webviewResults: [ + new TextSearchMatch('preview 1', new OneLineRange(1, 4, 11)), + new TextSearchMatch('preview 2', lineOneRange) + ] + }) + ]; testObject.add(target); - assert.strictEqual(3, testObject.count()); + assert.strictEqual(6, testObject.count()); // when a model is binded, the results are queried once again. assert.ok(modelTarget.calledTwice); @@ -544,6 +551,13 @@ suite('SearchResult', () => { return { resource: createFileUriFromPathFromRoot(resource), results }; } + function aRawFileMatchWithCells(resource: string, ...cellMatches: ICellMatch[]): IFileMatchWithCells { + return { + resource: createFileUriFromPathFromRoot(resource), + cellResults: cellMatches + }; + } + function stubModelService(instantiationService: TestInstantiationService): IModelService { instantiationService.stub(IThemeService, new TestThemeService()); const config = new TestConfigurationService(); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index a7f5b3d5a63d6..e7e55d19ad638 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -494,7 +494,13 @@ export class SearchEditor extends AbstractTextCodeEditor matchWholeWord: this.queryEditorWidget.searchInput?.getWholeWords() ?? false, useExcludeSettingsAndIgnoreFiles: this.inputPatternExcludes.useExcludesAndIgnoreFiles(), onlyOpenEditors: this.inputPatternIncludes.onlySearchInOpenEditors(), - showIncludesExcludes: this.showingIncludesExcludes + showIncludesExcludes: this.showingIncludesExcludes, + notebookSearchConfig: { + includeMarkupInput: this.queryEditorWidget.getNotebookFilters().markupInput, + includeMarkupPreview: this.queryEditorWidget.getNotebookFilters().markupPreview, + includeCodeInput: this.queryEditorWidget.getNotebookFilters().codeInput, + includeOutput: this.queryEditorWidget.getNotebookFilters().codeOutput, + } }; } @@ -537,7 +543,13 @@ export class SearchEditor extends AbstractTextCodeEditor afterContext: config.contextLines, beforeContext: config.contextLines, isSmartCase: this.searchConfig.smartCase, - expandPatterns: true + expandPatterns: true, + notebookSearchConfig: { + includeMarkupInput: config.notebookSearchConfig.includeMarkupInput, + includeMarkupPreview: config.notebookSearchConfig.includeMarkupPreview, + includeCodeInput: config.notebookSearchConfig.includeCodeInput, + includeOutput: config.notebookSearchConfig.includeOutput, + } }; const folderResources = this.contextService.getWorkspace().folders; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts index 5d7d63b8f5e34..b7541e9776080 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts @@ -44,6 +44,12 @@ export type SearchConfiguration = { useExcludeSettingsAndIgnoreFiles: boolean; showIncludesExcludes: boolean; onlyOpenEditors: boolean; + notebookSearchConfig: { + includeMarkupInput: boolean; + includeMarkupPreview: boolean; + includeCodeInput: boolean; + includeOutput: boolean; + }; }; export const SEARCH_EDITOR_EXT = '.code-search'; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts index de40b0c6636b4..90b10bce30daa 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts @@ -10,7 +10,7 @@ import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { Range } from 'vs/editor/common/core/range'; import type { ITextModel } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; -import { FileMatch, Match, searchMatchComparer, SearchResult, FolderMatch } from 'vs/workbench/contrib/search/browser/searchModel'; +import { FileMatch, Match, searchMatchComparer, SearchResult, FolderMatch, CellMatch } from 'vs/workbench/contrib/search/browser/searchModel'; import type { SearchConfiguration } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; import { ITextQuery, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -60,18 +60,23 @@ const matchToSearchResultFormat = (match: Match, longestLineNumber: number): { l type SearchResultSerialization = { text: string[]; matchRanges: Range[] }; -function fileMatchToSearchResultFormat(fileMatch: FileMatch, labelFormatter: (x: URI) => string): SearchResultSerialization { - const sortedMatches = fileMatch.matches().sort(searchMatchComparer); +function fileMatchToSearchResultFormat(fileMatch: FileMatch, labelFormatter: (x: URI) => string): SearchResultSerialization[] { + + const textSerializations = fileMatch.textMatches().length > 0 ? matchesToSearchResultFormat(fileMatch.resource, fileMatch.textMatches().sort(searchMatchComparer), fileMatch.context, labelFormatter) : undefined; + const cellSerializations = fileMatch.cellMatches().sort((a, b) => a.cellIndex - b.cellIndex).sort().filter(cellMatch => cellMatch.contentMatches.length > 0).map((cellMatch, index) => cellMatchToSearchResultFormat(cellMatch, labelFormatter, index === 0)); + + return [textSerializations, ...cellSerializations].filter(x => !!x) as SearchResultSerialization[]; +} +function matchesToSearchResultFormat(resource: URI, sortedMatches: Match[], matchContext: Map, labelFormatter: (x: URI) => string, shouldUseHeader = true): SearchResultSerialization { const longestLineNumber = sortedMatches[sortedMatches.length - 1].range().endLineNumber.toString().length; - const uriString = labelFormatter(fileMatch.resource); - const text: string[] = [`${uriString}:`]; + const text: string[] = shouldUseHeader ? [`${labelFormatter(resource)}:`] : []; const matchRanges: Range[] = []; const targetLineNumberToOffset: Record = {}; const context: { line: string; lineNumber: number }[] = []; - fileMatch.context.forEach((line, lineNumber) => context.push({ line, lineNumber })); + matchContext.forEach((line, lineNumber) => context.push({ line, lineNumber })); context.sort((a, b) => a.lineNumber - b.lineNumber); let lastLine: number | undefined = undefined; @@ -107,6 +112,10 @@ function fileMatchToSearchResultFormat(fileMatch: FileMatch, labelFormatter: (x: return { text, matchRanges }; } +function cellMatchToSearchResultFormat(cellMatch: CellMatch, labelFormatter: (x: URI) => string, shouldUseHeader: boolean): SearchResultSerialization { + return matchesToSearchResultFormat(cellMatch.cell.uri, cellMatch.contentMatches.sort(searchMatchComparer), cellMatch.context, labelFormatter, shouldUseHeader); +} + const contentPatternToSearchConfiguration = (pattern: ITextQuery, includes: string, excludes: string, contextLines: number): SearchConfiguration => { return { query: pattern.contentPattern.pattern, @@ -118,6 +127,12 @@ const contentPatternToSearchConfiguration = (pattern: ITextQuery, includes: stri useExcludeSettingsAndIgnoreFiles: (pattern?.userDisabledExcludesAndIgnoreFiles === undefined ? true : !pattern.userDisabledExcludesAndIgnoreFiles), contextLines, onlyOpenEditors: !!pattern.onlyOpenEditors, + notebookSearchConfig: { + includeMarkupInput: !!pattern.contentPattern.notebookInfo?.isInNotebookMarkdownInput, + includeMarkupPreview: !pattern.contentPattern.notebookInfo?.isInNotebookMarkdownInput, + includeCodeInput: !pattern.contentPattern.notebookInfo?.isInNotebookCellInput, + includeOutput: !pattern.contentPattern.notebookInfo?.isInNotebookCellOutput, + } }; }; @@ -158,6 +173,12 @@ export const defaultSearchConfig = (): SearchConfiguration => ({ contextLines: 0, showIncludesExcludes: false, onlyOpenEditors: false, + notebookSearchConfig: { + includeMarkupInput: true, + includeMarkupPreview: false, + includeCodeInput: true, + includeOutput: true, + } }); export const extractSearchQueryFromLines = (lines: string[]): SearchConfiguration => { @@ -237,7 +258,7 @@ export const serializeSearchResultForEditor = flatten( searchResult.folderMatches().sort(matchComparer) .map(folderMatch => folderMatch.allDownstreamFileMatches().sort(matchComparer) - .map(fileMatch => fileMatchToSearchResultFormat(fileMatch, labelFormatter))))); + .flatMap(fileMatch => fileMatchToSearchResultFormat(fileMatch, labelFormatter))))); return { matchRanges: allResults.matchRanges.map(translateRangeLines(info.length)), diff --git a/src/vs/workbench/services/search/common/queryBuilder.ts b/src/vs/workbench/services/search/common/queryBuilder.ts index 269455924fc6d..cbb6a738408bf 100644 --- a/src/vs/workbench/services/search/common/queryBuilder.ts +++ b/src/vs/workbench/services/search/common/queryBuilder.ts @@ -80,6 +80,12 @@ export interface ITextQueryBuilderOptions extends ICommonQueryBuilderOptions { beforeContext?: number; afterContext?: number; isSmartCase?: boolean; + notebookSearchConfig?: { + includeMarkupInput: boolean; + includeMarkupPreview: boolean; + includeCodeInput: boolean; + includeOutput: boolean; + }; } export class QueryBuilder { @@ -112,7 +118,8 @@ export class QueryBuilder { usePCRE2: searchConfig.search.usePCRE2 || fallbackToPCRE || false, beforeContext: options.beforeContext, afterContext: options.afterContext, - userDisabledExcludesAndIgnoreFiles: options.disregardExcludeSettings && options.disregardIgnoreFiles + userDisabledExcludesAndIgnoreFiles: options.disregardExcludeSettings && options.disregardIgnoreFiles, + }; } @@ -139,6 +146,27 @@ export class QueryBuilder { newPattern.isMultiline = true; } + if (options.notebookSearchConfig?.includeMarkupInput) { + if (!newPattern.notebookInfo) { + newPattern.notebookInfo = {}; + } + newPattern.notebookInfo.isInNotebookMarkdownInput = options.notebookSearchConfig.includeMarkupInput; + } + + if (options.notebookSearchConfig?.includeCodeInput) { + if (!newPattern.notebookInfo) { + newPattern.notebookInfo = {}; + } + newPattern.notebookInfo.isInNotebookCellInput = options.notebookSearchConfig.includeCodeInput; + } + + if (options.notebookSearchConfig?.includeOutput) { + if (!newPattern.notebookInfo) { + newPattern.notebookInfo = {}; + } + newPattern.notebookInfo.isInNotebookCellOutput = options.notebookSearchConfig.includeOutput; + } + return newPattern; } diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 2a33627a41e2e..66c9bce85d951 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -187,6 +187,7 @@ export interface ITextSearchMatch { uri?: URI; ranges: ISearchRange | ISearchRange[]; preview: ITextSearchResultPreview; + webviewIndex?: number; } export interface ITextSearchContext { @@ -280,9 +281,11 @@ export class FileMatch implements IFileMatch { export class TextSearchMatch implements ITextSearchMatch { ranges: ISearchRange | ISearchRange[]; preview: ITextSearchResultPreview; + webviewIndex?: number; - constructor(text: string, range: ISearchRange | ISearchRange[], previewOptions?: ITextSearchPreviewOptions) { + constructor(text: string, range: ISearchRange | ISearchRange[], previewOptions?: ITextSearchPreviewOptions, webviewIndex?: number) { this.ranges = range; + this.webviewIndex = webviewIndex; // Trim preview if this is one match and a single-line match with a preview requested. // Otherwise send the full text, like for replace or for showing multiple previews. From ca9404bd2f033125fbb0d436f923140cac93b2af Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 12 Apr 2023 10:52:37 -0700 Subject: [PATCH 22/59] cli: read tunnel lock from product.json (#179800) Fixes #179265 --- build/azure-pipelines/cli/prepare.js | 4 +++- build/azure-pipelines/cli/prepare.ts | 2 ++ cli/src/commands/tunnels.rs | 6 +++--- cli/src/constants.rs | 17 +++-------------- package.json | 2 +- 5 files changed, 12 insertions(+), 19 deletions(-) diff --git a/build/azure-pipelines/cli/prepare.js b/build/azure-pipelines/cli/prepare.js index 62f66aafe72f4..7a1ae74a4504d 100644 --- a/build/azure-pipelines/cli/prepare.js +++ b/build/azure-pipelines/cli/prepare.js @@ -48,6 +48,8 @@ const setLauncherEnvironmentVars = () => { ['VSCODE_CLI_DOCUMENTATION_URL', product.documentationUrl], ['VSCODE_CLI_APPLICATION_NAME', product.applicationName], ['VSCODE_CLI_EDITOR_WEB_URL', product.tunnelApplicationConfig?.editorWebUrl], + ['VSCODE_CLI_TUNNEL_SERVICE_MUTEX', product.tunnelApplicationConfig?.win32TunnelServiceMutex], + ['VSCODE_CLI_TUNNEL_CLI_MUTEX', product.tunnelApplicationConfig?.win32TunnelMutex], ['VSCODE_CLI_COMMIT', commit], [ 'VSCODE_CLI_WIN32_APP_IDS', @@ -86,4 +88,4 @@ const setLauncherEnvironmentVars = () => { if (require.main === module) { setLauncherEnvironmentVars(); } -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJlcGFyZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInByZXBhcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOztBQUVoRyxxREFBa0Q7QUFDbEQseUJBQXlCO0FBQ3pCLDZCQUE2QjtBQUM3QixxREFBcUQ7QUFFckQsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyx1QkFBdUIsSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDeEcsTUFBTSxRQUFRLEdBQUcsQ0FBQyxJQUFZLEVBQUUsRUFBRSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLFlBQVksQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQztBQUU3RSxJQUFJLGVBQXVCLENBQUM7QUFDNUIsTUFBTSxLQUFLLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFjLEtBQUssS0FBSyxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFjLENBQUM7QUFDbEYsSUFBSSxLQUFLLEVBQUU7SUFDVixlQUFlLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsY0FBYyxDQUFDLENBQUM7Q0FDbEQ7S0FBTTtJQUNOLGVBQWUsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxPQUFPLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFlLEVBQUUsY0FBYyxDQUFDLENBQUM7Q0FDeEY7QUFFRCxPQUFPLENBQUMsS0FBSyxDQUFDLDJCQUEyQixFQUFFLGVBQWUsQ0FBQyxDQUFDO0FBQzVELE1BQU0sT0FBTyxHQUFHLFFBQVEsQ0FBQyxlQUFlLENBQUMsQ0FBQztBQUMxQyxNQUFNLHVCQUF1QixHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFD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 \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJlcGFyZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInByZXBhcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOztBQUVoRyxxREFBa0Q7QUFDbEQseUJBQXlCO0FBQ3pCLDZCQUE2QjtBQUM3QixxREFBcUQ7QUFFckQsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyx1QkFBdUIsSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDeEcsTUFBTSxRQUFRLEdBQUcsQ0FBQyxJQUFZLEVBQUUsRUFBRSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLFlBQVksQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQztBQUU3RSxJQUFJLGVBQXVCLENBQUM7QUFDNUIsTUFBTSxLQUFLLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFjLEtBQUssS0FBSyxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFjLENBQUM7QUFDbEYsSUFBSSxLQUFLLEVBQUU7SUFDVixlQUFlLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsY0FBYyxDQUFDLENBQUM7Q0FDbEQ7S0FBTTtJQUNOLGVBQWUsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxPQUFPLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFlLEVBQUUsY0FBYyxDQUFDLENBQUM7Q0FDeEY7QUFFRCxPQUFPLENBQUMsS0FBSyxDQUFDLDJCQUEyQixFQUFFLGVBQWUsQ0FBQyxDQUFDO0FBQzVELE1BQU0sT0FBTyxHQUFHLFFBQVEsQ0FBQyxlQUFlLENBQUMsQ0FBQztBQUMxQyxNQUFNLHVCQUF1QixHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFD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 \ No newline at end of file diff --git a/build/azure-pipelines/cli/prepare.ts b/build/azure-pipelines/cli/prepare.ts index dcec1bc944357..50b7ab039c27c 100644 --- a/build/azure-pipelines/cli/prepare.ts +++ b/build/azure-pipelines/cli/prepare.ts @@ -51,6 +51,8 @@ const setLauncherEnvironmentVars = () => { ['VSCODE_CLI_DOCUMENTATION_URL', product.documentationUrl], ['VSCODE_CLI_APPLICATION_NAME', product.applicationName], ['VSCODE_CLI_EDITOR_WEB_URL', product.tunnelApplicationConfig?.editorWebUrl], + ['VSCODE_CLI_TUNNEL_SERVICE_MUTEX', product.tunnelApplicationConfig?.win32TunnelServiceMutex], + ['VSCODE_CLI_TUNNEL_CLI_MUTEX', product.tunnelApplicationConfig?.win32TunnelMutex], ['VSCODE_CLI_COMMIT', commit], [ 'VSCODE_CLI_WIN32_APP_IDS', diff --git a/cli/src/commands/tunnels.rs b/cli/src/commands/tunnels.rs index 8a390930f9bca..3f7b9020bee83 100644 --- a/cli/src/commands/tunnels.rs +++ b/cli/src/commands/tunnels.rs @@ -20,7 +20,7 @@ use super::{ use crate::{ async_pipe::socket_stream_split, auth::Auth, - constants::{APPLICATION_NAME, TUNNEL_NO_SERVICE_LOCK_NAME, TUNNEL_SERVICE_LOCK_NAME}, + constants::{APPLICATION_NAME, TUNNEL_CLI_LOCK_NAME, TUNNEL_SERVICE_LOCK_NAME}, json_rpc::{new_json_rpc, start_json_rpc}, log, singleton::connect_as_client, @@ -149,7 +149,7 @@ pub async fn service( manager.show_logs().await?; } TunnelServiceSubCommands::InternalRun => { - let _lock = AppMutex::new(TUNNEL_SERVICE_LOCK_NAME); + let _lock = TUNNEL_SERVICE_LOCK_NAME.map(AppMutex::new); manager .run(ctx.paths.clone(), TunnelServiceContainer::new(ctx.args)) .await?; @@ -386,7 +386,7 @@ async fn serve_with_csa( let mut server = make_singleton_server(log_broadcast.clone(), log.clone(), server, shutdown.clone()); let platform = spanf!(log, log.span("prereq"), PreReqChecker::new().verify())?; - let _lock = AppMutex::new(TUNNEL_NO_SERVICE_LOCK_NAME).unwrap(); + let _lock = TUNNEL_CLI_LOCK_NAME.map(AppMutex::new); let auth = Auth::new(&paths, log.clone()); let mut dt = dev_tunnels::DevTunnels::new(&log, auth, &paths); diff --git a/cli/src/constants.rs b/cli/src/constants.rs index 5a263fa20d108..db4dc9d3e6cc8 100644 --- a/cli/src/constants.rs +++ b/cli/src/constants.rs @@ -36,23 +36,12 @@ pub const VSCODE_CLI_UPDATE_ENDPOINT: Option<&'static str> = /// Windows lock name for the running tunnel service. Used by the setup script /// to detect a tunnel process. See #179265. -pub const TUNNEL_SERVICE_LOCK_NAME: &str = concatcp!( - "code_tunnel_service_", - match VSCODE_CLI_QUALITY { - Some(n) => n, - None => "oss", - } -); +pub const TUNNEL_SERVICE_LOCK_NAME: Option<&'static str> = + option_env!("VSCODE_CLI_TUNNEL_SERVICE_MUTEX"); /// Windows lock name for the running tunnel without a service. Used by the setup /// script to detect a tunnel process. See #179265. -pub const TUNNEL_NO_SERVICE_LOCK_NAME: &str = concatcp!( - "code_tunnel_", - match VSCODE_CLI_QUALITY { - Some(n) => n, - None => "oss", - } -); +pub const TUNNEL_CLI_LOCK_NAME: Option<&'static str> = option_env!("VSCODE_CLI_TUNNEL_CLI_MUTEX"); pub const TUNNEL_SERVICE_USER_AGENT_ENV_VAR: &str = "TUNNEL_SERVICE_USER_AGENT"; diff --git a/package.json b/package.json index d3843fb462fb3..a311a5c01e212 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.78.0", - "distro": "7ba84c948f5b342f0295a3ba3c8547cca65410dd", + "distro": "3eae5e756c195281ebb3348c7492b0ef0d4f3a16", "author": { "name": "Microsoft Corporation" }, From b317e81e0b6d3dc357cf9f316b0650ccdd8472f5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 12 Apr 2023 20:39:53 +0200 Subject: [PATCH 23/59] revert this until codespaces is ready (#179805) --- src/vs/workbench/services/userData/browser/userDataInit.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/vs/workbench/services/userData/browser/userDataInit.ts b/src/vs/workbench/services/userData/browser/userDataInit.ts index cbb271ccd23c7..d6158b36af1f3 100644 --- a/src/vs/workbench/services/userData/browser/userDataInit.ts +++ b/src/vs/workbench/services/userData/browser/userDataInit.ts @@ -97,11 +97,6 @@ export class UserDataInitializationService implements IUserDataInitializationSer return; } - if (!this.environmentService.options?.settingsSyncOptions?.enabled) { - this.logService.trace(`Skipping initializing user data as settings sync is disabled`); - return; - } - let authenticationSession; try { authenticationSession = await getCurrentAuthenticationSessionInfo(this.credentialsService, this.productService); From 3cf3ef8897cda4ae7be8b9e5ac5f069fc5bfa0e5 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 12 Apr 2023 12:14:24 -0700 Subject: [PATCH 24/59] Encode slash-delimited branch and file segments in links (#179801) --- extensions/github/src/links.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/extensions/github/src/links.ts b/extensions/github/src/links.ts index e689a28dc90de..455662baf1806 100644 --- a/extensions/github/src/links.ts +++ b/extensions/github/src/links.ts @@ -119,6 +119,16 @@ export function notebookCellRangeString(index: number | undefined, range: vscode return hash; } +function encodeURIComponentExceptSlashes(path: string) { + // There may be special characters like # and whitespace in the path. + // These characters are not escaped by encodeURI(), so it is not sufficient to + // feed the full URI to encodeURI(). + // Additonally, if we feed the full path into encodeURIComponent(), + // this will also encode the path separators, leading to an invalid path. + // Therefore, split on the path separator and encode each segment individually. + return path.split('/').map((segment) => encodeURIComponent(segment)).join('/'); +} + export function getLink(gitAPI: GitAPI, useSelection: boolean, hostPrefix?: string, linkType: 'permalink' | 'headlink' = 'permalink', context?: LinkContext, useRange?: boolean): string | undefined { hostPrefix = hostPrefix ?? 'https://github.com'; const fileAndPosition = getFileAndPosition(context); @@ -149,10 +159,11 @@ export function getLink(gitAPI: GitAPI, useSelection: boolean, hostPrefix?: stri return; } - const blobSegment = gitRepo.state.HEAD ? (`/blob/${linkType === 'headlink' && gitRepo.state.HEAD.name ? gitRepo.state.HEAD.name : gitRepo.state.HEAD?.commit}`) : ''; + const blobSegment = gitRepo.state.HEAD ? (`/blob/${linkType === 'headlink' && gitRepo.state.HEAD.name ? encodeURIComponentExceptSlashes(gitRepo.state.HEAD.name) : gitRepo.state.HEAD?.commit}`) : ''; + const encodedFilePath = encodeURIComponentExceptSlashes(uri.path.substring(gitRepo.rootUri.path.length)); const fileSegments = fileAndPosition.type === LinkType.File - ? (useSelection ? `${uri.path.substring(gitRepo.rootUri.path.length)}${useRange ? rangeString(fileAndPosition.range) : ''}` : '') - : (useSelection ? `${uri.path.substring(gitRepo.rootUri.path.length)}${useRange ? notebookCellRangeString(fileAndPosition.cellIndex, fileAndPosition.range) : ''}` : ''); + ? (useSelection ? `${encodedFilePath}${useRange ? rangeString(fileAndPosition.range) : ''}` : '') + : (useSelection ? `${encodedFilePath}${useRange ? notebookCellRangeString(fileAndPosition.cellIndex, fileAndPosition.range) : ''}` : ''); return `${hostPrefix}/${repo.owner}/${repo.repo}${blobSegment }${fileSegments}`; From 26ccce443f748431c1d735961cc03dffb21fa9c4 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 12 Apr 2023 12:42:59 -0700 Subject: [PATCH 25/59] Enable renaming of matching jsx tags (#179806) Fixes #159534 Uses the new linked editing api to make f2 rename matching jsx tags --- .../typescript-language-features/package.json | 12 ++ .../package.nls.json | 1 + .../src/languageFeatures/rename.ts | 120 +++++++++++++----- .../src/languageProvider.ts | 2 +- 4 files changed, 105 insertions(+), 30 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 13846bdca1815..27ee01e695a0f 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -1007,6 +1007,18 @@ "description": "%typescript.preferences.useAliasesForRenames%", "scope": "language-overridable" }, + "javascript.preferences.renameMatchingJsxTags": { + "type": "boolean", + "default": true, + "description": "%typescript.preferences.renameMatchingJsxTags%", + "scope": "language-overridable" + }, + "typescript.preferences.renameMatchingJsxTags": { + "type": "boolean", + "default": true, + "description": "%typescript.preferences.renameMatchingJsxTags%", + "scope": "language-overridable" + }, "typescript.updateImportsOnFileMove.enabled": { "type": "string", "enum": [ diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index b3a3b26798295..dbbb5b62e81a5 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -165,6 +165,7 @@ "configuration.tsserver.watchOptions.synchronousWatchDirectory": "Disable deferred watching on directories. Deferred watching is useful when lots of file changes might occur at once (e.g. a change in node_modules from running npm install), but you might want to disable it with this flag for some less-common setups.", "typescript.preferences.renameShorthandProperties.deprecationMessage": "The setting 'typescript.preferences.renameShorthandProperties' has been deprecated in favor of 'typescript.preferences.useAliasesForRenames'", "typescript.preferences.useAliasesForRenames": "Enable/disable introducing aliases for object shorthand properties during renames.", + "typescript.preferences.renameMatchingJsxTags": "When on a JSX tag, try to rename the matching tag instead of renaming the symbol. Requires using TypeScript 5.1+ in the workspace.", "typescript.workspaceSymbols.scope": "Controls which files are searched by [Go to Symbol in Workspace](https://code.visualstudio.com/docs/editor/editingevolved#_open-symbol-by-name).", "typescript.workspaceSymbols.scope.allOpenProjects": "Search all open JavaScript or TypeScript projects for symbols.", "typescript.workspaceSymbols.scope.currentProject": "Only search for symbols in the current JavaScript or TypeScript project.", diff --git a/extensions/typescript-language-features/src/languageFeatures/rename.ts b/extensions/typescript-language-features/src/languageFeatures/rename.ts index 2e182d0999fd9..3dcca16f5b278 100644 --- a/extensions/typescript-language-features/src/languageFeatures/rename.ts +++ b/extensions/typescript-language-features/src/languageFeatures/rename.ts @@ -6,16 +6,27 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { DocumentSelector } from '../configuration/documentSelector'; +import * as languageIds from '../configuration/languageIds'; import { API } from '../tsServer/api'; import type * as Proto from '../tsServer/protocol/protocol'; import * as typeConverters from '../typeConverters'; -import { ClientCapability, ITypeScriptServiceClient, ServerResponse } from '../typescriptService'; +import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService'; import FileConfigurationManager from './fileConfigurationManager'; import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration'; +import { LanguageDescription } from '../configuration/languageDescription'; +type RenameResponse = { + readonly type: 'rename'; + readonly body: Proto.RenameResponseBody; +} | { + readonly type: 'jsxLinkedEditing'; + readonly spans: readonly Proto.TextSpan[]; +}; class TypeScriptRenameProvider implements vscode.RenameProvider { + public constructor( + private readonly language: LanguageDescription, private readonly client: ITypeScriptServiceClient, private readonly fileConfigurationManager: FileConfigurationManager ) { } @@ -24,22 +35,30 @@ class TypeScriptRenameProvider implements vscode.RenameProvider { document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken - ): Promise { + ): Promise { if (this.client.apiVersion.lt(API.v310)) { - return null; + return undefined; } const response = await this.execRename(document, position, token); - if (response?.type !== 'response' || !response.body) { - return null; + if (!response) { + return undefined; } - const renameInfo = response.body.info; - if (!renameInfo.canRename) { - return Promise.reject(renameInfo.localizedErrorMessage); + switch (response.type) { + case 'rename': { + const renameInfo = response.body.info; + if (!renameInfo.canRename) { + return Promise.reject(renameInfo.localizedErrorMessage); + } + return typeConverters.Range.fromTextSpan(renameInfo.triggerSpan); + } + case 'jsxLinkedEditing': { + return response.spans + .map(typeConverters.Range.fromTextSpan) + .find(range => range.contains(position)); + } } - - return typeConverters.Range.fromTextSpan(renameInfo.triggerSpan); } public async provideRenameEdits( @@ -47,51 +66,93 @@ class TypeScriptRenameProvider implements vscode.RenameProvider { position: vscode.Position, newName: string, token: vscode.CancellationToken - ): Promise { - const response = await this.execRename(document, position, token); - if (!response || response.type !== 'response' || !response.body) { - return null; + ): Promise { + const file = this.client.toOpenTsFilePath(document); + if (!file) { + return undefined; } - const renameInfo = response.body.info; - if (!renameInfo.canRename) { - return Promise.reject(renameInfo.localizedErrorMessage); + const response = await this.execRename(document, position, token); + if (!response || token.isCancellationRequested) { + return undefined; } - if (renameInfo.fileToRename) { - const edits = await this.renameFile(renameInfo.fileToRename, newName, token); - if (edits) { - return edits; - } else { - return Promise.reject(vscode.l10n.t("An error occurred while renaming file")); + switch (response.type) { + case 'rename': { + const renameInfo = response.body.info; + if (!renameInfo.canRename) { + return Promise.reject(renameInfo.localizedErrorMessage); + } + + if (renameInfo.fileToRename) { + const edits = await this.renameFile(renameInfo.fileToRename, newName, token); + if (edits) { + return edits; + } else { + return Promise.reject(vscode.l10n.t("An error occurred while renaming file")); + } + } + + return this.updateLocs(response.body.locs, newName); + } + case 'jsxLinkedEditing': { + return this.updateLocs([{ + file, + locs: response.spans.map((span): Proto.RenameTextSpan => ({ ...span })), + }], newName); } } - - return this.updateLocs(response.body.locs, newName); } public async execRename( document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken - ): Promise | undefined> { + ): Promise { const file = this.client.toOpenTsFilePath(document); if (!file) { return undefined; } + // Prefer renaming matching jsx tag when available + if (this.client.apiVersion.gte(API.v510) && + vscode.workspace.getConfiguration(this.language.id).get('preferences.renameMatchingJsxTags', true) && + this.looksLikePotentialJsxTagContext(document, position) + ) { + const args = typeConverters.Position.toFileLocationRequestArgs(file, position); + const response = await this.client.execute('linkedEditingRange', args, token); + if (response.type !== 'response' || !response.body) { + return undefined; + } + + return { type: 'jsxLinkedEditing', spans: response.body.ranges }; + } + const args: Proto.RenameRequestArgs = { ...typeConverters.Position.toFileLocationRequestArgs(file, position), findInStrings: false, findInComments: false }; - return this.client.interruptGetErr(() => { + return this.client.interruptGetErr(async () => { this.fileConfigurationManager.ensureConfigurationForDocument(document, token); - return this.client.execute('rename', args, token); + const response = await this.client.execute('rename', args, token); + if (response.type !== 'response' || !response.body) { + return undefined; + } + return { type: 'rename', body: response.body }; }); } + private looksLikePotentialJsxTagContext(document: vscode.TextDocument, position: vscode.Position): boolean { + if (![languageIds.typescriptreact, languageIds.javascript, languageIds.javascriptreact].includes(document.languageId)) { + return false; + } + + const prefix = document.getText(new vscode.Range(position.line, 0, position.line, position.character)); + return /\<\/?\s*[\w\d_$.]*$/.test(prefix); + } + private updateLocs( locations: ReadonlyArray, newName: string @@ -138,6 +199,7 @@ class TypeScriptRenameProvider implements vscode.RenameProvider { export function register( selector: DocumentSelector, + language: LanguageDescription, client: ITypeScriptServiceClient, fileConfigurationManager: FileConfigurationManager, ) { @@ -145,6 +207,6 @@ export function register( requireSomeCapability(client, ClientCapability.Semantic), ], () => { return vscode.languages.registerRenameProvider(selector.semantic, - new TypeScriptRenameProvider(client, fileConfigurationManager)); + new TypeScriptRenameProvider(language, client, fileConfigurationManager)); }); } diff --git a/extensions/typescript-language-features/src/languageProvider.ts b/extensions/typescript-language-features/src/languageProvider.ts index cf0a6f46b482e..f978c800c9513 100644 --- a/extensions/typescript-language-features/src/languageProvider.ts +++ b/extensions/typescript-language-features/src/languageProvider.ts @@ -80,7 +80,7 @@ export default class LanguageProvider extends Disposable { import('./languageFeatures/quickFix').then(provider => this._register(provider.register(selector, this.client, this.fileConfigurationManager, this.commandManager, this.client.diagnosticsManager, this.telemetryReporter))), import('./languageFeatures/refactor').then(provider => this._register(provider.register(selector, this.client, this.fileConfigurationManager, this.commandManager, this.telemetryReporter))), import('./languageFeatures/references').then(provider => this._register(provider.register(selector, this.client))), - import('./languageFeatures/rename').then(provider => this._register(provider.register(selector, this.client, this.fileConfigurationManager))), + import('./languageFeatures/rename').then(provider => this._register(provider.register(selector, this.description, this.client, this.fileConfigurationManager))), import('./languageFeatures/semanticTokens').then(provider => this._register(provider.register(selector, this.client))), import('./languageFeatures/signatureHelp').then(provider => this._register(provider.register(selector, this.client))), import('./languageFeatures/smartSelect').then(provider => this._register(provider.register(selector, this.client))), From c96ff534eb552fe5424a78e5e33fce9cfaf13b6a Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 12 Apr 2023 14:39:19 -0700 Subject: [PATCH 26/59] Allow force applying unrelated changes from Cloud Changes view (#179808) --- .../browser/editSessions.contribution.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts index d213f0fc593d4..100c00a474d32 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts @@ -173,13 +173,13 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo if (this.environmentService.editSessionId !== undefined) { this.logService.info(`Resuming cloud changes, reason: found editSessionId ${this.environmentService.editSessionId} in environment service...`); - await this.progressService.withProgress(resumeProgressOptions, async (progress) => await this.resumeEditSession(this.environmentService.editSessionId, undefined, undefined, progress).finally(() => this.environmentService.editSessionId = undefined)); + await this.progressService.withProgress(resumeProgressOptions, async (progress) => await this.resumeEditSession(this.environmentService.editSessionId, undefined, undefined, undefined, progress).finally(() => this.environmentService.editSessionId = undefined)); } else if (shouldAutoResumeOnReload && this.editSessionsStorageService.isSignedIn) { this.logService.info('Resuming cloud changes, reason: cloud changes enabled...'); // Attempt to resume edit session based on edit workspace identifier // Note: at this point if the user is not signed into edit sessions, // we don't want them to be prompted to sign in and should just return early - await this.progressService.withProgress(resumeProgressOptions, async (progress) => await this.resumeEditSession(undefined, true, undefined, progress)); + await this.progressService.withProgress(resumeProgressOptions, async (progress) => await this.resumeEditSession(undefined, true, undefined, undefined, progress)); } else if (shouldAutoResumeOnReload) { // The application has previously launched via a protocol URL Continue On flow const hasApplicationLaunchedFromContinueOnFlow = this.storageService.getBoolean(EditSessionsContribution.APPLICATION_LAUNCHED_VIA_CONTINUE_ON_STORAGE_KEY, StorageScope.APPLICATION, false); @@ -190,7 +190,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo // attempt a resume if we are in a pending state and the user just signed in const disposable = this.editSessionsStorageService.onDidSignIn(async () => { disposable.dispose(); - await this.progressService.withProgress(resumeProgressOptions, async (progress) => await this.resumeEditSession(undefined, true, undefined, progress)); + await this.progressService.withProgress(resumeProgressOptions, async (progress) => await this.resumeEditSession(undefined, true, undefined, undefined, progress)); this.storageService.remove(EditSessionsContribution.APPLICATION_LAUNCHED_VIA_CONTINUE_ON_STORAGE_KEY, StorageScope.APPLICATION); this.environmentService.continueOn = undefined; }); @@ -204,7 +204,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo this.storageService.store(EditSessionsContribution.APPLICATION_LAUNCHED_VIA_CONTINUE_ON_STORAGE_KEY, true, StorageScope.APPLICATION, StorageTarget.MACHINE); await this.editSessionsStorageService.initialize(); if (this.editSessionsStorageService.isSignedIn) { - await this.progressService.withProgress(resumeProgressOptions, async (progress) => await this.resumeEditSession(undefined, true, undefined, progress)); + await this.progressService.withProgress(resumeProgressOptions, async (progress) => await this.resumeEditSession(undefined, true, undefined, undefined, progress)); } else { handlePendingEditSessions(); } @@ -401,8 +401,8 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo }); } - async run(accessor: ServicesAccessor, editSessionId?: string, force?: boolean): Promise { - await that.progressService.withProgress({ ...resumeProgressOptions, title: resumeProgressOptionsTitle }, async () => await that.resumeEditSession(editSessionId, undefined, force)); + async run(accessor: ServicesAccessor, editSessionId?: string, forceApplyUnrelatedChange?: boolean): Promise { + await that.progressService.withProgress({ ...resumeProgressOptions, title: resumeProgressOptionsTitle }, async () => await that.resumeEditSession(editSessionId, undefined, forceApplyUnrelatedChange)); } })); } @@ -440,7 +440,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo })); } - async resumeEditSession(ref?: string, silent?: boolean, force?: boolean, progress?: IProgress): Promise { + async resumeEditSession(ref?: string, silent?: boolean, forceApplyUnrelatedChange?: boolean, applyPartialMatch?: boolean, progress?: IProgress): Promise { // Wait for the remote environment to become available, if any await this.remoteAgentService.getEnvironment(); @@ -489,7 +489,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo } try { - const { changes, conflictingChanges, contributedStateHandlers } = await this.generateChanges(editSession, ref, force); + const { changes, conflictingChanges, contributedStateHandlers } = await this.generateChanges(editSession, ref, forceApplyUnrelatedChange, applyPartialMatch); if (changes.length === 0 && contributedStateHandlers.length === 0) { return; } @@ -536,7 +536,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo performance.mark('code/didResumeEditSessionFromIdentifier'); } - private async generateChanges(editSession: EditSession, ref: string, force = false) { + private async generateChanges(editSession: EditSession, ref: string, forceApplyUnrelatedChange = false, applyPartialMatch = false) { const changes: ({ uri: URI; type: ChangeType; contents: string | undefined })[] = []; const contributedStateHandlers: (() => void)[] = []; const conflictingChanges = []; @@ -552,7 +552,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo const identity = await this.editSessionIdentityService.getEditSessionIdentifier(f, cancellationTokenSource.token); this.logService.info(`Matching identity ${identity} against edit session folder identity ${folder.canonicalIdentity}...`); - if (equals(identity, folder.canonicalIdentity)) { + if (equals(identity, folder.canonicalIdentity) || forceApplyUnrelatedChange) { folderRoot = f; break; } @@ -565,12 +565,12 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo } else if (match === EditSessionIdentityMatch.Partial && this.configurationService.getValue('workbench.experimental.cloudChanges.partialMatches.enabled') === true ) { - if (!force) { + if (!applyPartialMatch) { // Surface partially matching edit session this.notificationService.prompt( Severity.Info, localize('editSessionPartialMatch', 'You have pending working changes in the cloud for this workspace. Would you like to resume them?'), - [{ label: localize('resume', 'Resume'), run: () => this.resumeEditSession(ref, false, true) }] + [{ label: localize('resume', 'Resume'), run: () => this.resumeEditSession(ref, false, undefined, true) }] ); } else { folderRoot = f; From 36a8c3b1060dd94927578470d75beda0e6633d4a Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 12 Apr 2023 14:46:16 -0700 Subject: [PATCH 27/59] Fix opening URLs from remote help get started (#179814) --- src/vs/workbench/contrib/remote/browser/remote.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index 12c477f7f382d..91e4d0c657270 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -428,7 +428,7 @@ class GetStartedHelpItem extends HelpItemBase { } protected async takeAction(extensionDescription: IExtensionDescription, urlOrWalkthroughId: string): Promise { - if (URI.parse(urlOrWalkthroughId).scheme in [Schemas.http, Schemas.https]) { + if ([Schemas.http, Schemas.https].includes(URI.parse(urlOrWalkthroughId).scheme)) { this.openerService.open(urlOrWalkthroughId, { allowCommands: true }); return; } From 763076d7e601c49e7742cd89afc621dd9a5ec713 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 12 Apr 2023 15:43:07 -0700 Subject: [PATCH 28/59] :notebook: Enable extension other than install if it is already installed. (#179815) * :notebook: Enable extension other than install if it is already installed. * Update wording --- .../notebook/browser/notebookEditor.ts | 42 ++++++++++++------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 99e5356f74a2c..c841e50fb768d 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -41,6 +41,8 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { InstallRecommendedExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import { EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState'; @@ -82,6 +84,7 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @IEditorProgressService private readonly _editorProgressService: IEditorProgressService, @INotebookService private readonly _notebookService: INotebookService, + @IExtensionsWorkbenchService private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService, ) { super(NotebookEditor.ID, telemetryService, themeService, storageService); this._editorMemento = this.getEditorMemento(_editorGroupService, configurationService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY); @@ -231,24 +234,31 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { if (model === null) { const knownProvider = this._notebookService.getViewTypeProvider(input.viewType); - if (knownProvider) { - throw createEditorOpenError(new Error(localize('fail.noEditor', "Cannot open resource with notebook editor type '{0}', please check if you have the right extension installed and enabled.", input.viewType)), [ - toAction({ - id: 'workbench.notebook.action.installMissing', label: localize('notebookOpenInstallMissingViewType', "Install extension for '{0}'", input.viewType), run: async () => { - const d = this._notebookService.onAddViewType(viewType => { - if (viewType === input.viewType) { - // serializer is registered, try to open again - this._editorService.openEditor({ resource: input.resource }); - d.dispose(); - } - }); - await this._instantiationService.createInstance(InstallRecommendedExtensionAction, knownProvider).run(); - } - }) - ], { allowDialog: true }); - } else { + if (!knownProvider) { throw new Error(localize('fail.noEditor', "Cannot open resource with notebook editor type '{0}', please check if you have the right extension installed and enabled.", input.viewType)); } + + throw createEditorOpenError(new Error(localize('fail.noEditor.extensionMissing', "Cannot open resource with notebook editor type '{0}', please check if you have the right extension installed and enabled.", input.viewType)), [ + toAction({ + id: 'workbench.notebook.action.installOrEnableMissing', label: localize('notebookOpenInstallOrEnableMissingViewType', "Install and enable extension for '{0}'", input.viewType), run: async () => { + const d = this._notebookService.onAddViewType(viewType => { + if (viewType === input.viewType) { + // serializer is registered, try to open again + this._editorService.openEditor({ resource: input.resource }); + d.dispose(); + } + }); + const extensionInfo = this._extensionsWorkbenchService.local.find(e => e.identifier.id === knownProvider); + + if (extensionInfo) { + await this._extensionsWorkbenchService.setEnablement(extensionInfo, extensionInfo.enablementState === EnablementState.DisabledWorkspace ? EnablementState.EnabledWorkspace : EnablementState.EnabledGlobally); + } else { + await this._instantiationService.createInstance(InstallRecommendedExtensionAction, knownProvider).run(); + } + } + }) + ], { allowDialog: true }); + } this._widgetDisposableStore.add(model.notebook.onDidChangeContent(() => this._onDidChangeSelection.fire({ reason: EditorPaneSelectionChangeReason.EDIT }))); From 0ae876929eb0558822c83efab6ca5fa0beb8e735 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 12 Apr 2023 15:43:27 -0700 Subject: [PATCH 29/59] Change how waiting for model initialization works, add tests (#179504) * Change how waiting for model initialization works, add tests * Fix warning --- .../browser/mainThreadInteractiveSession.ts | 3 +- .../browser/interactiveEditorController.ts | 5 +- .../common/interactiveSessionModel.ts | 12 ++ .../common/interactiveSessionService.ts | 7 +- .../common/interactiveSessionServiceImpl.ts | 62 +++----- .../common/interactiveSessionModel.test.ts | 55 +++++++ .../common/interactiveSessionService.test.ts | 141 ++++++++++++++---- 7 files changed, 207 insertions(+), 78 deletions(-) create mode 100644 src/vs/workbench/contrib/interactiveSession/test/common/interactiveSessionModel.test.ts diff --git a/src/vs/workbench/api/browser/mainThreadInteractiveSession.ts b/src/vs/workbench/api/browser/mainThreadInteractiveSession.ts index 289fe4710ff51..9d9be282736ad 100644 --- a/src/vs/workbench/api/browser/mainThreadInteractiveSession.ts +++ b/src/vs/workbench/api/browser/mainThreadInteractiveSession.ts @@ -119,7 +119,8 @@ export class MainThreadInteractiveSession extends Disposable implements MainThre this._interactiveSessionService.addInteractiveRequest(context); } - $sendInteractiveRequestToProvider(providerId: string, message: IInteractiveSessionDynamicRequest): void { + async $sendInteractiveRequestToProvider(providerId: string, message: IInteractiveSessionDynamicRequest): Promise { + await this._interactiveSessionService.revealSessionForProvider(providerId); return this._interactiveSessionService.sendInteractiveRequestToProvider(providerId, message); } diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts index 7c47e7588015a..31d30fc65a5d7 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts @@ -810,7 +810,10 @@ function installSlashCommandSupport(accessor: ServicesAccessor, editor: IActiveC async function showMessageResponse(accessor: ServicesAccessor, query: string, response: string) { const interactiveSessionService = accessor.get(IInteractiveSessionService); - interactiveSessionService.addCompleteRequest(query, { message: response }); + const providerId = interactiveSessionService.getProviders()[0]; + if (await interactiveSessionService.revealSessionForProvider(providerId)) { + interactiveSessionService.addCompleteRequest(providerId, query, { message: response }); + } } async function sendRequest(accessor: ServicesAccessor, query: string) { diff --git a/src/vs/workbench/contrib/interactiveSession/common/interactiveSessionModel.ts b/src/vs/workbench/contrib/interactiveSession/common/interactiveSessionModel.ts index 1cf96a5c0635a..f65668595e9b9 100644 --- a/src/vs/workbench/contrib/interactiveSession/common/interactiveSessionModel.ts +++ b/src/vs/workbench/contrib/interactiveSession/common/interactiveSessionModel.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { DeferredPromise } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -156,6 +157,7 @@ export interface IInteractiveSessionModel { readonly requestInProgress: boolean; readonly inputPlaceholder?: string; getRequests(): IInteractiveRequestModel[]; + waitForInitialization(): Promise; } export interface ISerializableInteractiveSessionsData { @@ -208,6 +210,7 @@ export class InteractiveSessionModel extends Disposable implements IInteractiveS readonly onDidChange = this._onDidChange.event; private _requests: InteractiveRequestModel[]; + private _isInitializedDeferred = new DeferredPromise(); private _session: IInteractiveSession | undefined; get session(): IInteractiveSession | undefined { @@ -267,6 +270,11 @@ export class InteractiveSessionModel extends Disposable implements IInteractiveS initialize(session: IInteractiveSession, welcomeMessage: InteractiveSessionWelcomeMessageModel | undefined): void { this._session = session; this._welcomeMessage = welcomeMessage; + this._isInitializedDeferred.complete(); + } + + waitForInitialization(): Promise { + return this._isInitializedDeferred.p; } acceptNewProviderState(providerState: any): void { @@ -374,6 +382,10 @@ export class InteractiveSessionModel extends Disposable implements IInteractiveS this._session?.dispose?.(); this._requests.forEach(r => r.response?.dispose()); this._onDidDispose.fire(); + if (!this._isInitializedDeferred.isSettled) { + this._isInitializedDeferred.error(new Error('model disposed')); + } + super.dispose(); } } diff --git a/src/vs/workbench/contrib/interactiveSession/common/interactiveSessionService.ts b/src/vs/workbench/contrib/interactiveSession/common/interactiveSessionService.ts index fc090d6cad0cd..88c32fe2e0185 100644 --- a/src/vs/workbench/contrib/interactiveSession/common/interactiveSessionService.ts +++ b/src/vs/workbench/contrib/interactiveSession/common/interactiveSessionService.ts @@ -12,7 +12,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { InteractiveSessionModel } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionModel'; export interface IInteractiveSession { - id: number; // TODO This is only used by the provider internally, we can probably get rid of it + id: number; requesterUsername: string; requesterAvatarIconUri?: URI; responderUsername: string; @@ -148,7 +148,6 @@ export interface IInteractiveSessionService { _serviceBrand: undefined; registerProvider(provider: IInteractiveProvider): IDisposable; startSession(providerId: string, allowRestoringSession: boolean, token: CancellationToken): InteractiveSessionModel | undefined; - waitForSessionInitialization(sessionId: number): Promise; /** * Returns whether the request was accepted. @@ -158,8 +157,10 @@ export interface IInteractiveSessionService { getSlashCommands(sessionId: number, token: CancellationToken): Promise; clearSession(sessionId: number): void; acceptNewSessionState(sessionId: number, state: any): void; + getProviders(): string[]; + revealSessionForProvider(providerId: string): Promise; addInteractiveRequest(context: any): void; - addCompleteRequest(message: string, response: IInteractiveSessionCompleteResponse): void; + addCompleteRequest(providerId: string, message: string, response: IInteractiveSessionCompleteResponse): void; sendInteractiveRequestToProvider(providerId: string, message: IInteractiveSessionDynamicRequest): void; releaseSession(sessionId: number): void; diff --git a/src/vs/workbench/contrib/interactiveSession/common/interactiveSessionServiceImpl.ts b/src/vs/workbench/contrib/interactiveSession/common/interactiveSessionServiceImpl.ts index 77c5a072800b5..9e54f6cff09fd 100644 --- a/src/vs/workbench/contrib/interactiveSession/common/interactiveSessionServiceImpl.ts +++ b/src/vs/workbench/contrib/interactiveSession/common/interactiveSessionServiceImpl.ts @@ -95,7 +95,6 @@ export class InteractiveSessionService extends Disposable implements IInteractiv private readonly _providers = new Map(); private readonly _sessionModels = new Map(); - private readonly _initializedSessionModels = new Map>(); private readonly _releasedSessions = new Set(); private readonly _pendingRequests = new Map>(); private readonly _unprocessedPersistedSessions: ISerializableInteractiveSessionsData; @@ -132,10 +131,6 @@ export class InteractiveSessionService extends Disposable implements IInteractiv })); } - async waitForSessionInitialization(sessionId: number): Promise { - return await this._initializedSessionModels.get(sessionId); - } - notifyUserAction(action: IInteractiveSessionUserActionEvent): void { if (action.action.kind === 'vote') { this.telemetryService.publicLog2('interactiveSessionVote', { @@ -200,17 +195,14 @@ export class InteractiveSessionService extends Disposable implements IInteractiv const model = this.instantiationService.createInstance(InteractiveSessionModel, providerId, someSessionHistory); this._sessionModels.set(model.sessionId, model); const modelInitPromise = this.initializeSession(model, someSessionHistory, token); - this._initializedSessionModels.set(model.sessionId, modelInitPromise); modelInitPromise.then(resolvedModel => { if (!resolvedModel) { model.dispose(); this._sessionModels.delete(model.sessionId); - this._initializedSessionModels.delete(model.sessionId); } }).catch(() => { model.dispose(); this._sessionModels.delete(model.sessionId); - this._initializedSessionModels.delete(model.sessionId); }); return model; @@ -276,16 +268,12 @@ export class InteractiveSessionService extends Disposable implements IInteractiv return false; } - const modelPromise = this._initializedSessionModels.get(sessionId); - if (!modelPromise) { - throw new Error(`Unknown session: ${sessionId}`); - } - - const model = await modelPromise; + const model = this._sessionModels.get(sessionId); if (!model) { - throw new Error(`Session ${sessionId} failed to start`); + throw new Error(`Unknown session: ${sessionId}`); } + await model.waitForInitialization(); const provider = this._providers.get(model.providerId); if (!provider) { throw new Error(`Unknown provider: ${model.providerId}`); @@ -376,11 +364,12 @@ export class InteractiveSessionService extends Disposable implements IInteractiv } async getSlashCommands(sessionId: number, token: CancellationToken): Promise { - const model = await this._initializedSessionModels.get(sessionId); + const model = this._sessionModels.get(sessionId); if (!model) { throw new Error(`Unknown session: ${sessionId}`); } + await model.waitForInitialization(); const provider = this._providers.get(model.providerId); if (!provider) { throw new Error(`Unknown provider: ${model.providerId}`); @@ -430,13 +419,13 @@ export class InteractiveSessionService extends Disposable implements IInteractiv this.sendRequest(model.sessionId, request.message); } + async revealSessionForProvider(providerId: string): Promise { + const viewId = this.interactiveSessionContributionService.getViewIdForProvider(providerId); + return !!(await this.viewsService.openView(viewId)); + } + async sendInteractiveRequestToProvider(providerId: string, message: IInteractiveSessionDynamicRequest): Promise { this.trace('sendInteractiveRequestToProvider', `providerId: ${providerId}`); - const viewId = this.interactiveSessionContributionService.getViewIdForProvider(providerId); - const view = await this.viewsService.openView(viewId); - if (!view) { - return; - } // Currently we only support one session per provider const modelForProvider = Iterable.find(this._sessionModels.values(), model => model.providerId === providerId); @@ -447,17 +436,12 @@ export class InteractiveSessionService extends Disposable implements IInteractiv await this.sendRequest(modelForProvider.sessionId, message.message); } - async addCompleteRequest(message: string, response: IInteractiveSessionCompleteResponse): Promise { - this.trace('addCompleteRequest', `message: ${message}`); - - // TODO this api should take a providerId, but there is no relation between the interactive editor provider and this provider, so just grab the first one - const providerId = Iterable.first(this._providers.keys()); - if (!providerId) { - throw new Error('No providers available'); - } + getProviders(): string[] { + return Array.from(this._providers.keys()); + } - const viewId = this.interactiveSessionContributionService.getViewIdForProvider(providerId); - await this.viewsService.openView(viewId); + async addCompleteRequest(providerId: string, message: string, response: IInteractiveSessionCompleteResponse): Promise { + this.trace('addCompleteRequest', `message: ${message}`); // Currently we only support one session per provider const modelForProvider = Iterable.find(this._sessionModels.values(), model => model.providerId === providerId); @@ -466,18 +450,13 @@ export class InteractiveSessionService extends Disposable implements IInteractiv throw new Error(`Could not start session for provider ${providerId}`); } - // TODO hmmmm... - const initializedModel = await this._initializedSessionModels.get(modelForProvider.sessionId); - if (!initializedModel) { - throw new Error(`Model failed to initialize for provider ${providerId}`); - } - - const request = initializedModel.addRequest(message); - initializedModel.acceptResponseProgress(request, { + await modelForProvider.waitForInitialization(); + const request = modelForProvider.addRequest(message); + modelForProvider.acceptResponseProgress(request, { content: response.message, }); - initializedModel.completeResponse(request, { - session: initializedModel.session!, + modelForProvider.completeResponse(request, { + session: modelForProvider.session!, errorDetails: response.errorDetails, }); } @@ -496,7 +475,6 @@ export class InteractiveSessionService extends Disposable implements IInteractiv model.dispose(); this._sessionModels.delete(sessionId); - this._initializedSessionModels.delete(sessionId); this._pendingRequests.get(sessionId)?.cancel(); this._releasedSessions.delete(sessionId); } diff --git a/src/vs/workbench/contrib/interactiveSession/test/common/interactiveSessionModel.test.ts b/src/vs/workbench/contrib/interactiveSession/test/common/interactiveSessionModel.test.ts new file mode 100644 index 0000000000000..db5bf99f21c44 --- /dev/null +++ b/src/vs/workbench/contrib/interactiveSession/test/common/interactiveSessionModel.test.ts @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { timeout } from 'vs/base/common/async'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { ILogService, NullLogService } from 'vs/platform/log/common/log'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { InteractiveSessionModel } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionModel'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; + +suite('InteractiveSessionModel', () => { + const testDisposables = new DisposableStore(); + + let instantiationService: TestInstantiationService; + + suiteSetup(async () => { + instantiationService = new TestInstantiationService(); + instantiationService.stub(IStorageService, new TestStorageService()); + instantiationService.stub(ILogService, new NullLogService()); + instantiationService.stub(IExtensionService, new TestExtensionService()); + }); + + teardown(() => { + testDisposables.clear(); + }); + + test('Waits for initialization', async () => { + const model = new InteractiveSessionModel('provider', undefined, new NullLogService()); + + let hasInitialized = false; + model.waitForInitialization().then(() => { + hasInitialized = true; + }); + + await timeout(0); + assert.strictEqual(hasInitialized, false); + + model.initialize(null!, undefined); + await timeout(0); + assert.strictEqual(hasInitialized, true); + }); + + test('Initialization fails when model is disposed', async () => { + const model = new InteractiveSessionModel('provider', undefined, new NullLogService()); + model.dispose(); + + await assert.rejects(() => model.waitForInitialization()); + }); +}); + diff --git a/src/vs/workbench/contrib/interactiveSession/test/common/interactiveSessionService.test.ts b/src/vs/workbench/contrib/interactiveSession/test/common/interactiveSessionService.test.ts index b1946a8d31e23..e7d2228eeb33b 100644 --- a/src/vs/workbench/contrib/interactiveSession/test/common/interactiveSessionService.test.ts +++ b/src/vs/workbench/contrib/interactiveSession/test/common/interactiveSessionService.test.ts @@ -10,11 +10,35 @@ import { ProviderResult } from 'vs/editor/common/languages'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IInteractiveProgress, IInteractiveProvider, IInteractiveRequest, IInteractiveResponse, IInteractiveSession, IPersistedInteractiveState } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService'; +import { IViewsService } from 'vs/workbench/common/views'; +import { IInteractiveSessionContributionService } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionContributionService'; +import { IInteractiveProgress, IInteractiveProvider, IInteractiveRequest, IInteractiveResponse, IInteractiveSession, IInteractiveSlashCommand, IPersistedInteractiveState } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService'; import { InteractiveSessionService } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionServiceImpl'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; +class SimpleTestProvider implements IInteractiveProvider { + private static sessionId = 0; + + lastInitialState = undefined; + + constructor(readonly id: string) { } + + prepareSession(initialState: any) { + this.lastInitialState = initialState; + return Promise.resolve({ + id: SimpleTestProvider.sessionId++, + username: 'test', + responderUsername: 'test', + requesterUsername: 'test' + }); + } + + async provideReply(request: IInteractiveRequest) { + return { session: request.session, followups: [] }; + } +} + suite('InteractiveSession', () => { const testDisposables = new DisposableStore(); @@ -26,6 +50,8 @@ suite('InteractiveSession', () => { instantiationService.stub(IStorageService, storageService = new TestStorageService()); instantiationService.stub(ILogService, new NullLogService()); instantiationService.stub(IExtensionService, new TestExtensionService()); + instantiationService.stub(IViewsService, new TestExtensionService()); + instantiationService.stub(IInteractiveSessionContributionService, new TestExtensionService()); }); teardown(() => { @@ -33,41 +59,18 @@ suite('InteractiveSession', () => { }); test('Restores state for the correct provider', async () => { - let sessionId = 0; - function getTestProvider(providerId: string) { - return new class implements IInteractiveProvider { - readonly id = providerId; - - lastInitialState = undefined; - - prepareSession(initialState: any) { - this.lastInitialState = initialState; - return Promise.resolve({ - id: sessionId++, - username: 'test', - responderUsername: 'test', - requesterUsername: 'test' - }); - } - - async provideReply(request: IInteractiveRequest) { - return { session: request.session, followups: [] }; - } - }; - } - const testService = instantiationService.createInstance(InteractiveSessionService); - const provider1 = getTestProvider('provider1'); - const provider2 = getTestProvider('provider2'); + const provider1 = new SimpleTestProvider('provider1'); + const provider2 = new SimpleTestProvider('provider2'); testService.registerProvider(provider1); testService.registerProvider(provider2); let session1 = testService.startSession('provider1', true, CancellationToken.None); - await testService.waitForSessionInitialization(session1.sessionId); + await session1.waitForInitialization(); session1!.addRequest('request 1'); let session2 = testService.startSession('provider2', true, CancellationToken.None); - await testService.waitForSessionInitialization(session2.sessionId); + await session2.waitForInitialization(); session2!.addRequest('request 2'); assert.strictEqual(provider1.lastInitialState, undefined); @@ -80,13 +83,38 @@ suite('InteractiveSession', () => { testService2.registerProvider(provider1); testService2.registerProvider(provider2); session1 = testService2.startSession('provider1', true, CancellationToken.None); - await testService2.waitForSessionInitialization(session1.sessionId); + await session1.waitForInitialization(); session2 = testService2.startSession('provider2', true, CancellationToken.None); - await testService2.waitForSessionInitialization(session2.sessionId); + await session2.waitForInitialization(); assert.deepStrictEqual(provider1.lastInitialState, { state: 'provider1_state' }); assert.deepStrictEqual(provider2.lastInitialState, { state: 'provider2_state' }); }); + test('Handles failed session startup', async () => { + function getFailProvider(providerId: string) { + return new class implements IInteractiveProvider { + readonly id = providerId; + + lastInitialState = undefined; + + prepareSession(initialState: any): ProviderResult { + throw new Error('Failed to start session'); + } + + async provideReply(request: IInteractiveRequest) { + return { session: request.session, followups: [] }; + } + }; + } + + const testService = instantiationService.createInstance(InteractiveSessionService); + const provider1 = getFailProvider('provider1'); + testService.registerProvider(provider1); + + const session1 = testService.startSession('provider1', true, CancellationToken.None); + await assert.rejects(() => session1.waitForInitialization()); + }); + test('Can\'t register same provider id twice', async () => { const testService = instantiationService.createInstance(InteractiveSessionService); const id = 'testProvider'; @@ -112,6 +140,57 @@ suite('InteractiveSession', () => { }); }, 'Expected to throw for dupe provider'); }); -}); + test('getSlashCommands', async () => { + const testService = instantiationService.createInstance(InteractiveSessionService); + const provider = new class extends SimpleTestProvider { + constructor() { + super('testProvider'); + } + + provideSlashCommands(): ProviderResult { + return [ + { + command: 'command', + detail: 'detail', + sortText: 'sortText', + } + ]; + } + }; + + testService.registerProvider(provider); + + const model = testService.startSession('testProvider', true, CancellationToken.None); + const commands = await testService.getSlashCommands(model.sessionId, CancellationToken.None); + + assert.strictEqual(commands?.length, 1); + assert.strictEqual(commands?.[0].command, 'command'); + assert.strictEqual(commands?.[0].detail, 'detail'); + assert.strictEqual(commands?.[0].sortText, 'sortText'); + }); + + test('sendInteractiveRequestToProvider', async () => { + const testService = instantiationService.createInstance(InteractiveSessionService); + testService.registerProvider(new SimpleTestProvider('testProvider')); + + const model = testService.startSession('testProvider', true, CancellationToken.None); + assert.strictEqual(model.getRequests().length, 0); + await testService.sendInteractiveRequestToProvider('testProvider', { message: 'test request' }); + assert.strictEqual(model.getRequests().length, 1); + }); + + test('addCompleteRequest', async () => { + const testService = instantiationService.createInstance(InteractiveSessionService); + testService.registerProvider(new SimpleTestProvider('testProvider')); + + const model = testService.startSession('testProvider', true, CancellationToken.None); + assert.strictEqual(model.getRequests().length, 0); + + await testService.addCompleteRequest('testProvider', 'test request', { message: 'test response' }); + assert.strictEqual(model.getRequests().length, 1); + assert.ok(model.getRequests()[0].response); + assert.strictEqual(model.getRequests()[0].response?.response.value, 'test response'); + }); +}); From c639eeb2060ff5fe4ecdb6f3a5fcf602d9fd8532 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Wed, 12 Apr 2023 15:43:40 -0700 Subject: [PATCH 30/59] search result aria label should prioritze content over location (#179795) Fixes #179718 --- src/vs/workbench/contrib/search/browser/searchResultsView.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchResultsView.ts b/src/vs/workbench/contrib/search/browser/searchResultsView.ts index 06479dcff3f7e..80bde436867a9 100644 --- a/src/vs/workbench/contrib/search/browser/searchResultsView.ts +++ b/src/vs/workbench/contrib/search/browser/searchResultsView.ts @@ -401,10 +401,10 @@ export class SearchAccessibilityProvider implements IListAccessibilityProvider Date: Thu, 13 Apr 2023 00:45:39 +0200 Subject: [PATCH 31/59] Set new default theme (#179812) --- extensions/theme-defaults/package.nls.json | 8 +-- .../theme-defaults/themes/dark_plus.json | 2 +- .../themes/dark_plus_experimental.json | 2 +- .../theme-defaults/themes/light_plus.json | 2 +- .../themes/light_plus_experimental.json | 2 +- .../themes/browser/themes.contribution.ts | 56 ++++++++++++++++++- .../common/media/theme_picker.ts | 9 +-- .../themes/browser/productIconThemeData.ts | 5 +- .../themes/browser/workbenchThemeService.ts | 20 ++++--- .../services/themes/common/colorThemeData.ts | 16 ++++++ .../themes/common/themeConfiguration.ts | 27 +++------ .../themes/common/themeExtensionPoints.ts | 12 ++-- .../themes/common/workbenchThemeService.ts | 15 +++++ 13 files changed, 126 insertions(+), 50 deletions(-) diff --git a/extensions/theme-defaults/package.nls.json b/extensions/theme-defaults/package.nls.json index bcb5691a95b2d..d96a488ae5916 100644 --- a/extensions/theme-defaults/package.nls.json +++ b/extensions/theme-defaults/package.nls.json @@ -1,10 +1,10 @@ { "displayName": "Default Themes", "description": "The default Visual Studio light and dark themes", - "darkPlusColorThemeLabel": "Dark+ (default dark)", - "darkPlusExperimentalColorThemeLabel": "Dark+ V2 (Experimental)", - "lightPlusColorThemeLabel": "Light+ (default light)", - "lightPlusExperimentalColorThemeLabel": "Light+ V2 (Experimental)", + "darkPlusColorThemeLabel": "Dark+", + "darkPlusExperimentalColorThemeLabel": "Dark+ Experimental", + "lightPlusColorThemeLabel": "Light+", + "lightPlusExperimentalColorThemeLabel": "Light+ Experimental", "darkColorThemeLabel": "Dark (Visual Studio)", "lightColorThemeLabel": "Light (Visual Studio)", "hcColorThemeLabel": "Dark High Contrast", diff --git a/extensions/theme-defaults/themes/dark_plus.json b/extensions/theme-defaults/themes/dark_plus.json index fc2fd7d7f32e4..ed80b1785f6e1 100644 --- a/extensions/theme-defaults/themes/dark_plus.json +++ b/extensions/theme-defaults/themes/dark_plus.json @@ -1,6 +1,6 @@ { "$schema": "vscode://schemas/color-theme", - "name": "Dark+ (default dark)", + "name": "Dark+", "include": "./dark_vs.json", "tokenColors": [ { diff --git a/extensions/theme-defaults/themes/dark_plus_experimental.json b/extensions/theme-defaults/themes/dark_plus_experimental.json index 053d659313a21..04236f7ed9d71 100644 --- a/extensions/theme-defaults/themes/dark_plus_experimental.json +++ b/extensions/theme-defaults/themes/dark_plus_experimental.json @@ -134,6 +134,6 @@ "titleBar.inactiveForeground": "#8b949e", "welcomePage.tileBackground": "#ffffff0f", "welcomePage.progress.foreground": "#0078d4", - "widgetBorder": "#ffffff15", + "widget.border": "#ffffff15", }, } diff --git a/extensions/theme-defaults/themes/light_plus.json b/extensions/theme-defaults/themes/light_plus.json index d24599e6feb14..f73b79579f0e0 100644 --- a/extensions/theme-defaults/themes/light_plus.json +++ b/extensions/theme-defaults/themes/light_plus.json @@ -1,6 +1,6 @@ { "$schema": "vscode://schemas/color-theme", - "name": "Light+ (default light)", + "name": "Light+", "include": "./light_vs.json", "tokenColors": [ // adds rules to the light vs rules { diff --git a/extensions/theme-defaults/themes/light_plus_experimental.json b/extensions/theme-defaults/themes/light_plus_experimental.json index c1c0729ff8e75..73d42822fccc4 100644 --- a/extensions/theme-defaults/themes/light_plus_experimental.json +++ b/extensions/theme-defaults/themes/light_plus_experimental.json @@ -147,6 +147,6 @@ "titleBar.inactiveBackground": "#f8f8f8", "titleBar.inactiveForeground": "#8b949e", "welcomePage.tileBackground": "#f3f3f3", - "widgetBorder": "#0000001a", + "widget.border": "#0000001a", }, } diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index c94be398bf376..45e86052a1b84 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -9,7 +9,7 @@ import { MenuRegistry, MenuId, Action2, registerAction2, ISubmenuItem } from 'vs import { equalsIgnoreCase } from 'vs/base/common/strings'; import { Registry } from 'vs/platform/registry/common/platform'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; -import { IWorkbenchThemeService, IWorkbenchTheme, ThemeSettingTarget, IWorkbenchColorTheme, IWorkbenchFileIconTheme, IWorkbenchProductIconTheme, ThemeSettings } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService, IWorkbenchTheme, ThemeSettingTarget, IWorkbenchColorTheme, IWorkbenchFileIconTheme, IWorkbenchProductIconTheme, ThemeSettings, ThemeSettingDefaults } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { VIEWLET_ID, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/common/extensions'; import { IExtensionGalleryService, IExtensionManagementService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IColorRegistry, Extensions as ColorRegistryExtensions } from 'vs/platform/theme/common/colorRegistry'; @@ -33,10 +33,16 @@ import { Emitter } from 'vs/base/common/event'; import { IExtensionResourceLoaderService } from 'vs/platform/extensionResourceLoader/common/extensionResourceLoader'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { FileIconThemeData } from 'vs/workbench/services/themes/browser/fileIconThemeData'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { toAction } from 'vs/base/common/actions'; +import { isWeb } from 'vs/base/common/platform'; export const manageExtensionIcon = registerIcon('theme-selection-manage-extension', Codicon.gear, localize('manageExtensionIcon', 'Icon for the \'Manage\' action in the theme selection quick pick.')); @@ -693,3 +699,49 @@ MenuRegistry.appendMenuItem(ThemesSubMenu, { order: 3 }); +class DefaultThemeUpdatedNotificationContribution implements IWorkbenchContribution { + + static STORAGE_KEY = 'themeUpdatedNotificationShown'; + + constructor( + @INotificationService private readonly _notificationService: INotificationService, + @IWorkbenchThemeService private readonly _workbenchThemeService: IWorkbenchThemeService, + @IStorageService private readonly _storageService: IStorageService, + @ICommandService private readonly _commandService: ICommandService, + ) { + if (!this._workbenchThemeService.hasUpdatedDefaultThemes()) { + return; + } + if (_storageService.getBoolean(DefaultThemeUpdatedNotificationContribution.STORAGE_KEY, StorageScope.APPLICATION)) { + return; + } + setTimeout(() => { + this._showNotification(); + }, 6000); + } + + private async _showNotification(): Promise { + this._storageService.store(DefaultThemeUpdatedNotificationContribution.STORAGE_KEY, true, StorageScope.APPLICATION, StorageTarget.USER); + await this._notificationService.notify({ + id: 'themeUpdatedNotification', + severity: Severity.Info, + message: localize({ key: 'themeUpdatedNotification', comment: ['{0} is the name of the new default theme'] }, "VS Code now ships with a new default theme '{0}'. We hope you like it. If not, you can switch back to the old theme or try one of the many other color themes available.", this._workbenchThemeService.getColorTheme().label), + actions: { + primary: [ + toAction({ id: 'themeUpdated.browseThemes', label: localize('browseThemes', "Browse Themes"), run: () => this._commandService.executeCommand(SelectColorThemeCommandId) }), + toAction({ + id: 'themeUpdated.revert', label: localize('revert', "Revert"), run: async () => { + const oldSettingsId = isWeb ? ThemeSettingDefaults.COLOR_THEME_LIGHT_OLD : ThemeSettingDefaults.COLOR_THEME_DARK_OLD; + const oldTheme = (await this._workbenchThemeService.getColorThemes()).find(theme => theme.settingsId === oldSettingsId); + if (oldTheme) { + this._workbenchThemeService.setColorTheme(oldTheme, 'auto'); + } + } + }) + ] + } + }); + } +} +const workbenchRegistry = Registry.as(Extensions.Workbench); +workbenchRegistry.registerWorkbenchContribution(DefaultThemeUpdatedNotificationContribution, LifecyclePhase.Ready); diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/common/media/theme_picker.ts b/src/vs/workbench/contrib/welcomeGettingStarted/common/media/theme_picker.ts index 766134e5f6dfb..40a664e75818d 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/common/media/theme_picker.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/common/media/theme_picker.ts @@ -5,25 +5,26 @@ import { escape } from 'vs/base/common/strings'; import { localize } from 'vs/nls'; +import { ThemeSettingDefaults } from 'vs/workbench/services/themes/common/workbenchThemeService'; export default () => `
- + ${escape(localize('dark', "Dark"))} - + ${escape(localize('light', "Light"))}
- + ${escape(localize('HighContrast', "Dark High Contrast"))} - + ${escape(localize('HighContrastLight', "Light High Contrast"))} diff --git a/src/vs/workbench/services/themes/browser/productIconThemeData.ts b/src/vs/workbench/services/themes/browser/productIconThemeData.ts index da1add769355d..98de9b94fc88d 100644 --- a/src/vs/workbench/services/themes/browser/productIconThemeData.ts +++ b/src/vs/workbench/services/themes/browser/productIconThemeData.ts @@ -8,10 +8,9 @@ import * as nls from 'vs/nls'; import * as Paths from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import * as Json from 'vs/base/common/json'; -import { ExtensionData, IThemeExtensionPoint, IWorkbenchProductIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ExtensionData, IThemeExtensionPoint, IWorkbenchProductIconTheme, ThemeSettingDefaults } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { DEFAULT_PRODUCT_ICON_THEME_SETTING_VALUE } from 'vs/workbench/services/themes/common/themeConfiguration'; import { fontIdRegex, fontWeightRegex, fontStyleRegex, fontFormatRegex } from 'vs/workbench/services/themes/common/productIconThemeSchema'; import { isObject, isString } from 'vs/base/common/types'; import { ILogService } from 'vs/platform/log/common/log'; @@ -98,7 +97,7 @@ export class ProductIconThemeData implements IWorkbenchProductIconTheme { static get defaultTheme(): ProductIconThemeData { let themeData = ProductIconThemeData._defaultProductIconTheme; if (!themeData) { - themeData = ProductIconThemeData._defaultProductIconTheme = new ProductIconThemeData(DEFAULT_PRODUCT_ICON_THEME_ID, nls.localize('defaultTheme', 'Default'), DEFAULT_PRODUCT_ICON_THEME_SETTING_VALUE); + themeData = ProductIconThemeData._defaultProductIconTheme = new ProductIconThemeData(DEFAULT_PRODUCT_ICON_THEME_ID, nls.localize('defaultTheme', 'Default'), ThemeSettingDefaults.PRODUCT_ICON_THEME); themeData.isLoaded = true; themeData.extensionData = undefined; themeData.watch = false; diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 41b75f7daf4d7..0ae7e479016c7 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkbenchThemeService, IWorkbenchColorTheme, IWorkbenchFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, VS_HC_LIGHT_THEME, ThemeSettings, IWorkbenchProductIconTheme, ThemeSettingTarget } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService, IWorkbenchColorTheme, IWorkbenchFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, VS_HC_LIGHT_THEME, ThemeSettings, IWorkbenchProductIconTheme, ThemeSettingTarget, ThemeSettingDefaults } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -44,9 +44,6 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; // implementation -const DEFAULT_COLOR_THEME_ID = 'vs-dark vscode-theme-defaults-themes-dark_plus-json'; -const DEFAULT_LIGHT_COLOR_THEME_ID = 'vs vscode-theme-defaults-themes-light_plus-json'; - const PERSISTED_OS_COLOR_SCHEME = 'osColorScheme'; const PERSISTED_OS_COLOR_SCHEME_SCOPE = StorageScope.APPLICATION; // the OS scheme depends on settings in the OS @@ -104,6 +101,8 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { private themeSettingIdBeforeSchemeSwitch: string | undefined; + private hasDefaultUpdated: boolean = false; + constructor( @IExtensionService extensionService: IExtensionService, @IStorageService private readonly storageService: IStorageService, @@ -145,6 +144,8 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { // a color theme document with good defaults until the theme is loaded let themeData: ColorThemeData | undefined = ColorThemeData.fromStorageData(this.storageService); if (themeData && this.settings.colorTheme !== themeData.settingsId && this.settings.isDefaultColorTheme()) { + this.hasDefaultUpdated = themeData.settingsId === ThemeSettingDefaults.COLOR_THEME_DARK_OLD || themeData.settingsId === ThemeSettingDefaults.COLOR_THEME_LIGHT_OLD; + // the web has different defaults than the desktop, therefore do not restore when the setting is the default theme and the storage doesn't match that. themeData = undefined; } @@ -206,7 +207,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { if (devThemes.length) { return this.setColorTheme(devThemes[0].id, ConfigurationTarget.MEMORY); } - const fallbackTheme = this.currentColorTheme.type === ColorScheme.LIGHT ? DEFAULT_LIGHT_COLOR_THEME_ID : DEFAULT_COLOR_THEME_ID; + const fallbackTheme = this.currentColorTheme.type === ColorScheme.LIGHT ? ThemeSettingDefaults.COLOR_THEME_LIGHT : ThemeSettingDefaults.COLOR_THEME_DARK; const theme = this.colorThemeRegistry.findThemeBySettingsId(this.settings.colorTheme, fallbackTheme); const preferredColorScheme = this.getPreferredColorScheme(); @@ -307,7 +308,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { updateColorThemeConfigurationSchemas(event.themes); if (await this.restoreColorTheme()) { // checks if theme from settings exists and is set // restore theme - if (this.currentColorTheme.id === DEFAULT_COLOR_THEME_ID && !types.isUndefined(prevColorId) && await this.colorThemeRegistry.findThemeById(prevColorId)) { + if (this.currentColorTheme.settingsId === ThemeSettingDefaults.COLOR_THEME_DARK && !types.isUndefined(prevColorId) && await this.colorThemeRegistry.findThemeById(prevColorId)) { await this.setColorTheme(prevColorId, 'auto'); prevColorId = undefined; } else if (event.added.some(t => t.settingsId === this.currentColorTheme.settingsId)) { @@ -316,7 +317,8 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } else if (event.removed.some(t => t.settingsId === this.currentColorTheme.settingsId)) { // current theme is no longer available prevColorId = this.currentColorTheme.id; - await this.setColorTheme(DEFAULT_COLOR_THEME_ID, 'auto'); + const defaultTheme = this.colorThemeRegistry.findThemeBySettingsId(ThemeSettingDefaults.COLOR_THEME_DARK); + await this.setColorTheme(defaultTheme, 'auto'); } }); @@ -423,6 +425,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return null; } + public hasUpdatedDefaultThemes(): boolean { + return this.hasDefaultUpdated; + } + public getColorTheme(): IWorkbenchColorTheme { return this.currentColorTheme; } diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index c498d6c4da4a8..257a77e705194 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -587,6 +587,22 @@ export class ColorThemeData implements IWorkbenchColorTheme { // constructors static createUnloadedThemeForThemeType(themeType: ColorScheme, colorMap?: { [id: string]: string }): ColorThemeData { + if (!colorMap) { + if (themeType === ColorScheme.LIGHT) { + colorMap = { + 'activityBar.background': '#f8f8f8', + 'statusBar.background': '#f8f8f8', + 'statusBar.noFolderBackground': '#f8f8f8' + }; + } else { + colorMap = { + 'activityBar.background': '#181818', + 'statusBar.background': '#181818', + 'statusBar.noFolderBackground': '#1f1f1f', + }; + } + + } return ColorThemeData.createUnloadedTheme(getThemeTypeSelector(themeType), colorMap); } diff --git a/src/vs/workbench/services/themes/common/themeConfiguration.ts b/src/vs/workbench/services/themes/common/themeConfiguration.ts index 556201b09b32e..5e54716254c66 100644 --- a/src/vs/workbench/services/themes/common/themeConfiguration.ts +++ b/src/vs/workbench/services/themes/common/themeConfiguration.ts @@ -12,19 +12,10 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { textmateColorsSchemaId, textmateColorGroupSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema'; import { workbenchColorsSchemaId } from 'vs/platform/theme/common/colorRegistry'; import { tokenStylingSchemaId } from 'vs/platform/theme/common/tokenClassificationRegistry'; -import { ThemeSettings, IWorkbenchColorTheme, IWorkbenchFileIconTheme, IColorCustomizations, ITokenColorCustomizations, IWorkbenchProductIconTheme, ISemanticTokenColorCustomizations, ThemeSettingTarget } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ThemeSettings, IWorkbenchColorTheme, IWorkbenchFileIconTheme, IColorCustomizations, ITokenColorCustomizations, IWorkbenchProductIconTheme, ISemanticTokenColorCustomizations, ThemeSettingTarget, ThemeSettingDefaults } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { isWeb } from 'vs/base/common/platform'; -const DEFAULT_THEME_DARK_SETTING_VALUE = 'Default Dark+'; -const DEFAULT_THEME_LIGHT_SETTING_VALUE = 'Default Light+'; -const DEFAULT_THEME_HC_DARK_SETTING_VALUE = 'Default High Contrast'; -const DEFAULT_THEME_HC_LIGHT_SETTING_VALUE = 'Default High Contrast Light'; - -const DEFAULT_FILE_ICON_THEME_SETTING_VALUE = 'vs-seti'; - -export const DEFAULT_PRODUCT_ICON_THEME_SETTING_VALUE = 'Default'; - // Configuration: Themes const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -35,7 +26,7 @@ const colorThemeSettingEnumDescriptions: string[] = []; const colorThemeSettingSchema: IConfigurationPropertySchema = { type: 'string', description: nls.localize('colorTheme', "Specifies the color theme used in the workbench."), - default: isWeb ? DEFAULT_THEME_LIGHT_SETTING_VALUE : DEFAULT_THEME_DARK_SETTING_VALUE, + default: isWeb ? ThemeSettingDefaults.COLOR_THEME_LIGHT : ThemeSettingDefaults.COLOR_THEME_DARK, enum: colorThemeSettingEnum, enumDescriptions: colorThemeSettingEnumDescriptions, enumItemLabels: colorThemeSettingEnumItemLabels, @@ -44,7 +35,7 @@ const colorThemeSettingSchema: IConfigurationPropertySchema = { const preferredDarkThemeSettingSchema: IConfigurationPropertySchema = { type: 'string', // markdownDescription: nls.localize({ key: 'preferredDarkColorTheme', comment: ['`#{0}#` will become a link to an other setting. Do not remove backtick or #'] }, 'Specifies the preferred color theme for dark OS appearance when `#{0}#` is enabled.', ThemeSettings.DETECT_COLOR_SCHEME), - default: DEFAULT_THEME_DARK_SETTING_VALUE, + default: ThemeSettingDefaults.COLOR_THEME_DARK, enum: colorThemeSettingEnum, enumDescriptions: colorThemeSettingEnumDescriptions, enumItemLabels: colorThemeSettingEnumItemLabels, @@ -53,7 +44,7 @@ const preferredDarkThemeSettingSchema: IConfigurationPropertySchema = { const preferredLightThemeSettingSchema: IConfigurationPropertySchema = { type: 'string', markdownDescription: nls.localize({ key: 'preferredLightColorTheme', comment: ['`#{0}#` will become a link to an other setting. Do not remove backtick or #'] }, 'Specifies the preferred color theme for light OS appearance when `#{0}#` is enabled.', ThemeSettings.DETECT_COLOR_SCHEME), - default: DEFAULT_THEME_LIGHT_SETTING_VALUE, + default: ThemeSettingDefaults.COLOR_THEME_LIGHT, enum: colorThemeSettingEnum, enumDescriptions: colorThemeSettingEnumDescriptions, enumItemLabels: colorThemeSettingEnumItemLabels, @@ -62,7 +53,7 @@ const preferredLightThemeSettingSchema: IConfigurationPropertySchema = { const preferredHCDarkThemeSettingSchema: IConfigurationPropertySchema = { type: 'string', markdownDescription: nls.localize({ key: 'preferredHCDarkColorTheme', comment: ['`#{0}#` will become a link to an other setting. Do not remove backtick or #'] }, 'Specifies the preferred color theme used in high contrast dark mode when `#{0}#` is enabled.', ThemeSettings.DETECT_HC), - default: DEFAULT_THEME_HC_DARK_SETTING_VALUE, + default: ThemeSettingDefaults.COLOR_THEME_HC_DARK, enum: colorThemeSettingEnum, enumDescriptions: colorThemeSettingEnumDescriptions, enumItemLabels: colorThemeSettingEnumItemLabels, @@ -71,7 +62,7 @@ const preferredHCDarkThemeSettingSchema: IConfigurationPropertySchema = { const preferredHCLightThemeSettingSchema: IConfigurationPropertySchema = { type: 'string', markdownDescription: nls.localize({ key: 'preferredHCLightColorTheme', comment: ['`#{0}#` will become a link to an other setting. Do not remove backtick or #'] }, 'Specifies the preferred color theme used in high contrast light mode when `#{0}#` is enabled.', ThemeSettings.DETECT_HC), - default: DEFAULT_THEME_HC_LIGHT_SETTING_VALUE, + default: ThemeSettingDefaults.COLOR_THEME_HC_LIGHT, enum: colorThemeSettingEnum, enumDescriptions: colorThemeSettingEnumDescriptions, enumItemLabels: colorThemeSettingEnumItemLabels, @@ -95,7 +86,7 @@ const colorCustomizationsSchema: IConfigurationPropertySchema = { }; const fileIconThemeSettingSchema: IConfigurationPropertySchema = { type: ['string', 'null'], - default: DEFAULT_FILE_ICON_THEME_SETTING_VALUE, + default: ThemeSettingDefaults.FILE_ICON_THEME, description: nls.localize('iconTheme', "Specifies the file icon theme used in the workbench or 'null' to not show any file icons."), enum: [null], enumItemLabels: [nls.localize('noIconThemeLabel', 'None')], @@ -104,9 +95,9 @@ const fileIconThemeSettingSchema: IConfigurationPropertySchema = { }; const productIconThemeSettingSchema: IConfigurationPropertySchema = { type: ['string', 'null'], - default: DEFAULT_PRODUCT_ICON_THEME_SETTING_VALUE, + default: ThemeSettingDefaults.PRODUCT_ICON_THEME, description: nls.localize('productIconTheme', "Specifies the product icon theme used."), - enum: [DEFAULT_PRODUCT_ICON_THEME_SETTING_VALUE], + enum: [ThemeSettingDefaults.PRODUCT_ICON_THEME], enumItemLabels: [nls.localize('defaultProductIconThemeLabel', 'Default')], enumDescriptions: [nls.localize('defaultProductIconThemeDesc', 'Default')], errorMessage: nls.localize('productIconThemeError', "Product icon theme is unknown or not installed.") diff --git a/src/vs/workbench/services/themes/common/themeExtensionPoints.ts b/src/vs/workbench/services/themes/common/themeExtensionPoints.ts index 741e79f8f85e6..8862328cba9bc 100644 --- a/src/vs/workbench/services/themes/common/themeExtensionPoints.ts +++ b/src/vs/workbench/services/themes/common/themeExtensionPoints.ts @@ -197,24 +197,20 @@ export class ThemeRegistry { return resultingThemes; } - public findThemeById(themeId: string, defaultId?: string): T | undefined { + public findThemeById(themeId: string): T | undefined { if (this.builtInTheme && this.builtInTheme.id === themeId) { return this.builtInTheme; } const allThemes = this.getThemes(); - let defaultTheme: T | undefined = undefined; for (const t of allThemes) { if (t.id === themeId) { return t; } - if (t.id === defaultId) { - defaultTheme = t; - } } - return defaultTheme; + return undefined; } - public findThemeBySettingsId(settingsId: string | null, defaultId?: string): T | undefined { + public findThemeBySettingsId(settingsId: string | null, defaultSettingsId?: string): T | undefined { if (this.builtInTheme && this.builtInTheme.settingsId === settingsId) { return this.builtInTheme; } @@ -224,7 +220,7 @@ export class ThemeRegistry { if (t.settingsId === settingsId) { return t; } - if (t.id === defaultId) { + if (t.settingsId === defaultSettingsId) { defaultTheme = t; } } diff --git a/src/vs/workbench/services/themes/common/workbenchThemeService.ts b/src/vs/workbench/services/themes/common/workbenchThemeService.ts index dbcb760c1bc19..149b290979ff9 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -40,6 +40,19 @@ export enum ThemeSettings { DETECT_HC = 'window.autoDetectHighContrast' } +export enum ThemeSettingDefaults { + COLOR_THEME_DARK = 'Default Dark+ Experimental', + COLOR_THEME_LIGHT = 'Default Light+ Experimental', + COLOR_THEME_HC_DARK = 'Default High Contrast', + COLOR_THEME_HC_LIGHT = 'Default High Contrast Light', + + COLOR_THEME_DARK_OLD = 'Default Dark+', + COLOR_THEME_LIGHT_OLD = 'Default Light+', + + FILE_ICON_THEME = 'vs-seti', + PRODUCT_ICON_THEME = 'Default' +} + export interface IWorkbenchTheme { readonly id: string; readonly label: string; @@ -77,6 +90,8 @@ export interface IWorkbenchThemeService extends IThemeService { getMarketplaceColorThemes(publisher: string, name: string, version: string): Promise; onDidColorThemeChange: Event; + hasUpdatedDefaultThemes(): boolean; + setFileIconTheme(iconThemeId: string | undefined | IWorkbenchFileIconTheme, settingsTarget: ThemeSettingTarget): Promise; getFileIconTheme(): IWorkbenchFileIconTheme; getFileIconThemes(): Promise; From ab287c80c5e262b6811060301163571f7470eac7 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 12 Apr 2023 16:06:35 -0700 Subject: [PATCH 32/59] Clean up test jsdoc for l10n (#179822) Fixes the example blocks and adds a few new lines --- src/vscode-dts/vscode.d.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 6f8088e8aba0b..7db9bb4363bd8 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -15880,12 +15880,15 @@ declare module 'vscode' { * Marks a string for localization. If a localized bundle is available for the language specified by * {@link env.language} and the bundle has a localized value for this message, then that localized * value will be returned (with injected {@link args} values for any templated values). + * * @param message - The message to localize. Supports index templating where strings like `{0}` and `{1}` are * replaced by the item at that index in the {@link args} array. * @param args - The arguments to be used in the localized string. The index of the argument is used to * match the template placeholder in the localized string. * @returns localized string with injected arguments. - * @example `l10n.t('Hello {0}!', 'World');` + * + * @example + * l10n.t('Hello {0}!', 'World'); */ export function t(message: string, ...args: Array): string; @@ -15893,18 +15896,22 @@ declare module 'vscode' { * Marks a string for localization. If a localized bundle is available for the language specified by * {@link env.language} and the bundle has a localized value for this message, then that localized * value will be returned (with injected {@link args} values for any templated values). + * * @param message The message to localize. Supports named templating where strings like `{foo}` and `{bar}` are * replaced by the value in the Record for that key (foo, bar, etc). * @param args The arguments to be used in the localized string. The name of the key in the record is used to * match the template placeholder in the localized string. * @returns localized string with injected arguments. - * @example `l10n.t('Hello {name}', { name: 'Erich' });` + * + * @example + * l10n.t('Hello {name}', { name: 'Erich' }); */ export function t(message: string, args: Record): string; /** * Marks a string for localization. If a localized bundle is available for the language specified by * {@link env.language} and the bundle has a localized value for this message, then that localized * value will be returned (with injected args values for any templated values). + * * @param options The options to use when localizing the message. * @returns localized string with injected arguments. */ From 1b3fceea8b67b36c5d1ad1f8d5fd16cdd287cd97 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 12 Apr 2023 16:29:54 -0700 Subject: [PATCH 33/59] Add method to query Interactive Session providers, and a context key (#179818) --- .../common/interactiveSessionContextKeys.ts | 2 ++ .../common/interactiveSessionService.ts | 1 + .../common/interactiveSessionServiceImpl.ts | 13 +++++++++++-- .../test/common/interactiveSessionService.test.ts | 3 +++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/interactiveSession/common/interactiveSessionContextKeys.ts b/src/vs/workbench/contrib/interactiveSession/common/interactiveSessionContextKeys.ts index 41824e2144850..09883ed65acbc 100644 --- a/src/vs/workbench/contrib/interactiveSession/common/interactiveSessionContextKeys.ts +++ b/src/vs/workbench/contrib/interactiveSession/common/interactiveSessionContextKeys.ts @@ -13,3 +13,5 @@ export const CONTEXT_INTERACTIVE_REQUEST_IN_PROGRESS = new RawContextKey('interactiveInputHasText', false, { type: 'boolean', description: localize('interactiveInputHasText', "True when the interactive input has text.") }); export const CONTEXT_IN_INTERACTIVE_INPUT = new RawContextKey('inInteractiveInput', false, { type: 'boolean', description: localize('inInteractiveInput', "True when focus is in the interactive input, false otherwise.") }); export const CONTEXT_IN_INTERACTIVE_SESSION = new RawContextKey('inInteractiveSession', false, { type: 'boolean', description: localize('inInteractiveSession', "True when focus is in the interactive session widget, false otherwise.") }); + +export const CONTEXT_PROVIDER_EXISTS = new RawContextKey('hasInteractiveSessionProvider', false, { type: 'boolean', description: localize('hasInteractiveSessionProvider', "True when some interactive session provider has been registered.") }); diff --git a/src/vs/workbench/contrib/interactiveSession/common/interactiveSessionService.ts b/src/vs/workbench/contrib/interactiveSession/common/interactiveSessionService.ts index 88c32fe2e0185..682588acbdc15 100644 --- a/src/vs/workbench/contrib/interactiveSession/common/interactiveSessionService.ts +++ b/src/vs/workbench/contrib/interactiveSession/common/interactiveSessionService.ts @@ -147,6 +147,7 @@ export const IInteractiveSessionService = createDecorator(); private readonly _pendingRequests = new Map>(); private readonly _unprocessedPersistedSessions: ISerializableInteractiveSessionsData; + private readonly _hasProvider: IContextKey; private readonly _onDidPerformUserAction = this._register(new Emitter()); public readonly onDidPerformUserAction: Event = this._onDidPerformUserAction.event; @@ -110,8 +113,12 @@ export class InteractiveSessionService extends Disposable implements IInteractiv @ITelemetryService private readonly telemetryService: ITelemetryService, @IViewsService private readonly viewsService: IViewsService, @IInteractiveSessionContributionService private readonly interactiveSessionContributionService: IInteractiveSessionContributionService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { super(); + + this._hasProvider = CONTEXT_PROVIDER_EXISTS.bindTo(this.contextKeyService); + const sessionData = storageService.get(serializedInteractiveSessionKey, StorageScope.WORKSPACE, ''); if (sessionData) { this._unprocessedPersistedSessions = this.deserializeInteractiveSessions(sessionData); @@ -487,14 +494,16 @@ export class InteractiveSessionService extends Disposable implements IInteractiv } this._providers.set(provider.id, provider); + this._hasProvider.set(true); return toDisposable(() => { this.trace('registerProvider', `Disposing interactive session provider`); this._providers.delete(provider.id); + this._hasProvider.set(this._providers.size > 0); }); } - getAll() { - return [...this._providers]; + getProviderIds(): string[] { + return Array.from(this._providers.keys()); } } diff --git a/src/vs/workbench/contrib/interactiveSession/test/common/interactiveSessionService.test.ts b/src/vs/workbench/contrib/interactiveSession/test/common/interactiveSessionService.test.ts index e7d2228eeb33b..f4b3d7190f0e6 100644 --- a/src/vs/workbench/contrib/interactiveSession/test/common/interactiveSessionService.test.ts +++ b/src/vs/workbench/contrib/interactiveSession/test/common/interactiveSessionService.test.ts @@ -7,7 +7,9 @@ import * as assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ProviderResult } from 'vs/editor/common/languages'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IViewsService } from 'vs/workbench/common/views'; @@ -50,6 +52,7 @@ suite('InteractiveSession', () => { instantiationService.stub(IStorageService, storageService = new TestStorageService()); instantiationService.stub(ILogService, new NullLogService()); instantiationService.stub(IExtensionService, new TestExtensionService()); + instantiationService.stub(IContextKeyService, new MockContextKeyService()); instantiationService.stub(IViewsService, new TestExtensionService()); instantiationService.stub(IInteractiveSessionContributionService, new TestExtensionService()); }); From c9df8ee8e8c316f38dcc75a894ba150024ce3718 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 12 Apr 2023 17:50:57 -0700 Subject: [PATCH 34/59] Clean up Interactive Session styles to make them easier to use outside of the normal view, and rendering fix (#179829) * Clean up Interactive Session styles to make them easier to use outside of the normal view * Reduce layout shifting from the "cursor" and never render it after "Thinking..." --- .../browser/interactiveSessionListRenderer.ts | 11 +- .../browser/media/interactiveSession.css | 112 +++++++++--------- .../common/interactiveSessionViewModel.ts | 12 +- 3 files changed, 67 insertions(+), 68 deletions(-) diff --git a/src/vs/workbench/contrib/interactiveSession/browser/interactiveSessionListRenderer.ts b/src/vs/workbench/contrib/interactiveSession/browser/interactiveSessionListRenderer.ts index a51b226eadaf8..ed6d43cae9e4f 100644 --- a/src/vs/workbench/contrib/interactiveSession/browser/interactiveSessionListRenderer.ts +++ b/src/vs/workbench/contrib/interactiveSession/browser/interactiveSessionListRenderer.ts @@ -126,7 +126,7 @@ export class InteractiveListItemRenderer extends Disposable implements ITreeRend } } - private shouldRenderProgressively(): boolean { + private progressiveRenderEnabled(): boolean { return !this.configService.getValue('interactive.experimental.disableProgressiveRendering'); } @@ -226,10 +226,11 @@ export class InteractiveListItemRenderer extends Disposable implements ITreeRend // Do a progressive render if // - This the last response in the list + // - And it is not a placeholder response ("Thinking...") // - And the response is not complete // - Or, we previously started a progressive rendering of this element (if the element is complete, we will finish progressive rendering with a very fast rate) // - And, the feature is not disabled in configuration - if (isResponseVM(element) && index === this.delegate.getListLength() - 1 && (!element.isComplete || element.renderData) && this.shouldRenderProgressively()) { + if (isResponseVM(element) && index === this.delegate.getListLength() - 1 && !element.isPlaceholder && (!element.isComplete || element.renderData) && this.progressiveRenderEnabled()) { this.traceLayout('renderElement', `start progressive render ${kind}, index=${index}`); const progressiveRenderingDisposables = templateData.elementDisposables.add(new DisposableStore()); const timer = templateData.elementDisposables.add(new IntervalTimer()); @@ -348,11 +349,13 @@ export class InteractiveListItemRenderer extends Disposable implements ITreeRend isFullyRendered: renderValue.isFullString }; - // Doing the progressive render - const plusCursor = renderValue.value.match(/```.*$/) ? + // Don't add the cursor if it will go after a codeblock, since this will always cause layout shifting + // when the codeblock is the last thing in the response, and that happens often. + const plusCursor = renderValue.value.match(/```\s*$/) ? renderValue.value : renderValue.value + ` ${InteractiveListItemRenderer.cursorCharacter}`; const result = this.renderMarkdown(new MarkdownString(plusCursor), element, disposables, templateData, true); + // Doing the progressive render dom.clearNode(templateData.value); templateData.value.appendChild(result.element); disposables.add(result); diff --git a/src/vs/workbench/contrib/interactiveSession/browser/media/interactiveSession.css b/src/vs/workbench/contrib/interactiveSession/browser/media/interactiveSession.css index a5faf6f468472..663a93adaf150 100644 --- a/src/vs/workbench/contrib/interactiveSession/browser/media/interactiveSession.css +++ b/src/vs/workbench/contrib/interactiveSession/browser/media/interactiveSession.css @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ .interactive-list .monaco-list-row .monaco-tl-twistie { - /* Hide twisties */ + /* Hide twisties from tree rows */ display: none !important; } -.interactive-list .interactive-item-container { +.interactive-item-container { padding: 16px 20px 0px 20px; display: flex; flex-direction: column; @@ -19,25 +19,25 @@ -webkit-user-select: text; } -.interactive-list .interactive-item-container .header { +.interactive-item-container .header { display: flex; align-items: center; justify-content: space-between; } -.interactive-list .interactive-item-container .header .user { +.interactive-item-container .header .user { display: flex; align-items: center; gap: 6px; } -.interactive-list .interactive-item-container .header .username { +.interactive-item-container .header .username { margin: 0; font-size: 12px; font-weight: 600; } -.interactive-list .interactive-item-container .header .avatar { +.interactive-item-container .header .avatar { display: flex; align-items: center; justify-content: center; @@ -49,60 +49,60 @@ user-select: none; } -.interactive-list .interactive-item-container .header .avatar .icon { +.interactive-item-container .header .avatar .icon { width: 24px; height: 24px; border-radius: 50%; } -.interactive-list .interactive-item-container .header .avatar .codicon { +.interactive-item-container .header .avatar .codicon { color: var(--vscode-badge-foreground) !important; } -.interactive-list .interactive-item-container .header .monaco-toolbar { +.interactive-item-container .header .monaco-toolbar { display: none; } -.interactive-list .interactive-item-container.interactive-response:hover .header .monaco-toolbar { +.interactive-item-container.interactive-response:hover .header .monaco-toolbar { display: initial; } -.interactive-list .interactive-item-container .header .monaco-toolbar .action-label { +.interactive-item-container .header .monaco-toolbar .action-label { border: 1px solid transparent; padding: 2px; } -.interactive-list .interactive-item-container .header .monaco-toolbar .checked .action-label, -.interactive-list .interactive-item-container .header .monaco-toolbar .checked .action-label:hover { +.interactive-item-container .header .monaco-toolbar .checked .action-label, +.interactive-item-container .header .monaco-toolbar .checked .action-label:hover { color: var(--vscode-inputOption-activeForeground) !important; border-color: var(--vscode-inputOption-activeBorder); background-color: var(--vscode-inputOption-activeBackground); } -.interactive-list .interactive-item-container .value { +.interactive-item-container .value { width: 100%; } -.interactive-list .interactive-item-container .value table { +.interactive-item-container .value table { width: 100%; text-align: left; margin-bottom: 16px; } -.interactive-list .interactive-item-container .value table, -.interactive-list .interactive-item-container .value table td, -.interactive-list .interactive-item-container .value table th { +.interactive-item-container .value table, +.interactive-item-container .value table td, +.interactive-item-container .value table th { border: 1px solid var(--vscode-interactive-requestBorder); border-collapse: collapse; padding: 4px 6px; } -.interactive-list .interactive-item-container .value a { +.interactive-item-container .value a { color: var(--vscode-textLink-foreground); } -.interactive-list .interactive-item-container .value a:hover, -.interactive-list .interactive-item-container .value a:active { +.interactive-item-container .value a:hover, +.interactive-item-container .value a:active { color: var(--vscode-textLink-activeForeground); } @@ -110,54 +110,54 @@ overflow: hidden; } -.interactive-list .monaco-list-row .interactive-request { +.interactive-request { background-color: var(--vscode-interactive-requestBackground); border-bottom: 1px solid var(--vscode-interactive-requestBorder); border-top: 1px solid var(--vscode-interactive-requestBorder); } -.interactive-list .monaco-list-row .value { +.interactive-item-container .value { white-space: normal; min-height: 36px; word-wrap: break-word; } -.interactive-list .monaco-list-row .value > :last-child:not(.rendered-markdown) { +.interactive-item-container .value > :last-child:not(.rendered-markdown) { /* The container has padding on all sides except the bottom. The last element needs to provide this margin. rendered-markdown has its own margin. TODO Another approach could be removing the margin on the very last element inside the markdown container? */ margin-bottom: 16px; } -.interactive-list .monaco-list-row .value > .interactive-response-error-details:not(:last-child) { +.interactive-item-container .value > .interactive-response-error-details:not(:last-child) { margin-bottom: 8px; } -.interactive-list .monaco-list-row .value h1 { +.interactive-item-container .value h1 { font-size: 20px; font-weight: 600; margin: 16px 0; } -.interactive-list .monaco-list-row .value h2 { +.interactive-item-container .value h2 { font-size: 16px; font-weight: 600; margin: 16px 0; } -.interactive-list .monaco-list-row .value h3 { +.interactive-item-container .value h3 { font-size: 14px; font-weight: 600; margin: 16px 0; } -.interactive-list .monaco-list-row .value p { +.interactive-item-container .value p { margin: 0 0 16px 0; line-height: 1.6em; } -.interactive-list .monaco-list-row .monaco-tokenized-source, -.interactive-list .monaco-list-row code { +.interactive-item-container .monaco-tokenized-source, +.interactive-item-container code { font-family: var(--monaco-monospace-font); color: var(--vscode-textPreformat-foreground); } @@ -200,71 +200,67 @@ color: var(--vscode-icon-foreground) !important; } -.interactive-session .monaco-inputbox { - width: 100%; -} - -.interactive-session .interactive-result-editor-wrapper { +.interactive-item-container .interactive-result-editor-wrapper { position: relative; } -.interactive-session .interactive-result-editor-wrapper .monaco-toolbar { +.interactive-item-container .interactive-result-editor-wrapper .monaco-toolbar { display: none; position: absolute; top: -13px; right: 10px; height: 26px; - background-color: var(--vscode-interactive-result-editor-background-color); + background-color: var(--vscode-interactive-result-editor-background-color, var(--vscode-editor-background)); border: 1px solid var(--vscode-interactive-requestBorder); z-index: 100; } -.interactive-session .interactive-result-editor-wrapper .monaco-toolbar .action-item { +.interactive-item-container .interactive-result-editor-wrapper .monaco-toolbar .action-item { height: 24px; width: 24px; margin: 1px 2px; } -.interactive-session .interactive-result-editor-wrapper .monaco-toolbar .action-item .codicon { +.interactive-item-container .interactive-result-editor-wrapper .monaco-toolbar .action-item .codicon { margin: 1px; } -.interactive-session .interactive-result-editor-wrapper:hover .monaco-toolbar { +.interactive-item-container .interactive-result-editor-wrapper:hover .monaco-toolbar { display: initial; } -.interactive-session .interactive-result-editor-wrapper .interactive-result-editor { +.interactive-item-container .interactive-result-editor-wrapper .interactive-result-editor { border: 1px solid var(--vscode-input-border, transparent); } -.interactive-session .interactive-response .monaco-editor .margin, -.interactive-session .interactive-response .monaco-editor .monaco-editor-background { +.interactive-response .monaco-editor .margin, +.interactive-response .monaco-editor .monaco-editor-background { background-color: var(--vscode-interactive-result-editor-background-color); } -.interactive-result-editor-wrapper { +.interactive-item-container .interactive-result-editor-wrapper { margin: 16px 0; } -.interactive-session .interactive-response .interactive-response-error-details { +.interactive-response .interactive-response-error-details { display: flex; align-items: start; gap: 6px; } -.interactive-session .interactive-response .interactive-response-error-details .codicon { +.interactive-response .interactive-response-error-details .codicon { margin-top: 1px; } -.interactive-session .interactive-response .interactive-response-error-details .codicon-error { +.interactive-response .interactive-response-error-details .codicon-error { color: var(--vscode-errorForeground) !important; /* Have to override default styles which apply to all lists */ } -.interactive-session .interactive-response .interactive-response-error-details .codicon-info { +.interactive-response .interactive-response-error-details .codicon-info { color: var(--vscode-notificationsInfoIcon-foreground) !important; /* Have to override default styles which apply to all lists */ } -.interactive-session .interactive-item-container .value .interactive-slash-command { +.interactive-item-container .value .interactive-slash-command { color: var(--vscode-textLink-foreground); } @@ -275,24 +271,24 @@ border-top: solid 1px var(--vscode-interactive-requestBorder); } -.interactive-session .interactive-session-followups { +.interactive-session-followups { display: flex; flex-direction: column; gap: 5px; align-items: start; } -.interactive-session .interactive-session-followups .monaco-button { +.interactive-session-followups .monaco-button { text-align: left; width: initial; } -.interactive-session .interactive-session-followups .monaco-button .codicon { +.interactive-session-followups .monaco-button .codicon { margin-left: 0; margin-top: 1px; } -.interactive-session .interactive-response-followups .monaco-button { +.interactive-item-container .interactive-response-followups .monaco-button { padding: 4px 8px; } @@ -317,23 +313,23 @@ float: left; } -.interactive-session .interactive-session-followups .monaco-button.interactive-followup-reply { +.interactive-session-followups .monaco-button.interactive-followup-reply { padding: 0px; font-size: 11px; font-weight: 600; border: none; } -.interactive-session .interactive-welcome .value .interactive-session-followups { +.interactive-welcome .value .interactive-session-followups { margin-bottom: 10px; } -.interactive-list .interactive-item-container .monaco-toolbar .codicon { +.interactive-item-container .monaco-toolbar .codicon { /* Very aggressive list styles try to apply focus colors to every codicon in a list row. */ color: var(--vscode-icon-foreground) !important; } -.interactive-session .interactive-item-container.filtered-response .value .rendered-markdown { +.interactive-item-container.filtered-response .value .rendered-markdown { -webkit-mask-image: linear-gradient(rgba(0, 0, 0, 0.85), rgba(0, 0, 0, 0.05)); mask-image: linear-gradient(rgba(0, 0, 0, 0.85), rgba(0, 0, 0, 0.05)); } diff --git a/src/vs/workbench/contrib/interactiveSession/common/interactiveSessionViewModel.ts b/src/vs/workbench/contrib/interactiveSession/common/interactiveSessionViewModel.ts index ad3831361be87..48a4cd7154188 100644 --- a/src/vs/workbench/contrib/interactiveSession/common/interactiveSessionViewModel.ts +++ b/src/vs/workbench/contrib/interactiveSession/common/interactiveSessionViewModel.ts @@ -68,6 +68,7 @@ export interface IInteractiveResponseViewModel { readonly response: IMarkdownString; readonly isComplete: boolean; readonly isCanceled: boolean; + readonly isPlaceholder: boolean; readonly vote: InteractiveSessionVoteDirection | undefined; readonly replyFollowups?: IInteractiveSessionReplyFollowup[]; readonly commandFollowups?: IInteractiveSessionResponseCommandFollowup[]; @@ -184,8 +185,6 @@ export class InteractiveResponseViewModel extends Disposable implements IInterac private readonly _onDidChange = this._register(new Emitter()); readonly onDidChange = this._onDidChange.event; - private _isPlaceholder = false; - get id() { return this._model.id + `_${this._modelChangeCount}`; } @@ -222,6 +221,11 @@ export class InteractiveResponseViewModel extends Disposable implements IInterac return this._model.isCanceled; } + private _isPlaceholder = false; + get isPlaceholder() { + return this._isPlaceholder; + } + get replyFollowups() { return this._model.followups?.filter((f): f is IInteractiveSessionReplyFollowup => f.kind === 'reply'); } @@ -266,11 +270,7 @@ export class InteractiveResponseViewModel extends Disposable implements IInterac this._register(_model.onDidChange(() => { if (this._isPlaceholder && (_model.response.value || this.isComplete)) { - // The VM is no longer rendered as a placeholder- clear the rendered word count this._isPlaceholder = false; - if (this.renderData) { - this.renderData.renderedWordCount = 0; - } } if (this._contentUpdateTimings) { From 213f4fa39dc5a969599b8bd6cfc7ff03e6e7ea9c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 13 Apr 2023 09:19:49 +0200 Subject: [PATCH 35/59] signin using web auth provider always (#179838) --- .../userDataSync/browser/userDataSyncWorkbenchService.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index 93b2dbbc5f818..fcceb4c446054 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -578,7 +578,11 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat sessionId = (await this.authenticationService.createSession(accountOrAuthProvider.id, accountOrAuthProvider.scopes)).id; } } else { - sessionId = accountOrAuthProvider.sessionId; + if (this.environmentService.options?.settingsSyncOptions?.authenticationProvider?.id === accountOrAuthProvider.authenticationProviderId) { + sessionId = await this.environmentService.options?.settingsSyncOptions?.authenticationProvider?.signIn(); + } else { + sessionId = accountOrAuthProvider.sessionId; + } } this.currentSessionId = sessionId; await this.update(); From 45a44d1786eece2ad1fd709c2cb35f8aaa43451c Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 13 Apr 2023 11:14:33 +0200 Subject: [PATCH 36/59] Git/GitHub - Branch protection refactoring (#179848) Branch protection refactoring --- extensions/git/src/api/git.d.ts | 7 ++++++- extensions/git/src/branchProtection.ts | 14 ++++++++------ extensions/git/src/repository.ts | 2 +- extensions/github/src/branchProtection.ts | 22 +++++++++------------- extensions/github/src/typings/git.d.ts | 7 ++++++- 5 files changed, 30 insertions(+), 22 deletions(-) diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index 6a20b9017f0a3..997e170bbf08c 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -274,9 +274,14 @@ export interface PushErrorHandler { handlePushError(repository: Repository, remote: Remote, refspec: string, error: Error & { gitErrorCode: GitErrorCodes }): Promise; } +export interface BranchProtection { + readonly remote: string; + readonly branches: string[]; +} + export interface BranchProtectionProvider { onDidChangeBranchProtection: Event; - provideBranchProtection(): Map; + provideBranchProtection(): BranchProtection[]; } export type APIState = 'uninitialized' | 'initialized'; diff --git a/extensions/git/src/branchProtection.ts b/extensions/git/src/branchProtection.ts index 0aece314f4545..7ca1705c1257d 100644 --- a/extensions/git/src/branchProtection.ts +++ b/extensions/git/src/branchProtection.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, Event, EventEmitter, Uri, workspace } from 'vscode'; -import { BranchProtectionProvider } from './api/git'; +import { BranchProtection, BranchProtectionProvider } from './api/git'; import { dispose, filterEvent } from './util'; export interface IBranchProtectionProviderRegistry { @@ -19,7 +19,8 @@ export class GitBranchProtectionProvider implements BranchProtectionProvider { private readonly _onDidChangeBranchProtection = new EventEmitter(); onDidChangeBranchProtection = this._onDidChangeBranchProtection.event; - private branchProtection = new Map<'', string[]>(); + private branchProtection!: BranchProtection; + private disposables: Disposable[] = []; constructor(private readonly repositoryRoot: Uri) { @@ -28,8 +29,8 @@ export class GitBranchProtectionProvider implements BranchProtectionProvider { this.updateBranchProtection(); } - provideBranchProtection(): Map { - return this.branchProtection; + provideBranchProtection(): BranchProtection[] { + return [this.branchProtection]; } private updateBranchProtection(): void { @@ -37,10 +38,11 @@ export class GitBranchProtectionProvider implements BranchProtectionProvider { const branchProtectionConfig = scopedConfig.get('branchProtection') ?? []; const branchProtectionValues = Array.isArray(branchProtectionConfig) ? branchProtectionConfig : [branchProtectionConfig]; - this.branchProtection.set('', branchProtectionValues + const branches = branchProtectionValues .map(bp => typeof bp === 'string' ? bp.trim() : '') - .filter(bp => bp !== '')); + .filter(bp => bp !== ''); + this.branchProtection = { remote: '', branches }; this._onDidChangeBranchProtection.fire(this.repositoryRoot); } diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 75e7c9097883a..65919283a83de 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -2367,7 +2367,7 @@ export class Repository implements Disposable { this.branchProtection.clear(); for (const provider of this.branchProtectionProviderRegistry.getBranchProtectionProviders(root)) { - for (const [remote, branches] of provider.provideBranchProtection().entries()) { + for (const { remote, branches } of provider.provideBranchProtection()) { this.branchProtection.set(remote, branches.length !== 0 ? picomatch(branches) : undefined); } } diff --git a/extensions/github/src/branchProtection.ts b/extensions/github/src/branchProtection.ts index 05dee5465a9c7..e49a3eda3d449 100644 --- a/extensions/github/src/branchProtection.ts +++ b/extensions/github/src/branchProtection.ts @@ -5,7 +5,7 @@ import { EventEmitter, Uri, workspace } from 'vscode'; import { getOctokit } from './auth'; -import { API, BranchProtectionProvider, Repository } from './typings/git'; +import { API, BranchProtection, BranchProtectionProvider, Repository } from './typings/git'; import { DisposableStore, getRepositoryFromUrl } from './util'; export class GithubBranchProtectionProviderManager { @@ -62,15 +62,15 @@ export class GithubBranchProtectionProvider implements BranchProtectionProvider private readonly _onDidChangeBranchProtection = new EventEmitter(); onDidChangeBranchProtection = this._onDidChangeBranchProtection.event; - private branchProtection = new Map(); + private branchProtection!: BranchProtection[]; constructor(private readonly repository: Repository) { repository.status() .then(() => this.initializeBranchProtection()); } - provideBranchProtection(): Map { - return this.branchProtection; + provideBranchProtection(): BranchProtection[] { + return this.branchProtection ?? []; } private async initializeBranchProtection(): Promise { @@ -109,7 +109,7 @@ export class GithubBranchProtectionProvider implements BranchProtectionProvider return; } - this.branchProtection.set(remote.name, [HEAD.name]); + this.branchProtection = [{ remote: remote.name, branches: [HEAD.name] }]; this._onDidChangeBranchProtection.fire(this.repository.rootUri); } catch { // todo@lszomoru - add logging @@ -118,7 +118,7 @@ export class GithubBranchProtectionProvider implements BranchProtectionProvider private async updateBranchProtection(): Promise { try { - let branchProtectionUpdated = false; + const branchProtection: BranchProtection[] = []; for (const remote of this.repository.state.remotes) { const repository = getRepositoryFromUrl(remote.pushUrl ?? remote.fetchUrl ?? ''); @@ -143,15 +143,11 @@ export class GithubBranchProtectionProvider implements BranchProtectionProvider page++; } - if (protectedBranches.length > 0) { - this.branchProtection.set(remote.name, protectedBranches); - branchProtectionUpdated = true; - } + branchProtection.push({ remote: remote.name, branches: protectedBranches }); } - if (branchProtectionUpdated) { - this._onDidChangeBranchProtection.fire(this.repository.rootUri); - } + this.branchProtection = branchProtection; + this._onDidChangeBranchProtection.fire(this.repository.rootUri); } catch { // todo@lszomoru - add logging } diff --git a/extensions/github/src/typings/git.d.ts b/extensions/github/src/typings/git.d.ts index 5a84e952abbdd..fa270a382ee7e 100644 --- a/extensions/github/src/typings/git.d.ts +++ b/extensions/github/src/typings/git.d.ts @@ -268,9 +268,14 @@ export interface PushErrorHandler { handlePushError(repository: Repository, remote: Remote, refspec: string, error: Error & { gitErrorCode: GitErrorCodes }): Promise; } +export interface BranchProtection { + readonly remote: string; + readonly branches: string[]; +} + export interface BranchProtectionProvider { onDidChangeBranchProtection: Event; - provideBranchProtection(): Map; + provideBranchProtection(): BranchProtection[]; } export type APIState = 'uninitialized' | 'initialized'; From 8f5198cb30552e8752f1a55cb5c685f81185c075 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 13 Apr 2023 11:24:45 +0200 Subject: [PATCH 37/59] Support built in profile templates (#179849) --- src/vs/base/common/product.ts | 6 ++ .../browser/userDataProfile.ts | 99 ++++++++++++++++--- 2 files changed, 94 insertions(+), 11 deletions(-) diff --git a/src/vs/base/common/product.ts b/src/vs/base/common/product.ts index 564667c24180a..f56cb3281bac0 100644 --- a/src/vs/base/common/product.ts +++ b/src/vs/base/common/product.ts @@ -191,6 +191,12 @@ export interface IProductConfiguration { readonly 'editSessions.store'?: Omit; readonly darwinUniversalAssetId?: string; + readonly profileTemplates?: IProfileTemplateInfo[]; +} + +export interface IProfileTemplateInfo { + readonly name: string; + readonly url: string; } export interface ITunnelApplicationConfig { diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index 115af9d3991c8..c7489ba29da72 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -16,7 +16,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { RenameProfileAction } from 'vs/workbench/contrib/userDataProfile/browser/userDataProfileActions'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { CURRENT_PROFILE_CONTEXT, HAS_PROFILES_CONTEXT, IS_CURRENT_PROFILE_TRANSIENT_CONTEXT, IS_PROFILE_IMPORT_IN_PROGRESS_CONTEXT, IUserDataProfileImportExportService, IUserDataProfileManagementService, IUserDataProfileService, PROFILES_CATEGORY, PROFILE_FILTER, IS_PROFILE_EXPORT_IN_PROGRESS_CONTEXT, ProfilesMenu, PROFILES_ENABLEMENT_CONTEXT, PROFILES_TITLE } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; -import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputService, IQuickPickItem, QuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { URI } from 'vs/base/common/uri'; @@ -26,6 +26,8 @@ import { IWorkspaceTagsService } from 'vs/workbench/contrib/tags/common/workspac import { getErrorMessage } from 'vs/base/common/errors'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IProfileTemplateInfo } from 'vs/base/common/product'; const CREATE_EMPTY_PROFILE_ACTION_ID = 'workbench.profiles.actions.createEmptyProfile'; const CREATE_EMPTY_PROFILE_ACTION_TITLE = { @@ -39,6 +41,8 @@ const CREATE_FROM_CURRENT_PROFILE_ACTION_TITLE = { original: 'Create from Current Profile...' }; +type IProfileTemplateQuickPickItem = IQuickPickItem & IProfileTemplateInfo; + export class UserDataProfilesWorkbenchContribution extends Disposable implements IWorkbenchContribution { private readonly currentProfileContext: IContextKey; @@ -56,6 +60,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements @IQuickInputService private readonly quickInputService: IQuickInputService, @INotificationService private readonly notificationService: INotificationService, @ILifecycleService private readonly lifecycleService: ILifecycleService, + @IProductService private readonly productService: IProductService, ) { super(); @@ -97,6 +102,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements this.registerCreateFromCurrentProfileAction(); this.registerCreateProfileAction(); this.registerDeleteProfileAction(); + this.registerCreateProfileFromTemplatesAction(); this.registerHelpAction(); } @@ -445,6 +451,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements } private registerCreateProfileAction(): void { + const that = this; this._register(registerAction2(class CreateProfileAction extends Action2 { constructor() { super({ @@ -469,16 +476,35 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements async run(accessor: ServicesAccessor) { const quickInputService = accessor.get(IQuickInputService); const commandService = accessor.get(ICommandService); - const pick = await quickInputService.pick( - [{ - id: CREATE_EMPTY_PROFILE_ACTION_ID, - label: CREATE_EMPTY_PROFILE_ACTION_TITLE.value, - }, { - id: CREATE_FROM_CURRENT_PROFILE_ACTION_ID, - label: CREATE_FROM_CURRENT_PROFILE_ACTION_TITLE.value, - }], { hideInput: true, canPickMany: false, title: localize('create profile title', "{0}: Create...", PROFILES_CATEGORY.value) }); - if (pick?.id) { - return commandService.executeCommand(pick.id); + const userDataProfileImportExportService = accessor.get(IUserDataProfileImportExportService); + const quickPickItems: QuickPickItem[] = [{ + id: CREATE_EMPTY_PROFILE_ACTION_ID, + label: CREATE_EMPTY_PROFILE_ACTION_TITLE.value, + }, { + id: CREATE_FROM_CURRENT_PROFILE_ACTION_ID, + label: CREATE_FROM_CURRENT_PROFILE_ACTION_TITLE.value, + }]; + const profileTemplateQuickPickItems = that.getProfileTemplatesQuickPickItems(); + if (profileTemplateQuickPickItems.length) { + quickPickItems.push({ + type: 'separator', + label: localize('templates', "Profile Templates") + }, ...profileTemplateQuickPickItems); + } + const pick = await quickInputService.pick(quickPickItems, + { + hideInput: true, + canPickMany: false, + title: localize('create profile title', "{0}: Create...", PROFILES_CATEGORY.value) + }); + if (pick) { + if (pick.id) { + return commandService.executeCommand(pick.id); + } + if ((pick).url) { + const uri = URI.parse((pick).url); + return userDataProfileImportExportService.importProfile(uri); + } } } })); @@ -539,6 +565,44 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements }); } + private registerCreateProfileFromTemplatesAction(): void { + const that = this; + this._register(registerAction2(class CreateProfileFromTemplatesAction extends Action2 { + constructor() { + super({ + id: 'workbench.profiles.actions.createProfileFromTemplates', + title: { + value: localize('create profile from templates', "Create Profile from Templates..."), + original: 'Create Profile from Templates...' + }, + category: PROFILES_CATEGORY, + precondition: PROFILES_ENABLEMENT_CONTEXT, + }); + } + + async run(accessor: ServicesAccessor) { + const quickInputService = accessor.get(IQuickInputService); + const userDataProfileImportExportService = accessor.get(IUserDataProfileImportExportService); + const notificationService = accessor.get(INotificationService); + const profileTemplateQuickPickItems = that.getProfileTemplatesQuickPickItems(); + if (profileTemplateQuickPickItems.length) { + const pick = await quickInputService.pick(profileTemplateQuickPickItems, + { + hideInput: true, + canPickMany: false, + title: localize('create profile title', "{0}: Create...", PROFILES_CATEGORY.value) + }); + if ((pick)?.url) { + const uri = URI.parse((pick).url); + return userDataProfileImportExportService.importProfile(uri); + } + } else { + notificationService.info(localize('no templates', "There are no templates to create from")); + } + } + })); + } + private registerHelpAction(): void { this._register(registerAction2(class HelpAction extends Action2 { constructor() { @@ -557,6 +621,19 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements })); } + private getProfileTemplatesQuickPickItems(): IProfileTemplateQuickPickItem[] { + const quickPickItems: IProfileTemplateQuickPickItem[] = []; + if (this.productService.profileTemplates) { + for (const template of this.productService.profileTemplates) { + quickPickItems.push({ + label: localize('create from template', "Create {0} Profile...", template.name), + ...template + }); + } + } + return quickPickItems; + } + private async reportWorkspaceProfileInfo(): Promise { await this.lifecycleService.when(LifecyclePhase.Eventually); const workspaceId = await this.workspaceTagsService.getTelemetryWorkspaceId(this.workspaceContextService.getWorkspace(), this.workspaceContextService.getWorkbenchState()); From a4e74a222739f80ccf3481ae460d1b4c43d276f4 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 13 Apr 2023 11:37:24 +0200 Subject: [PATCH 38/59] reveal single new file (#179853) --- .../browser/interactiveEditorActions.ts | 15 ++++++- .../browser/interactiveEditorController.ts | 39 ++++++++++--------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts index 3cf3a42c26c81..0baa817e53325 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts @@ -20,6 +20,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor'; +import { ILogService } from 'vs/platform/log/common/log'; export class StartSessionAction extends EditorAction2 { @@ -406,9 +407,19 @@ export class ApplyPreviewEdits extends AbstractInteractiveEditorAction { }); } - override async runInteractiveEditorCommand(_accessor: ServicesAccessor, ctrl: InteractiveEditorController): Promise { - await ctrl.applyChanges(); + override async runInteractiveEditorCommand(accessor: ServicesAccessor, ctrl: InteractiveEditorController): Promise { + const logService = accessor.get(ILogService); + const editorService = accessor.get(IEditorService); + const edit = await ctrl.applyChanges(); + if (!edit) { + logService.warn('FAILED to apply changes, no edit response'); + return; + } ctrl.cancelSession(); + if (edit.singleCreateFileEdit) { + editorService.openEditor({ resource: edit.singleCreateFileEdit.uri }, SIDE_GROUP); + } + } } diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts index 31d30fc65a5d7..daf05e025ed2e 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts @@ -154,7 +154,7 @@ class InlineDiffDecorations { } } -class UIEditResponse { +export class EditResponse { readonly localEdits: TextEdit[] = []; readonly singleCreateFileEdit: { uri: URI; edits: TextEdit[] } | undefined; @@ -218,7 +218,7 @@ class LastEditorState { readonly modelVersionId: number, readonly provider: IInteractiveEditorSessionProvider, readonly session: IInteractiveEditorSession, - readonly response: UIEditResponse, + readonly response: EditResponse, ) { } } @@ -511,7 +511,7 @@ export class InteractiveEditorController implements IEditorContribution { continue; } - const editResponse = new UIEditResponse(textModel.uri, reply); + const editResponse = new EditResponse(textModel.uri, reply); if (editResponse.workspaceEdits && (!editResponse.singleCreateFileEdit || editMode === 'direct')) { this._bulkEditService.apply(editResponse.workspaceEdits, { editor: this._editor, label: localize('ie', "{0}", input), showPreview: true }); @@ -712,24 +712,25 @@ export class InteractiveEditorController implements IEditorContribution { } async applyChanges() { - if (this._lastEditState) { - const { model, modelVersionId, response } = this._lastEditState; - - if (response.workspaceEdits) { - await this._bulkEditService.apply(response.workspaceEdits); - return true; - - } else if (!response.workspaceEditsIncludeLocalEdits) { - if (model.getAlternativeVersionId() === modelVersionId) { - model.pushStackElement(); - const edits = response.localEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)); - model.pushEditOperations(null, edits, () => null); - model.pushStackElement(); - return true; - } + if (!this._lastEditState) { + return undefined; + } + + const { model, modelVersionId, response } = this._lastEditState; + + if (response.workspaceEdits) { + await this._bulkEditService.apply(response.workspaceEdits); + + } else if (!response.workspaceEditsIncludeLocalEdits) { + if (model.getAlternativeVersionId() === modelVersionId) { + model.pushStackElement(); + const edits = response.localEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)); + model.pushEditOperations(null, edits, () => null); + model.pushStackElement(); } } - return false; + + return response; } } From fcbf58cdd542452d0d51e218483cae1fbf0c86c8 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 13 Apr 2023 11:44:00 +0200 Subject: [PATCH 39/59] Notify to try new theme (#179854) --- .../themes/browser/themes.contribution.ts | 44 +++++++++++++++---- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index 45e86052a1b84..0f2b43ac6ad9b 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -39,7 +39,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { toAction } from 'vs/base/common/actions'; import { isWeb } from 'vs/base/common/platform'; @@ -709,25 +709,32 @@ class DefaultThemeUpdatedNotificationContribution implements IWorkbenchContribut @IStorageService private readonly _storageService: IStorageService, @ICommandService private readonly _commandService: ICommandService, ) { - if (!this._workbenchThemeService.hasUpdatedDefaultThemes()) { - return; - } if (_storageService.getBoolean(DefaultThemeUpdatedNotificationContribution.STORAGE_KEY, StorageScope.APPLICATION)) { return; } - setTimeout(() => { - this._showNotification(); - }, 6000); + if (this._workbenchThemeService.hasUpdatedDefaultThemes()) { + setTimeout(() => { + this._showYouGotMigratedNotification(); + }, 6000); + } else { + const currentTheme = this._workbenchThemeService.getColorTheme().settingsId; + if (currentTheme === ThemeSettingDefaults.COLOR_THEME_LIGHT_OLD || currentTheme === ThemeSettingDefaults.COLOR_THEME_DARK_OLD) { + setTimeout(() => { + this._tryNewThemeNotification(); + }, 6000); + } + } } - private async _showNotification(): Promise { + private async _showYouGotMigratedNotification(): Promise { this._storageService.store(DefaultThemeUpdatedNotificationContribution.STORAGE_KEY, true, StorageScope.APPLICATION, StorageTarget.USER); await this._notificationService.notify({ id: 'themeUpdatedNotification', severity: Severity.Info, - message: localize({ key: 'themeUpdatedNotification', comment: ['{0} is the name of the new default theme'] }, "VS Code now ships with a new default theme '{0}'. We hope you like it. If not, you can switch back to the old theme or try one of the many other color themes available.", this._workbenchThemeService.getColorTheme().label), + message: localize({ key: 'themeUpdatedNotification', comment: ['{0} is the name of the new default theme'] }, "Visual Studio Code now ships with a new default theme '{0}'. If you prefer, you can switch back to the old theme or try one of the many other color themes available.", this._workbenchThemeService.getColorTheme().label), actions: { primary: [ + toAction({ id: 'themeUpdated.keep', label: localize('okButton', "Keep"), run: () => { } }), toAction({ id: 'themeUpdated.browseThemes', label: localize('browseThemes', "Browse Themes"), run: () => this._commandService.executeCommand(SelectColorThemeCommandId) }), toAction({ id: 'themeUpdated.revert', label: localize('revert', "Revert"), run: async () => { @@ -742,6 +749,25 @@ class DefaultThemeUpdatedNotificationContribution implements IWorkbenchContribut } }); } + + private async _tryNewThemeNotification(): Promise { + this._storageService.store(DefaultThemeUpdatedNotificationContribution.STORAGE_KEY, true, StorageScope.APPLICATION, StorageTarget.USER); + const newThemeSettingsId = this._workbenchThemeService.getColorTheme().type === ColorScheme.LIGHT ? ThemeSettingDefaults.COLOR_THEME_LIGHT : ThemeSettingDefaults.COLOR_THEME_DARK; + const theme = (await this._workbenchThemeService.getColorThemes()).find(theme => theme.settingsId === newThemeSettingsId); + if (theme) { + const choices: IPromptChoice[] = [{ + label: localize('button.tryTheme', "Try New Theme"), + run: () => this._workbenchThemeService.setColorTheme(theme, 'auto') + }, + { + label: localize('button.cancel', "Cancel"), + run: () => { } + }]; + await this._notificationService.prompt(Severity.Info, localize({ key: 'newThemeNotification', comment: ['{0} is the name of the new default theme'] }, "Visual Studio Code now ships with a new default theme '{0}'. Do you want to give it a try?", theme.label), choices); + } + + + } } const workbenchRegistry = Registry.as(Extensions.Workbench); workbenchRegistry.registerWorkbenchContribution(DefaultThemeUpdatedNotificationContribution, LifecyclePhase.Ready); From 7a0fc6fe21f1661167b9795afa9784ebca9475dd Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 13 Apr 2023 13:44:19 +0200 Subject: [PATCH 40/59] tweak editor options (#179856) --- .../browser/interactiveEditorWidget.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts index f1cf3ef3b8dc1..6ebd59cb02c6d 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts @@ -42,9 +42,7 @@ const _commonEditorOptions: IEditorConstructionOptions = { padding: { top: 3, bottom: 2 }, overviewRulerLanes: 0, glyphMargin: false, - lineNumbers: 'off', folding: false, - selectOnLineNumbers: false, hideCursorInOverviewRuler: true, selectionHighlight: false, scrollbar: { @@ -53,7 +51,6 @@ const _commonEditorOptions: IEditorConstructionOptions = { horizontal: 'auto', alwaysConsumeMouseWheel: false }, - lineDecorationsWidth: 0, overviewRulerBorder: false, scrollBeyondLastLine: false, renderLineHighlight: 'none', @@ -63,12 +60,9 @@ const _commonEditorOptions: IEditorConstructionOptions = { minimap: { enabled: false }, guides: { indentation: false }, rulers: [], - cursorWidth: 1, wrappingStrategy: 'advanced', wrappingIndent: 'none', - renderWhitespace: 'none', dropIntoEditor: { enabled: true }, - quickSuggestions: false, suggest: { showIcons: false, @@ -79,6 +73,11 @@ const _commonEditorOptions: IEditorConstructionOptions = { const _inputEditorOptions: IEditorConstructionOptions = { ..._commonEditorOptions, + lineNumbers: 'off', + selectOnLineNumbers: false, + lineDecorationsWidth: 0, + renderWhitespace: 'none', + cursorWidth: 1, wordWrap: 'on', ariaLabel: localize('aria-label', "Interactive Editor Input"), fontFamily: DEFAULT_FONT_FAMILY, @@ -529,11 +528,6 @@ export class InteractiveEditorZoneWidget extends ZoneWidget { container.appendChild(this.widget.domNode); } - // protected override _onWidth(_widthInPixel: number): void { - // if (this._dimension) { - // this._doLayout(this._dimension.height); - // } - // } protected override _doLayout(heightInPixel: number): void { From 086f59e4821e4df6d56f6aace44a377c73876c7b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 13 Apr 2023 13:59:52 +0200 Subject: [PATCH 41/59] align import and create profile flows (#179859) - align import and create profile flows - allow to select data when creating from current profile --- .../browser/userDataProfile.ts | 19 +++++++----- .../userDataProfileImportExportService.ts | 30 ++++++++++--------- .../userDataProfile/common/userDataProfile.ts | 1 + 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index c7489ba29da72..037dde8c9059a 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -53,6 +53,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, @IUserDataProfileManagementService private readonly userDataProfileManagementService: IUserDataProfileManagementService, + @IUserDataProfileImportExportService private readonly userDataProfileImportExportService: IUserDataProfileImportExportService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IWorkspaceTagsService private readonly workspaceTagsService: IWorkspaceTagsService, @@ -339,11 +340,11 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements const disposables = new DisposableStore(); const quickPick = disposables.add(quickInputService.createQuickPick()); const updateQuickPickItems = (value?: string) => { - const selectFromFileItem: IQuickPickItem = { label: localize('import from file', "Import from profile file") }; - quickPick.items = value ? [{ label: localize('import from url', "Import from URL"), description: quickPick.value }, selectFromFileItem] : [selectFromFileItem]; + const selectFromFileItem: IQuickPickItem = { label: localize('import from file', "Create from profile template file") }; + quickPick.items = value ? [{ label: localize('import from url', "Create from profile template URL"), description: quickPick.value }, selectFromFileItem] : [selectFromFileItem]; }; - quickPick.title = localize('import profile quick pick title', "Import Profile"); - quickPick.placeholder = localize('import profile placeholder', "Provide profile URL or select profile file to import"); + quickPick.title = localize('import profile quick pick title', "Create Profile from Profile Template..."); + quickPick.placeholder = localize('import profile placeholder', "Provide profile template URL or select profile template file"); quickPick.ignoreFocusOut = true; disposables.add(quickPick.onDidChangeValue(updateQuickPickItems)); updateQuickPickItems(); @@ -357,7 +358,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements await userDataProfileImportExportService.importProfile(profile); } } catch (error) { - notificationService.error(localize('profile import error', "Error while importing profile: {0}", getErrorMessage(error))); + notificationService.error(localize('profile import error', "Error while creating profile: {0}", getErrorMessage(error))); } })); disposables.add(quickPick.onDidHide(() => disposables.dispose())); @@ -370,7 +371,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements canSelectFiles: true, canSelectMany: false, filters: PROFILE_FILTER, - title: localize('import profile dialog', "Import Profile"), + title: localize('import profile dialog', "Select Profile Template File"), }); if (!profileLocation) { return null; @@ -444,7 +445,11 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements return; } try { - await this.userDataProfileManagementService.createAndEnterProfile(name, undefined, fromExisting); + if (fromExisting) { + await this.userDataProfileImportExportService.createFromCurrentProfile(name); + } else { + await this.userDataProfileManagementService.createAndEnterProfile(name, undefined, false); + } } catch (error) { this.notificationService.error(error); } diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts index ab3d6da48ebc8..bfc1c7169e127 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -202,7 +202,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU const profileTemplate = await this.progressService.withProgress({ location: ProgressLocation.Window, command: showWindowLogActionId, - title: localize('resolving uri', "{0}: Resolving profile content...", options?.preview ? localize('preview profile', "Preview Profile") : localize('import profile', "Import Profile")), + title: localize('resolving uri', "{0}: Resolving profile content...", options?.preview ? localize('preview profile', "Preview Profile") : localize('import profile', "Create Profile")), }, () => this.resolveProfileTemplate(uri)); if (!profileTemplate) { return; @@ -210,7 +210,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU if (options?.preview) { await this.previewProfile(uri, profileTemplate); } else { - await this.doImportProfile(uri, profileTemplate); + await this.doImportProfile(profileTemplate); } } finally { disposables.dispose(); @@ -246,6 +246,16 @@ export class UserDataProfileImportExportService extends Disposable implements IU } } + async createFromCurrentProfile(name: string): Promise { + const userDataProfilesExportState = this.instantiationService.createInstance(UserDataProfileExportState, this.userDataProfileService.currentProfile); + try { + const profileTemplate = await userDataProfilesExportState.getProfileTemplate(name, undefined); + await this.doImportProfile(profileTemplate); + } finally { + userDataProfilesExportState.dispose(); + } + } + private async doExportProfile(userDataProfilesExportState: UserDataProfileExportState): Promise { const profile = await userDataProfilesExportState.getProfileToExport(); if (!profile) { @@ -341,7 +351,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU const barrier = new Barrier(); const importAction = this.getImportAction(barrier, userDataProfileImportState); const primaryAction = isWeb - ? new Action('importInDesktop', localize('import in desktop', "Import Profile in {1}", importedProfile.name, this.productService.nameLong), undefined, true, async () => this.openerService.open(uri, { openExternal: true })) + ? new Action('importInDesktop', localize('import in desktop', "Create Profile in {0}", this.productService.nameLong), undefined, true, async () => this.openerService.open(uri, { openExternal: true })) : importAction; const secondaryAction = isWeb ? importAction @@ -398,9 +408,8 @@ export class UserDataProfileImportExportService extends Disposable implements IU } } - private async doImportProfile(uri: URI, profileTemplate: IUserDataProfileTemplate): Promise { + private async doImportProfile(profileTemplate: IUserDataProfileTemplate): Promise { const disposables = new DisposableStore(); - try { const userDataProfileImportState = disposables.add(this.instantiationService.createInstance(UserDataProfileImportState, profileTemplate)); const barrier = new Barrier(); @@ -418,7 +427,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU } private getImportAction(barrier: Barrier, userDataProfileImportState: UserDataProfileImportState): IAction { - const title = localize('import', "Import Profile", userDataProfileImportState.profile.name); + const title = localize('import', "Create Profile", userDataProfileImportState.profile.name); const importAction = new BarrierAction(barrier, new Action('import', title, undefined, true, () => { const importProfileFn = async () => { importAction.enabled = false; @@ -427,13 +436,6 @@ export class UserDataProfileImportExportService extends Disposable implements IU if (!importedProfile) { return; } - this.notificationService.notify({ - severity: Severity.Info, - message: localize('imported profile', "Profile '{0}' is imported successfully.", importedProfile.name), - actions: { - primary: [new Action('profiles.showProfileContents', localize('show profile contents', "Show Profile Contents"), undefined, true, () => this.showProfileContents())] - } - }); }; if (userDataProfileImportState.isEmpty()) { return importProfileFn(); @@ -943,7 +945,7 @@ abstract class UserDataProfileImportExportState extends Disposable implements IT return this.roots.some(root => this.isSelected(root)); } - protected async getProfileTemplate(name: string, shortName: string | undefined): Promise { + async getProfileTemplate(name: string, shortName: string | undefined): Promise { const roots = await this.getRoots(); let settings: string | undefined; let keybindings: string | undefined; diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts index 9ac17c00d3b9d..077c2859eb8dc 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts @@ -88,6 +88,7 @@ export interface IUserDataProfileImportExportService { exportProfile(): Promise; importProfile(uri: URI, options?: IProfileImportOptions): Promise; showProfileContents(): Promise; + createFromCurrentProfile(name: string): Promise; setProfile(profile: IUserDataProfileTemplate): Promise; } From 1307c7afbeba977e636671567f50a2f60def6b11 Mon Sep 17 00:00:00 2001 From: Robo Date: Thu, 13 Apr 2023 05:32:24 -0700 Subject: [PATCH 42/59] chore: update electron@22.4.5 (#179857) * chore: update electron@22.4.5 * chore: avoid replacing ffmpeg --- build/gulpfile.vscode.js | 2 +- build/lib/electron.js | 8 ++++---- build/lib/electron.ts | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 3c6d3fb1eda36..e73f72125d404 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -335,7 +335,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op .pipe(util.skipDirectories()) .pipe(util.fixWin32DirectoryPermissions()) .pipe(filter(['**', '!**/.github/**'], { dot: true })) // https://github.com/microsoft/vscode/issues/116523 - .pipe(electron({ ...config, platform, arch: arch === 'armhf' ? 'arm' : arch, ffmpegChromium: true })) + .pipe(electron({ ...config, platform, arch: arch === 'armhf' ? 'arm' : arch, ffmpegChromium: false })) .pipe(filter(['**', '!LICENSE', '!LICENSES.chromium.html', '!version'], { dot: true })); if (platform === 'linux') { diff --git a/build/lib/electron.js b/build/lib/electron.js index 6baea0527f1db..d3d81cb07fdd0 100644 --- a/build/lib/electron.js +++ b/build/lib/electron.js @@ -76,7 +76,7 @@ function darwinBundleDocumentTypes(types, icon) { }); } exports.config = { - version: product.electronRepository ? '22.4.4' : util.getElectronVersion(), + version: product.electronRepository ? '22.4.5' : util.getElectronVersion(), productAppName: product.nameLong, companyName: 'Microsoft Corporation', copyright: 'Copyright (C) 2023 Microsoft. All rights reserved', @@ -182,7 +182,7 @@ function getElectron(arch) { const electronOpts = _.extend({}, exports.config, { platform: process.platform, arch: arch === 'armhf' ? 'arm' : arch, - ffmpegChromium: true, + ffmpegChromium: false, keepDefaultApp: true }); return vfs.src('package.json') @@ -193,7 +193,7 @@ function getElectron(arch) { }; } async function main(arch = process.arch) { - const version = product.electronRepository ? '22.4.4' : util.getElectronVersion(); + const version = product.electronRepository ? '22.4.5' : util.getElectronVersion(); const electronPath = path.join(root, '.build', 'electron'); const versionFile = path.join(electronPath, 'version'); const isUpToDate = fs.existsSync(versionFile) && fs.readFileSync(versionFile, 'utf8') === `${version}`; @@ -208,4 +208,4 @@ if (require.main === module) { process.exit(1); }); } -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZWxlY3Ryb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJlbGVjdHJvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUVoRyx5QkFBeUI7QUFDekIsNkJBQTZCO0FBQzdCLGdDQUFnQztBQUNoQyxzQ0FBc0M7QUFDdEMsZ0NBQWdDO0FBQ2hDLCtCQUErQjtBQUMvQiw2Q0FBMEM7QUFZMUMsU0FBUyxnQkFBZ0IsQ0FBQyxHQUFZO0lBQ3JDLE9BQU8sR0FBRyxLQUFLLFVBQVUsSUFBSSxHQUFHLEtBQUssUUFBUSxJQUFJLEdBQUcsS0FBSyxNQUFNLElBQUksR0FBRyxLQUFLLGFBQWEsQ0FBQztBQUMxRixDQUFDO0FBRUQsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUM7QUFDbkQsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLGNBQWMsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUM7QUFDckYsTUFBTSxNQUFNLEdBQUcsSUFBQSx1QkFBVSxFQUFDLElBQUksQ0FBQyxDQUFDO0FBRWhDLE1BQU0scUJBQXFCLEdBQUcsT0FBTyxDQUFDLGFBQWEsSUFBSSxDQUFDLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsT0FBTyxDQUFDLGFBQWEsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUM7QUFFbkk7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQWtCRztBQUNILFNBQVMsd0JBQXdCLENBQUMsVUFBb0IsRUFBRSxJQUFZLEVBQUUsWUFBNEMsRUFBRSxJQUFlO0lBQ2xJLDJGQUEyRjtJQUMzRixJQUFJLGdCQUFnQixDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsWUFB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 \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZWxlY3Ryb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJlbGVjdHJvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUVoRyx5QkFBeUI7QUFDekIsNkJBQTZCO0FBQzdCLGdDQUFnQztBQUNoQyxzQ0FBc0M7QUFDdEMsZ0NBQWdDO0FBQ2hDLCtCQUErQjtBQUMvQiw2Q0FBMEM7QUFZMUMsU0FBUyxnQkFBZ0IsQ0FBQyxHQUFZO0lBQ3JDLE9BQU8sR0FBRyxLQUFLLFVBQVUsSUFBSSxHQUFHLEtBQUssUUFBUSxJQUFJLEdBQUcsS0FBSyxNQUFNLElBQUksR0FBRyxLQUFLLGFBQWEsQ0FBQztBQUMxRixDQUFDO0FBRUQsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUM7QUFDbkQsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLGNBQWMsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUM7QUFDckYsTUFBTSxNQUFNLEdBQUcsSUFBQSx1QkFBVSxFQUFDLElBQUksQ0FBQyxDQUFDO0FBRWhDLE1BQU0scUJBQXFCLEdBQUcsT0FBTyxDQUFDLGFBQWEsSUFBSSxDQUFDLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsT0FBTyxDQUFDLGFBQWEsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUM7QUFFbkk7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQWtCRztBQUNILFNBQVMsd0JBQXdCLENBQUMsVUFBb0IsRUFBRSxJQUFZLEVBQUUsWUFBNEMsRUFBRSxJQUFlO0lBQ2xJLDJGQUEyRjtJQUMzRixJQUFJLGdCQUFnQixDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsWUFBWSxFQUFFO1FBQ3BELFlBQVksR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLFdBQVcsRUFBRSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLEdBQUcsR0FBRyxHQUFHLENBQUMsWUFBWSxJQUFJLFVBQVUsQ0FBQyxDQUFDO0tBQ2pHO0lBRUQsT0FBTztRQUNOLElBQUksRUFBRSxZQUFZO1FBQ2xCLElBQUksRUFBRSxRQUFRO1FBQ2QsT0FBTyxFQUFFLENBQUMsTUFBTSxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsTUFBTSxDQUFDO1FBQ3pDLFVBQVU7UUFDVixRQUFRLEVBQUUsbUJBQW1CLEdBQUcsSUFBSSxHQUFHLE9BQU87UUFDOUMsSUFBSTtLQUNKLENBQUM7QUFDSCxDQUFDO0FBRUQ7Ozs7Ozs7Ozs7R0FVRztBQUNILFNBQVMseUJBQXlCLENBQUMsS0FBNEMsRUFBRSxJQUFZO0lBQzVGLE9BQU8sTUFBTSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFZLEVBQXNCLEVBQUU7UUFDbEUsTUFBTSxVQUFVLEdBQUcsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQy9CLE9BQU87WUFDTixJQUFJO1lBQ0osSUFBSSxFQUFFLFFBQVE7WUFDZCxPQUFPLEVBQUUsQ0FBQyxNQUFNLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxNQUFNLENBQUM7WUFDekMsVUFBVSxFQUFFLEtBQUssQ0FBQyxPQUFPLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxVQUFVLENBQUM7WUFDakUsUUFBUSxFQUFFLG1CQUFtQixHQUFHLElBQUksR0FBRyxPQUFPO1NBQ3hCLENBQUM7SUFDekIsQ0FBQyxDQUFDLENBQUM7QUFDSixDQUFDO0FBRVksUUFBQSxNQUFNLEdBQUc7SUFDckIsT0FBTyxFQUFFLE9BQU8sQ0FBQyxrQkFBa0IsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsa0JBQWtCLEVBQUU7SUFDMUUsY0FBYyxFQUFFLE9BQU8sQ0FBQyxRQUFRO0lBQ2hDLFdBQVcsRUFBRSx1QkFBdUI7SUFDcEMsU0FBUyxFQUFFLG1EQUFtRDtJQUM5RCxVQUFVLEVBQUUsNEJBQTRCO0lBQ3hDLHNCQUFzQixFQUFFLE9BQU8sQ0FBQyxzQkFBc0I7SUFDdEQsNkJBQTZCLEVBQUUscUNBQXFDO0lBQ3BFLG9CQUFvQixFQUFFLGtCQUFrQjtJQUN4QyxrQkFBa0IsRUFBRSxrQkFBa0I7SUFDdEMseUJBQXlCLEVBQUU7UUFDMUIsR0FBRyx5QkFBeUIsQ0FBQyxFQUFFLGVBQWUsRUFBRSxHQUFHLEVBQUUsZUFBZSxFQUFFLEdBQUcsRUFBRSxFQUFFLEdBQUcsQ0FBQztRQUNqRixHQUFHLHlCQUF5QixDQUFDLEVBQUUsd0JBQXdCLEVBQUUsQ0FBQyxlQUFlLEVBQUUsV0FBVyxFQUFFLFdBQVcsQ0FBQyxFQUFFLEVBQUUsUUFBUSxDQUFDO1FBQ2pILEdBQUcseUJBQXlCLENBQUMsRUFBRSx3QkFBd0IsRUFBRSxDQUFDLEtBQUssRUFBRSxNQUFNLEVBQUUsUUFBUSxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsT0FBTyxFQUFFLE9BQU8sQ0FBQyxFQUFFLEVBQUUsTUFBTSxDQUFDO1FBQy9ILHdCQUF3QixDQUFDLENBQUMsS0FBSyxFQUFFLEtBQUssQ0FBQyxFQUFFLEtBQUssRUFBRSx3QkFBd0IsQ0FBQztRQUN6RSx3QkFBd0IsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxFQUFFLE9BQU8sQ0FBQztRQUM5Qyx3QkFBd0IsQ0FBQyxDQUFDLFFBQVEsRUFBRSxjQUFjLEVBQUUsS0FBSyxFQUFFLEtBQUssQ0FBQyxFQUFFLFFBQVEsRUFBRSxvQkFBb0IsQ0FBQztRQUNsRyx3QkFBd0IsQ0FBQyxDQUFDLElBQUksRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLEtBQUssQ0FBQyxFQUFFLEtBQUssRUFBRSxpQkFBaUIsQ0FBQztRQUMvRSx3QkFBd0IsQ0FBQyxDQUFDLElBQUksRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLEtBQUssQ0FBQyxFQUFFLEtBQUssRUFBRSxpQkFBaUIsQ0FBQztRQUMvRSx3QkFBd0IsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxFQUFFLFNBQVMsRUFBRSx5QkFBeUIsQ0FBQztRQUNyRSx3QkFBd0IsQ0FBQyxDQUFDLElBQUksQ0FBQyxFQUFFLEtBQUssRUFBRSwyQkFBMkIsQ0FBQztRQUNwRSx3QkFBd0IsQ0FBQyxDQUFDLElBQUksRUFBRSxLQUFLLENBQUMsRUFBRSxRQUFRLEVBQUUsZ0JBQWdCLENBQUM7UUFDbkUsd0JBQXdCLENBQUMsQ0FBQyxLQUFLLENBQUMsRUFBRSxLQUFLLEVBQUUsS0FBSyxDQUFDO1FBQy9DLHdCQUF3QixDQUFDLENBQUMsSUFBSSxDQUFDLEVBQUUsSUFBSSxFQUFFLGdCQUFnQixDQUFDO1FBQ3hELHdCQUF3QixDQUFDLENBQUMsS0FBSyxFQUFFLE1BQU0sRUFBRSxPQUFPLENBQUMsRUFBRSxNQUFNLENBQUM7UUFDMUQsd0JBQXdCLENBQUMsQ0FBQyxNQUFNLENBQUMsRUFBRSxNQUFNLENBQUM7UUFDMUMsd0JBQXdCLENBQUMsQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLEVBQUUsTUFBTSxDQUFDO1FBQ2pELHdCQUF3QixDQUFDLENBQUMsSUFBSSxFQUFFLFFBQVEsRUFBRSxVQUFVLEVBQUUsS0FBSyxFQUFFLEtBQUssQ0FBQyxFQUFFLFlBQVksRUFBRSxNQUFNLENBQUM7UUFDMUYsd0JBQXdCLENBQUMsQ0FBQyxNQUFNLENBQUMsRUFBRSxNQUFNLENBQUM7UUFDMUMsd0JBQXdCLENBQUMsQ0FBQyxNQUFNLENBQUMsRUFBRSxNQUFNLENBQUM7UUFDMUMsd0JBQXdCLENBQUMsQ0FBQyxVQUFVLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxPQUFPLEVBQUUsUUFBUSxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFFLE1BQU0sQ0FBQyxFQUFFLFVBQVUsQ0FBQztRQUNuSCx3QkFBd0IsQ0FBQyxDQUFDLEtBQUssQ0FBQyxFQUFFLEtBQUssRUFBRSxhQUFhLENBQUM7UUFDdkQsd0JBQXdCLENBQUMsQ0FBQyxLQUFLLEVBQUUsTUFBTSxFQUFFLE1BQU0sQ0FBQyxFQUFFLFlBQVksRUFBRSxRQUFRLENBQUM7UUFDekUsd0JBQXdCLENBQUMsQ0FBQyxJQUFJLEVBQUUsS0FBSyxDQUFDLEVBQUUsUUFBUSxFQUFFLFFBQVEsQ0FBQztRQUMzRCx3QkFBd0IsQ0FBQyxDQUFDLFNBQVMsRUFBRSxJQUFJLEVBQUUsS0FBSyxDQUFDLEVBQUUsTUFBTSxFQUFFLGFBQWEsQ0FBQztRQUN6RSx3QkFBd0IsQ0FBQyxDQUFDLE1BQU0sRUFBRSxNQUFNLENBQUMsRUFBRSxNQUFNLEVBQUUsTUFBTSxDQUFDO1FBQzFELHdCQUF3QixDQUFDLENBQUMsS0FBSyxDQUFDLEVBQUUsS0FBSyxFQUFFLFFBQVEsQ0FBQztRQUNsRCx3QkFBd0IsQ0FBQyxDQUFDLElBQUksQ0FBQyxFQUFFLFlBQVksRUFBRSxNQUFNLENBQUM7UUFDdEQsd0JBQXdCLENBQUMsQ0FBQyxLQUFLLEVBQUUsS0FBSyxDQUFDLEVBQUUsT0FBTyxFQUFFLGFBQWEsQ0FBQztRQUNoRSx3QkFBd0IsQ0FBQyxDQUFDLEtBQUssQ0FBQyxFQUFFLEtBQUssRUFBRSxhQUFhLENBQUM7UUFDdkQsd0JBQXdCLENBQUMsQ0FBQyxNQUFNLEVBQUUsUUFBUSxFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLE1BQU0sQ0FBQyxFQUFFLEtBQUssQ0FBQztRQUN2Ryx3QkFBd0IsQ0FBQyxDQUFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLEtBQUssQ0FBQyxFQUFFLE1BQU0sQ0FBQztRQUNsRSx3QkFBd0IsQ0FBQztZQUN4QixNQUFNLEVBQUUsWUFBWSxFQUFFLGFBQWEsRUFBRSxjQUFjLEVBQUUsUUFBUTtZQUM3RCxTQUFTLEVBQUUsVUFBVSxFQUFFLFVBQVUsRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLFNBQVM7WUFDNUQsVUFBVSxFQUFFLEtBQUssRUFBRSxRQUFRLEVBQUUsT0FBTztTQUNwQyxFQUFFLE9BQU8sRUFBRSxRQUFRLENBQUM7UUFDckIsb0NBQW9DO1FBQ3BDLEdBQUcseUJBQXlCLENBQUM7WUFDNUIscUJBQXFCLEVBQUUsQ0FBQyxLQUFLLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxTQUFTLENBQUM7WUFDekQsd0JBQXdCLEVBQUUsZ0JBQWdCO1lBQzFDLDBCQUEwQixFQUFFLFFBQVE7WUFDcEMsd0JBQXdCLEVBQUUsS0FBSztZQUMvQixjQUFjLEVBQUUsT0FBTztZQUN2QixhQUFhLEVBQUUsTUFBTTtZQUNyQixXQUFXLEVBQUUsTUFBTTtZQUNuQixZQUFZLEVBQUUsWUFBWTtZQUMxQixhQUFhLEVBQUUsUUFBUTtZQUN2QixlQUFlLEVBQUUsUUFBUTtZQUN6QixVQUFVLEVBQUUsQ0FBQyxVQUFVLEVBQUUsSUFBSSxDQUFDO1lBQzlCLFlBQVksRUFBRSxLQUFLO1lBQ25CLGNBQWMsRUFBRSxLQUFLO1lBQ3JCLFNBQVMsRUFBRSxPQUFPO1lBQ2xCLFVBQVUsRUFBRSxNQUFNO1lBQ2xCLFVBQVUsRUFBRSxLQUFLO1lBQ2pCLGlCQUFpQixFQUFFLEtBQUs7WUFDeEIsb0JBQW9CLEVBQUUsV0FBVztZQUNqQyxzQkFBc0IsRUFBRSxhQUFhO1lBQ3JDLHFCQUFxQixFQUFFLElBQUk7WUFDM0IsZUFBZSxFQUFFLEdBQUc7WUFDcEIsa0JBQWtCLEVBQUUsSUFBSTtZQUN4Qiw0QkFBNEIsRUFBRSxLQUFLO1lBQ25DLGdCQUFnQixFQUFFLENBQUMsS0FBSyxFQUFFLEtBQUssQ0FBQztZQUNoQyxnQkFBZ0IsRUFBRSxJQUFJO1lBQ3RCLG1CQUFtQixFQUFFLEtBQUs7WUFDMUIsV0FBVyxFQUFFLENBQUMsS0FBSyxFQUFFLFVBQVUsQ0FBQztZQUNoQyxjQUFjLEVBQUUsQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDO1lBQy9CLGVBQWUsRUFBRSxNQUFNO1lBQ3ZCLG1CQUFtQixFQUFFLE9BQU87U0FDNUIsRUFBRSxTQUFTLENBQUM7UUFDYixpQ0FBaUM7UUFDakMsd0JBQXdCLENBQUM7WUFDeEIsZUFBZSxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLFlBQVksRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLEtBQUs7WUFDdEUsSUFBSSxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsWUFBWSxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUUsR0FBRztTQUN0RSxFQUFFLFNBQVMsRUFBRSxPQUFPLENBQUMsUUFBUSxHQUFHLFdBQVcsQ0FBQztRQUM3QyxvQkFBb0I7UUFDcEIsd0JBQXdCLENBQUMsRUFBRSxFQUFFLFNBQVMsRUFBRSxRQUFRLEVBQUUsQ0FBQyxlQUFlLENBQUMsQ0FBQztLQUNwRTtJQUNELG9CQUFvQixFQUFFLENBQUM7WUFDdEIsSUFBSSxFQUFFLFFBQVE7WUFDZCxJQUFJLEVBQUUsT0FBTyxDQUFDLFFBQVE7WUFDdEIsVUFBVSxFQUFFLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQztTQUNqQyxDQUFDO0lBQ0YsMEJBQTBCLEVBQUUsSUFBSTtJQUNoQyxhQUFhLEVBQUUscUJBQXFCLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMscUJBQXFCLENBQUMsRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLElBQUksRUFBRSxJQUFJLElBQUksRUFBRSxDQUFDLFdBQVcsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxTQUFTO0lBQ3pJLG1CQUFtQixFQUFFLE9BQU8sQ0FBQyxlQUFlO0lBQzVDLE9BQU8sRUFBRSwwQkFBMEI7SUFDbkMsS0FBSyxFQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMsY0FBYyxDQUFDO0lBQ2xDLElBQUksRUFBRSxPQUFPLENBQUMsa0JBQWtCLElBQUksU0FBUztDQUM3QyxDQUFDO0FBRUYsU0FBUyxXQUFXLENBQUMsSUFBWTtJQUNoQyxPQUFPLEdBQUcsRUFBRTtRQUNYLE1BQU0sUUFBUSxHQUFHLE9BQU8sQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO1FBQy9DLE1BQU0sSUFBSSxHQUFHLE9BQU8sQ0FBQyxrQkFBa0IsQ0FBc0MsQ0FBQztRQUU5RSxNQUFNLFlBQVksR0FBRyxDQUFDLENBQUMsTUFBTSxDQUFDLEVBQUUsRUFBRSxjQUFNLEVBQUU7WUFDekMsUUFBUSxFQUFFLE9BQU8sQ0FBQyxRQUFRO1lBQzFCLElBQUksRUFBRSxJQUFJLEtBQUssT0FBTyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLElBQUk7WUFDckMsY0FBYyxFQUFFLEtBQUs7WUFDckIsY0FBYyxFQUFFLElBQUk7U0FDcEIsQ0FBQyxDQUFDO1FBRUgsT0FBTyxHQUFHLENBQUMsR0FBRyxDQUFDLGNBQWMsQ0FBQzthQUM1QixJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsSUFBSSxFQUFFLE9BQU8sQ0FBQyxTQUFTLEVBQUUsQ0FBQyxDQUFDO2FBQ3ZDLElBQUksQ0FBQyxRQUFRLENBQUMsWUFBWSxDQUFDLENBQUM7YUFDNUIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLElBQUksRUFBRSxzQkFBc0IsQ0FBQyxDQUFDLENBQUM7YUFDNUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsaUJBQWlCLENBQUMsQ0FBQyxDQUFDO0lBQ3JDLENBQUMsQ0FBQztBQUNILENBQUM7QUFFRCxLQUFLLFVBQVUsSUFBSSxDQUFDLElBQUksR0FBRyxPQUFPLENBQUMsSUFBSTtJQUN0QyxNQUFNLE9BQU8sR0FBRyxPQUFPLENBQUMsa0JBQWtCLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLGtCQUFrQixFQUFFLENBQUM7SUFDbEYsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsUUFBUSxFQUFFLFVBQVUsQ0FBQyxDQUFDO0lBQzNELE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsWUFBWSxFQUFFLFNBQVMsQ0FBQyxDQUFDO0lBQ3ZELE1BQU0sVUFBVSxHQUFHLEVBQUUsQ0FBQyxVQUFVLENBQUMsV0FBVyxDQUFDLElBQUksRUFBRSxDQUFDLFlBQVksQ0FBQyxXQUFXLEVBQUUsTUFBTSxDQUFDLEtBQUssR0FBRyxPQUFPLEVBQUUsQ0FBQztJQUV2RyxJQUFJLENBQUMsVUFBVSxFQUFFO1FBQ2hCLE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxZQUFZLENBQUMsRUFBRSxDQUFDO1FBQ2xDLE1BQU0sSUFBSSxDQUFDLGVBQWUsQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDO0tBQ2hEO0FBQ0YsQ0FBQztBQUVELElBQUksT0FBTyxDQUFDLElBQUksS0FBSyxNQUFNLEVBQUU7SUFDNUIsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLEVBQUU7UUFDakMsT0FBTyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNuQixPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ2pCLENBQUMsQ0FBQyxDQUFDO0NBQ0gifQ== \ No newline at end of file diff --git a/build/lib/electron.ts b/build/lib/electron.ts index 19a867199b4c1..1b0b9bec5aece 100644 --- a/build/lib/electron.ts +++ b/build/lib/electron.ts @@ -91,7 +91,7 @@ function darwinBundleDocumentTypes(types: { [name: string]: string | string[] }, } export const config = { - version: product.electronRepository ? '22.4.4' : util.getElectronVersion(), + version: product.electronRepository ? '22.4.5' : util.getElectronVersion(), productAppName: product.nameLong, companyName: 'Microsoft Corporation', copyright: 'Copyright (C) 2023 Microsoft. All rights reserved', @@ -199,7 +199,7 @@ function getElectron(arch: string): () => NodeJS.ReadWriteStream { const electronOpts = _.extend({}, config, { platform: process.platform, arch: arch === 'armhf' ? 'arm' : arch, - ffmpegChromium: true, + ffmpegChromium: false, keepDefaultApp: true }); @@ -212,7 +212,7 @@ function getElectron(arch: string): () => NodeJS.ReadWriteStream { } async function main(arch = process.arch): Promise { - const version = product.electronRepository ? '22.4.4' : util.getElectronVersion(); + const version = product.electronRepository ? '22.4.5' : util.getElectronVersion(); const electronPath = path.join(root, '.build', 'electron'); const versionFile = path.join(electronPath, 'version'); const isUpToDate = fs.existsSync(versionFile) && fs.readFileSync(versionFile, 'utf8') === `${version}`; From f972a319388e1129e3402b887640a824ff8437b0 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 13 Apr 2023 14:43:12 +0200 Subject: [PATCH 43/59] GitHub - restore/save branch protection to global state (#179855) --- extensions/github/src/branchProtection.ts | 21 ++++++++++++++------- extensions/github/src/extension.ts | 6 +++--- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/extensions/github/src/branchProtection.ts b/extensions/github/src/branchProtection.ts index e49a3eda3d449..6ba43eac70bd3 100644 --- a/extensions/github/src/branchProtection.ts +++ b/extensions/github/src/branchProtection.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { EventEmitter, Uri, workspace } from 'vscode'; +import { EventEmitter, Memento, Uri, workspace } from 'vscode'; import { getOctokit } from './auth'; import { API, BranchProtection, BranchProtectionProvider, Repository } from './typings/git'; import { DisposableStore, getRepositoryFromUrl } from './util'; @@ -21,7 +21,7 @@ export class GithubBranchProtectionProviderManager { if (enabled) { for (const repository of this.gitAPI.repositories) { - this.providerDisposables.add(this.gitAPI.registerBranchProtectionProvider(repository.rootUri, new GithubBranchProtectionProvider(repository))); + this.providerDisposables.add(this.gitAPI.registerBranchProtectionProvider(repository.rootUri, new GithubBranchProtectionProvider(repository, this.globalState))); } } else { this.providerDisposables.dispose(); @@ -30,10 +30,10 @@ export class GithubBranchProtectionProviderManager { this._enabled = enabled; } - constructor(private gitAPI: API) { + constructor(private readonly gitAPI: API, private readonly globalState: Memento) { this.disposables.add(this.gitAPI.onDidOpenRepository(repository => { if (this._enabled) { - this.providerDisposables.add(gitAPI.registerBranchProtectionProvider(repository.rootUri, new GithubBranchProtectionProvider(repository))); + this.providerDisposables.add(gitAPI.registerBranchProtectionProvider(repository.rootUri, new GithubBranchProtectionProvider(repository, this.globalState))); } })); @@ -62,15 +62,19 @@ export class GithubBranchProtectionProvider implements BranchProtectionProvider private readonly _onDidChangeBranchProtection = new EventEmitter(); onDidChangeBranchProtection = this._onDidChangeBranchProtection.event; - private branchProtection!: BranchProtection[]; + private branchProtection: BranchProtection[]; + private readonly globalStateKey = `branchProtection:${this.repository.rootUri.toString()}`; + + constructor(private readonly repository: Repository, private readonly globalState: Memento) { + // Restore branch protection from global state + this.branchProtection = this.globalState.get(this.globalStateKey, []); - constructor(private readonly repository: Repository) { repository.status() .then(() => this.initializeBranchProtection()); } provideBranchProtection(): BranchProtection[] { - return this.branchProtection ?? []; + return this.branchProtection; } private async initializeBranchProtection(): Promise { @@ -148,6 +152,9 @@ export class GithubBranchProtectionProvider implements BranchProtectionProvider this.branchProtection = branchProtection; this._onDidChangeBranchProtection.fire(this.repository.rootUri); + + // Save branch protection to global state + await this.globalState.update(this.globalStateKey, branchProtection); } catch { // todo@lszomoru - add logging } diff --git a/extensions/github/src/extension.ts b/extensions/github/src/extension.ts index b58e23683f029..cf72df61aa10f 100644 --- a/extensions/github/src/extension.ts +++ b/extensions/github/src/extension.ts @@ -16,7 +16,7 @@ import { GithubBranchProtectionProviderManager } from './branchProtection'; export function activate(context: ExtensionContext): void { context.subscriptions.push(initializeGitBaseExtension()); - context.subscriptions.push(initializeGitExtension()); + context.subscriptions.push(initializeGitExtension(context)); } function initializeGitBaseExtension(): Disposable { @@ -64,7 +64,7 @@ function setGitHubContext(gitAPI: API, disposables: DisposableStore) { } } -function initializeGitExtension(): Disposable { +function initializeGitExtension(context: ExtensionContext): Disposable { const disposables = new DisposableStore(); let gitExtension = extensions.getExtension('vscode.git'); @@ -78,7 +78,7 @@ function initializeGitExtension(): Disposable { disposables.add(registerCommands(gitAPI)); disposables.add(new GithubCredentialProviderManager(gitAPI)); - disposables.add(new GithubBranchProtectionProviderManager(gitAPI)); + disposables.add(new GithubBranchProtectionProviderManager(gitAPI, context.globalState)); disposables.add(gitAPI.registerPushErrorHandler(new GithubPushErrorHandler())); disposables.add(gitAPI.registerRemoteSourcePublisher(new GithubRemoteSourcePublisher(gitAPI))); setGitHubContext(gitAPI, disposables); From 898fcd63dc3eb51a2e1a4a0fe49860fc3a1bb72e Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 13 Apr 2023 15:12:27 +0200 Subject: [PATCH 44/59] Updates VS Code workspace settings (#179864) * the experimental diff algorithm is now the default in insiders * turns on asyncTokenizationVerification to investigate a reported bug --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index ecb1291708f45..52ab4c096fcda 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -125,5 +125,5 @@ ], "application.experimental.rendererProfiling": true, "editor.experimental.asyncTokenization": true, - "diffEditor.diffAlgorithm": "experimental" + "editor.experimental.asyncTokenizationVerification": true, } From d1f1229fee1a756dc3419300bc8b62e711970cd6 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 13 Apr 2023 16:06:25 +0200 Subject: [PATCH 45/59] Fix #179207 (#179867) --- .../common/extensionManagementCLI.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts index 7ea6fbe02f392..1515c45cca286 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts @@ -10,7 +10,7 @@ import { basename } from 'vs/base/common/resources'; import { gt } from 'vs/base/common/semver/semver'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; -import { IExtensionGalleryService, IExtensionManagementService, IGalleryExtension, ILocalExtension, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { EXTENSION_IDENTIFIER_REGEX, IExtensionGalleryService, IExtensionInfo, IExtensionManagementService, IGalleryExtension, ILocalExtension, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, getGalleryExtensionId, getIdAndVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType, EXTENSION_CATEGORIES, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { ILogger } from 'vs/platform/log/common/log'; @@ -201,9 +201,17 @@ export class ExtensionManagementCLI { const galleryExtensions = new Map(); const preRelease = extensions.some(e => e.installOptions.installPreReleaseVersion); const targetPlatform = await this.extensionManagementService.getTargetPlatform(); - const result = await this.extensionGalleryService.getExtensions(extensions.map(e => ({ ...e, preRelease })), { targetPlatform }, CancellationToken.None); - for (const extension of result) { - galleryExtensions.set(extension.identifier.id.toLowerCase(), extension); + const extensionInfos: IExtensionInfo[] = []; + for (const extension of extensions) { + if (EXTENSION_IDENTIFIER_REGEX.test(extension.id)) { + extensionInfos.push({ ...extension, preRelease }); + } + } + if (extensionInfos.length) { + const result = await this.extensionGalleryService.getExtensions(extensionInfos, { targetPlatform }, CancellationToken.None); + for (const extension of result) { + galleryExtensions.set(extension.identifier.id.toLowerCase(), extension); + } } return galleryExtensions; } From 00e3acf1ee6bdfef8d4d71b899af743cfe0f182c Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 13 Apr 2023 16:07:43 +0200 Subject: [PATCH 46/59] default theme update: add telemetry (#179874) --- .../themes/browser/themes.contribution.ts | 90 ++++++++++++++----- 1 file changed, 67 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index 0f2b43ac6ad9b..4fb3ece7a17ce 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -41,8 +41,8 @@ import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } f import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { toAction } from 'vs/base/common/actions'; import { isWeb } from 'vs/base/common/platform'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export const manageExtensionIcon = registerIcon('theme-selection-manage-extension', Codicon.gear, localize('manageExtensionIcon', 'Icon for the \'Manage\' action in the theme selection quick pick.')); @@ -699,6 +699,8 @@ MenuRegistry.appendMenuItem(ThemesSubMenu, { order: 3 }); +type DefaultThemeUpdatedNotificationReaction = 'keepNew' | 'keepOld' | 'tryNew' | 'cancel' | 'browse'; + class DefaultThemeUpdatedNotificationContribution implements IWorkbenchContribution { static STORAGE_KEY = 'themeUpdatedNotificationShown'; @@ -708,6 +710,7 @@ class DefaultThemeUpdatedNotificationContribution implements IWorkbenchContribut @IWorkbenchThemeService private readonly _workbenchThemeService: IWorkbenchThemeService, @IStorageService private readonly _storageService: IStorageService, @ICommandService private readonly _commandService: ICommandService, + @ITelemetryService private readonly _telemetryService: ITelemetryService, ) { if (_storageService.getBoolean(DefaultThemeUpdatedNotificationContribution.STORAGE_KEY, StorageScope.APPLICATION)) { return; @@ -728,26 +731,41 @@ class DefaultThemeUpdatedNotificationContribution implements IWorkbenchContribut private async _showYouGotMigratedNotification(): Promise { this._storageService.store(DefaultThemeUpdatedNotificationContribution.STORAGE_KEY, true, StorageScope.APPLICATION, StorageTarget.USER); - await this._notificationService.notify({ - id: 'themeUpdatedNotification', - severity: Severity.Info, - message: localize({ key: 'themeUpdatedNotification', comment: ['{0} is the name of the new default theme'] }, "Visual Studio Code now ships with a new default theme '{0}'. If you prefer, you can switch back to the old theme or try one of the many other color themes available.", this._workbenchThemeService.getColorTheme().label), - actions: { - primary: [ - toAction({ id: 'themeUpdated.keep', label: localize('okButton', "Keep"), run: () => { } }), - toAction({ id: 'themeUpdated.browseThemes', label: localize('browseThemes', "Browse Themes"), run: () => this._commandService.executeCommand(SelectColorThemeCommandId) }), - toAction({ - id: 'themeUpdated.revert', label: localize('revert', "Revert"), run: async () => { - const oldSettingsId = isWeb ? ThemeSettingDefaults.COLOR_THEME_LIGHT_OLD : ThemeSettingDefaults.COLOR_THEME_DARK_OLD; - const oldTheme = (await this._workbenchThemeService.getColorThemes()).find(theme => theme.settingsId === oldSettingsId); - if (oldTheme) { - this._workbenchThemeService.setColorTheme(oldTheme, 'auto'); - } - } - }) - ] + const choices = [ + { + label: localize('button.keep', "Keep New Theme"), + run: () => { + this._writeTelemetry('keepNew'); + } + }, + { + label: localize('button.browse', "Browse Themes"), + run: () => { + this._writeTelemetry('browse'); + this._commandService.executeCommand(SelectColorThemeCommandId); + } + }, + { + label: localize('button.revert', "Revert"), + run: async () => { + this._writeTelemetry('keepOld'); + const oldSettingsId = isWeb ? ThemeSettingDefaults.COLOR_THEME_LIGHT_OLD : ThemeSettingDefaults.COLOR_THEME_DARK_OLD; + const oldTheme = (await this._workbenchThemeService.getColorThemes()).find(theme => theme.settingsId === oldSettingsId); + if (oldTheme) { + this._workbenchThemeService.setColorTheme(oldTheme, 'auto'); + } + } } - }); + ]; + await this._notificationService.prompt( + Severity.Info, + localize({ key: 'themeUpdatedNotification', comment: ['{0} is the name of the new default theme'] }, "Visual Studio Code now ships with a new default theme '{0}'. If you prefer, you can switch back to the old theme or try one of the many other color themes available.", this._workbenchThemeService.getColorTheme().label), + choices, + { + onCancel: () => this._writeTelemetry('cancel') + } + ); + } private async _tryNewThemeNotification(): Promise { @@ -757,16 +775,42 @@ class DefaultThemeUpdatedNotificationContribution implements IWorkbenchContribut if (theme) { const choices: IPromptChoice[] = [{ label: localize('button.tryTheme', "Try New Theme"), - run: () => this._workbenchThemeService.setColorTheme(theme, 'auto') + run: () => { + this._writeTelemetry('tryNew'); + this._workbenchThemeService.setColorTheme(theme, 'auto'); + } }, { label: localize('button.cancel', "Cancel"), - run: () => { } + run: () => { + this._writeTelemetry('cancel'); + } }]; - await this._notificationService.prompt(Severity.Info, localize({ key: 'newThemeNotification', comment: ['{0} is the name of the new default theme'] }, "Visual Studio Code now ships with a new default theme '{0}'. Do you want to give it a try?", theme.label), choices); + await this._notificationService.prompt( + Severity.Info, + localize({ key: 'newThemeNotification', comment: ['{0} is the name of the new default theme'] }, "Visual Studio Code now ships with a new default theme '{0}'. Do you want to give it a try?", theme.label), + choices, + { onCancel: () => this._writeTelemetry('cancel') } + ); } + } + private _writeTelemetry(outcome: DefaultThemeUpdatedNotificationReaction): void { + type ThemeUpdatedNoticationClassification = { + owner: 'aeschli'; + comment: 'Reaction to the notification that theme has updated to a new default theme'; + web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Whether this is running on web' }; + reaction: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Outcome of the notification' }; + }; + type ThemeUpdatedNoticationEvent = { + web: boolean; + reaction: DefaultThemeUpdatedNotificationReaction; + }; + this._telemetryService.publicLog2('themeUpdatedNotication', { + web: isWeb, + reaction: outcome + }); } } const workbenchRegistry = Registry.as(Extensions.Workbench); From c9c8e53032bb91036aef7cefabc91ce264113a33 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 13 Apr 2023 16:39:35 +0200 Subject: [PATCH 47/59] Replaces http-server with playground-server (#179876) --- .vscode/launch.json | 2 +- .vscode/tasks.json | 2 +- package.json | 1 - scripts/playground-server.ts | 710 +++++++++++++++++++++++++++++++++++ yarn.lock | 142 +------ 5 files changed, 719 insertions(+), 138 deletions(-) create mode 100644 scripts/playground-server.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 8d147034907ac..1ba9c3000cfe6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -526,7 +526,7 @@ "name": "Monaco Editor Playground", "type": "chrome", "request": "launch", - "url": "https://microsoft.github.io/monaco-editor/playground.html?source=http%3A%2F%2Flocalhost%3A5001%2Fout%2Fvs", + "url": "http://localhost:5001", "preLaunchTask": "Launch Http Server", "presentation": { "group": "monaco", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f97b01e8c6277..1a6554bec658e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -260,7 +260,7 @@ // Used for monaco editor playground launch config "label": "Launch Http Server", "type": "shell", - "command": "node_modules/.bin/http-server --cors --port 5001 -a 127.0.0.1 -c-1 -s", + "command": "node_modules/.bin/ts-node -T ./scripts/playground-server", "isBackground": true, "problemMatcher": { "pattern": { diff --git a/package.json b/package.json index a311a5c01e212..51124f5bc9251 100644 --- a/package.json +++ b/package.json @@ -169,7 +169,6 @@ "gulp-sourcemaps": "^3.0.0", "gulp-svgmin": "^4.1.0", "gulp-untar": "^0.0.7", - "http-server": "^14.1.1", "husky": "^0.13.1", "innosetup": "6.0.5", "is": "^3.1.0", diff --git a/scripts/playground-server.ts b/scripts/playground-server.ts new file mode 100644 index 0000000000000..133bd375d8940 --- /dev/null +++ b/scripts/playground-server.ts @@ -0,0 +1,710 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fsPromise from 'fs/promises'; +import path from 'path'; +import * as http from 'http'; +import * as parcelWatcher from '@parcel/watcher'; + +/** + * Launches the server for the monaco editor playground + */ +function main() { + const server = new HttpServer({ host: 'localhost', port: 5001, cors: true }); + server.use('/', redirectToMonacoEditorPlayground()); + + const rootDir = path.join(__dirname, '..'); + const fileServer = new FileServer(rootDir); + server.use(fileServer.handleRequest); + + const moduleIdMapper = new SimpleModuleIdPathMapper(path.join(rootDir, 'out')); + const editorMainBundle = new CachedBundle('vs/editor/editor.main', moduleIdMapper); + fileServer.overrideFileContent(editorMainBundle.entryModulePath, () => editorMainBundle.bundle()); + + const hotReloadJsCode = getHotReloadCode(new URL('/file-changes', server.url)); + const loaderPath = path.join(rootDir, 'out/vs/loader.js'); + fileServer.overrideFileContent(loaderPath, async () => + Buffer.from(new TextEncoder().encode(`${await fsPromise.readFile(loaderPath, 'utf8')}\n${hotReloadJsCode}`)) + ); + + const watcher = DirWatcher.watchRecursively(moduleIdMapper.rootDir); + watcher.onDidChange((path, newContent) => { + editorMainBundle.setModuleContent(path, newContent); + editorMainBundle.bundle(); + console.log(`${new Date().toLocaleTimeString()}, file change: ${path}`); + }); + server.use('/file-changes', handleGetFileChangesRequest(watcher, fileServer)); + + console.log(`Server listening on ${server.url}`); +} +setTimeout(main, 0); + +// #region Http/File Server + +type RequestHandler = (req: http.IncomingMessage, res: http.ServerResponse) => Promise; +type ChainableRequestHandler = (req: http.IncomingMessage, res: http.ServerResponse, next: RequestHandler) => Promise; + +class HttpServer { + private readonly server: http.Server; + public readonly url: URL; + + private handler: ChainableRequestHandler[] = []; + + constructor(options: { host: string; port: number; cors: boolean }) { + this.server = http.createServer(async (req, res) => { + if (options.cors) { + res.setHeader('Access-Control-Allow-Origin', '*'); + } + + let i = 0; + const next = async (req: http.IncomingMessage, res: http.ServerResponse) => { + if (i >= this.handler.length) { + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('404 Not Found'); + return; + } + const handler = this.handler[i]; + i++; + await handler(req, res, next); + }; + await next(req, res); + }); + this.server.listen(options.port, options.host); + this.url = new URL(`http://${options.host}:${options.port}`); + } + + use(handler: ChainableRequestHandler); + use(path: string, handler: ChainableRequestHandler); + use(...args: [path: string, handler: ChainableRequestHandler] | [handler: ChainableRequestHandler]) { + const handler = args.length === 1 ? args[0] : (req, res, next) => { + const path = args[0]; + const requestedUrl = new URL(req.url, this.url); + if (requestedUrl.pathname === path) { + return args[1](req, res, next); + } else { + return next(req, res); + } + }; + + this.handler.push(handler); + } +} + +function redirectToMonacoEditorPlayground(): ChainableRequestHandler { + return async (req, res) => { + const url = new URL('https://microsoft.github.io/monaco-editor/playground.html'); + url.searchParams.append('source', `http://${req.headers.host}/out/vs`); + res.writeHead(302, { Location: url.toString() }); + res.end(); + }; +} + +class FileServer { + private readonly overrides = new Map Promise>(); + + constructor(public readonly publicDir: string) { } + + public readonly handleRequest: ChainableRequestHandler = async (req, res, next) => { + const requestedUrl = new URL(req.url!, `http://${req.headers.host}`); + + const pathName = requestedUrl.pathname; + + const filePath = path.join(this.publicDir, pathName); + if (!filePath.startsWith(this.publicDir)) { + res.writeHead(403, { 'Content-Type': 'text/plain' }); + res.end('403 Forbidden'); + return; + } + + try { + const override = this.overrides.get(filePath); + let content: Buffer; + if (override) { + content = await override(); + } else { + content = await fsPromise.readFile(filePath); + } + + const contentType = getContentType(filePath); + res.writeHead(200, { 'Content-Type': contentType }); + res.end(content); + } catch (err) { + if (err.code === 'ENOENT') { + next(req, res); + } else { + res.writeHead(500, { 'Content-Type': 'text/plain' }); + res.end('500 Internal Server Error'); + } + } + }; + + public filePathToUrlPath(filePath: string): string | undefined { + const relative = path.relative(this.publicDir, filePath); + const isSubPath = !!relative && !relative.startsWith('..') && !path.isAbsolute(relative); + + if (!isSubPath) { + return undefined; + } + const relativePath = relative.replace(/\\/g, '/'); + return `/${relativePath}`; + } + + public overrideFileContent(filePath: string, content: () => Promise): void { + this.overrides.set(filePath, content); + } +} + +function getContentType(filePath: string): string { + const extname = path.extname(filePath); + switch (extname) { + case '.js': + return 'text/javascript'; + case '.css': + return 'text/css'; + case '.json': + return 'application/json'; + case '.png': + return 'image/png'; + case '.jpg': + return 'image/jpg'; + default: + return 'text/plain'; + } +} + +// #endregion + +// #region File Watching + +interface IDisposable { + dispose(): void; +} + +class DirWatcher { + public static watchRecursively(dir: string): DirWatcher { + const listeners: ((path: string, newContent: string) => void)[] = []; + const fileContents = new Map(); + const event = (handler: (path: string, newContent: string) => void) => { + listeners.push(handler); + return { + dispose: () => { + const idx = listeners.indexOf(handler); + if (idx >= 0) { + listeners.splice(idx, 1); + } + } + }; + }; + parcelWatcher.subscribe(dir, async (err, events) => { + for (const e of events) { + if (e.type === 'update') { + const newContent = await fsPromise.readFile(e.path, 'utf8'); + if (fileContents.get(e.path) !== newContent) { + fileContents.set(e.path, newContent); + listeners.forEach(l => l(e.path, newContent)); + } + } + } + }); + return new DirWatcher(event); + } + + constructor(public readonly onDidChange: (handler: (path: string, newContent: string) => void) => IDisposable) { + } +} + +function handleGetFileChangesRequest(watcher: DirWatcher, fileServer: FileServer): ChainableRequestHandler { + return async (req, res) => { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + const d = watcher.onDidChange(fsPath => { + const path = fileServer.filePathToUrlPath(fsPath); + if (path) { + res.write(JSON.stringify({ changedPath: path }) + '\n'); + } + }); + res.on('close', () => d.dispose()); + }; +} + +function getHotReloadCode(fileChangesUrl: URL): string { + const additionalJsCode = ` +function $watchChanges() { + console.log("Connecting to server to watch for changes..."); + fetch(${JSON.stringify(fileChangesUrl)}) + .then(async request => { + const reader = request.body.getReader(); + let buffer = ''; + while (true) { + const { done, value } = await reader.read(); + if (done) { break; } + buffer += new TextDecoder().decode(value); + const lines = buffer.split('\\n'); + buffer = lines.pop(); + for (const line of lines) { + const data = JSON.parse(line); + if (data.changedPath.endsWith('.css')) { + console.log('css changed', data.changedPath); + const styleSheet = [...document.querySelectorAll("link[rel='stylesheet']")].find(l => new URL(l.href, document.location.href).pathname.endsWith(data.changedPath)); + if (styleSheet) { + styleSheet.href = styleSheet.href.replace(/\\?.*/, '') + '?' + Date.now(); + } + } else { + $sendMessageToParent({ kind: "reload" }); + } + } + } + }) + .catch(err => { + console.error(err); + setTimeout($watchChanges, 1000); + }); + +} +$watchChanges(); +`; + return additionalJsCode; +} + +// #endregion + +// #region Bundling + +class CachedBundle { + public readonly entryModulePath = this.mapper.resolveRequestToPath(this.moduleId)!; + + constructor( + private readonly moduleId: string, + private readonly mapper: SimpleModuleIdPathMapper, + ) { + } + + private loader: ModuleLoader | undefined = undefined; + + private bundlePromise: Promise | undefined = undefined; + public async bundle(): Promise { + if (!this.bundlePromise) { + this.bundlePromise = (async () => { + if (!this.loader) { + this.loader = new ModuleLoader(this.mapper); + await this.loader.addModuleAndDependencies(this.entryModulePath); + } + const editorEntryPoint = await this.loader.getModule(this.entryModulePath); + const content = bundleWithDependencies(editorEntryPoint!); + return content; + })(); + } + return this.bundlePromise; + } + + public async setModuleContent(path: string, newContent: string): Promise { + if (!this.loader) { + return; + } + const module = await this.loader!.getModule(path); + if (module) { + if (!this.loader.updateContent(module, newContent)) { + this.loader = undefined; + } + } + this.bundlePromise = undefined; + } +} + +function bundleWithDependencies(module: IModule): Buffer { + const visited = new Set(); + const builder = new SourceMapBuilder(); + + function visit(module: IModule) { + if (visited.has(module)) { + return; + } + visited.add(module); + for (const dep of module.dependencies) { + visit(dep); + } + builder.addSource(module.source); + } + + visit(module); + + const sourceMap = builder.toSourceMap(); + sourceMap.sourceRoot = module.source.sourceMap.sourceRoot; + const sourceMapBase64Str = Buffer.from(JSON.stringify(sourceMap)).toString('base64'); + + builder.addLine(`//# sourceMappingURL=data:application/json;base64,${sourceMapBase64Str}`); + + return builder.toContent(); +} + +class ModuleLoader { + private readonly modules = new Map>(); + + constructor(private readonly mapper: SimpleModuleIdPathMapper) { } + + public getModule(path: string): Promise { + return Promise.resolve(this.modules.get(path)); + } + + public updateContent(module: IModule, newContent: string): boolean { + const parsedModule = parseModule(newContent, module.path, this.mapper); + if (!parsedModule) { + return false; + } + if (!arrayEquals(parsedModule.dependencyRequests, module.dependencyRequests)) { + return false; + } + + module.dependencyRequests = parsedModule.dependencyRequests; + module.source = parsedModule.source; + + return true; + } + + async addModuleAndDependencies(path: string): Promise { + if (this.modules.has(path)) { + return this.modules.get(path)!; + } + + const promise = (async () => { + const content = await fsPromise.readFile(path, { encoding: 'utf-8' }); + + const parsedModule = parseModule(content, path, this.mapper); + if (!parsedModule) { + return undefined; + } + + const dependencies = (await Promise.all(parsedModule.dependencyRequests.map(async r => { + if (r === 'require' || r === 'exports' || r === 'module') { + return null; + } + + const depPath = this.mapper.resolveRequestToPath(r, path); + if (!depPath) { + return null; + } + return await this.addModuleAndDependencies(depPath); + }))).filter((d): d is IModule => !!d); + + const module: IModule = { + id: this.mapper.getModuleId(path)!, + dependencyRequests: parsedModule.dependencyRequests, + dependencies, + path, + source: parsedModule.source, + }; + return module; + })(); + + this.modules.set(path, promise); + return promise; + } +} + +function arrayEquals(a: T[], b: T[]): boolean { + if (a.length !== b.length) { + return false; + } + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) { + return false; + } + } + return true; +} + +const encoder = new TextEncoder(); + +function parseModule(content: string, path: string, mapper: SimpleModuleIdPathMapper): { source: Source; dependencyRequests: string[] } | undefined { + const m = content.match(/define\((\[.*?\])/); + if (!m) { + return undefined; + } + + const dependencyRequests = JSON.parse(m[1].replace(/'/g, '"')) as string[]; + + const sourceMapHeader = '//# sourceMappingURL=data:application/json;base64,'; + const idx = content.indexOf(sourceMapHeader); + + let sourceMap: any = null; + if (idx !== -1) { + const sourceMapJsonStr = Buffer.from(content.substring(idx + sourceMapHeader.length), 'base64').toString('utf-8'); + sourceMap = JSON.parse(sourceMapJsonStr); + content = content.substring(0, idx); + } + + content = content.replace('define([', `define("${mapper.getModuleId(path)}", [`); + + const contentBuffer = Buffer.from(encoder.encode(content)); + const source = new Source(contentBuffer, sourceMap); + + return { dependencyRequests, source }; +} + +class SimpleModuleIdPathMapper { + constructor(public readonly rootDir: string) { } + + public getModuleId(path: string): string | null { + if (!path.startsWith(this.rootDir) || !path.endsWith('.js')) { + return null; + } + const moduleId = path.substring(this.rootDir.length + 1); + + + return moduleId.replace(/\\/g, '/').substring(0, moduleId.length - 3); + } + + public resolveRequestToPath(request: string, requestingModulePath?: string): string | null { + if (request.indexOf('css!') !== -1) { + return null; + } + + if (request.startsWith('.')) { + return path.join(path.dirname(requestingModulePath!), request + '.js'); + } else { + return path.join(this.rootDir, request + '.js'); + } + } +} + +interface IModule { + id: string; + dependencyRequests: string[]; + dependencies: IModule[]; + path: string; + source: Source; +} + +// #endregion + +// #region SourceMapBuilder + +// From https://stackoverflow.com/questions/29905373/how-to-create-sourcemaps-for-concatenated-files with modifications + +class Source { + // Ends with \n + public readonly content: Buffer; + public readonly sourceMap: SourceMap; + public readonly sourceLines: number; + + public readonly sourceMapMappings: Buffer; + + + constructor(content: Buffer, sourceMap: SourceMap | undefined) { + if (!sourceMap) { + sourceMap = SourceMapBuilder.emptySourceMap; + } + + let sourceLines = countNL(content); + if (content.length > 0 && content[content.length - 1] !== 10) { + sourceLines++; + content = Buffer.concat([content, Buffer.from([10])]); + } + + this.content = content; + this.sourceMap = sourceMap; + this.sourceLines = sourceLines; + this.sourceMapMappings = typeof this.sourceMap.mappings === 'string' + ? Buffer.from(encoder.encode(sourceMap.mappings as string)) + : this.sourceMap.mappings; + } +} + +class SourceMapBuilder { + public static emptySourceMap: SourceMap = { version: 3, sources: [], mappings: Buffer.alloc(0) }; + + private readonly outputBuffer = new DynamicBuffer(); + private readonly sources: string[] = []; + private readonly mappings = new DynamicBuffer(); + private lastSourceIndex = 0; + private lastSourceLine = 0; + private lastSourceCol = 0; + + addLine(text: string) { + this.outputBuffer.addString(text); + this.outputBuffer.addByte(10); + this.mappings.addByte(59); // ; + } + + addSource(source: Source) { + const sourceMap = source.sourceMap; + this.outputBuffer.addBuffer(source.content); + + const sourceRemap: number[] = []; + for (const v of sourceMap.sources) { + let pos = this.sources.indexOf(v); + if (pos < 0) { + pos = this.sources.length; + this.sources.push(v); + } + sourceRemap.push(pos); + } + let lastOutputCol = 0; + + const inputMappings = source.sourceMapMappings; + let outputLine = 0; + let ip = 0; + let inOutputCol = 0; + let inSourceIndex = 0; + let inSourceLine = 0; + let inSourceCol = 0; + let shift = 0; + let value = 0; + let valpos = 0; + const commit = () => { + if (valpos === 0) { return; } + this.mappings.addVLQ(inOutputCol - lastOutputCol); + lastOutputCol = inOutputCol; + if (valpos === 1) { + valpos = 0; + return; + } + const outSourceIndex = sourceRemap[inSourceIndex]; + this.mappings.addVLQ(outSourceIndex - this.lastSourceIndex); + this.lastSourceIndex = outSourceIndex; + this.mappings.addVLQ(inSourceLine - this.lastSourceLine); + this.lastSourceLine = inSourceLine; + this.mappings.addVLQ(inSourceCol - this.lastSourceCol); + this.lastSourceCol = inSourceCol; + valpos = 0; + }; + while (ip < inputMappings.length) { + let b = inputMappings[ip++]; + if (b === 59) { // ; + commit(); + this.mappings.addByte(59); + inOutputCol = 0; + lastOutputCol = 0; + outputLine++; + } else if (b === 44) { // , + commit(); + this.mappings.addByte(44); + } else { + b = charToInteger[b]; + if (b === 255) { throw new Error('Invalid sourceMap'); } + value += (b & 31) << shift; + if (b & 32) { + shift += 5; + } else { + const shouldNegate = value & 1; + value >>= 1; + if (shouldNegate) { value = -value; } + switch (valpos) { + case 0: inOutputCol += value; break; + case 1: inSourceIndex += value; break; + case 2: inSourceLine += value; break; + case 3: inSourceCol += value; break; + } + valpos++; + value = shift = 0; + } + } + } + commit(); + while (outputLine < source.sourceLines) { + this.mappings.addByte(59); + outputLine++; + } + } + + toContent(): Buffer { + return this.outputBuffer.toBuffer(); + } + + toSourceMap(sourceRoot?: string): SourceMap { + return { version: 3, sourceRoot, sources: this.sources, mappings: this.mappings.toBuffer().toString() }; + } +} + +export interface SourceMap { + version: number; // always 3 + file?: string; + sourceRoot?: string; + sources: string[]; + sourcesContent?: string[]; + names?: string[]; + mappings: string | Buffer; +} + +const charToInteger = Buffer.alloc(256); +const integerToChar = Buffer.alloc(64); + +charToInteger.fill(255); + +'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split('').forEach((char, i) => { + charToInteger[char.charCodeAt(0)] = i; + integerToChar[i] = char.charCodeAt(0); +}); + +class DynamicBuffer { + private buffer: Buffer; + private size: number; + + constructor() { + this.buffer = Buffer.alloc(512); + this.size = 0; + } + + ensureCapacity(capacity: number) { + if (this.buffer.length >= capacity) { + return; + } + const oldBuffer = this.buffer; + this.buffer = Buffer.alloc(Math.max(oldBuffer.length * 2, capacity)); + oldBuffer.copy(this.buffer); + } + + addByte(b: number) { + this.ensureCapacity(this.size + 1); + this.buffer[this.size++] = b; + } + + addVLQ(num: number) { + let clamped: number; + + if (num < 0) { + num = (-num << 1) | 1; + } else { + num <<= 1; + } + + do { + clamped = num & 31; + num >>= 5; + + if (num > 0) { + clamped |= 32; + } + + this.addByte(integerToChar[clamped]); + } while (num > 0); + } + + addString(s: string) { + const l = Buffer.byteLength(s); + this.ensureCapacity(this.size + l); + this.buffer.write(s, this.size); + this.size += l; + } + + addBuffer(b: Buffer) { + this.ensureCapacity(this.size + b.length); + b.copy(this.buffer, this.size); + this.size += b.length; + } + + toBuffer(): Buffer { + return this.buffer.slice(0, this.size); + } +} + +function countNL(b: Buffer): number { + let res = 0; + for (let i = 0; i < b.length; i++) { + if (b[i] === 10) { res++; } + } + return res; +} + +// #endregion diff --git a/yarn.lock b/yarn.lock index 0522a5dbe8af3..c1128d176711b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2007,13 +2007,6 @@ async-settle@^1.0.0: dependencies: async-done "^1.2.2" -async@^2.6.4: - version "2.6.4" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" - integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== - dependencies: - lodash "^4.17.14" - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -2067,7 +2060,7 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" -basic-auth@^2.0.1, basic-auth@~2.0.1: +basic-auth@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg== @@ -2422,7 +2415,7 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.1.2, chalk@^4.x: +chalk@^4.x: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -2893,11 +2886,6 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -corser@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/corser/-/corser-2.0.1.tgz#8eda252ecaab5840dcd975ceb90d9370c819ff87" - integrity sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ== - cosmiconfig@^5.0.0: version "5.2.1" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" @@ -3157,7 +3145,7 @@ debug@3.1.0: dependencies: ms "2.0.0" -debug@3.X, debug@^3.2.7: +debug@3.X: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== @@ -4074,11 +4062,6 @@ event-stream@~3.3.4: stream-combiner "^0.2.2" through "^2.3.8" -eventemitter3@^4.0.0: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - events@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" @@ -4452,11 +4435,6 @@ flush-write-stream@^1.0.2: inherits "^2.0.3" readable-stream "^2.3.6" -follow-redirects@^1.0.0: - version "1.15.2" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" - integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== - for-in@^1.0.1, for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -5276,7 +5254,7 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -he@1.2.0, he@^1.2.0: +he@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== @@ -5317,13 +5295,6 @@ hsla-regex@^1.0.0: resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= -html-encoding-sniffer@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" - integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA== - dependencies: - whatwg-encoding "^2.0.0" - html-escaper@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.0.tgz#71e87f931de3fe09e56661ab9a29aadec707b491" @@ -5400,34 +5371,6 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" -http-proxy@^1.18.1: - version "1.18.1" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" - integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== - dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - -http-server@^14.1.1: - version "14.1.1" - resolved "https://registry.yarnpkg.com/http-server/-/http-server-14.1.1.tgz#d60fbb37d7c2fdff0f0fbff0d0ee6670bd285e2e" - integrity sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A== - dependencies: - basic-auth "^2.0.1" - chalk "^4.1.2" - corser "^2.0.1" - he "^1.2.0" - html-encoding-sniffer "^3.0.0" - http-proxy "^1.18.1" - mime "^1.6.0" - minimist "^1.2.6" - opener "^1.5.1" - portfinder "^1.0.28" - secure-compare "3.0.1" - union "~0.5.0" - url-join "^4.0.1" - http2-wrapper@^1.0.0-beta.5.2: version "1.0.3" resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" @@ -5470,13 +5413,6 @@ husky@^0.13.1: is-ci "^1.0.9" normalize-path "^1.0.0" -iconv-lite@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - iconv-lite@^0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" @@ -6822,7 +6758,7 @@ mime@^1.3.4: resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== -mime@^1.4.1, mime@^1.6.0: +mime@^1.4.1: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== @@ -6923,7 +6859,7 @@ mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: dependencies: minimist "^1.2.5" -mkdirp@^0.5.5, mkdirp@^0.5.6: +mkdirp@^0.5.5: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== @@ -7346,11 +7282,6 @@ object-inspect@^1.8.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== -object-inspect@^1.9.0: - version "1.12.3" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" - integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== - object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -7461,11 +7392,6 @@ only@~0.0.2: resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4" integrity sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q= -opener@^1.5.1: - version "1.5.2" - resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" - integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== - opn@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/opn/-/opn-6.0.0.tgz#3c5b0db676d5f97da1233d1ed42d182bc5a27d2d" @@ -7899,15 +7825,6 @@ plugin-error@^1.0.0, plugin-error@^1.0.1: arr-union "^3.1.0" extend-shallow "^3.0.2" -portfinder@^1.0.28: - version "1.0.32" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.32.tgz#2fe1b9e58389712429dc2bea5beb2146146c7f81" - integrity sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg== - dependencies: - async "^2.6.4" - debug "^3.2.7" - mkdirp "^0.5.6" - posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" @@ -8390,13 +8307,6 @@ q@^1.1.2: resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= -qs@^6.4.0: - version "6.11.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== - dependencies: - side-channel "^1.0.4" - queue@3.0.6: version "3.0.6" resolved "https://registry.yarnpkg.com/queue/-/queue-3.0.6.tgz#66c0ffd0a1d9d28045adebda966a2d3946ab9f13" @@ -8640,11 +8550,6 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== - resolve-alpn@^1.0.0: version "1.2.1" resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" @@ -8826,7 +8731,7 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": +"safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -8864,11 +8769,6 @@ schema-utils@^4.0.0: ajv-formats "^2.1.1" ajv-keywords "^5.0.0" -secure-compare@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/secure-compare/-/secure-compare-3.0.1.tgz#f1a0329b308b221fae37b9974f3d578d0ca999e3" - integrity sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw== - seek-bzip@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.6.tgz#35c4171f55a680916b52a07859ecf3b5857f21c4" @@ -9009,15 +8909,6 @@ shell-quote@^1.6.1: resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw== -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - sigmund@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" @@ -10122,13 +10013,6 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^2.0.1" -union@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/union/-/union-0.5.0.tgz#b2c11be84f60538537b846edb9ba266ba0090075" - integrity sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA== - dependencies: - qs "^6.4.0" - uniq@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" @@ -10195,11 +10079,6 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= -url-join@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" - integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== - url-parse-lax@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" @@ -10549,13 +10428,6 @@ webpack@^5.77.0: watchpack "^2.4.0" webpack-sources "^3.2.3" -whatwg-encoding@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" - integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== - dependencies: - iconv-lite "0.6.3" - whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" From 2c7cc4ddeabf2f90d6555ca907ddc023da151074 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 13 Apr 2023 16:42:12 +0200 Subject: [PATCH 48/59] GitHub - do not get branch protection if the user does not have push permission (#179879) --- extensions/github/src/branchProtection.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/extensions/github/src/branchProtection.ts b/extensions/github/src/branchProtection.ts index 6ba43eac70bd3..fe89ac3ed96b0 100644 --- a/extensions/github/src/branchProtection.ts +++ b/extensions/github/src/branchProtection.ts @@ -85,6 +85,18 @@ export class GithubBranchProtectionProvider implements BranchProtectionProvider await this.updateBranchProtection(); } + private async checkPushPermission(repository: { owner: string; repo: string }): Promise { + try { + const octokit = await getOctokit(); + const response = await octokit.repos.get({ ...repository }); + + return response.data.permissions?.push === true; + } catch { + // todo@lszomoru - add logging + return false; + } + } + private async updateHEADBranchProtection(): Promise { try { const HEAD = this.repository.state.HEAD; @@ -106,6 +118,10 @@ export class GithubBranchProtectionProvider implements BranchProtectionProvider return; } + if (!(await this.checkPushPermission(repository))) { + return; + } + const octokit = await getOctokit(); const response = await octokit.repos.getBranch({ ...repository, branch: HEAD.name }); @@ -131,6 +147,10 @@ export class GithubBranchProtectionProvider implements BranchProtectionProvider continue; } + if (!(await this.checkPushPermission(repository))) { + continue; + } + const octokit = await getOctokit(); let page = 1; From 858533f95569043e397da0522d744c83fc5ebbcd Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 13 Apr 2023 16:43:33 +0200 Subject: [PATCH 49/59] fix #179756 (#179880) --- .../browser/deprecatedExtensionsChecker.ts | 80 +++++++++++++++++++ .../browser/extensions.contribution.ts | 2 + 2 files changed, 82 insertions(+) create mode 100644 src/vs/workbench/contrib/extensions/browser/deprecatedExtensionsChecker.ts diff --git a/src/vs/workbench/contrib/extensions/browser/deprecatedExtensionsChecker.ts b/src/vs/workbench/contrib/extensions/browser/deprecatedExtensionsChecker.ts new file mode 100644 index 0000000000000..2345dedabd3aa --- /dev/null +++ b/src/vs/workbench/contrib/extensions/browser/deprecatedExtensionsChecker.ts @@ -0,0 +1,80 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { localize } from 'vs/nls'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { SearchExtensionsAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { distinct } from 'vs/base/common/arrays'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; + +export class DeprecatedExtensionsChecker extends Disposable implements IWorkbenchContribution { + + constructor( + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionManagementService extensionManagementService: IExtensionManagementService, + @IStorageService private readonly storageService: IStorageService, + @INotificationService private readonly notificationService: INotificationService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { + super(); + this.checkForDeprecatedExtensions(); + this._register(extensionManagementService.onDidInstallExtensions(e => { + const ids: string[] = []; + for (const { local } of e) { + if (local && extensionsWorkbenchService.local.find(extension => areSameExtensions(extension.identifier, local.identifier))?.deprecationInfo) { + ids.push(local.identifier.id.toLowerCase()); + } + } + if (ids.length) { + this.setNotifiedDeprecatedExtensions(ids); + } + })); + } + + private async checkForDeprecatedExtensions(): Promise { + if (this.storageService.getBoolean('extensionsAssistant/doNotCheckDeprecated', StorageScope.PROFILE, false)) { + return; + } + const local = await this.extensionsWorkbenchService.queryLocal(); + const previouslyNotified = this.getNotifiedDeprecatedExtensions(); + const toNotify = local.filter(e => !!e.deprecationInfo).filter(e => !previouslyNotified.includes(e.identifier.id.toLowerCase())); + if (toNotify.length) { + this.notificationService.prompt( + Severity.Warning, + localize('deprecated extensions', "You have deprecated extensions installed. We recommend to review them and migrate to alternatives."), + [{ + label: localize('showDeprecated', "Show Deprecated Extensions"), + run: async () => { + this.setNotifiedDeprecatedExtensions(toNotify.map(e => e.identifier.id.toLowerCase())); + const action = this.instantiationService.createInstance(SearchExtensionsAction, toNotify.map(extension => `@id:${extension.identifier.id}`).join(' ')); + try { + await action.run(); + } finally { + action.dispose(); + } + } + }, { + label: localize('neverShowAgain', "Don't Show Again"), + isSecondary: true, + run: () => this.storageService.store('extensionsAssistant/doNotCheckDeprecated', true, StorageScope.PROFILE, StorageTarget.USER) + }] + ); + } + } + + private getNotifiedDeprecatedExtensions(): string[] { + return JSON.parse(this.storageService.get('extensionsAssistant/deprecated', StorageScope.PROFILE, '[]')); + } + + private setNotifiedDeprecatedExtensions(notified: string[]): void { + this.storageService.store('extensionsAssistant/deprecated', JSON.stringify(distinct([...this.getNotifiedDeprecatedExtensions(), ...notified])), StorageScope.PROFILE, StorageTarget.USER); + } +} diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index bf9aeb002360a..ca2ee9d49e436 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -77,6 +77,7 @@ import { ExtensionStorageService } from 'vs/platform/extensionManagement/common/ import { IStorageService } from 'vs/platform/storage/common/storage'; import { IStringDictionary } from 'vs/base/common/collections'; import { CONTEXT_KEYBINDINGS_EDITOR } from 'vs/workbench/contrib/preferences/common/preferences'; +import { DeprecatedExtensionsChecker } from 'vs/workbench/contrib/extensions/browser/deprecatedExtensionsChecker'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService, InstantiationType.Eager /* Auto updates extensions */); @@ -1617,6 +1618,7 @@ workbenchRegistry.registerWorkbenchContribution(ExtensionDependencyChecker, Life workbenchRegistry.registerWorkbenchContribution(ExtensionEnablementWorkspaceTrustTransitionParticipant, LifecyclePhase.Restored); workbenchRegistry.registerWorkbenchContribution(ExtensionsCompletionItemsProvider, LifecyclePhase.Restored); workbenchRegistry.registerWorkbenchContribution(UnsupportedExtensionsMigrationContrib, LifecyclePhase.Eventually); +workbenchRegistry.registerWorkbenchContribution(DeprecatedExtensionsChecker, LifecyclePhase.Eventually); if (isWeb) { workbenchRegistry.registerWorkbenchContribution(ExtensionStorageCleaner, LifecyclePhase.Eventually); } From 3ae0149227789fe5bd54e0948d18032f8fb44a83 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Thu, 13 Apr 2023 16:46:28 +0200 Subject: [PATCH 50/59] Add special cases for Firefox specific key codes (#179425) Fixes #178098: Add special cases for Firefox specific key codes --- src/vs/base/browser/keyboardEvent.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/base/browser/keyboardEvent.ts b/src/vs/base/browser/keyboardEvent.ts index 9dd0862c003b7..57ba7407845a7 100644 --- a/src/vs/base/browser/keyboardEvent.ts +++ b/src/vs/base/browser/keyboardEvent.ts @@ -25,13 +25,17 @@ function extractKeyCode(e: KeyboardEvent): KeyCode { } else if (browser.isFirefox) { switch (keyCode) { case 59: return KeyCode.Semicolon; - + case 60: + if (platform.isLinux) { return KeyCode.IntlBackslash; } + break; + case 61: return KeyCode.Equal; // based on: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode#numpad_keys case 107: return KeyCode.NumpadAdd; case 109: return KeyCode.NumpadSubtract; - + case 173: return KeyCode.Minus; case 224: if (platform.isMacintosh) { return KeyCode.Meta; } + break; } } else if (browser.isWebKit) { if (platform.isMacintosh && keyCode === 93) { From 6f0421755f071e52fff2e9536a9d7ed253f45ef9 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Thu, 13 Apr 2023 08:04:41 -0700 Subject: [PATCH 51/59] Use custom node-gyp in product build (#179612) Co-authored-by: Robo --- .../win32/product-build-win32.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index de14aa1a89315..3cbd999d8673d 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -82,10 +82,28 @@ steps: condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) displayName: Setup NPM Authentication + - powershell: | + mkdir -Force .build/node-gyp + displayName: Create custom node-gyp directory + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - powershell: | + . ../../build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + # TODO: Should be replaced with upstream URL once https://github.com/nodejs/node-gyp/pull/2825 + # gets merged. + exec { git clone https://github.com/rzhao271/node-gyp.git . } "Cloning rzhao271/node-gyp failed" + exec { git checkout 102b347da0c92c29f9c67df22e864e70249cf086 } "Checking out 102b347 failed" + exec { npm install } "Building rzhao271/node-gyp failed" + displayName: Install custom node-gyp + workingDirectory: .build/node-gyp + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + - powershell: | . build/azure-pipelines/win32/exec.ps1 . build/azure-pipelines/win32/retry.ps1 $ErrorActionPreference = "Stop" + $env:npm_config_node_gyp="$(Join-Path $pwd.Path '.build/node-gyp/bin/node-gyp.js')" $env:npm_config_arch="$(VSCODE_ARCH)" $env:CHILD_CONCURRENCY="1" retry { exec { yarn --frozen-lockfile --check-files } } From b782ae6e67c92464bf0306b9466f36083fc2a403 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 13 Apr 2023 17:37:08 +0200 Subject: [PATCH 52/59] fix #176783 (#179884) * fix #176783 * fix tests --- .../browser/workspaceRecommendations.ts | 28 ++----------------- .../extensionRecommendationsService.test.ts | 6 ++-- 2 files changed, 5 insertions(+), 29 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/workspaceRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/workspaceRecommendations.ts index 84ec5199f0aad..e2e34e8f8fc3e 100644 --- a/src/vs/workbench/contrib/extensions/browser/workspaceRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/workspaceRecommendations.ts @@ -3,13 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { EXTENSION_IDENTIFIER_PATTERN, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { EXTENSION_IDENTIFIER_PATTERN } from 'vs/platform/extensionManagement/common/extensionManagement'; import { distinct, flatten } from 'vs/base/common/arrays'; import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; -import { ILogService } from 'vs/platform/log/common/log'; -import { CancellationToken } from 'vs/base/common/cancellation'; import { localize } from 'vs/nls'; import { Emitter } from 'vs/base/common/event'; import { IExtensionsConfigContent, IWorkspaceExtensionsConfigService } from 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig'; @@ -27,8 +25,6 @@ export class WorkspaceRecommendations extends ExtensionRecommendations { constructor( @IWorkspaceExtensionsConfigService private readonly workspaceExtensionsConfigService: IWorkspaceExtensionsConfigService, - @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, - @ILogService private readonly logService: ILogService, @INotificationService private readonly notificationService: INotificationService, ) { super(); @@ -82,39 +78,19 @@ export class WorkspaceRecommendations extends ExtensionRecommendations { const validExtensions: string[] = []; const invalidExtensions: string[] = []; - const extensionsToQuery: string[] = []; let message = ''; const allRecommendations = distinct(flatten(contents.map(({ recommendations }) => recommendations || []))); const regEx = new RegExp(EXTENSION_IDENTIFIER_PATTERN); for (const extensionId of allRecommendations) { if (regEx.test(extensionId)) { - extensionsToQuery.push(extensionId); + validExtensions.push(extensionId); } else { invalidExtensions.push(extensionId); message += `${extensionId} (bad format) Expected: .\n`; } } - if (extensionsToQuery.length) { - try { - const galleryExtensions = await this.galleryService.getExtensions(extensionsToQuery.map(id => ({ id })), CancellationToken.None); - const extensions = galleryExtensions.map(extension => extension.identifier.id.toLowerCase()); - - for (const extensionId of extensionsToQuery) { - if (extensions.indexOf(extensionId) === -1) { - invalidExtensions.push(extensionId); - message += `${extensionId} (not found in marketplace)\n`; - } else { - validExtensions.push(extensionId); - } - } - - } catch (e) { - this.logService.warn('Error querying extensions gallery', e); - } - } - return { validRecommendations: validExtensions, invalidRecommendations: invalidExtensions, message }; } diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts index 97cd682bb0a4d..01e91a63ebfb4 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts @@ -398,11 +398,11 @@ suite('ExtensionRecommendationsService Test', () => { await Event.toPromise(promptedEmitter.event); const recommendations = Object.keys(testObject.getAllRecommendationsWithReason()); - assert.strictEqual(recommendations.length, mockTestData.validRecommendedExtensions.length); - mockTestData.validRecommendedExtensions.forEach(x => { + const expected = [...mockTestData.validRecommendedExtensions, 'unknown.extension']; + assert.strictEqual(recommendations.length, expected.length); + expected.forEach(x => { assert.strictEqual(recommendations.indexOf(x.toLowerCase()) > -1, true); }); - }); test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if they are already installed', () => { From 24c44070aefd4d14a9e7b8f1dbd4572b13c83bbd Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 13 Apr 2023 10:55:06 -0700 Subject: [PATCH 53/59] Revert "Nicely format logged objects (#179405)" (#179894) This reverts commit 5d3f960b67f4908c300b5ad86ca526302e543521. Based on comments in https://github.com/microsoft/vscode/pull/179405#issuecomment-1506843399 --- extensions/typescript-language-features/src/logging/tracer.ts | 2 +- src/vs/platform/log/common/log.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/typescript-language-features/src/logging/tracer.ts b/extensions/typescript-language-features/src/logging/tracer.ts index 40b5568839ffb..e273181075da9 100644 --- a/extensions/typescript-language-features/src/logging/tracer.ts +++ b/extensions/typescript-language-features/src/logging/tracer.ts @@ -45,6 +45,6 @@ export default class Tracer extends Disposable { } public trace(serverId: string, message: string, data?: unknown): void { - this.logger.trace(`<${serverId}> ${message}`, data); + this.logger.trace(`<${serverId}> ${message}`, ...(data ? [JSON.stringify(data, null, 4)] : [])); } } diff --git a/src/vs/platform/log/common/log.ts b/src/vs/platform/log/common/log.ts index f8db74e660f4a..3a0a4b2a7e9c3 100644 --- a/src/vs/platform/log/common/log.ts +++ b/src/vs/platform/log/common/log.ts @@ -79,7 +79,7 @@ function format(args: any, verbose: boolean = false): string { if (typeof a === 'object') { try { - a = JSON.stringify(a, null, 2); + a = JSON.stringify(a); } catch (e) { } } From f743297aa1efa30ca4edaadeefb29742081d96f9 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 13 Apr 2023 11:18:48 -0700 Subject: [PATCH 54/59] cli: add acquire_cli (#179837) * cli: add acquire_cli As given in my draft document, pipes a CLI of the given platform to the specified process, for example: ```js const cmd = await rpc.call('acquire_cli', { command: 'node', args: [ '-e', 'process.stdin.pipe(fs.createWriteStream("c:/users/conno/downloads/hello-cli"))', ], platform: Platform.LinuxX64, quality: 'insider', }); ``` It genericizes caching so that the CLI is also cached on the host, just like servers. * fix bug --- cli/Cargo.toml | 2 +- cli/src/download_cache.rs | 119 +++++++++++++++++ cli/src/lib.rs | 1 + cli/src/rpc.rs | 165 ++++++++++++----------- cli/src/self_update.rs | 2 +- cli/src/state.rs | 21 ++- cli/src/tunnels/code_server.rs | 213 ++++++++++++------------------ cli/src/tunnels/control_server.rs | 173 ++++++++++++++---------- cli/src/tunnels/paths.rs | 124 +++++------------ cli/src/tunnels/protocol.rs | 10 ++ cli/src/tunnels/wsl_server.rs | 2 +- cli/src/update_service.rs | 16 +-- cli/src/util.rs | 4 - cli/src/util/errors.rs | 6 +- cli/src/util/http.rs | 32 +++-- 15 files changed, 487 insertions(+), 403 deletions(-) create mode 100644 cli/src/download_cache.rs diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 97affef0a64a7..6b5c8d07c3f81 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -55,6 +55,7 @@ cfg-if = "1.0.0" pin-project = "1.0" console = "0.15" bytes = "1.4" +tar = { version = "0.4" } [build-dependencies] serde = { version = "1.0" } @@ -68,7 +69,6 @@ winapi = "0.3.9" core-foundation = "0.9.3" [target.'cfg(target_os = "linux")'.dependencies] -tar = { version = "0.4" } zbus = { version = "3.4", default-features = false, features = ["tokio"] } [patch.crates-io] diff --git a/cli/src/download_cache.rs b/cli/src/download_cache.rs new file mode 100644 index 0000000000000..869fcf6235758 --- /dev/null +++ b/cli/src/download_cache.rs @@ -0,0 +1,119 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::{ + fs::create_dir_all, + path::{Path, PathBuf}, +}; + +use futures::Future; +use tokio::fs::remove_dir_all; + +use crate::{ + state::PersistedState, + util::errors::{wrap, AnyError, WrappedError}, +}; + +const KEEP_LRU: usize = 5; +const STAGING_SUFFIX: &str = ".staging"; + +#[derive(Clone)] +pub struct DownloadCache { + path: PathBuf, + state: PersistedState>, +} + +impl DownloadCache { + pub fn new(path: PathBuf) -> DownloadCache { + DownloadCache { + state: PersistedState::new(path.join("lru.json")), + path, + } + } + + /// Gets the download cache path. Names of cache entries can be formed by + /// joining them to the path. + pub fn path(&self) -> &Path { + &self.path + } + + /// Gets whether a cache exists with the name already. Marks it as recently + /// used if it does exist. + pub fn exists(&self, name: &str) -> Option { + let p = self.path.join(name); + if !p.exists() { + return None; + } + + let _ = self.touch(name.to_string()); + Some(p) + } + + /// Removes the item from the cache, if it exists + pub fn delete(&self, name: &str) -> Result<(), WrappedError> { + let f = self.path.join(name); + if f.exists() { + std::fs::remove_dir_all(f).map_err(|e| wrap(e, "error removing cached folder"))?; + } + + self.state.update(|l| { + l.retain(|n| n != name); + }) + } + + /// Calls the function to create the cached folder if it doesn't exist, + /// returning the path where the folder is. Note that the path passed to + /// the `do_create` method is a staging path and will not be the same as the + /// final returned path. + pub async fn create( + &self, + name: impl AsRef, + do_create: F, + ) -> Result + where + F: FnOnce(PathBuf) -> T, + T: Future> + Send, + { + let name = name.as_ref(); + let target_dir = self.path.join(name); + if target_dir.exists() { + return Ok(target_dir); + } + + let temp_dir = self.path.join(format!("{}{}", name, STAGING_SUFFIX)); + let _ = remove_dir_all(&temp_dir).await; // cleanup any existing + + create_dir_all(&temp_dir).map_err(|e| wrap(e, "error creating server directory"))?; + do_create(temp_dir.clone()).await?; + + let _ = self.touch(name.to_string()); + std::fs::rename(&temp_dir, &target_dir) + .map_err(|e| wrap(e, "error renaming downloaded server"))?; + + Ok(target_dir) + } + + fn touch(&self, name: String) -> Result<(), AnyError> { + self.state.update(|l| { + if let Some(index) = l.iter().position(|s| s == &name) { + l.remove(index); + } + l.insert(0, name); + + if l.len() <= KEEP_LRU { + return; + } + + if let Some(f) = l.last() { + let f = self.path.join(f); + if !f.exists() || std::fs::remove_dir_all(f).is_ok() { + l.pop(); + } + } + })?; + + Ok(()) + } +} diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 0fe65c9d8c807..93777852df28a 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -18,6 +18,7 @@ pub mod tunnels; pub mod update_service; pub mod util; +mod download_cache; mod async_pipe; mod json_rpc; mod msgpack_rpc; diff --git a/cli/src/rpc.rs b/cli/src/rpc.rs index f3c68321590ba..28dfc0efb4786 100644 --- a/cli/src/rpc.rs +++ b/cli/src/rpc.rs @@ -93,7 +93,7 @@ pub struct RpcMethodBuilder { #[derive(Serialize)] struct DuplexStreamStarted { pub for_request_id: u32, - pub stream_id: u32, + pub stream_ids: Vec, } impl RpcMethodBuilder { @@ -196,12 +196,16 @@ impl RpcMethodBuilder { /// Registers an async rpc call that returns a Future containing a duplex /// stream that should be handled by the client. - pub fn register_duplex(&mut self, method_name: &'static str, callback: F) - where + pub fn register_duplex( + &mut self, + method_name: &'static str, + streams: usize, + callback: F, + ) where P: DeserializeOwned + Send + 'static, R: Serialize + Send + Sync + 'static, Fut: Future> + Send, - F: (Fn(DuplexStream, P, Arc) -> Fut) + Clone + Send + Sync + 'static, + F: (Fn(Vec, P, Arc) -> Fut) + Clone + Send + Sync + 'static, { let serial = self.serializer.clone(); let context = self.context.clone(); @@ -230,11 +234,21 @@ impl RpcMethodBuilder { let callback = callback.clone(); let serial = serial.clone(); let context = context.clone(); - let stream_id = next_message_id(); - let (client, server) = tokio::io::duplex(8192); + + let mut dto = StreamDto { + req_id: id.unwrap_or(0), + streams: Vec::with_capacity(streams), + }; + let mut servers = Vec::with_capacity(streams); + + for _ in 0..streams { + let (client, server) = tokio::io::duplex(8192); + servers.push(server); + dto.streams.push((next_message_id(), client)); + } let fut = async move { - match callback(server, param.params, context).await { + match callback(servers, param.params, context).await { Ok(r) => id.map(|id| serial.serialize(&SuccessResponse { id, result: r })), Err(err) => id.map(|id| { serial.serialize(&ErrorResponse { @@ -248,14 +262,7 @@ impl RpcMethodBuilder { } }; - ( - Some(StreamDto { - req_id: id.unwrap_or(0), - stream_id, - duplex: client, - }), - fut.boxed(), - ) + (Some(dto), fut.boxed()) })), ); } @@ -447,74 +454,73 @@ impl RpcDispatcher { write_tx: mpsc::Sender> + Send>, dto: StreamDto, ) { - let stream_id = dto.stream_id; - let for_request_id = dto.req_id; - let (mut read, write) = tokio::io::split(dto.duplex); - let serial = self.serializer.clone(); - - self.streams.lock().await.insert(dto.stream_id, write); - - tokio::spawn(async move { - let r = write_tx - .send( - serial - .serialize(&FullRequest { - id: None, - method: METHOD_STREAM_STARTED, - params: DuplexStreamStarted { - stream_id, - for_request_id, - }, - }) - .into(), - ) - .await; + let r = write_tx + .send( + self.serializer + .serialize(&FullRequest { + id: None, + method: METHOD_STREAMS_STARTED, + params: DuplexStreamStarted { + stream_ids: dto.streams.iter().map(|(id, _)| *id).collect(), + for_request_id: dto.req_id, + }, + }) + .into(), + ) + .await; - if r.is_err() { - return; - } + if r.is_err() { + return; + } - let mut buf = Vec::with_capacity(4096); - loop { - match read.read_buf(&mut buf).await { - Ok(0) | Err(_) => break, - Ok(n) => { - let r = write_tx - .send( - serial - .serialize(&FullRequest { - id: None, - method: METHOD_STREAM_DATA, - params: StreamDataParams { - segment: &buf[..n], - stream: stream_id, - }, - }) - .into(), - ) - .await; - - if r.is_err() { - return; + let mut streams_map = self.streams.lock().await; + for (stream_id, duplex) in dto.streams { + let (mut read, write) = tokio::io::split(duplex); + streams_map.insert(stream_id, write); + + let write_tx = write_tx.clone(); + let serial = self.serializer.clone(); + tokio::spawn(async move { + let mut buf = vec![0; 4096]; + loop { + match read.read(&mut buf).await { + Ok(0) | Err(_) => break, + Ok(n) => { + let r = write_tx + .send( + serial + .serialize(&FullRequest { + id: None, + method: METHOD_STREAM_DATA, + params: StreamDataParams { + segment: &buf[..n], + stream: stream_id, + }, + }) + .into(), + ) + .await; + + if r.is_err() { + return; + } } - - buf.truncate(0); } } - } - let _ = write_tx - .send( - serial - .serialize(&FullRequest { - id: None, - method: METHOD_STREAM_ENDED, - params: StreamEndedParams { stream: stream_id }, - }) - .into(), - ) - .await; - }); + let _ = write_tx + .send( + serial + .serialize(&FullRequest { + id: None, + method: METHOD_STREAM_ENDED, + params: StreamEndedParams { stream: stream_id }, + }) + .into(), + ) + .await; + }); + } } pub fn context(&self) -> Arc { @@ -522,7 +528,7 @@ impl RpcDispatcher { } } -const METHOD_STREAM_STARTED: &str = "stream_started"; +const METHOD_STREAMS_STARTED: &str = "streams_started"; const METHOD_STREAM_DATA: &str = "stream_data"; const METHOD_STREAM_ENDED: &str = "stream_ended"; @@ -592,9 +598,8 @@ enum Outcome { } pub struct StreamDto { - stream_id: u32, req_id: u32, - duplex: DuplexStream, + streams: Vec<(u32, DuplexStream)>, } pub enum MaybeSync { diff --git a/cli/src/self_update.rs b/cli/src/self_update.rs index 33201a345e356..2e95719a3b9b3 100644 --- a/cli/src/self_update.rs +++ b/cli/src/self_update.rs @@ -65,8 +65,8 @@ impl<'a> SelfUpdate<'a> { ) -> Result<(), AnyError> { // 1. Download the archive into a temporary directory let tempdir = tempdir().map_err(|e| wrap(e, "Failed to create temp dir"))?; - let archive_path = tempdir.path().join("archive"); let stream = self.update_service.get_download_stream(release).await?; + let archive_path = tempdir.path().join(stream.url_path_basename().unwrap()); http::download_into_file(&archive_path, progress, stream).await?; // 2. Unzip the archive and get the binary diff --git a/cli/src/state.rs b/cli/src/state.rs index 296d1b535d828..3f6ae4f227cce 100644 --- a/cli/src/state.rs +++ b/cli/src/state.rs @@ -15,6 +15,7 @@ use serde::{de::DeserializeOwned, Serialize}; use crate::{ constants::VSCODE_CLI_QUALITY, + download_cache::DownloadCache, util::errors::{wrap, AnyError, NoHomeForLauncherError, WrappedError}, }; @@ -22,6 +23,8 @@ const HOME_DIR_ALTS: [&str; 2] = ["$HOME", "~"]; #[derive(Clone)] pub struct LauncherPaths { + pub server_cache: DownloadCache, + pub cli_cache: DownloadCache, root: PathBuf, } @@ -95,14 +98,10 @@ where } /// Mutates persisted state. - pub fn update_with( - &self, - v: V, - mutator: fn(v: V, state: &mut T) -> R, - ) -> Result { + pub fn update(&self, mutator: impl FnOnce(&mut T) -> R) -> Result { let mut container = self.container.lock().unwrap(); let mut state = container.load_or_get(); - let r = mutator(v, &mut state); + let r = mutator(&mut state); container.save(state).map(|_| r) } } @@ -132,7 +131,15 @@ impl LauncherPaths { } pub fn new_without_replacements(root: PathBuf) -> LauncherPaths { - LauncherPaths { root } + // cleanup folders that existed before the new LRU strategy: + let _ = std::fs::remove_dir_all(root.join("server-insiders")); + let _ = std::fs::remove_dir_all(root.join("server-stable")); + + LauncherPaths { + server_cache: DownloadCache::new(root.join("servers")), + cli_cache: DownloadCache::new(root.join("cli")), + root, + } } /// Root directory for the server launcher diff --git a/cli/src/tunnels/code_server.rs b/cli/src/tunnels/code_server.rs index 677bbfc2546ef..1246e1c944146 100644 --- a/cli/src/tunnels/code_server.rs +++ b/cli/src/tunnels/code_server.rs @@ -2,31 +2,31 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -use super::paths::{InstalledServer, LastUsedServers, ServerPaths}; +use super::paths::{InstalledServer, ServerPaths}; use crate::async_pipe::get_socket_name; use crate::constants::{ APPLICATION_NAME, EDITOR_WEB_URL, QUALITYLESS_PRODUCT_NAME, QUALITYLESS_SERVER_NAME, }; +use crate::download_cache::DownloadCache; use crate::options::{Quality, TelemetryLevel}; use crate::state::LauncherPaths; +use crate::tunnels::paths::{get_server_folder_name, SERVER_FOLDER_NAME}; use crate::update_service::{ unzip_downloaded_release, Platform, Release, TargetKind, UpdateService, }; use crate::util::command::{capture_command, kill_tree}; -use crate::util::errors::{ - wrap, AnyError, ExtensionInstallFailed, MissingEntrypointError, WrappedError, -}; +use crate::util::errors::{wrap, AnyError, CodeError, ExtensionInstallFailed, WrappedError}; use crate::util::http::{self, BoxedHttp}; use crate::util::io::SilentCopyProgress; use crate::util::machine::process_exists; -use crate::{debug, info, log, span, spanf, trace, warning}; +use crate::{debug, info, log, spanf, trace, warning}; use lazy_static::lazy_static; use opentelemetry::KeyValue; use regex::Regex; use serde::Deserialize; use std::fs; use std::fs::File; -use std::io::{ErrorKind, Write}; +use std::io::Write; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::Duration; @@ -42,8 +42,6 @@ lazy_static! { static ref WEB_UI_RE: Regex = Regex::new(r"Web UI available at (.+)").unwrap(); } -const MAX_RETAINED_SERVERS: usize = 5; - #[derive(Clone, Debug, Default)] pub struct CodeServerArgs { pub host: Option, @@ -276,102 +274,6 @@ impl CodeServerOrigin { } } -async fn check_and_create_dir(path: &Path) -> Result<(), WrappedError> { - tokio::fs::create_dir_all(path) - .await - .map_err(|e| wrap(e, "error creating server directory"))?; - Ok(()) -} - -async fn install_server_if_needed( - log: &log::Logger, - paths: &ServerPaths, - release: &Release, - http: BoxedHttp, - existing_archive_path: Option, -) -> Result<(), AnyError> { - if paths.executable.exists() { - info!( - log, - "Found existing installation at {}", - paths.server_dir.display() - ); - return Ok(()); - } - - let tar_file_path = match existing_archive_path { - Some(p) => p, - None => spanf!( - log, - log.span("server.download"), - download_server(&paths.server_dir, release, log, http) - )?, - }; - - span!( - log, - log.span("server.extract"), - install_server(&tar_file_path, paths, log) - )?; - - Ok(()) -} - -async fn download_server( - path: &Path, - release: &Release, - log: &log::Logger, - http: BoxedHttp, -) -> Result { - let response = UpdateService::new(log.clone(), http) - .get_download_stream(release) - .await?; - - let mut save_path = path.to_owned(); - save_path.push("archive"); - - info!( - log, - "Downloading {} server -> {}", - QUALITYLESS_PRODUCT_NAME, - save_path.display() - ); - - http::download_into_file( - &save_path, - log.get_download_logger("server download progress:"), - response, - ) - .await?; - - Ok(save_path) -} - -fn install_server( - compressed_file: &Path, - paths: &ServerPaths, - log: &log::Logger, -) -> Result<(), AnyError> { - info!(log, "Setting up server..."); - - unzip_downloaded_release(compressed_file, &paths.server_dir, SilentCopyProgress())?; - - match fs::remove_file(compressed_file) { - Ok(()) => {} - Err(e) => { - if e.kind() != ErrorKind::NotFound { - return Err(AnyError::from(wrap(e, "error removing downloaded file"))); - } - } - } - - if !paths.executable.exists() { - return Err(AnyError::from(MissingEntrypointError())); - } - - Ok(()) -} - /// Ensures the given list of extensions are installed on the running server. async fn do_extension_install_on_running_server( start_script_path: &Path, @@ -406,7 +308,7 @@ async fn do_extension_install_on_running_server( pub struct ServerBuilder<'a> { logger: &'a log::Logger, server_params: &'a ResolvedServerParams, - last_used: LastUsedServers<'a>, + launcher_paths: &'a LauncherPaths, server_paths: ServerPaths, http: BoxedHttp, } @@ -421,7 +323,7 @@ impl<'a> ServerBuilder<'a> { Self { logger, server_params, - last_used: LastUsedServers::new(launcher_paths), + launcher_paths, server_paths: server_params .as_installed_server() .server_paths(launcher_paths), @@ -477,31 +379,54 @@ impl<'a> ServerBuilder<'a> { } /// Ensures the server is set up in the configured directory. - pub async fn setup(&self, existing_archive_path: Option) -> Result<(), AnyError> { + pub async fn setup(&self) -> Result<(), AnyError> { debug!( self.logger, "Installing and setting up {}...", QUALITYLESS_SERVER_NAME ); - check_and_create_dir(&self.server_paths.server_dir).await?; - install_server_if_needed( - self.logger, - &self.server_paths, - &self.server_params.release, - self.http.clone(), - existing_archive_path, - ) - .await?; - debug!(self.logger, "Server setup complete"); - match self.last_used.add(self.server_params.as_installed_server()) { - Err(e) => warning!(self.logger, "Error adding server to last used: {}", e), - Ok(count) if count > MAX_RETAINED_SERVERS => { - if let Err(e) = self.last_used.trim(self.logger, MAX_RETAINED_SERVERS) { - warning!(self.logger, "Error trimming old servers: {}", e); - } - } - Ok(_) => {} - } + let update_service = UpdateService::new(self.logger.clone(), self.http.clone()); + let name = get_server_folder_name( + self.server_params.release.quality, + &self.server_params.release.commit, + ); + + self.launcher_paths + .server_cache + .create(name, |target_dir| async move { + let tmpdir = + tempfile::tempdir().map_err(|e| wrap(e, "error creating temp download dir"))?; + + let response = update_service + .get_download_stream(&self.server_params.release) + .await?; + let archive_path = tmpdir.path().join(response.url_path_basename().unwrap()); + + info!( + self.logger, + "Downloading {} server -> {}", + QUALITYLESS_PRODUCT_NAME, + archive_path.display() + ); + + http::download_into_file( + &archive_path, + self.logger.get_download_logger("server download progress:"), + response, + ) + .await?; + + unzip_downloaded_release( + &archive_path, + &target_dir.join(SERVER_FOLDER_NAME), + SilentCopyProgress(), + )?; + + Ok(()) + }) + .await?; + + debug!(self.logger, "Server setup complete"); Ok(()) } @@ -836,3 +761,39 @@ pub fn print_listening(log: &log::Logger, tunnel_name: &str) { let message = &format!("\nOpen this link in your browser {}\n", addr); log.result(message); } + +pub async fn download_cli_into_cache( + cache: &DownloadCache, + release: &Release, + update_service: &UpdateService, +) -> Result { + let cache_name = format!( + "{}-{}-{}", + release.quality, release.commit, release.platform + ); + let cli_dir = cache + .create(&cache_name, |target_dir| async move { + let tmpdir = + tempfile::tempdir().map_err(|e| wrap(e, "error creating temp download dir"))?; + let response = update_service.get_download_stream(release).await?; + + let name = response.url_path_basename().unwrap(); + let archive_path = tmpdir.path().join(name); + http::download_into_file(&archive_path, SilentCopyProgress(), response).await?; + unzip_downloaded_release(&archive_path, &target_dir, SilentCopyProgress())?; + Ok(()) + }) + .await?; + + let cli = std::fs::read_dir(cli_dir) + .map_err(|_| CodeError::CorruptDownload("could not read cli folder contents"))? + .next(); + + match cli { + Some(Ok(cli)) => Ok(cli.path()), + _ => { + let _ = cache.delete(&cache_name); + Err(CodeError::CorruptDownload("cli directory is empty").into()) + } + } +} diff --git a/cli/src/tunnels/control_server.rs b/cli/src/tunnels/control_server.rs index bf8ce00380f77..ef1a8d0328449 100644 --- a/cli/src/tunnels/control_server.rs +++ b/cli/src/tunnels/control_server.rs @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ use crate::async_pipe::get_socket_rw_stream; -use crate::constants::CONTROL_PORT; +use crate::constants::{CONTROL_PORT, PRODUCT_NAME_LONG}; use crate::log; use crate::msgpack_rpc::U32PrefixedCodec; use crate::rpc::{MaybeSync, RpcBuilder, RpcDispatcher, Serialization}; @@ -11,7 +11,7 @@ use crate::self_update::SelfUpdate; use crate::state::LauncherPaths; use crate::tunnels::protocol::HttpRequestParams; use crate::tunnels::socket_signal::CloseReason; -use crate::update_service::{Platform, UpdateService}; +use crate::update_service::{Platform, Release, TargetKind, UpdateService}; use crate::util::errors::{ wrap, AnyError, CodeError, InvalidRpcDataError, MismatchedLaunchModeError, NoAttachedServerError, @@ -23,6 +23,8 @@ use crate::util::io::SilentCopyProgress; use crate::util::is_integrated_cli; use crate::util::sync::{new_barrier, Barrier}; +use futures::stream::FuturesUnordered; +use futures::FutureExt; use opentelemetry::trace::SpanKind; use opentelemetry::KeyValue; use std::collections::HashMap; @@ -37,16 +39,17 @@ use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, BufReader, D use tokio::sync::{mpsc, Mutex}; use super::code_server::{ - AnyCodeServer, CodeServerArgs, ServerBuilder, ServerParamsRaw, SocketCodeServer, + download_cli_into_cache, AnyCodeServer, CodeServerArgs, ServerBuilder, ServerParamsRaw, + SocketCodeServer, }; use super::dev_tunnels::ActiveTunnel; use super::paths::prune_stopped_servers; use super::port_forwarder::{PortForwarding, PortForwardingProcessor}; use super::protocol::{ - CallServerHttpParams, CallServerHttpResult, ClientRequestMethod, EmptyObject, ForwardParams, - ForwardResult, GetHostnameResponse, HttpBodyParams, HttpHeadersParams, ServeParams, ServerLog, - ServerMessageParams, SpawnParams, SpawnResult, ToClientRequest, UnforwardParams, UpdateParams, - UpdateResult, VersionParams, + AcquireCliParams, CallServerHttpParams, CallServerHttpResult, ClientRequestMethod, EmptyObject, + ForwardParams, ForwardResult, GetHostnameResponse, HttpBodyParams, HttpHeadersParams, + ServeParams, ServerLog, ServerMessageParams, SpawnParams, SpawnResult, ToClientRequest, + UnforwardParams, UpdateParams, UpdateResult, VersionParams, }; use super::server_bridge::ServerBridge; use super::server_multiplexer::ServerMultiplexer; @@ -284,8 +287,18 @@ async fn process_socket( rpc.register_async("unforward", |p: UnforwardParams, c| async move { handle_unforward(&c.log, &c.port_forwarding, p).await }); - rpc.register_duplex("spawn", |stream, p: SpawnParams, c| async move { - handle_spawn(&c.log, stream, p).await + rpc.register_async("acquire_cli", |p: AcquireCliParams, c| async move { + handle_acquire_cli(&c.launcher_paths, &c.http, &c.log, p).await + }); + rpc.register_duplex("spawn", 3, |mut streams, p: SpawnParams, c| async move { + handle_spawn( + &c.log, + p, + Some(streams.remove(0)), + Some(streams.remove(0)), + Some(streams.remove(0)), + ) + .await }); rpc.register_sync("httpheaders", |p: HttpHeadersParams, c| { if let Some(req) = c.http_requests.lock().unwrap().get(&p.req_id) { @@ -507,7 +520,7 @@ async fn handle_serve( Some(AnyCodeServer::Socket(s)) => s, Some(_) => return Err(AnyError::from(MismatchedLaunchModeError())), None => { - $sb.setup(None).await?; + $sb.setup().await?; $sb.listen_on_default_socket().await? } } @@ -734,82 +747,106 @@ async fn handle_call_server_http( }) } -async fn handle_spawn( +async fn handle_acquire_cli( + paths: &LauncherPaths, + http: &Arc, log: &log::Logger, - mut duplex: DuplexStream, - params: SpawnParams, + params: AcquireCliParams, ) -> Result { + let update_service = UpdateService::new(log.clone(), http.clone()); + + let release = match params.commit_id { + Some(commit) => Release { + name: format!("{} CLI", PRODUCT_NAME_LONG), + commit, + platform: params.platform, + quality: params.quality, + target: TargetKind::Cli, + }, + None => { + update_service + .get_latest_commit(params.platform, TargetKind::Cli, params.quality) + .await? + } + }; + + let cli = download_cli_into_cache(&paths.cli_cache, &release, &update_service).await?; + let file = tokio::fs::File::open(cli) + .await + .map_err(|e| wrap(e, "error opening cli file"))?; + + handle_spawn::<_, DuplexStream>(log, params.spawn, Some(file), None, None).await +} + +async fn handle_spawn( + log: &log::Logger, + params: SpawnParams, + stdin: Option, + stdout: Option, + stderr: Option, +) -> Result +where + Stdin: AsyncRead + Unpin + Send, + StdoutAndErr: AsyncWrite + Unpin + Send, +{ debug!( log, "requested to spawn {} with args {:?}", params.command, params.args ); + macro_rules! pipe_if_some { + ($e: expr) => { + if $e.is_some() { + Stdio::piped() + } else { + Stdio::null() + } + }; + } + let mut p = tokio::process::Command::new(¶ms.command) .args(¶ms.args) .envs(¶ms.env) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) + .stdin(pipe_if_some!(stdin)) + .stdout(pipe_if_some!(stdout)) + .stderr(pipe_if_some!(stderr)) .spawn() .map_err(CodeError::ProcessSpawnFailed)?; - let mut stdout = p.stdout.take().unwrap(); - let mut stderr = p.stderr.take().unwrap(); - let mut stdin = p.stdin.take().unwrap(); - let (tx, mut rx) = mpsc::channel(4); - - macro_rules! copy_stream_to { - ($target:expr) => { - let tx = tx.clone(); - tokio::spawn(async move { - let mut buf = vec![0; 4096]; - loop { - let n = match $target.read(&mut buf).await { - Ok(0) | Err(_) => return, - Ok(n) => n, - }; - if !tx.send(buf[..n].to_vec()).await.is_ok() { - return; - } - } - }); - }; + let futs = FuturesUnordered::new(); + if let (Some(mut a), Some(mut b)) = (p.stdout.take(), stdout) { + futs.push(async move { tokio::io::copy(&mut a, &mut b).await }.boxed()); + } + if let (Some(mut a), Some(mut b)) = (p.stderr.take(), stderr) { + futs.push(async move { tokio::io::copy(&mut a, &mut b).await }.boxed()); + } + if let (Some(mut b), Some(mut a)) = (p.stdin.take(), stdin) { + futs.push(async move { tokio::io::copy(&mut a, &mut b).await }.boxed()); } - copy_stream_to!(stdout); - copy_stream_to!(stderr); - - let mut stdin_buf = vec![0; 4096]; let closed = p.wait(); pin!(closed); - loop { - tokio::select! { - Ok(n) = duplex.read(&mut stdin_buf) => { - let _ = stdin.write_all(&stdin_buf[..n]).await; - }, - Some(m) = rx.recv() => { - let _ = duplex.write_all(&m).await; - }, - r = &mut closed => { - let r = match r { - Ok(e) => SpawnResult { - message: e.to_string(), - exit_code: e.code().unwrap_or(-1), - }, - Err(e) => SpawnResult { - message: e.to_string(), - exit_code: -1, - }, - }; + let r = tokio::select! { + _ = futures::future::join_all(futs) => closed.await, + r = &mut closed => r + }; - debug!( - log, - "spawned command {} exited with code {}", params.command, r.exit_code - ); + let r = match r { + Ok(e) => SpawnResult { + message: e.to_string(), + exit_code: e.code().unwrap_or(-1), + }, + Err(e) => SpawnResult { + message: e.to_string(), + exit_code: -1, + }, + }; - return Ok(r) - }, - } - } + debug!( + log, + "spawned command {} exited with code {}", params.command, r.exit_code + ); + + Ok(r) } diff --git a/cli/src/tunnels/paths.rs b/cli/src/tunnels/paths.rs index cdf6cef6f51da..fa06db5dd7aa9 100644 --- a/cli/src/tunnels/paths.rs +++ b/cli/src/tunnels/paths.rs @@ -11,19 +11,15 @@ use std::{ use serde::{Deserialize, Serialize}; use crate::{ - log, options, - state::{LauncherPaths, PersistedState}, + options::{self, Quality}, + state::LauncherPaths, util::{ errors::{wrap, AnyError, WrappedError}, machine, }, }; -const INSIDERS_INSTALL_FOLDER: &str = "server-insiders"; -const STABLE_INSTALL_FOLDER: &str = "server-stable"; -const EXPLORATION_INSTALL_FOLDER: &str = "server-exploration"; -const PIDFILE_SUFFIX: &str = ".pid"; -const LOGFILE_SUFFIX: &str = ".log"; +pub const SERVER_FOLDER_NAME: &str = "server"; pub struct ServerPaths { // Directory into which the server is downloaded @@ -93,76 +89,27 @@ pub struct InstalledServer { impl InstalledServer { /// Gets path information about where a specific server should be stored. pub fn server_paths(&self, p: &LauncherPaths) -> ServerPaths { - let base_folder = self.get_install_folder(p); - let server_dir = base_folder.join("bin").join(&self.commit); + let server_dir = self.get_install_folder(p); ServerPaths { executable: server_dir + .join(SERVER_FOLDER_NAME) .join("bin") .join(self.quality.server_entrypoint()), + logfile: server_dir.join("log.txt"), + pidfile: server_dir.join("pid.txt"), server_dir, - logfile: base_folder.join(format!(".{}{}", self.commit, LOGFILE_SUFFIX)), - pidfile: base_folder.join(format!(".{}{}", self.commit, PIDFILE_SUFFIX)), } } fn get_install_folder(&self, p: &LauncherPaths) -> PathBuf { - let name = match self.quality { - options::Quality::Insiders => INSIDERS_INSTALL_FOLDER, - options::Quality::Exploration => EXPLORATION_INSTALL_FOLDER, - options::Quality::Stable => STABLE_INSTALL_FOLDER, - }; - - p.root().join(if !self.headless { - format!("{}-web", name) + p.server_cache.path().join(if !self.headless { + format!("{}-web", get_server_folder_name(self.quality, &self.commit)) } else { - name.to_string() + get_server_folder_name(self.quality, &self.commit) }) } } -pub struct LastUsedServers<'a> { - state: PersistedState>, - paths: &'a LauncherPaths, -} - -impl<'a> LastUsedServers<'a> { - pub fn new(paths: &'a LauncherPaths) -> LastUsedServers { - LastUsedServers { - state: PersistedState::new(paths.root().join("last-used-servers.json")), - paths, - } - } - - /// Adds a server as having been used most recently. Returns the number of retained server. - pub fn add(&self, server: InstalledServer) -> Result { - self.state.update_with(server, |server, l| { - if let Some(index) = l.iter().position(|s| s == &server) { - l.remove(index); - } - l.insert(0, server); - l.len() - }) - } - - /// Trims so that at most `max_servers` are saved on disk. - pub fn trim(&self, log: &log::Logger, max_servers: usize) -> Result<(), WrappedError> { - let mut servers = self.state.load(); - while servers.len() > max_servers { - let server = servers.pop().unwrap(); - debug!( - log, - "Removing old server {}/{}", - server.quality.get_machine_name(), - server.commit - ); - let server_paths = server.server_paths(self.paths); - server_paths.delete()?; - } - self.state.save(servers)?; - Ok(()) - } -} - /// Prunes servers not currently running, and returns the deleted servers. pub fn prune_stopped_servers(launcher_paths: &LauncherPaths) -> Result, AnyError> { get_all_servers(launcher_paths) @@ -177,40 +124,31 @@ pub fn prune_stopped_servers(launcher_paths: &LauncherPaths) -> Result Vec { let mut servers: Vec = vec![]; - let mut server = InstalledServer { - commit: "".to_owned(), - headless: false, - quality: options::Quality::Stable, - }; - - add_server_paths_in_folder(lp, &server, &mut servers); - - server.headless = true; - add_server_paths_in_folder(lp, &server, &mut servers); - - server.headless = false; - server.quality = options::Quality::Insiders; - add_server_paths_in_folder(lp, &server, &mut servers); - - server.headless = true; - add_server_paths_in_folder(lp, &server, &mut servers); + if let Ok(children) = read_dir(lp.server_cache.path()) { + for child in children.flatten() { + let fname = child.file_name(); + let fname = fname.to_string_lossy(); + let (quality, commit) = match fname.split_once('-') { + Some(r) => r, + None => continue, + }; - servers -} + let quality = match options::Quality::try_from(quality) { + Ok(q) => q, + Err(_) => continue, + }; -fn add_server_paths_in_folder( - lp: &LauncherPaths, - server: &InstalledServer, - servers: &mut Vec, -) { - let dir = server.get_install_folder(lp).join("bin"); - if let Ok(children) = read_dir(dir) { - for bin in children.flatten() { servers.push(InstalledServer { - quality: server.quality, - headless: server.headless, - commit: bin.file_name().to_string_lossy().into(), + quality, + commit: commit.to_string(), + headless: true, }); } } + + servers +} + +pub fn get_server_folder_name(quality: Quality, commit: &str) -> String { + format!("{}-{}", quality, commit) } diff --git a/cli/src/tunnels/protocol.rs b/cli/src/tunnels/protocol.rs index 89f9c3acb2807..2093b1d1896c6 100644 --- a/cli/src/tunnels/protocol.rs +++ b/cli/src/tunnels/protocol.rs @@ -7,6 +7,7 @@ use std::collections::HashMap; use crate::{ constants::{PROTOCOL_VERSION, VSCODE_CLI_VERSION}, options::Quality, + update_service::Platform, }; use serde::{Deserialize, Serialize}; @@ -166,6 +167,15 @@ pub struct SpawnParams { pub env: HashMap, } +#[derive(Deserialize)] +pub struct AcquireCliParams { + pub platform: Platform, + pub quality: Quality, + pub commit_id: Option, + #[serde(flatten)] + pub spawn: SpawnParams, +} + #[derive(Serialize)] pub struct SpawnResult { pub message: String, diff --git a/cli/src/tunnels/wsl_server.rs b/cli/src/tunnels/wsl_server.rs index 69d7eb943898c..3eafae92f3ad5 100644 --- a/cli/src/tunnels/wsl_server.rs +++ b/cli/src/tunnels/wsl_server.rs @@ -151,7 +151,7 @@ async fn handle_serve( Some(AnyCodeServer::Socket(s)) => s, Some(_) => return Err(MismatchedLaunchModeError().into()), None => { - sb.setup(Some(params.archive_path.into())).await?; + sb.setup().await?; sb.listen_on_default_socket().await? } }; diff --git a/cli/src/update_service.rs b/cli/src/update_service.rs index e56d378180483..b03d8ea596319 100644 --- a/cli/src/update_service.rs +++ b/cli/src/update_service.rs @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -use std::{fmt, path::Path}; +use std::{ffi::OsStr, fmt, path::Path}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use crate::{ constants::VSCODE_CLI_UPDATE_ENDPOINT, @@ -14,6 +14,7 @@ use crate::{ errors::{AnyError, CodeError, UpdatesNotConfigured, WrappedError}, http::{BoxedHttp, SimpleResponse}, io::ReportCopyProgress, + tar, zipper, }, }; @@ -175,14 +176,9 @@ pub fn unzip_downloaded_release( where T: ReportCopyProgress, { - #[cfg(any(target_os = "windows", target_os = "macos"))] - { - use crate::util::zipper; + if compressed_file.extension() == Some(OsStr::new("zip")) { zipper::unzip_file(compressed_file, target_dir, reporter) - } - #[cfg(target_os = "linux")] - { - use crate::util::tar; + } else { tar::decompress_tarball(compressed_file, target_dir, reporter) } } @@ -206,7 +202,7 @@ impl TargetKind { } } -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] pub enum Platform { LinuxAlpineX64, LinuxAlpineARM64, diff --git a/cli/src/util.rs b/cli/src/util.rs index 1478394d69689..f45f74de13c6c 100644 --- a/cli/src/util.rs +++ b/cli/src/util.rs @@ -17,9 +17,5 @@ pub mod sync; pub use is_integrated::*; pub mod app_lock; pub mod file_lock; - -#[cfg(target_os = "linux")] pub mod tar; - -#[cfg(any(target_os = "windows", target_os = "macos"))] pub mod zipper; diff --git a/cli/src/util/errors.rs b/cli/src/util/errors.rs index b5ca10660466b..9ab421d3301e8 100644 --- a/cli/src/util/errors.rs +++ b/cli/src/util/errors.rs @@ -477,9 +477,11 @@ pub enum CodeError { UnsupportedPlatform(String), #[error("This machine not meet {name}'s prerequisites, expected either...: {bullets}")] PrerequisitesFailed { name: &'static str, bullets: String }, - #[error("failed to spawn process: {0:?}")] - ProcessSpawnFailed(std::io::Error) + ProcessSpawnFailed(std::io::Error), + + #[error("download appears corrupted, please retry ({0})")] + CorruptDownload(&'static str), } makeAnyError!( diff --git a/cli/src/util/http.rs b/cli/src/util/http.rs index 953dba678c386..e49120578a77c 100644 --- a/cli/src/util/http.rs +++ b/cli/src/util/http.rs @@ -59,14 +59,23 @@ pub struct SimpleResponse { pub status_code: StatusCode, pub headers: HeaderMap, pub read: Pin>, - pub url: String, + pub url: Option, } impl SimpleResponse { - pub fn generic_error(url: String) -> Self { + pub fn url_path_basename(&self) -> Option { + self.url.as_ref().and_then(|u| { + u.path_segments() + .and_then(|s| s.last().map(|s| s.to_owned())) + }) + } +} + +impl SimpleResponse { + pub fn generic_error(url: &str) -> Self { let (_, rx) = mpsc::unbounded_channel(); SimpleResponse { - url, + url: url::Url::parse(url).ok(), status_code: StatusCode::INTERNAL_SERVER_ERROR, headers: HeaderMap::new(), read: Box::pin(DelegatedReader::new(rx)), @@ -79,7 +88,10 @@ impl SimpleResponse { self.read.read_to_string(&mut body).await.ok(); StatusError { - url: self.url, + url: self + .url + .map(|u| u.to_string()) + .unwrap_or_else(|| "".to_owned()), status_code: self.status_code.as_u16(), body, } @@ -97,7 +109,7 @@ impl SimpleResponse { .map_err(|e| wrap(e, "error reading response"))?; let t = serde_json::from_slice(&buf) - .map_err(|e| wrap(e, format!("error decoding json from {}", self.url)))?; + .map_err(|e| wrap(e, format!("error decoding json from {:?}", self.url)))?; Ok(t) } @@ -161,7 +173,7 @@ impl SimpleHttp for ReqwestSimpleHttp { Ok(SimpleResponse { status_code: res.status(), headers: res.headers().clone(), - url, + url: Some(res.url().clone()), read: Box::pin( res.bytes_stream() .map_err(|e| futures::io::Error::new(futures::io::ErrorKind::Other, e)) @@ -250,7 +262,7 @@ impl SimpleHttp for DelegatedSimpleHttp { .await; if sent.is_err() { - return Ok(SimpleResponse::generic_error(url)); // sender shut down + return Ok(SimpleResponse::generic_error(&url)); // sender shut down } match rx.recv().await { @@ -275,16 +287,16 @@ impl SimpleHttp for DelegatedSimpleHttp { } Ok(SimpleResponse { - url, + url: url::Url::parse(&url).ok(), status_code: StatusCode::from_u16(status_code) .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR), headers: headers_map, read: Box::pin(DelegatedReader::new(rx)), }) } - Some(DelegatedHttpEvent::End) => Ok(SimpleResponse::generic_error(url)), + Some(DelegatedHttpEvent::End) => Ok(SimpleResponse::generic_error(&url)), Some(_) => panic!("expected initresponse as first message from delegated http"), - None => Ok(SimpleResponse::generic_error(url)), // sender shut down + None => Ok(SimpleResponse::generic_error(&url)), // sender shut down } } } From c9071ead5e8bacea2786550306ba8974b4c95027 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Thu, 13 Apr 2023 11:20:13 -0700 Subject: [PATCH 55/59] Remove old way & limit similar commands to 3 (#179893) also fix a typo --- .../browser/commandsQuickAccess.ts | 7 +- .../common/semanticSimilarityService.ts | 119 +----------------- 2 files changed, 12 insertions(+), 114 deletions(-) diff --git a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts index fb6985718d1ea..b4bc3ae1f3e49 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @@ -37,6 +37,8 @@ import { ISemanticSimilarityService } from 'vs/workbench/services/semanticSimila import { timeout } from 'vs/base/common/async'; export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider { + private static SEMANTIC_SIMILARITY_MAX_PICKS = 3; + private static SEMANTIC_SIMILARITY_THRESHOLD = 0.8; // TODO: bring this back once we have a chosen strategy for FastAndSlowPicks where Fast is also Promise based // If extensions are not yet registered, we wait for a little moment to give them @@ -155,14 +157,17 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce label: localize('semanticSimilarity', "similar commands") }] : []; + + let numOfSmartPicks = 0; for (const i of sortedIndices) { const score = scores[i]; - if (score < 0.8) { + if (score < CommandsQuickAccessProvider.SEMANTIC_SIMILARITY_THRESHOLD || numOfSmartPicks === CommandsQuickAccessProvider.SEMANTIC_SIMILARITY_MAX_PICKS) { break; } const pick = allPicks[i]; if (!setOfPicksSoFar.has(pick.commandId)) { additionalPicks.push(pick); + numOfSmartPicks++; } } return additionalPicks; diff --git a/src/vs/workbench/services/semanticSimilarity/common/semanticSimilarityService.ts b/src/vs/workbench/services/semanticSimilarity/common/semanticSimilarityService.ts index eefcbc5e40777..19b97cc072bd3 100644 --- a/src/vs/workbench/services/semanticSimilarity/common/semanticSimilarityService.ts +++ b/src/vs/workbench/services/semanticSimilarity/common/semanticSimilarityService.ts @@ -3,12 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; -import { IFileService } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { URI } from 'vs/base/common/uri'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { CancelablePromise, createCancelablePromise, raceCancellablePromises, raceCancellation, timeout } from 'vs/base/common/async'; +import { CancelablePromise, createCancelablePromise, raceCancellablePromises, timeout } from 'vs/base/common/async'; import { IDisposable } from 'vs/base/common/lifecycle'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -32,19 +29,9 @@ export class SemanticSimilarityService implements ISemanticSimilarityService { static readonly DEFAULT_TIMEOUT = 1000 * 10; // 10 seconds private readonly _providers: ISemanticSimilarityProvider[] = []; - // remove when we move over to API - private readonly oldService: OldSemanticSimilarityService; - - constructor( - // for the old service - @ICommandService commandService: ICommandService, - @IFileService fileService: IFileService - ) { - this.oldService = new OldSemanticSimilarityService(commandService, fileService); - } isEnabled(): boolean { - return this._providers.length > 0 || this.oldService.isEnabled(); + return this._providers.length > 0; } registerSemanticSimilarityProvider(provider: ISemanticSimilarityProvider): IDisposable { @@ -61,18 +48,14 @@ export class SemanticSimilarityService implements ISemanticSimilarityService { async getSimilarityScore(string1: string, comparisons: string[], token: CancellationToken): Promise { if (this._providers.length === 0) { - // Remove when we have a provider shipping in extensions - if (this.oldService.isEnabled()) { - return this.oldService.getSimilarityScore(string1, comparisons, token); - } throw new Error('No semantic similarity providers registered'); } const cancellablePromises: Array> = []; const timer = timeout(SemanticSimilarityService.DEFAULT_TIMEOUT); - const disposible = token.onCancellationRequested(() => { - disposible.dispose(); + const disposable = token.onCancellationRequested(() => { + disposable.dispose(); timer.cancel(); }); @@ -92,9 +75,9 @@ export class SemanticSimilarityService implements ISemanticSimilarityService { } cancellablePromises.push(createCancelablePromise(async (t) => { - const disposible = t.onCancellationRequested(() => { + const disposable = t.onCancellationRequested(() => { timer.cancel(); - disposible.dispose(); + disposable.dispose(); }); await timer; throw new Error('Semantic similarity provider timed out'); @@ -105,94 +88,4 @@ export class SemanticSimilarityService implements ISemanticSimilarityService { } } -// TODO: remove this when the extensions are updated - -interface ICommandsEmbeddingsCache { - [commandId: string]: { embedding: number[] }; -} - -interface INewCommandsEmbeddingsCacheFormat { - core: ICommandsEmbeddingsCache; -} - -class OldSemanticSimilarityService { - declare _serviceBrand: undefined; - - static readonly CALCULATE_EMBEDDING_COMMAND_ID = '_vscode.ai.calculateEmbedding'; - static readonly COMMAND_EMBEDDING_CACHE_COMMAND_ID = '_vscode.ai.commandEmbeddingsCache'; - - private cache: Promise; - - constructor( - @ICommandService private readonly commandService: ICommandService, - @IFileService private readonly fileService: IFileService - ) { - this.cache = this.loadCache(); - } - - private async loadCache(): Promise { - const path = await this.commandService.executeCommand(OldSemanticSimilarityService.COMMAND_EMBEDDING_CACHE_COMMAND_ID); - if (!path) { - return {}; - } - const content = await this.fileService.readFile(URI.parse(path)); - const parsed = JSON.parse(content.value.toString()) as INewCommandsEmbeddingsCacheFormat | ICommandsEmbeddingsCache; - if ('core' in parsed) { - return (parsed as INewCommandsEmbeddingsCacheFormat).core; - } - return parsed; - } - - isEnabled(): boolean { - return !!CommandsRegistry.getCommand(OldSemanticSimilarityService.CALCULATE_EMBEDDING_COMMAND_ID); - } - - async getSimilarityScore(str: string, comparisons: string[], token: CancellationToken): Promise { - const embedding1 = await this.computeEmbedding(str, token); - const scores: number[] = []; - for (const comparison of comparisons) { - if (token.isCancellationRequested) { - scores.push(0); - continue; - } - const embedding2 = await this.getCommandEmbeddingFromCache(comparison, token); - if (embedding2) { - scores.push(this.getEmbeddingSimilarityScore(embedding1, embedding2)); - continue; - } - scores.push(0); - } - return scores; - } - - private async computeEmbedding(text: string, token: CancellationToken): Promise { - if (!this.isEnabled()) { - throw new Error('Embeddings are not enabled'); - } - const result = await raceCancellation(this.commandService.executeCommand(OldSemanticSimilarityService.CALCULATE_EMBEDDING_COMMAND_ID, text), token); - if (!result) { - throw new Error('No result'); - } - return result[0]; - } - - private async getCommandEmbeddingFromCache(commandId: string, token: CancellationToken): Promise { - const cache = await raceCancellation(this.cache, token); - return cache?.[commandId]?.embedding; - } - - /** - * Performs cosine similarity on two vectors to determine their similarity. - * @param embedding1 The first embedding - * @param embedding2 The second embedding - * @returns A number between 0 and 1 for how similar the two embeddings are - */ - private getEmbeddingSimilarityScore(embedding1: number[], embedding2: number[]): number { - const dotProduct = embedding1.reduce((sum, value, index) => sum + value * embedding2[index], 0); - const magnitude1 = Math.sqrt(embedding1.reduce((sum, value) => sum + value * value, 0)); - const magnitude2 = Math.sqrt(embedding2.reduce((sum, value) => sum + value * value, 0)); - return dotProduct / (magnitude1 * magnitude2); - } -} - registerSingleton(ISemanticSimilarityService, SemanticSimilarityService, InstantiationType.Delayed); From b0d7acec38cfab4d82630cba64d581bf1cd1a305 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Thu, 13 Apr 2023 11:26:36 -0700 Subject: [PATCH 56/59] Logger per auth provider (#179896) So that we can have an output channel for each. --- .../microsoft-authentication/src/AADHelper.ts | 80 +++++++++---------- .../microsoft-authentication/src/extension.ts | 13 ++- 2 files changed, 51 insertions(+), 42 deletions(-) diff --git a/extensions/microsoft-authentication/src/AADHelper.ts b/extensions/microsoft-authentication/src/AADHelper.ts index d06c9e3d97b2f..18a7e5d07ac29 100644 --- a/extensions/microsoft-authentication/src/AADHelper.ts +++ b/extensions/microsoft-authentication/src/AADHelper.ts @@ -6,7 +6,6 @@ import * as vscode from 'vscode'; import * as querystring from 'querystring'; import * as path from 'path'; -import Logger from './logger'; import { isSupportedEnvironment } from './utils'; import { generateCodeChallenge, generateCodeVerifier, randomUUID } from './cryptoUtils'; import { BetterTokenStorage, IDidChangeInOtherWindowEvent } from './betterSecretStorage'; @@ -89,6 +88,7 @@ export class AzureActiveDirectoryService { private _codeVerfifiers = new Map(); constructor( + private readonly _logger: vscode.LogOutputChannel, private readonly _context: vscode.ExtensionContext, private readonly _uriHandler: UriEventHandler, private readonly _tokenStorage: BetterTokenStorage, @@ -98,12 +98,12 @@ export class AzureActiveDirectoryService { } public async initialize(): Promise { - Logger.info('Reading sessions from secret storage...'); + this._logger.info('Reading sessions from secret storage...'); const sessions = await this._tokenStorage.getAll(item => this.sessionMatchesEndpoint(item)); - Logger.info(`Got ${sessions.length} stored sessions`); + this._logger.info(`Got ${sessions.length} stored sessions`); const refreshes = sessions.map(async session => { - Logger.trace(`Read the following stored session with scopes: ${session.scope}`); + this._logger.trace(`Read the following stored session with scopes: ${session.scope}`); const scopes = session.scope.split(' '); const scopeData: IScopeData = { scopes, @@ -130,7 +130,7 @@ export class AzureActiveDirectoryService { }); } else { vscode.window.showErrorMessage(vscode.l10n.t('You have been signed out because reading stored authentication information failed.')); - Logger.error(e); + this._logger.error(e); await this.removeSessionByIToken({ accessToken: undefined, refreshToken: session.refreshToken, @@ -148,7 +148,7 @@ export class AzureActiveDirectoryService { const result = await Promise.allSettled(refreshes); for (const res of result) { if (res.status === 'rejected') { - Logger.error(`Failed to initialize stored data: ${res.reason}`); + this._logger.error(`Failed to initialize stored data: ${res.reason}`); this.clearSessions(); } } @@ -162,9 +162,9 @@ export class AzureActiveDirectoryService { async getSessions(scopes?: string[]): Promise { if (!scopes) { - Logger.info('Getting sessions for all scopes...'); + this._logger.info('Getting sessions for all scopes...'); const sessions = this._tokens.map(token => this.convertToSessionSync(token)); - Logger.info(`Got ${sessions.length} sessions for all scopes...`); + this._logger.info(`Got ${sessions.length} sessions for all scopes...`); return sessions; } @@ -184,10 +184,10 @@ export class AzureActiveDirectoryService { modifiedScopes = modifiedScopes.sort(); let modifiedScopesStr = modifiedScopes.join(' '); - Logger.info(`Getting sessions for the following scopes: ${modifiedScopesStr}`); + this._logger.info(`Getting sessions for the following scopes: ${modifiedScopesStr}`); if (this._refreshingPromise) { - Logger.info('Refreshing in progress. Waiting for completion before continuing.'); + this._logger.info('Refreshing in progress. Waiting for completion before continuing.'); try { await this._refreshingPromise; } catch (e) { @@ -202,7 +202,7 @@ export class AzureActiveDirectoryService { // without an idtoken. if (!matchingTokens.length) { const fallbackOrderedScopes = scopes.sort().join(' '); - Logger.trace(`No session found with idtoken scopes... Using fallback scope list of: ${fallbackOrderedScopes}`); + this._logger.trace(`No session found with idtoken scopes... Using fallback scope list of: ${fallbackOrderedScopes}`); matchingTokens = this._tokens.filter(token => token.scope === fallbackOrderedScopes); if (matchingTokens.length) { modifiedScopesStr = fallbackOrderedScopes; @@ -235,12 +235,12 @@ export class AzureActiveDirectoryService { const itoken = await this.refreshToken(token.refreshToken, scopeData); matchingTokens.push(itoken); } catch (err) { - Logger.error(`Attempted to get a new session for scopes '${scopeData.scopeStr}' using the existing session with scopes '${token.scope}' but it failed due to: ${err.message ?? err}`); + this._logger.error(`Attempted to get a new session for scopes '${scopeData.scopeStr}' using the existing session with scopes '${token.scope}' but it failed due to: ${err.message ?? err}`); } } } - Logger.info(`Got ${matchingTokens.length} sessions for scopes: ${modifiedScopesStr}`); + this._logger.info(`Got ${matchingTokens.length} sessions for scopes: ${modifiedScopesStr}`); return Promise.all(matchingTokens.map(token => this.convertToSession(token, scopeData))); } @@ -269,7 +269,7 @@ export class AzureActiveDirectoryService { tenant: this.getTenantId(scopes), }; - Logger.info(`Logging in for the following scopes: ${scopeData.scopeStr}`); + this._logger.info(`Logging in for the following scopes: ${scopeData.scopeStr}`); const runsRemote = vscode.env.remoteName !== undefined; const runsServerless = vscode.env.remoteName === undefined && vscode.env.uiKind === vscode.UIKind.Web; @@ -287,7 +287,7 @@ export class AzureActiveDirectoryService { this._sessionChangeEmitter.fire({ added: [session], removed: [], changed: [] }); return session; } catch (e) { - Logger.error(`Error creating session for scopes: ${scopeData.scopeStr} Error: ${e}`); + this._logger.error(`Error creating session for scopes: ${scopeData.scopeStr} Error: ${e}`); // If the error was about starting the server, try directly hitting the login endpoint instead if (e.message === 'Error listening to server' || e.message === 'Closed' || e.message === 'Timeout waiting for port') { @@ -392,10 +392,10 @@ export class AzureActiveDirectoryService { } public async removeSessionById(sessionId: string, writeToDisk: boolean = true): Promise { - Logger.info(`Logging out of session '${sessionId}'`); + this._logger.info(`Logging out of session '${sessionId}'`); const tokenIndex = this._tokens.findIndex(token => token.sessionId === sessionId); if (tokenIndex === -1) { - Logger.info(`Session not found '${sessionId}'`); + this._logger.info(`Session not found '${sessionId}'`); return Promise.resolve(undefined); } @@ -410,7 +410,7 @@ export class AzureActiveDirectoryService { } public async clearSessions() { - Logger.info('Logging out of all sessions'); + this._logger.info('Logging out of all sessions'); this._tokens = []; await this._tokenStorage.deleteAll(item => this.sessionMatchesEndpoint(item)); @@ -434,9 +434,9 @@ export class AzureActiveDirectoryService { } const session = this.convertToSessionSync(token); - Logger.info(`Sending change event for session that was removed with scopes: ${token.scope}`); + this._logger.info(`Sending change event for session that was removed with scopes: ${token.scope}`); this._sessionChangeEmitter.fire({ added: [], removed: [session], changed: [] }); - Logger.info(`Logged out of session '${token.sessionId}' with scopes: ${token.scope}`); + this._logger.info(`Logged out of session '${token.sessionId}' with scopes: ${token.scope}`); return session; } @@ -449,7 +449,7 @@ export class AzureActiveDirectoryService { this._refreshTimeouts.set(sessionId, setTimeout(async () => { try { const refreshedToken = await this.refreshToken(refreshToken, scopeData, sessionId); - Logger.info('Triggering change session event...'); + this._logger.info('Triggering change session event...'); this._sessionChangeEmitter.fire({ added: [], removed: [], changed: [this.convertToSessionSync(refreshedToken)] }); } catch (e) { if (e.message !== REFRESH_NETWORK_FAILURE) { @@ -479,7 +479,7 @@ export class AzureActiveDirectoryService { if (json.id_token) { claims = JSON.parse(base64Decode(json.id_token.split('.')[1])); } else { - Logger.info('Attempting to parse access_token instead since no id_token was included in the response.'); + this._logger.info('Attempting to parse access_token instead since no id_token was included in the response.'); claims = JSON.parse(base64Decode(json.access_token.split('.')[1])); } } catch (e) { @@ -526,8 +526,8 @@ export class AzureActiveDirectoryService { private async convertToSession(token: IToken, scopeData: IScopeData): Promise { if (token.accessToken && (!token.expiresAt || token.expiresAt > Date.now())) { token.expiresAt - ? Logger.info(`Token available from cache (for scopes ${token.scope}), expires in ${token.expiresAt - Date.now()} milliseconds`) - : Logger.info('Token available from cache (for scopes ${token.scope})'); + ? this._logger.info(`Token available from cache (for scopes ${token.scope}), expires in ${token.expiresAt - Date.now()} milliseconds`) + : this._logger.info('Token available from cache (for scopes ${token.scope})'); return { id: token.sessionId, accessToken: token.accessToken, @@ -538,7 +538,7 @@ export class AzureActiveDirectoryService { } try { - Logger.info(`Token expired or unavailable (for scopes ${token.scope}), trying refresh`); + this._logger.info(`Token expired or unavailable (for scopes ${token.scope}), trying refresh`); const refreshedToken = await this.refreshToken(token.refreshToken, scopeData, token.sessionId); if (refreshedToken.accessToken) { return { @@ -572,7 +572,7 @@ export class AzureActiveDirectoryService { } private async doRefreshToken(refreshToken: string, scopeData: IScopeData, sessionId?: string): Promise { - Logger.info(`Refreshing token for scopes: ${scopeData.scopeStr}`); + this._logger.info(`Refreshing token for scopes: ${scopeData.scopeStr}`); const postData = querystring.stringify({ refresh_token: refreshToken, client_id: scopeData.clientId, @@ -591,7 +591,7 @@ export class AzureActiveDirectoryService { this.setSessionTimeout(token.sessionId, token.refreshToken, scopeData, token.expiresIn * AzureActiveDirectoryService.REFRESH_TIMEOUT_MODIFIER); } this.setToken(token, scopeData); - Logger.info(`Token refresh success for scopes: ${token.scope}`); + this._logger.info(`Token refresh success for scopes: ${token.scope}`); return token; } catch (e) { if (e.message === REFRESH_NETWORK_FAILURE) { @@ -602,7 +602,7 @@ export class AzureActiveDirectoryService { } throw e; } - Logger.error(`Refreshing token failed (for scopes: ${scopeData.scopeStr}): ${e.message}`); + this._logger.error(`Refreshing token failed (for scopes: ${scopeData.scopeStr}): ${e.message}`); throw e; } } @@ -704,7 +704,7 @@ export class AzureActiveDirectoryService { } private async exchangeCodeForSession(code: string, codeVerifier: string, scopeData: IScopeData): Promise { - Logger.info(`Exchanging login code for token for scopes: ${scopeData.scopeStr}`); + this._logger.info(`Exchanging login code for token for scopes: ${scopeData.scopeStr}`); let token: IToken | undefined; try { const postData = querystring.stringify({ @@ -729,10 +729,10 @@ export class AzureActiveDirectoryService { const endpoint = `${endpointUrl}${scopeData.tenant}/oauth2/v2.0/token`; const json = await this.fetchTokenResponse(endpoint, postData, scopeData); - Logger.info(`Exchanging login code for token (for scopes: ${scopeData.scopeStr}) succeeded!`); + this._logger.info(`Exchanging login code for token (for scopes: ${scopeData.scopeStr}) succeeded!`); token = this.convertToTokenSync(json, scopeData); } catch (e) { - Logger.error(`Error exchanging code for token (for scopes ${scopeData.scopeStr}): ${e}`); + this._logger.error(`Error exchanging code for token (for scopes ${scopeData.scopeStr}): ${e}`); throw e; } @@ -740,7 +740,7 @@ export class AzureActiveDirectoryService { this.setSessionTimeout(token.sessionId, token.refreshToken, scopeData, token.expiresIn * AzureActiveDirectoryService.REFRESH_TIMEOUT_MODIFIER); } this.setToken(token, scopeData); - Logger.info(`Login successful for scopes: ${scopeData.scopeStr}`); + this._logger.info(`Login successful for scopes: ${scopeData.scopeStr}`); return await this.convertToSession(token, scopeData); } @@ -765,7 +765,7 @@ export class AzureActiveDirectoryService { if (!result || result.status > 499) { if (attempts > 3) { - Logger.error(`Fetching token failed for scopes (${scopeData.scopeStr}): ${result ? await result.text() : errorMessage}`); + this._logger.error(`Fetching token failed for scopes (${scopeData.scopeStr}): ${result ? await result.text() : errorMessage}`); break; } // Exponential backoff @@ -789,7 +789,7 @@ export class AzureActiveDirectoryService { //#region storage operations private setToken(token: IToken, scopeData: IScopeData): void { - Logger.info(`Setting token for scopes: ${scopeData.scopeStr}`); + this._logger.info(`Setting token for scopes: ${scopeData.scopeStr}`); const existingTokenIndex = this._tokens.findIndex(t => t.sessionId === token.sessionId); if (existingTokenIndex > -1) { @@ -831,7 +831,7 @@ export class AzureActiveDirectoryService { }); if (!shouldStore) { - Logger.info(`Not storing token for scopes ${scopeData.scopeStr} because it was added in another window`); + this._logger.info(`Not storing token for scopes ${scopeData.scopeStr} because it was added in another window`); return; } } @@ -843,14 +843,14 @@ export class AzureActiveDirectoryService { account: token.account, endpoint: this._loginEndpointUrl, }); - Logger.info(`Stored token for scopes: ${scopeData.scopeStr}`); + this._logger.info(`Stored token for scopes: ${scopeData.scopeStr}`); } private async checkForUpdates(e: IDidChangeInOtherWindowEvent): Promise { for (const key of e.added) { const session = await this._tokenStorage.get(key); if (!session) { - Logger.error('session not found that was apparently just added'); + this._logger.error('session not found that was apparently just added'); return; } @@ -871,9 +871,9 @@ export class AzureActiveDirectoryService { clientId: this.getClientId(scopes), tenant: this.getTenantId(scopes), }; - Logger.info(`Session added in another window with scopes: ${session.scope}`); + this._logger.info(`Session added in another window with scopes: ${session.scope}`); const token = await this.refreshToken(session.refreshToken, scopeData, session.id); - Logger.info(`Sending change event for session that was added with scopes: ${scopeData.scopeStr}`); + this._logger.info(`Sending change event for session that was added with scopes: ${scopeData.scopeStr}`); this._sessionChangeEmitter.fire({ added: [this.convertToSessionSync(token)], removed: [], changed: [] }); return; } catch (e) { @@ -893,7 +893,7 @@ export class AzureActiveDirectoryService { continue; } - Logger.info(`Session removed in another window with scopes: ${value.scope}`); + this._logger.info(`Session removed in another window with scopes: ${value.scope}`); await this.removeSessionById(value.id, false); } diff --git a/extensions/microsoft-authentication/src/extension.ts b/extensions/microsoft-authentication/src/extension.ts index 9858785b5cfb1..96265e9ffd800 100644 --- a/extensions/microsoft-authentication/src/extension.ts +++ b/extensions/microsoft-authentication/src/extension.ts @@ -36,7 +36,12 @@ async function initAzureCloudAuthProvider(context: vscode.ExtensionContext, tele settingValue += '/'; } - const azureEnterpriseAuthProvider = new AzureActiveDirectoryService(context, uriHandler, tokenStorage, settingValue); + const azureEnterpriseAuthProvider = new AzureActiveDirectoryService( + vscode.window.createOutputChannel(vscode.l10n.t('Microsoft Sovereign Cloud Authentication'), { log: true }), + context, + uriHandler, + tokenStorage, + settingValue); await azureEnterpriseAuthProvider.initialize(); authProviderName ||= uri.authority; @@ -98,7 +103,11 @@ export async function activate(context: vscode.ExtensionContext) { const betterSecretStorage = new BetterTokenStorage('microsoft.login.keylist', context); - const loginService = new AzureActiveDirectoryService(context, uriHandler, betterSecretStorage); + const loginService = new AzureActiveDirectoryService( + vscode.window.createOutputChannel(vscode.l10n.t('Microsoft Authentication'), { log: true }), + context, + uriHandler, + betterSecretStorage); await loginService.initialize(); context.subscriptions.push(vscode.authentication.registerAuthenticationProvider('microsoft', 'Microsoft', { From a4972bca0381bb2fa24eb8f78b87121c248fd0c1 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 13 Apr 2023 11:48:04 -0700 Subject: [PATCH 57/59] Try to fix check of active service worker (#179824) Fixes #179295 This should fix the `Cannot read properties of undefined (reading 'active')` error a few webviews were seeing when trying to load resources by using the current controller directly instead of trying to get it through the registration --- .../webview/browser/pre/index-no-csp.html | 22 +++++++++------- .../contrib/webview/browser/pre/index.html | 26 +++++++++++-------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html b/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html index 718114ac3d6a8..d8b7f4ebd3129 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html @@ -261,13 +261,21 @@ // service worker already loaded & ready to receive messages postVersionMessage(currentController); } else { - console.log(`Found unexpected service worker controller. Found: ${currentController?.scriptURL}. Expected: ${swPath}`); + if (currentController) { + console.log(`Found unexpected service worker controller. Found: ${currentController.scriptURL}. Expected: ${swPath}. Waiting for controllerchange.`); + } else { + console.log(`No service worker controller found. Waiting for controllerchange.`); + } - // either there's no controlling service worker, or it's an old one: - // wait for it to change before posting the message + // Either there's no controlling service worker, or it's an old one. + // Wait for it to change before posting the message const onControllerChange = () => { navigator.serviceWorker.removeEventListener('controllerchange', onControllerChange); + if (navigator.serviceWorker.controller) { postVersionMessage(navigator.serviceWorker.controller); + } else { + return reject(new Error('No controller found.')); + } }; navigator.serviceWorker.addEventListener('controllerchange', onControllerChange); } @@ -441,16 +449,12 @@ if (!disableServiceWorker) { hostMessaging.onMessage('did-load-resource', (_event, data) => { - navigator.serviceWorker.getRegistration().then(registration => { - assertIsDefined(registration.active).postMessage({ channel: 'did-load-resource', data }, data.data?.buffer ? [data.data.buffer] : []); + assertIsDefined(navigator.serviceWorker.controller).postMessage({ channel: 'did-load-resource', data }, data.data?.buffer ? [data.data.buffer] : []); }); - }); hostMessaging.onMessage('did-load-localhost', (_event, data) => { - navigator.serviceWorker.getRegistration().then(registration => { - assertIsDefined(registration.active).postMessage({ channel: 'did-load-localhost', data }); + assertIsDefined(navigator.serviceWorker.controller).postMessage({ channel: 'did-load-localhost', data }); }); - }); navigator.serviceWorker.addEventListener('message', event => { switch (event.data.channel) { diff --git a/src/vs/workbench/contrib/webview/browser/pre/index.html b/src/vs/workbench/contrib/webview/browser/pre/index.html index cf4c1c1b66899..cbf61f2131355 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index.html @@ -5,7 +5,7 @@ + content="default-src 'none'; script-src 'sha256-7T0Xm7l4AYKDwm8oYNQ65wcQX7K/ndvxH/2ZgHWUE3w=' 'self'; frame-src 'self'; style-src 'unsafe-inline';"> { navigator.serviceWorker.removeEventListener('controllerchange', onControllerChange); - postVersionMessage(navigator.serviceWorker.controller); + if (navigator.serviceWorker.controller) { + postVersionMessage(navigator.serviceWorker.controller); + } else { + return reject(new Error('No controller found.')); + } }; navigator.serviceWorker.addEventListener('controllerchange', onControllerChange); } @@ -442,15 +450,11 @@ if (!disableServiceWorker) { hostMessaging.onMessage('did-load-resource', (_event, data) => { - navigator.serviceWorker.getRegistration().then(registration => { - assertIsDefined(registration.active).postMessage({ channel: 'did-load-resource', data }, data.data?.buffer ? [data.data.buffer] : []); - }); + assertIsDefined(navigator.serviceWorker.controller).postMessage({ channel: 'did-load-resource', data }, data.data?.buffer ? [data.data.buffer] : []); }); hostMessaging.onMessage('did-load-localhost', (_event, data) => { - navigator.serviceWorker.getRegistration().then(registration => { - assertIsDefined(registration.active).postMessage({ channel: 'did-load-localhost', data }); - }); + assertIsDefined(navigator.serviceWorker.controller).postMessage({ channel: 'did-load-localhost', data }); }); navigator.serviceWorker.addEventListener('message', event => { From 2d8896aaa71eb3d9a7265650e593a9d4aa99aec5 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 13 Apr 2023 20:48:23 +0200 Subject: [PATCH 58/59] theme update notification: missing the theme name (#179897) --- .../themes/browser/themes.contribution.ts | 69 ++++++++++--------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index 4fb3ece7a17ce..d2e559701458e 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -731,41 +731,44 @@ class DefaultThemeUpdatedNotificationContribution implements IWorkbenchContribut private async _showYouGotMigratedNotification(): Promise { this._storageService.store(DefaultThemeUpdatedNotificationContribution.STORAGE_KEY, true, StorageScope.APPLICATION, StorageTarget.USER); - const choices = [ - { - label: localize('button.keep', "Keep New Theme"), - run: () => { - this._writeTelemetry('keepNew'); - } - }, - { - label: localize('button.browse', "Browse Themes"), - run: () => { - this._writeTelemetry('browse'); - this._commandService.executeCommand(SelectColorThemeCommandId); - } - }, - { - label: localize('button.revert', "Revert"), - run: async () => { - this._writeTelemetry('keepOld'); - const oldSettingsId = isWeb ? ThemeSettingDefaults.COLOR_THEME_LIGHT_OLD : ThemeSettingDefaults.COLOR_THEME_DARK_OLD; - const oldTheme = (await this._workbenchThemeService.getColorThemes()).find(theme => theme.settingsId === oldSettingsId); - if (oldTheme) { - this._workbenchThemeService.setColorTheme(oldTheme, 'auto'); + const newThemeSettingsId = isWeb ? ThemeSettingDefaults.COLOR_THEME_LIGHT : ThemeSettingDefaults.COLOR_THEME_DARK; + const newTheme = (await this._workbenchThemeService.getColorThemes()).find(theme => theme.settingsId === newThemeSettingsId); + if (newTheme) { + const choices = [ + { + label: localize('button.keep', "Keep New Theme"), + run: () => { + this._writeTelemetry('keepNew'); + } + }, + { + label: localize('button.browse', "Browse Themes"), + run: () => { + this._writeTelemetry('browse'); + this._commandService.executeCommand(SelectColorThemeCommandId); + } + }, + { + label: localize('button.revert', "Revert"), + run: async () => { + this._writeTelemetry('keepOld'); + const oldSettingsId = isWeb ? ThemeSettingDefaults.COLOR_THEME_LIGHT_OLD : ThemeSettingDefaults.COLOR_THEME_DARK_OLD; + const oldTheme = (await this._workbenchThemeService.getColorThemes()).find(theme => theme.settingsId === oldSettingsId); + if (oldTheme) { + this._workbenchThemeService.setColorTheme(oldTheme, 'auto'); + } } } - } - ]; - await this._notificationService.prompt( - Severity.Info, - localize({ key: 'themeUpdatedNotification', comment: ['{0} is the name of the new default theme'] }, "Visual Studio Code now ships with a new default theme '{0}'. If you prefer, you can switch back to the old theme or try one of the many other color themes available.", this._workbenchThemeService.getColorTheme().label), - choices, - { - onCancel: () => this._writeTelemetry('cancel') - } - ); - + ]; + await this._notificationService.prompt( + Severity.Info, + localize({ key: 'themeUpdatedNotification', comment: ['{0} is the name of the new default theme'] }, "Visual Studio Code now ships with a new default theme '{0}'. If you prefer, you can switch back to the old theme or try one of the many other color themes available.", newTheme.label), + choices, + { + onCancel: () => this._writeTelemetry('cancel') + } + ); + } } private async _tryNewThemeNotification(): Promise { From 36f28bb970bf8c59f517292b62538fa7b3fa482d Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 13 Apr 2023 11:57:55 -0700 Subject: [PATCH 59/59] :notebook: Escape does not close replace widget (#179900) --- .../browser/contrib/find/notebookFindReplaceWidget.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts index c05b2859d6e75..ec4873f0e67f7 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts @@ -173,14 +173,6 @@ export class NotebookFindInputFilterButton extends Disposable { this._filterButtonContainer = dom.$('.find-filter-button'); this._filterButtonContainer.classList.add('monaco-custom-toggle'); this.createFilters(this._filterButtonContainer); - - this._register(this.filters.onDidChange(() => { - if (this.filters.codeInput !== true || this.filters.codeOutput !== true || this.filters.markupInput !== true || this.filters.markupPreview !== false) { - this._filtersAction.checked = true; - } else { - this._filtersAction.checked = false; - } - })); } get container() { @@ -462,7 +454,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { } }); - this._focusTracker = this._register(dom.trackFocus(this._innerFindDomNode)); + this._focusTracker = this._register(dom.trackFocus(this._domNode)); this._register(this._focusTracker.onDidFocus(this.onFocusTrackerFocus.bind(this))); this._register(this._focusTracker.onDidBlur(this.onFocusTrackerBlur.bind(this)));