diff --git a/Source/typings/editContext.d.ts b/Source/typings/editContext.d.ts index c8786cedcb083..1014bddcd390f 100644 --- a/Source/typings/editContext.d.ts +++ b/Source/typings/editContext.d.ts @@ -87,8 +87,8 @@ interface EditContextEventHandlersEventMap { type EventHandler = (event: TEvent) => void; -declare class TextUpdateEvent extends Event { - constructor(type: DOMString, options?: TextUpdateEventInit); +interface TextUpdateEvent extends Event { + new(type: DOMString, options?: TextUpdateEventInit): TextUpdateEvent; readonly updateRangeStart: number; readonly updateRangeEnd: number; diff --git a/Source/vs/base/common/product.ts b/Source/vs/base/common/product.ts index 8f4294730f7f1..506134370110d 100644 --- a/Source/vs/base/common/product.ts +++ b/Source/vs/base/common/product.ts @@ -116,7 +116,7 @@ export interface IProductConfiguration { readonly nlsBaseUrl: string; }; - readonly extensionPublisherMappings?: IStringDictionary; + readonly extensionPublisherOrgs?: readonly string[]; readonly extensionRecommendations?: IStringDictionary; readonly configBasedExtensionTips?: IStringDictionary; @@ -352,13 +352,16 @@ export interface IDefaultChatAgent { readonly chatWelcomeTitle: string; readonly documentationUrl: string; readonly privacyStatementUrl: string; - readonly collectionDocumentationUrl: string; readonly skusDocumentationUrl: string; + readonly publicCodeMatchesUrl: string; readonly providerId: string; readonly providerName: string; readonly providerScopes: string[][]; readonly entitlementUrl: string; readonly entitlementChatEnabled: string; - readonly entitlementSkuLimitedUrl: string; - readonly entitlementSkuLimitedEnabled: string; + readonly entitlementSignupLimitedUrl: string; + readonly entitlementCanSignupLimited: string; + readonly entitlementSkuType: string; + readonly entitlementSkuTypeLimited: string; + readonly entitlementSkuTypeLimitedName: string; } diff --git a/Source/vs/editor/browser/controller/editContext/native/nativeEditContext.ts b/Source/vs/editor/browser/controller/editContext/native/nativeEditContext.ts index 296cc89ea6dfd..cab5f079c99b0 100644 --- a/Source/vs/editor/browser/controller/editContext/native/nativeEditContext.ts +++ b/Source/vs/editor/browser/controller/editContext/native/nativeEditContext.ts @@ -3,69 +3,43 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import "./nativeEditContext.css"; - -import { isFirefox } from "../../../../../base/browser/browser.js"; -import { - addDisposableListener, - getActiveWindow, - getWindow, - getWindowId, -} from "../../../../../base/browser/dom.js"; -import { FastDomNode } from "../../../../../base/browser/fastDomNode.js"; -import { StandardKeyboardEvent } from "../../../../../base/browser/keyboardEvent.js"; -import { KeyCode } from "../../../../../base/common/keyCodes.js"; -import { - IDisposable, - MutableDisposable, -} from "../../../../../base/common/lifecycle.js"; -import { IAccessibilityService } from "../../../../../platform/accessibility/common/accessibility.js"; -import { IInstantiationService } from "../../../../../platform/instantiation/common/instantiation.js"; -import { EditorOption } from "../../../../common/config/editorOptions.js"; -import { Position } from "../../../../common/core/position.js"; -import { PositionOffsetTransformer } from "../../../../common/core/positionToOffset.js"; -import { Range } from "../../../../common/core/range.js"; -import { Selection } from "../../../../common/core/selection.js"; -import { - EndOfLinePreference, - EndOfLineSequence, - IModelDeltaDecoration, -} from "../../../../common/model.js"; -import { - ViewConfigurationChangedEvent, - ViewCursorStateChangedEvent, -} from "../../../../common/viewEvents.js"; -import { ViewContext } from "../../../../common/viewModel/viewContext.js"; -import { - RenderingContext, - RestrictedRenderingContext, -} from "../../../view/renderingContext.js"; -import { ViewController } from "../../../view/viewController.js"; -import { - ClipboardEventUtils, - ClipboardStoredMetadata, - getDataToCopy, - InMemoryClipboardMetadataManager, -} from "../clipboardUtils.js"; -import { AbstractEditContext } from "../editContext.js"; -import { IVisibleRangeProvider } from "../textArea/textAreaEditContext.js"; -import { EditContext } from "./editContextFactory.js"; -import { NativeEditContextRegistry } from "./nativeEditContextRegistry.js"; -import { - editContextAddDisposableListener, - FocusTracker, - ITypeData, -} from "./nativeEditContextUtils.js"; -import { ScreenReaderSupport } from "./screenReaderSupport.js"; +import './nativeEditContext.css'; +import { isFirefox } from '../../../../../base/browser/browser.js'; +import { addDisposableListener, getActiveWindow, getWindow, getWindowId } from '../../../../../base/browser/dom.js'; +import { FastDomNode } from '../../../../../base/browser/fastDomNode.js'; +import { StandardKeyboardEvent } from '../../../../../base/browser/keyboardEvent.js'; +import { KeyCode } from '../../../../../base/common/keyCodes.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { EditorOption } from '../../../../common/config/editorOptions.js'; +import { EndOfLinePreference, EndOfLineSequence, IModelDeltaDecoration } from '../../../../common/model.js'; +import { ViewConfigurationChangedEvent, ViewCursorStateChangedEvent } from '../../../../common/viewEvents.js'; +import { ViewContext } from '../../../../common/viewModel/viewContext.js'; +import { RestrictedRenderingContext, RenderingContext } from '../../../view/renderingContext.js'; +import { ViewController } from '../../../view/viewController.js'; +import { ClipboardEventUtils, ClipboardStoredMetadata, getDataToCopy, InMemoryClipboardMetadataManager } from '../clipboardUtils.js'; +import { AbstractEditContext } from '../editContext.js'; +import { editContextAddDisposableListener, FocusTracker, ITypeData } from './nativeEditContextUtils.js'; +import { ScreenReaderSupport } from './screenReaderSupport.js'; +import { Range } from '../../../../common/core/range.js'; +import { Selection } from '../../../../common/core/selection.js'; +import { Position } from '../../../../common/core/position.js'; +import { IVisibleRangeProvider } from '../textArea/textAreaEditContext.js'; +import { PositionOffsetTransformer } from '../../../../common/core/positionToOffset.js'; +import { IDisposable, MutableDisposable } from '../../../../../base/common/lifecycle.js'; +import { EditContext } from './editContextFactory.js'; +import { IAccessibilityService } from '../../../../../platform/accessibility/common/accessibility.js'; +import { NativeEditContextRegistry } from './nativeEditContextRegistry.js'; +import { IEditorAriaOptions } from '../../../editorBrowser.js'; // Corresponds to classes in nativeEditContext.css enum CompositionClassName { - NONE = "edit-context-composition-none", - SECONDARY = "edit-context-composition-secondary", - PRIMARY = "edit-context-composition-primary", + NONE = 'edit-context-composition-none', + SECONDARY = 'edit-context-composition-secondary', + PRIMARY = 'edit-context-composition-primary', } export class NativeEditContext extends AbstractEditContext { + // Text area used to handle paste events public readonly textArea: FastDomNode; public readonly domNode: FastDomNode; @@ -92,22 +66,16 @@ export class NativeEditContext extends AbstractEditContext { viewController: ViewController, private readonly _visibleRangeProvider: IVisibleRangeProvider, @IInstantiationService instantiationService: IInstantiationService, - @IAccessibilityService - private readonly _accessibilityService: IAccessibilityService, + @IAccessibilityService private readonly _accessibilityService: IAccessibilityService ) { super(context); - this.domNode = new FastDomNode(document.createElement("div")); + this.domNode = new FastDomNode(document.createElement('div')); this.domNode.setClassName(`native-edit-context`); - this.textArea = new FastDomNode(document.createElement("textarea")); - this.textArea.setClassName("native-edit-context-textarea"); - - this._register( - NativeEditContextRegistry.registerTextArea( - ownerID, - this.textArea.domNode, - ), - ); + this.textArea = new FastDomNode(document.createElement('textarea')); + this.textArea.setClassName('native-edit-context-textarea'); + + this._register(NativeEditContextRegistry.registerTextArea(ownerID, this.textArea.domNode)); this._updateDomAttributes(); @@ -116,170 +84,82 @@ export class NativeEditContext extends AbstractEditContext { this._parent = overflowGuardContainer.domNode; this._selectionChangeListener = this._register(new MutableDisposable()); - this._focusTracker = this._register( - new FocusTracker(this.domNode.domNode, (newFocusValue: boolean) => { - this._selectionChangeListener.value = newFocusValue - ? this._setSelectionChangeListener(viewController) - : undefined; - this._context.viewModel.setHasFocus(newFocusValue); - }), - ); + this._focusTracker = this._register(new FocusTracker(this.domNode.domNode, (newFocusValue: boolean) => { + this._selectionChangeListener.value = newFocusValue ? this._setSelectionChangeListener(viewController) : undefined; + this._context.viewModel.setHasFocus(newFocusValue); + })); const window = getWindow(this.domNode.domNode); this._editContext = EditContext.create(window); this.setEditContextOnDomNode(); - this._screenReaderSupport = instantiationService.createInstance( - ScreenReaderSupport, - this.domNode, - context, - ); + this._screenReaderSupport = instantiationService.createInstance(ScreenReaderSupport, this.domNode, context); - this._register( - addDisposableListener(this.domNode.domNode, "copy", (e) => - this._ensureClipboardGetsEditorSelection(e), - ), - ); - this._register( - addDisposableListener(this.domNode.domNode, "cut", (e) => { - this._ensureClipboardGetsEditorSelection(e); - viewController.cut(); - }), - ); + this._register(addDisposableListener(this.domNode.domNode, 'copy', (e) => this._ensureClipboardGetsEditorSelection(e))); + this._register(addDisposableListener(this.domNode.domNode, 'cut', (e) => { + this._ensureClipboardGetsEditorSelection(e); + viewController.cut(); + })); - this._register( - addDisposableListener(this.domNode.domNode, "keyup", (e) => - viewController.emitKeyUp(new StandardKeyboardEvent(e)), - ), - ); - this._register( - addDisposableListener( - this.domNode.domNode, - "keydown", - async (e) => { - const standardKeyboardEvent = new StandardKeyboardEvent(e); - - // When the IME is visible, the keys, like arrow-left and arrow-right, should be used to navigate in the IME, and should not be propagated further - if ( - standardKeyboardEvent.keyCode === - KeyCode.KEY_IN_COMPOSITION - ) { - standardKeyboardEvent.stopPropagation(); - } - viewController.emitKeyDown(standardKeyboardEvent); - }, - ), - ); - this._register( - addDisposableListener( - this.domNode.domNode, - "beforeinput", - async (e) => { - if ( - e.inputType === "insertParagraph" || - e.inputType === "insertLineBreak" - ) { - this._onType(viewController, { - text: "\n", - replacePrevCharCnt: 0, - replaceNextCharCnt: 0, - positionDelta: 0, - }); - } - }, - ), - ); + this._register(addDisposableListener(this.domNode.domNode, 'keyup', (e) => viewController.emitKeyUp(new StandardKeyboardEvent(e)))); + this._register(addDisposableListener(this.domNode.domNode, 'keydown', async (e) => { + + const standardKeyboardEvent = new StandardKeyboardEvent(e); + + // When the IME is visible, the keys, like arrow-left and arrow-right, should be used to navigate in the IME, and should not be propagated further + if (standardKeyboardEvent.keyCode === KeyCode.KEY_IN_COMPOSITION) { + standardKeyboardEvent.stopPropagation(); + } + viewController.emitKeyDown(standardKeyboardEvent); + })); + this._register(addDisposableListener(this.domNode.domNode, 'beforeinput', async (e) => { + if (e.inputType === 'insertParagraph' || e.inputType === 'insertLineBreak') { + this._onType(viewController, { text: '\n', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }); + } + })); // Edit context events - this._register( - editContextAddDisposableListener( - this._editContext, - "textformatupdate", - (e) => this._handleTextFormatUpdate(e), - ), - ); - this._register( - editContextAddDisposableListener( - this._editContext, - "characterboundsupdate", - (e) => this._updateCharacterBounds(e), - ), - ); - this._register( - editContextAddDisposableListener( - this._editContext, - "textupdate", - (e) => { - this._emitTypeEvent(viewController, e); - }, - ), - ); - this._register( - editContextAddDisposableListener( - this._editContext, - "compositionstart", - (e) => { - // Utlimately fires onDidCompositionStart() on the editor to notify for example suggest model of composition state - // Updates the composition state of the cursor controller which determines behavior of typing with interceptors - viewController.compositionStart(); - // Emits ViewCompositionStartEvent which can be depended on by ViewEventHandlers - this._context.viewModel.onCompositionStart(); - }, - ), - ); - this._register( - editContextAddDisposableListener( - this._editContext, - "compositionend", - (e) => { - // Utlimately fires compositionEnd() on the editor to notify for example suggest model of composition state - // Updates the composition state of the cursor controller which determines behavior of typing with interceptors - viewController.compositionEnd(); - // Emits ViewCompositionEndEvent which can be depended on by ViewEventHandlers - this._context.viewModel.onCompositionEnd(); - }, - ), - ); - this._register( - addDisposableListener(this.textArea.domNode, "paste", (e) => { - e.preventDefault(); - if (!e.clipboardData) { - return; - } - let [text, metadata] = ClipboardEventUtils.getTextData( - e.clipboardData, - ); - if (!text) { - return; - } - metadata = - metadata || - InMemoryClipboardMetadataManager.INSTANCE.get(text); - let pasteOnNewLine = false; - let multicursorText: string[] | null = null; - let mode: string | null = null; - if (metadata) { - const options = this._context.configuration.options; - const emptySelectionClipboard = options.get( - EditorOption.emptySelectionClipboard, - ); - pasteOnNewLine = - emptySelectionClipboard && - !!metadata.isFromEmptySelection; - multicursorText = - typeof metadata.multicursorText !== "undefined" - ? metadata.multicursorText - : null; - mode = metadata.mode; - } - viewController.paste( - text, - pasteOnNewLine, - multicursorText, - mode, - ); - }), - ); + this._register(editContextAddDisposableListener(this._editContext, 'textformatupdate', (e) => this._handleTextFormatUpdate(e))); + this._register(editContextAddDisposableListener(this._editContext, 'characterboundsupdate', (e) => this._updateCharacterBounds(e))); + this._register(editContextAddDisposableListener(this._editContext, 'textupdate', (e) => { + this._emitTypeEvent(viewController, e); + })); + this._register(editContextAddDisposableListener(this._editContext, 'compositionstart', (e) => { + // Utlimately fires onDidCompositionStart() on the editor to notify for example suggest model of composition state + // Updates the composition state of the cursor controller which determines behavior of typing with interceptors + viewController.compositionStart(); + // Emits ViewCompositionStartEvent which can be depended on by ViewEventHandlers + this._context.viewModel.onCompositionStart(); + })); + this._register(editContextAddDisposableListener(this._editContext, 'compositionend', (e) => { + // Utlimately fires compositionEnd() on the editor to notify for example suggest model of composition state + // Updates the composition state of the cursor controller which determines behavior of typing with interceptors + viewController.compositionEnd(); + // Emits ViewCompositionEndEvent which can be depended on by ViewEventHandlers + this._context.viewModel.onCompositionEnd(); + })); + this._register(addDisposableListener(this.textArea.domNode, 'paste', (e) => { + e.preventDefault(); + if (!e.clipboardData) { + return; + } + let [text, metadata] = ClipboardEventUtils.getTextData(e.clipboardData); + if (!text) { + return; + } + metadata = metadata || InMemoryClipboardMetadataManager.INSTANCE.get(text); + let pasteOnNewLine = false; + let multicursorText: string[] | null = null; + let mode: string | null = null; + if (metadata) { + const options = this._context.configuration.options; + const emptySelectionClipboard = options.get(EditorOption.emptySelectionClipboard); + pasteOnNewLine = emptySelectionClipboard && !!metadata.isFromEmptySelection; + multicursorText = typeof metadata.multicursorText !== 'undefined' ? metadata.multicursorText : null; + mode = metadata.mode; + } + viewController.paste(text, pasteOnNewLine, multicursorText, mode); + })); } // --- Public methods --- @@ -292,8 +172,8 @@ export class NativeEditContext extends AbstractEditContext { super.dispose(); } - public setAriaOptions(): void { - this._screenReaderSupport.setAriaOptions(); + public setAriaOptions(options: IEditorAriaOptions): void { + this._screenReaderSupport.setAriaOptions(options); } /* Last rendered data needed for correct hit-testing and determining the mouse position. @@ -312,18 +192,13 @@ export class NativeEditContext extends AbstractEditContext { this._screenReaderSupport.render(ctx); } - public override onCursorStateChanged( - e: ViewCursorStateChangedEvent, - ): boolean { - this._primarySelection = - e.modelSelections[0] ?? new Selection(1, 1, 1, 1); + public override onCursorStateChanged(e: ViewCursorStateChangedEvent): boolean { + this._primarySelection = e.modelSelections[0] ?? new Selection(1, 1, 1, 1); this._screenReaderSupport.onCursorStateChanged(e); return true; } - public override onConfigurationChanged( - e: ViewConfigurationChangedEvent, - ): boolean { + public override onConfigurationChanged(e: ViewConfigurationChangedEvent): boolean { this._screenReaderSupport.onConfigurationChanged(e); this._updateDomAttributes(); return true; @@ -334,10 +209,7 @@ export class NativeEditContext extends AbstractEditContext { } public isFocused(): boolean { - return ( - this._focusTracker.isFocused || - getActiveWindow().document.activeElement === this.textArea.domNode - ); + return this._focusTracker.isFocused || (getActiveWindow().document.activeElement === this.textArea.domNode); } public focus(): void { @@ -366,47 +238,26 @@ export class NativeEditContext extends AbstractEditContext { private _updateDomAttributes(): void { const options = this._context.configuration.options; - this.domNode.domNode.setAttribute( - "tabindex", - String(options.get(EditorOption.tabIndex)), - ); + this.domNode.domNode.setAttribute('tabindex', String(options.get(EditorOption.tabIndex))); } private _updateEditContext(): void { const editContextState = this._getNewEditContextState(); - this._editContext.updateText( - 0, - Number.MAX_SAFE_INTEGER, - editContextState.text, - ); - this._editContext.updateSelection( - editContextState.selectionStartOffset, - editContextState.selectionEndOffset, - ); - this._textStartPositionWithinEditor = - editContextState.textStartPositionWithinEditor; + this._editContext.updateText(0, Number.MAX_SAFE_INTEGER, editContextState.text); + this._editContext.updateSelection(editContextState.selectionStartOffset, editContextState.selectionEndOffset); + this._textStartPositionWithinEditor = editContextState.textStartPositionWithinEditor; } - private _emitTypeEvent( - viewController: ViewController, - e: TextUpdateEvent, - ): void { + private _emitTypeEvent(viewController: ViewController, e: TextUpdateEvent): void { if (!this._editContext) { return; } const model = this._context.viewModel.model; - const offsetOfStartOfText = model.getOffsetAt( - this._textStartPositionWithinEditor, - ); - const offsetOfSelectionEnd = model.getOffsetAt( - this._primarySelection.getEndPosition(), - ); - const offsetOfSelectionStart = model.getOffsetAt( - this._primarySelection.getStartPosition(), - ); + const offsetOfStartOfText = model.getOffsetAt(this._textStartPositionWithinEditor); + const offsetOfSelectionEnd = model.getOffsetAt(this._primarySelection.getEndPosition()); + const offsetOfSelectionStart = model.getOffsetAt(this._primarySelection.getStartPosition()); const selectionEndOffset = offsetOfSelectionEnd - offsetOfStartOfText; - const selectionStartOffset = - offsetOfSelectionStart - offsetOfStartOfText; + const selectionStartOffset = offsetOfSelectionStart - offsetOfStartOfText; let replaceNextCharCnt = 0; let replacePrevCharCnt = 0; @@ -416,33 +267,23 @@ export class NativeEditContext extends AbstractEditContext { if (e.updateRangeStart < selectionStartOffset) { replacePrevCharCnt = selectionStartOffset - e.updateRangeStart; } - let text = ""; + let text = ''; if (selectionStartOffset < e.updateRangeStart) { - text += this._editContext.text.substring( - selectionStartOffset, - e.updateRangeStart, - ); + text += this._editContext.text.substring(selectionStartOffset, e.updateRangeStart); } text += e.text; if (selectionEndOffset > e.updateRangeEnd) { - text += this._editContext.text.substring( - e.updateRangeEnd, - selectionEndOffset, - ); + text += this._editContext.text.substring(e.updateRangeEnd, selectionEndOffset); } let positionDelta = 0; - if ( - e.selectionStart === e.selectionEnd && - selectionStartOffset === selectionEndOffset - ) { - positionDelta = - e.selectionStart - (e.updateRangeStart + e.text.length); + if (e.selectionStart === e.selectionEnd && selectionStartOffset === selectionEndOffset) { + positionDelta = e.selectionStart - (e.updateRangeStart + e.text.length); } const typeInput: ITypeData = { text, replacePrevCharCnt, replaceNextCharCnt, - positionDelta, + positionDelta }; this._onType(viewController, typeInput); @@ -452,60 +293,29 @@ export class NativeEditContext extends AbstractEditContext { this._updateEditContext(); } - private _onType( - viewController: ViewController, - typeInput: ITypeData, - ): void { - if ( - typeInput.replacePrevCharCnt || - typeInput.replaceNextCharCnt || - typeInput.positionDelta - ) { - viewController.compositionType( - typeInput.text, - typeInput.replacePrevCharCnt, - typeInput.replaceNextCharCnt, - typeInput.positionDelta, - ); + private _onType(viewController: ViewController, typeInput: ITypeData): void { + if (typeInput.replacePrevCharCnt || typeInput.replaceNextCharCnt || typeInput.positionDelta) { + viewController.compositionType(typeInput.text, typeInput.replacePrevCharCnt, typeInput.replaceNextCharCnt, typeInput.positionDelta); } else { viewController.type(typeInput.text); } } - private _getNewEditContextState(): { - text: string; - selectionStartOffset: number; - selectionEndOffset: number; - textStartPositionWithinEditor: Position; - } { + private _getNewEditContextState(): { text: string; selectionStartOffset: number; selectionEndOffset: number; textStartPositionWithinEditor: Position } { const model = this._context.viewModel.model; - const primarySelectionStartLine = - this._primarySelection.startLineNumber; + const primarySelectionStartLine = this._primarySelection.startLineNumber; const primarySelectionEndLine = this._primarySelection.endLineNumber; - const endColumnOfEndLineNumber = model.getLineMaxColumn( - primarySelectionEndLine, - ); - const rangeOfText = new Range( - primarySelectionStartLine, - 1, - primarySelectionEndLine, - endColumnOfEndLineNumber, - ); - const text = model.getValueInRange( - rangeOfText, - EndOfLinePreference.TextDefined, - ); + const endColumnOfEndLineNumber = model.getLineMaxColumn(primarySelectionEndLine); + const rangeOfText = new Range(primarySelectionStartLine, 1, primarySelectionEndLine, endColumnOfEndLineNumber); + const text = model.getValueInRange(rangeOfText, EndOfLinePreference.TextDefined); const selectionStartOffset = this._primarySelection.startColumn - 1; - const selectionEndOffset = - text.length + - this._primarySelection.endColumn - - endColumnOfEndLineNumber; + const selectionEndOffset = text.length + this._primarySelection.endColumn - endColumnOfEndLineNumber; const textStartPositionWithinEditor = rangeOfText.getStartPosition(); return { text, selectionStartOffset, selectionEndOffset, - textStartPositionWithinEditor, + textStartPositionWithinEditor }; } @@ -514,46 +324,33 @@ export class NativeEditContext extends AbstractEditContext { return; } const formats = e.getTextFormats(); - const textStartPositionWithinEditor = - this._textStartPositionWithinEditor; + const textStartPositionWithinEditor = this._textStartPositionWithinEditor; const decorations: IModelDeltaDecoration[] = []; - formats.forEach((f) => { + formats.forEach(f => { const textModel = this._context.viewModel.model; - const offsetOfEditContextText = textModel.getOffsetAt( - textStartPositionWithinEditor, - ); - const startPositionOfDecoration = textModel.getPositionAt( - offsetOfEditContextText + f.rangeStart, - ); - const endPositionOfDecoration = textModel.getPositionAt( - offsetOfEditContextText + f.rangeEnd, - ); - const decorationRange = Range.fromPositions( - startPositionOfDecoration, - endPositionOfDecoration, - ); + const offsetOfEditContextText = textModel.getOffsetAt(textStartPositionWithinEditor); + const startPositionOfDecoration = textModel.getPositionAt(offsetOfEditContextText + f.rangeStart); + const endPositionOfDecoration = textModel.getPositionAt(offsetOfEditContextText + f.rangeEnd); + const decorationRange = Range.fromPositions(startPositionOfDecoration, endPositionOfDecoration); const thickness = f.underlineThickness.toLowerCase(); let decorationClassName: string = CompositionClassName.NONE; switch (thickness) { - case "thin": + case 'thin': decorationClassName = CompositionClassName.SECONDARY; break; - case "thick": + case 'thick': decorationClassName = CompositionClassName.PRIMARY; break; } decorations.push({ range: decorationRange, options: { - description: "textFormatDecoration", + description: 'textFormatDecoration', inlineClassName: decorationClassName, - }, + } }); }); - this._decorations = this._context.viewModel.model.deltaDecorations( - this._decorations, - decorations, - ); + this._decorations = this._context.viewModel.model.deltaDecorations(this._decorations, decorations); } private _updateSelectionAndControlBounds(ctx: RenderingContext) { @@ -565,30 +362,18 @@ export class NativeEditContext extends AbstractEditContext { const contentLeft = options.get(EditorOption.layoutInfo).contentLeft; const parentBounds = this._parent.getBoundingClientRect(); const modelStartPosition = this._primarySelection.getStartPosition(); - const viewStartPosition = - this._context.viewModel.coordinatesConverter.convertModelPositionToViewPosition( - modelStartPosition, - ); - const verticalOffsetStart = - this._context.viewLayout.getVerticalOffsetForLineNumber( - viewStartPosition.lineNumber, - ); + const viewStartPosition = this._context.viewModel.coordinatesConverter.convertModelPositionToViewPosition(modelStartPosition); + const verticalOffsetStart = this._context.viewLayout.getVerticalOffsetForLineNumber(viewStartPosition.lineNumber); const editorScrollTop = this._context.viewLayout.getCurrentScrollTop(); - const editorScrollLeft = - this._context.viewLayout.getCurrentScrollLeft(); + const editorScrollLeft = this._context.viewLayout.getCurrentScrollLeft(); const top = parentBounds.top + verticalOffsetStart - editorScrollTop; - const height = - (this._primarySelection.endLineNumber - - this._primarySelection.startLineNumber + - 1) * - lineHeight; + const height = (this._primarySelection.endLineNumber - this._primarySelection.startLineNumber + 1) * lineHeight; let left = parentBounds.left + contentLeft - editorScrollLeft; let width: number; if (this._primarySelection.isEmpty()) { - const linesVisibleRanges = - ctx.visibleRangeForPosition(viewStartPosition); + const linesVisibleRanges = ctx.visibleRangeForPosition(viewStartPosition); if (linesVisibleRanges) { left += linesVisibleRanges.left; } @@ -607,184 +392,105 @@ export class NativeEditContext extends AbstractEditContext { return; } const options = this._context.configuration.options; - const typicalHalfWidthCharacterWidth = options.get( - EditorOption.fontInfo, - ).typicalHalfwidthCharacterWidth; + const typicalHalfWidthCharacterWidth = options.get(EditorOption.fontInfo).typicalHalfwidthCharacterWidth; const lineHeight = options.get(EditorOption.lineHeight); const contentLeft = options.get(EditorOption.layoutInfo).contentLeft; const parentBounds = this._parent.getBoundingClientRect(); const characterBounds: DOMRect[] = []; - const offsetTransformer = new PositionOffsetTransformer( - this._editContext.text, - ); + const offsetTransformer = new PositionOffsetTransformer(this._editContext.text); for (let offset = e.rangeStart; offset < e.rangeEnd; offset++) { - const editContextStartPosition = - offsetTransformer.getPosition(offset); - const textStartLineOffsetWithinEditor = - this._textStartPositionWithinEditor.lineNumber - 1; - const characterStartPosition = new Position( - textStartLineOffsetWithinEditor + - editContextStartPosition.lineNumber, - editContextStartPosition.column, - ); + const editContextStartPosition = offsetTransformer.getPosition(offset); + const textStartLineOffsetWithinEditor = this._textStartPositionWithinEditor.lineNumber - 1; + const characterStartPosition = new Position(textStartLineOffsetWithinEditor + editContextStartPosition.lineNumber, editContextStartPosition.column); const characterEndPosition = characterStartPosition.delta(0, 1); - const characterModelRange = Range.fromPositions( - characterStartPosition, - characterEndPosition, - ); - const characterViewRange = - this._context.viewModel.coordinatesConverter.convertModelRangeToViewRange( - characterModelRange, - ); - const characterLinesVisibleRanges = - this._visibleRangeProvider.linesVisibleRangesForRange( - characterViewRange, - true, - ) ?? []; - const characterVerticalOffset = - this._context.viewLayout.getVerticalOffsetForLineNumber( - characterViewRange.startLineNumber, - ); - const editorScrollTop = - this._context.viewLayout.getCurrentScrollTop(); - const editorScrollLeft = - this._context.viewLayout.getCurrentScrollLeft(); - const top = - parentBounds.top + characterVerticalOffset - editorScrollTop; + const characterModelRange = Range.fromPositions(characterStartPosition, characterEndPosition); + const characterViewRange = this._context.viewModel.coordinatesConverter.convertModelRangeToViewRange(characterModelRange); + const characterLinesVisibleRanges = this._visibleRangeProvider.linesVisibleRangesForRange(characterViewRange, true) ?? []; + const characterVerticalOffset = this._context.viewLayout.getVerticalOffsetForLineNumber(characterViewRange.startLineNumber); + const editorScrollTop = this._context.viewLayout.getCurrentScrollTop(); + const editorScrollLeft = this._context.viewLayout.getCurrentScrollLeft(); + const top = parentBounds.top + characterVerticalOffset - editorScrollTop; let left = 0; let width = typicalHalfWidthCharacterWidth; if (characterLinesVisibleRanges.length > 0) { - for (const visibleRange of characterLinesVisibleRanges[0] - .ranges) { + for (const visibleRange of characterLinesVisibleRanges[0].ranges) { left = visibleRange.left; width = visibleRange.width; break; } } - characterBounds.push( - new DOMRect( - parentBounds.left + contentLeft + left - editorScrollLeft, - top, - width, - lineHeight, - ), - ); + characterBounds.push(new DOMRect(parentBounds.left + contentLeft + left - editorScrollLeft, top, width, lineHeight)); } this._editContext.updateCharacterBounds(e.rangeStart, characterBounds); } private _ensureClipboardGetsEditorSelection(e: ClipboardEvent): void { const options = this._context.configuration.options; - const emptySelectionClipboard = options.get( - EditorOption.emptySelectionClipboard, - ); - const copyWithSyntaxHighlighting = options.get( - EditorOption.copyWithSyntaxHighlighting, - ); - const selections = this._context.viewModel - .getCursorStates() - .map((cursorState) => cursorState.modelState.selection); - const dataToCopy = getDataToCopy( - this._context.viewModel, - selections, - emptySelectionClipboard, - copyWithSyntaxHighlighting, - ); + const emptySelectionClipboard = options.get(EditorOption.emptySelectionClipboard); + const copyWithSyntaxHighlighting = options.get(EditorOption.copyWithSyntaxHighlighting); + const selections = this._context.viewModel.getCursorStates().map(cursorState => cursorState.modelState.selection); + const dataToCopy = getDataToCopy(this._context.viewModel, selections, emptySelectionClipboard, copyWithSyntaxHighlighting); const storedMetadata: ClipboardStoredMetadata = { version: 1, isFromEmptySelection: dataToCopy.isFromEmptySelection, multicursorText: dataToCopy.multicursorText, - mode: dataToCopy.mode, + mode: dataToCopy.mode }; InMemoryClipboardMetadataManager.INSTANCE.set( // When writing "LINE\r\n" to the clipboard and then pasting, // Firefox pastes "LINE\n", so let's work around this quirk - isFirefox - ? dataToCopy.text.replace(/\r\n/g, "\n") - : dataToCopy.text, - storedMetadata, + (isFirefox ? dataToCopy.text.replace(/\r\n/g, '\n') : dataToCopy.text), + storedMetadata ); e.preventDefault(); if (e.clipboardData) { - ClipboardEventUtils.setTextData( - e.clipboardData, - dataToCopy.text, - dataToCopy.html, - storedMetadata, - ); + ClipboardEventUtils.setTextData(e.clipboardData, dataToCopy.text, dataToCopy.html, storedMetadata); } } - private _setSelectionChangeListener( - viewController: ViewController, - ): IDisposable { + private _setSelectionChangeListener(viewController: ViewController): IDisposable { // See https://github.com/microsoft/vscode/issues/27216 and https://github.com/microsoft/vscode/issues/98256 // When using a Braille display or NVDA for example, it is possible for users to reposition the // system caret. This is reflected in Chrome as a `selectionchange` event and needs to be reflected within the editor. - return addDisposableListener( - this.domNode.domNode.ownerDocument, - "selectionchange", - () => { - const isScreenReaderOptimized = - this._accessibilityService.isScreenReaderOptimized(); - if (!this.isFocused() || !isScreenReaderOptimized) { - return; - } - const screenReaderContentState = - this._screenReaderSupport.screenReaderContentState; - if (!screenReaderContentState) { - return; - } - const activeDocument = getActiveWindow().document; - const activeDocumentSelection = activeDocument.getSelection(); - if (!activeDocumentSelection) { - return; - } - const rangeCount = activeDocumentSelection.rangeCount; - if (rangeCount === 0) { - return; - } - const range = activeDocumentSelection.getRangeAt(0); - const model = this._context.viewModel.model; - const offsetOfStartOfScreenReaderContent = model.getOffsetAt( - screenReaderContentState.startPositionWithinEditor, - ); - let offsetOfSelectionStart = - range.startOffset + offsetOfStartOfScreenReaderContent; - let offsetOfSelectionEnd = - range.endOffset + offsetOfStartOfScreenReaderContent; - const modelUsesCRLF = - this._context.viewModel.model.getEndOfLineSequence() === - EndOfLineSequence.CRLF; - if (modelUsesCRLF) { - const screenReaderContentText = - screenReaderContentState.value; - const offsetTransformer = new PositionOffsetTransformer( - screenReaderContentText, - ); - const positionOfStartWithinText = - offsetTransformer.getPosition(range.startOffset); - const positionOfEndWithinText = - offsetTransformer.getPosition(range.endOffset); - offsetOfSelectionStart += - positionOfStartWithinText.lineNumber - 1; - offsetOfSelectionEnd += - positionOfEndWithinText.lineNumber - 1; - } - const positionOfSelectionStart = model.getPositionAt( - offsetOfSelectionStart, - ); - const positionOfSelectionEnd = - model.getPositionAt(offsetOfSelectionEnd); - const newSelection = Selection.fromPositions( - positionOfSelectionStart, - positionOfSelectionEnd, - ); - viewController.setSelection(newSelection); - }, - ); + return addDisposableListener(this.domNode.domNode.ownerDocument, 'selectionchange', () => { + const isScreenReaderOptimized = this._accessibilityService.isScreenReaderOptimized(); + if (!this.isFocused() || !isScreenReaderOptimized) { + return; + } + const screenReaderContentState = this._screenReaderSupport.screenReaderContentState; + if (!screenReaderContentState) { + return; + } + const activeDocument = getActiveWindow().document; + const activeDocumentSelection = activeDocument.getSelection(); + if (!activeDocumentSelection) { + return; + } + const rangeCount = activeDocumentSelection.rangeCount; + if (rangeCount === 0) { + return; + } + const range = activeDocumentSelection.getRangeAt(0); + const model = this._context.viewModel.model; + const offsetOfStartOfScreenReaderContent = model.getOffsetAt(screenReaderContentState.startPositionWithinEditor); + let offsetOfSelectionStart = range.startOffset + offsetOfStartOfScreenReaderContent; + let offsetOfSelectionEnd = range.endOffset + offsetOfStartOfScreenReaderContent; + const modelUsesCRLF = this._context.viewModel.model.getEndOfLineSequence() === EndOfLineSequence.CRLF; + if (modelUsesCRLF) { + const screenReaderContentText = screenReaderContentState.value; + const offsetTransformer = new PositionOffsetTransformer(screenReaderContentText); + const positionOfStartWithinText = offsetTransformer.getPosition(range.startOffset); + const positionOfEndWithinText = offsetTransformer.getPosition(range.endOffset); + offsetOfSelectionStart += positionOfStartWithinText.lineNumber - 1; + offsetOfSelectionEnd += positionOfEndWithinText.lineNumber - 1; + } + const positionOfSelectionStart = model.getPositionAt(offsetOfSelectionStart); + const positionOfSelectionEnd = model.getPositionAt(offsetOfSelectionEnd); + const newSelection = Selection.fromPositions(positionOfSelectionStart, positionOfSelectionEnd); + viewController.setSelection(newSelection); + }); } } diff --git a/Source/vs/editor/browser/controller/editContext/native/screenReaderSupport.ts b/Source/vs/editor/browser/controller/editContext/native/screenReaderSupport.ts index 0230ca7f81769..3089bd4171766 100644 --- a/Source/vs/editor/browser/controller/editContext/native/screenReaderSupport.ts +++ b/Source/vs/editor/browser/controller/editContext/native/screenReaderSupport.ts @@ -2,37 +2,24 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getActiveWindow } from "../../../../../base/browser/dom.js"; -import { FastDomNode } from "../../../../../base/browser/fastDomNode.js"; -import { localize } from "../../../../../nls.js"; -import { - AccessibilitySupport, - IAccessibilityService, -} from "../../../../../platform/accessibility/common/accessibility.js"; -import { IKeybindingService } from "../../../../../platform/keybinding/common/keybinding.js"; -import { EditorOption } from "../../../../common/config/editorOptions.js"; -import { FontInfo } from "../../../../common/config/fontInfo.js"; -import { Position } from "../../../../common/core/position.js"; -import { Range } from "../../../../common/core/range.js"; -import { Selection } from "../../../../common/core/selection.js"; -import { EndOfLinePreference } from "../../../../common/model.js"; -import { - ViewConfigurationChangedEvent, - ViewCursorStateChangedEvent, -} from "../../../../common/viewEvents.js"; -import { ViewContext } from "../../../../common/viewModel/viewContext.js"; -import { applyFontInfo } from "../../../config/domFontInfo.js"; -import { - RenderingContext, - RestrictedRenderingContext, -} from "../../../view/renderingContext.js"; -import { - ariaLabelForScreenReaderContent, - ISimpleModel, - newlinecount, - PagedScreenReaderStrategy, - ScreenReaderContentState, -} from "../screenReaderUtils.js"; + +import { getActiveWindow } from '../../../../../base/browser/dom.js'; +import { FastDomNode } from '../../../../../base/browser/fastDomNode.js'; +import { localize } from '../../../../../nls.js'; +import { AccessibilitySupport, IAccessibilityService } from '../../../../../platform/accessibility/common/accessibility.js'; +import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js'; +import { EditorOption } from '../../../../common/config/editorOptions.js'; +import { FontInfo } from '../../../../common/config/fontInfo.js'; +import { Position } from '../../../../common/core/position.js'; +import { Range } from '../../../../common/core/range.js'; +import { Selection } from '../../../../common/core/selection.js'; +import { EndOfLinePreference } from '../../../../common/model.js'; +import { ViewConfigurationChangedEvent, ViewCursorStateChangedEvent } from '../../../../common/viewEvents.js'; +import { ViewContext } from '../../../../common/viewModel/viewContext.js'; +import { applyFontInfo } from '../../../config/domFontInfo.js'; +import { IEditorAriaOptions } from '../../../editorBrowser.js'; +import { RestrictedRenderingContext, RenderingContext } from '../../../view/renderingContext.js'; +import { ariaLabelForScreenReaderContent, ISimpleModel, newlinecount, PagedScreenReaderStrategy, ScreenReaderContentState } from '../screenReaderUtils.js'; export class ScreenReaderSupport { // Configuration values @@ -139,7 +126,22 @@ export class ScreenReaderSupport { this._domNode.domNode.scrollTop = numberOfLinesOfContentBeforeSelection * this._lineHeight; } - public setAriaOptions(): void {} + + public setAriaOptions(options: IEditorAriaOptions): void { + if (options.activeDescendant) { + this._domNode.setAttribute('aria-haspopup', 'true'); + this._domNode.setAttribute('aria-autocomplete', 'list'); + this._domNode.setAttribute('aria-activedescendant', options.activeDescendant); + } else { + this._domNode.setAttribute('aria-haspopup', 'false'); + this._domNode.setAttribute('aria-autocomplete', 'both'); + this._domNode.removeAttribute('aria-activedescendant'); + } + if (options.role) { + this._domNode.setAttribute('role', options.role); + } + } + public writeScreenReaderContent(): void { const focusedElement = getActiveWindow().document.activeElement; diff --git a/Source/vs/editor/browser/gpu/atlas/atlas.ts b/Source/vs/editor/browser/gpu/atlas/atlas.ts index c083d17105b6d..68ffc39917100 100644 --- a/Source/vs/editor/browser/gpu/atlas/atlas.ts +++ b/Source/vs/editor/browser/gpu/atlas/atlas.ts @@ -31,6 +31,20 @@ export interface ITextureAtlasPageGlyph { originOffsetX: number; /** The y offset from {@link y} of the glyph's origin. */ originOffsetY: number; + /** + * The distance from the the glyph baseline to the top of the highest bounding rectangle of all + * fonts used to render the text. + * + * @see {@link TextMetrics.fontBoundingBoxAscent} + */ + fontBoundingBoxAscent: number; + /** + * The distance from the the glyph baseline to the bottom of the bounding rectangle of all fonts + * used to render the text. + * + * @see {@link TextMetrics.fontBoundingBoxDescent} + */ + fontBoundingBoxDescent: number; } /** * A texture atlas allocator is responsible for taking rasterized glyphs, drawing them to a texture diff --git a/Source/vs/editor/browser/gpu/atlas/textureAtlas.ts b/Source/vs/editor/browser/gpu/atlas/textureAtlas.ts index 4f8d63d276c74..9f527081e440d 100644 --- a/Source/vs/editor/browser/gpu/atlas/textureAtlas.ts +++ b/Source/vs/editor/browser/gpu/atlas/textureAtlas.ts @@ -3,29 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getActiveWindow } from "../../../../base/browser/dom.js"; -import { CharCode } from "../../../../base/common/charCode.js"; -import { BugIndicatingError } from "../../../../base/common/errors.js"; -import { Emitter, Event } from "../../../../base/common/event.js"; -import { - Disposable, - dispose, - MutableDisposable, - toDisposable, -} from "../../../../base/common/lifecycle.js"; -import { FourKeyMap } from "../../../../base/common/map.js"; -import { IInstantiationService } from "../../../../platform/instantiation/common/instantiation.js"; -import { IThemeService } from "../../../../platform/theme/common/themeService.js"; -import { MetadataConsts } from "../../../common/encodedTokenAttributes.js"; -import { GlyphRasterizer } from "../raster/glyphRasterizer.js"; -import type { IGlyphRasterizer } from "../raster/raster.js"; -import { IdleTaskQueue } from "../taskQueue.js"; -import type { - GlyphMap, - IReadableTextureAtlasPage, - ITextureAtlasPageGlyph, -} from "./atlas.js"; -import { AllocatorType, TextureAtlasPage } from "./textureAtlasPage.js"; +import { getActiveWindow } from '../../../../base/browser/dom.js'; +import { CharCode } from '../../../../base/common/charCode.js'; +import { BugIndicatingError } from '../../../../base/common/errors.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; +import { Disposable, dispose, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; +import { FourKeyMap } from '../../../../base/common/map.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { MetadataConsts } from '../../../common/encodedTokenAttributes.js'; +import { GlyphRasterizer } from '../raster/glyphRasterizer.js'; +import type { IGlyphRasterizer } from '../raster/raster.js'; +import { IdleTaskQueue } from '../taskQueue.js'; +import type { IReadableTextureAtlasPage, ITextureAtlasPageGlyph, GlyphMap } from './atlas.js'; +import { AllocatorType, TextureAtlasPage } from './textureAtlasPage.js'; export interface ITextureAtlasOptions { allocatorType?: AllocatorType; @@ -33,20 +24,23 @@ export interface ITextureAtlasOptions { export class TextureAtlas extends Disposable { private _colorMap?: string[]; - private readonly _warmUpTask: MutableDisposable = - this._register(new MutableDisposable()); + private readonly _warmUpTask: MutableDisposable = this._register(new MutableDisposable()); private readonly _warmedUpRasterizers = new Set(); private readonly _allocatorType: AllocatorType; + /** + * The maximum number of texture atlas pages. This is currently a hard static cap that must not + * be reached. + */ + static readonly maximumPageCount = 16; + /** * The main texture atlas pages which are both larger textures and more efficiently packed * relative to the scratch page. The idea is the main pages are drawn to and uploaded to the GPU * much less frequently so as to not drop frames. */ private readonly _pages: TextureAtlasPage[] = []; - get pages(): IReadableTextureAtlasPage[] { - return this._pages; - } + get pages(): IReadableTextureAtlasPage[] { return this._pages; } readonly pageSize: number; @@ -66,30 +60,20 @@ export class TextureAtlas extends Disposable { private readonly _maxTextureSize: number, options: ITextureAtlasOptions | undefined, @IThemeService private readonly _themeService: IThemeService, - @IInstantiationService - private readonly _instantiationService: IInstantiationService, + @IInstantiationService private readonly _instantiationService: IInstantiationService ) { super(); - this._allocatorType = options?.allocatorType ?? "slab"; + this._allocatorType = options?.allocatorType ?? 'slab'; - this._register( - Event.runAndSubscribe( - this._themeService.onDidColorThemeChange, - () => { - if (this._colorMap) { - this.clear(); - } - this._colorMap = - this._themeService.getColorTheme().tokenColorMap; - }, - ), - ); + this._register(Event.runAndSubscribe(this._themeService.onDidColorThemeChange, () => { + if (this._colorMap) { + this.clear(); + } + this._colorMap = this._themeService.getColorTheme().tokenColorMap; + })); - const dprFactor = Math.max( - 1, - Math.floor(getActiveWindow().devicePixelRatio), - ); + const dprFactor = Math.max(1, Math.floor(getActiveWindow().devicePixelRatio)); this.pageSize = Math.min(1024 * dprFactor, this._maxTextureSize); this._initFirstPage(); @@ -98,19 +82,14 @@ export class TextureAtlas extends Disposable { } private _initFirstPage() { - const firstPage = this._instantiationService.createInstance( - TextureAtlasPage, - 0, - this.pageSize, - this._allocatorType, - ); + const firstPage = this._instantiationService.createInstance(TextureAtlasPage, 0, this.pageSize, this._allocatorType); this._pages.push(firstPage); // IMPORTANT: The first glyph on the first page must be an empty glyph such that zeroed out // cells end up rendering nothing // TODO: This currently means the first slab is for 0x0 glyphs and is wasted - const nullRasterizer = new GlyphRasterizer(1, ""); - firstPage.getGlyph(nullRasterizer, "", 0, 0); + const nullRasterizer = new GlyphRasterizer(1, '', 1); + firstPage.getGlyph(nullRasterizer, '', 0, 0); nullRasterizer.dispose(); } @@ -131,19 +110,10 @@ export class TextureAtlas extends Disposable { this._onDidDeleteGlyphs.fire(); } - getGlyph( - rasterizer: IGlyphRasterizer, - chars: string, - tokenMetadata: number, - charMetadata: number, - ): Readonly { + getGlyph(rasterizer: IGlyphRasterizer, chars: string, tokenMetadata: number, charMetadata: number): Readonly { // TODO: Encode font size and family into key // Ignore metadata that doesn't affect the glyph - tokenMetadata &= ~( - MetadataConsts.LANGUAGEID_MASK | - MetadataConsts.TOKEN_TYPE_MASK | - MetadataConsts.BALANCED_BRACKETS_MASK - ); + tokenMetadata &= ~(MetadataConsts.LANGUAGEID_MASK | MetadataConsts.TOKEN_TYPE_MASK | MetadataConsts.BALANCED_BRACKETS_MASK); // Warm up common glyphs if (!this._warmedUpRasterizers.has(rasterizer.id)) { @@ -152,95 +122,35 @@ export class TextureAtlas extends Disposable { } // Try get the glyph, overflowing to a new page if necessary - return this._tryGetGlyph( - this._glyphPageIndex.get( - chars, - tokenMetadata, - charMetadata, - rasterizer.cacheKey, - ) ?? 0, - rasterizer, - chars, - tokenMetadata, - charMetadata, - ); + return this._tryGetGlyph(this._glyphPageIndex.get(chars, tokenMetadata, charMetadata, rasterizer.cacheKey) ?? 0, rasterizer, chars, tokenMetadata, charMetadata); } - private _tryGetGlyph( - pageIndex: number, - rasterizer: IGlyphRasterizer, - chars: string, - tokenMetadata: number, - charMetadata: number, - ): Readonly { - this._glyphPageIndex.set( - chars, - tokenMetadata, - charMetadata, - rasterizer.cacheKey, - pageIndex, - ); + private _tryGetGlyph(pageIndex: number, rasterizer: IGlyphRasterizer, chars: string, tokenMetadata: number, charMetadata: number): Readonly { + this._glyphPageIndex.set(chars, tokenMetadata, charMetadata, rasterizer.cacheKey, pageIndex); return ( - this._pages[pageIndex].getGlyph( - rasterizer, - chars, - tokenMetadata, - charMetadata, - ) ?? - (pageIndex + 1 < this._pages.length - ? this._tryGetGlyph( - pageIndex + 1, - rasterizer, - chars, - tokenMetadata, - charMetadata, - ) - : undefined) ?? - this._getGlyphFromNewPage( - rasterizer, - chars, - tokenMetadata, - charMetadata, - ) + this._pages[pageIndex].getGlyph(rasterizer, chars, tokenMetadata, charMetadata) + ?? (pageIndex + 1 < this._pages.length + ? this._tryGetGlyph(pageIndex + 1, rasterizer, chars, tokenMetadata, charMetadata) + : undefined) + ?? this._getGlyphFromNewPage(rasterizer, chars, tokenMetadata, charMetadata) ); } - private _getGlyphFromNewPage( - rasterizer: IGlyphRasterizer, - chars: string, - tokenMetadata: number, - charMetadata: number, - ): Readonly { - // TODO: Support more than 2 pages and the GPU texture layer limit - this._pages.push( - this._instantiationService.createInstance( - TextureAtlasPage, - this._pages.length, - this.pageSize, - this._allocatorType, - ), - ); - this._glyphPageIndex.set( - chars, - tokenMetadata, - charMetadata, - rasterizer.cacheKey, - this._pages.length - 1, - ); - return this._pages[this._pages.length - 1].getGlyph( - rasterizer, - chars, - tokenMetadata, - charMetadata, - )!; + private _getGlyphFromNewPage(rasterizer: IGlyphRasterizer, chars: string, tokenMetadata: number, charMetadata: number): Readonly { + if (this._pages.length >= TextureAtlas.maximumPageCount) { + throw new Error(`Attempt to create a texture atlas page past the limit ${TextureAtlas.maximumPageCount}`); + } + this._pages.push(this._instantiationService.createInstance(TextureAtlasPage, this._pages.length, this.pageSize, this._allocatorType)); + this._glyphPageIndex.set(chars, tokenMetadata, charMetadata, rasterizer.cacheKey, this._pages.length - 1); + return this._pages[this._pages.length - 1].getGlyph(rasterizer, chars, tokenMetadata, charMetadata)!; } public getUsagePreview(): Promise { - return Promise.all(this._pages.map((e) => e.getUsagePreview())); + return Promise.all(this._pages.map(e => e.getUsagePreview())); } public getStats(): string[] { - return this._pages.map((e) => e.getStats()); + return this._pages.map(e => e.getStats()); } /** @@ -250,22 +160,16 @@ export class TextureAtlas extends Disposable { private _warmUpAtlas(rasterizer: IGlyphRasterizer): void { const colorMap = this._colorMap; if (!colorMap) { - throw new BugIndicatingError("Cannot warm atlas without color map"); + throw new BugIndicatingError('Cannot warm atlas without color map'); } this._warmUpTask.value?.clear(); - const taskQueue = (this._warmUpTask.value = new IdleTaskQueue()); + const taskQueue = this._warmUpTask.value = new IdleTaskQueue(); // Warm up using roughly the larger glyphs first to help optimize atlas allocation // A-Z for (let code = CharCode.A; code <= CharCode.Z; code++) { taskQueue.enqueue(() => { for (const fgColor of colorMap.keys()) { - this.getGlyph( - rasterizer, - String.fromCharCode(code), - (fgColor << MetadataConsts.FOREGROUND_OFFSET) & - MetadataConsts.FOREGROUND_MASK, - 0, - ); + this.getGlyph(rasterizer, String.fromCharCode(code), (fgColor << MetadataConsts.FOREGROUND_OFFSET) & MetadataConsts.FOREGROUND_MASK, 0); } }); } @@ -273,33 +177,18 @@ export class TextureAtlas extends Disposable { for (let code = CharCode.a; code <= CharCode.z; code++) { taskQueue.enqueue(() => { for (const fgColor of colorMap.keys()) { - this.getGlyph( - rasterizer, - String.fromCharCode(code), - (fgColor << MetadataConsts.FOREGROUND_OFFSET) & - MetadataConsts.FOREGROUND_MASK, - 0, - ); + this.getGlyph(rasterizer, String.fromCharCode(code), (fgColor << MetadataConsts.FOREGROUND_OFFSET) & MetadataConsts.FOREGROUND_MASK, 0); } }); } // Remaining ascii - for ( - let code = CharCode.ExclamationMark; - code <= CharCode.Tilde; - code++ - ) { + for (let code = CharCode.ExclamationMark; code <= CharCode.Tilde; code++) { taskQueue.enqueue(() => { for (const fgColor of colorMap.keys()) { - this.getGlyph( - rasterizer, - String.fromCharCode(code), - (fgColor << MetadataConsts.FOREGROUND_OFFSET) & - MetadataConsts.FOREGROUND_MASK, - 0, - ); + this.getGlyph(rasterizer, String.fromCharCode(code), (fgColor << MetadataConsts.FOREGROUND_OFFSET) & MetadataConsts.FOREGROUND_MASK, 0); } }); } } } + diff --git a/Source/vs/editor/browser/gpu/atlas/textureAtlasShelfAllocator.ts b/Source/vs/editor/browser/gpu/atlas/textureAtlasShelfAllocator.ts index a88b9cf1d69ec..e5fe7535c490d 100644 --- a/Source/vs/editor/browser/gpu/atlas/textureAtlasShelfAllocator.ts +++ b/Source/vs/editor/browser/gpu/atlas/textureAtlasShelfAllocator.ts @@ -104,6 +104,8 @@ export class TextureAtlasShelfAllocator implements ITextureAtlasAllocator { h: glyphHeight, originOffsetX: rasterizedGlyph.originOffset.x, originOffsetY: rasterizedGlyph.originOffset.y, + fontBoundingBoxAscent: rasterizedGlyph.fontBoundingBoxAscent, + fontBoundingBoxDescent: rasterizedGlyph.fontBoundingBoxDescent, }; // Shift current row this._currentRow.x += glyphWidth; diff --git a/Source/vs/editor/browser/gpu/atlas/textureAtlasSlabAllocator.ts b/Source/vs/editor/browser/gpu/atlas/textureAtlasSlabAllocator.ts index 4327237853708..7d45fb29eb60e 100644 --- a/Source/vs/editor/browser/gpu/atlas/textureAtlasSlabAllocator.ts +++ b/Source/vs/editor/browser/gpu/atlas/textureAtlasSlabAllocator.ts @@ -349,6 +349,8 @@ export class TextureAtlasSlabAllocator implements ITextureAtlasAllocator { h: glyphHeight, originOffsetX: rasterizedGlyph.originOffset.x, originOffsetY: rasterizedGlyph.originOffset.y, + fontBoundingBoxAscent: rasterizedGlyph.fontBoundingBoxAscent, + fontBoundingBoxDescent: rasterizedGlyph.fontBoundingBoxDescent, }; // Set the glyph diff --git a/Source/vs/editor/browser/gpu/decorationCssRuleExtractor.ts b/Source/vs/editor/browser/gpu/decorationCssRuleExtractor.ts index 2004b10bdd12d..ecca6711acfd1 100644 --- a/Source/vs/editor/browser/gpu/decorationCssRuleExtractor.ts +++ b/Source/vs/editor/browser/gpu/decorationCssRuleExtractor.ts @@ -67,8 +67,17 @@ export class DecorationCssRuleExtractor extends Disposable { // Note that originally `.matches(rule.selectorText)` was used but this would // not pick up pseudo-classes which are important to determine support of the // returned styles. - if (rule.selectorText.includes(`.${className}`)) { - rules.push(rule); + // + // Since a selector could contain a class name lookup that is simple a prefix of + // the class name we are looking for, we need to also check the character after + // it. + const searchTerm = `.${className}`; + const index = rule.selectorText.indexOf(searchTerm); + if (index !== -1) { + const endOfResult = index + searchTerm.length; + if (rule.selectorText.length === endOfResult || rule.selectorText.substring(endOfResult, endOfResult + 1).match(/[ :]/)) { + rules.push(rule); + } } } } diff --git a/Source/vs/editor/browser/gpu/fullFileRenderStrategy.ts b/Source/vs/editor/browser/gpu/fullFileRenderStrategy.ts index ff08cf9d34ed2..a1d5358123e1d 100644 --- a/Source/vs/editor/browser/gpu/fullFileRenderStrategy.ts +++ b/Source/vs/editor/browser/gpu/fullFileRenderStrategy.ts @@ -3,39 +3,26 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getActiveWindow } from "../../../base/browser/dom.js"; -import { Color } from "../../../base/common/color.js"; -import { BugIndicatingError } from "../../../base/common/errors.js"; -import { MandatoryMutableDisposable } from "../../../base/common/lifecycle.js"; -import { EditorOption } from "../../common/config/editorOptions.js"; -import { CursorColumns } from "../../common/core/cursorColumns.js"; -import type { IViewLineTokens } from "../../common/tokens/lineTokens.js"; -import { ViewEventHandler } from "../../common/viewEventHandler.js"; -import { - ViewEventType, - type ViewConfigurationChangedEvent, - type ViewDecorationsChangedEvent, - type ViewLinesChangedEvent, - type ViewLinesDeletedEvent, - type ViewLinesInsertedEvent, - type ViewScrollChangedEvent, - type ViewTokensChangedEvent, - type ViewZonesChangedEvent, -} from "../../common/viewEvents.js"; -import type { ViewportData } from "../../common/viewLayout/viewLinesViewportData.js"; -import type { - InlineDecoration, - ViewLineRenderingData, -} from "../../common/viewModel.js"; -import type { ViewContext } from "../../common/viewModel/viewContext.js"; -import type { ViewLineOptions } from "../viewParts/viewLines/viewLineOptions.js"; -import type { ITextureAtlasPageGlyph } from "./atlas/atlas.js"; -import { fullFileRenderStrategyWgsl } from "./fullFileRenderStrategy.wgsl.js"; -import { BindingId, type IGpuRenderStrategy } from "./gpu.js"; -import { GPULifecycle } from "./gpuDisposable.js"; -import { quadVertices } from "./gpuUtils.js"; -import { GlyphRasterizer } from "./raster/glyphRasterizer.js"; -import { ViewGpuContext } from "./viewGpuContext.js"; +import { getActiveWindow } from '../../../base/browser/dom.js'; +import { BugIndicatingError } from '../../../base/common/errors.js'; +import { MandatoryMutableDisposable } from '../../../base/common/lifecycle.js'; +import { EditorOption } from '../../common/config/editorOptions.js'; +import { CursorColumns } from '../../common/core/cursorColumns.js'; +import type { IViewLineTokens } from '../../common/tokens/lineTokens.js'; +import { ViewEventHandler } from '../../common/viewEventHandler.js'; +import { ViewEventType, type ViewConfigurationChangedEvent, type ViewDecorationsChangedEvent, type ViewLineMappingChangedEvent, type ViewLinesChangedEvent, type ViewLinesDeletedEvent, type ViewLinesInsertedEvent, type ViewScrollChangedEvent, type ViewThemeChangedEvent, type ViewTokensChangedEvent, type ViewZonesChangedEvent } from '../../common/viewEvents.js'; +import type { ViewportData } from '../../common/viewLayout/viewLinesViewportData.js'; +import type { InlineDecoration, ViewLineRenderingData } from '../../common/viewModel.js'; +import type { ViewContext } from '../../common/viewModel/viewContext.js'; +import type { ViewLineOptions } from '../viewParts/viewLines/viewLineOptions.js'; +import type { ITextureAtlasPageGlyph } from './atlas/atlas.js'; +import { fullFileRenderStrategyWgsl } from './fullFileRenderStrategy.wgsl.js'; +import { BindingId, type IGpuRenderStrategy } from './gpu.js'; +import { GPULifecycle } from './gpuDisposable.js'; +import { quadVertices } from './gpuUtils.js'; +import { GlyphRasterizer } from './raster/glyphRasterizer.js'; +import { ViewGpuContext } from './viewGpuContext.js'; +import { Color } from '../../../base/common/color.js'; const enum Constants { IndicesPerCell = 6, @@ -52,15 +39,15 @@ const enum CellBufferInfo { TextureIndex = 5, } -type QueuedBufferEvent = - | ViewConfigurationChangedEvent - | ViewLinesDeletedEvent - | ViewZonesChangedEvent; +type QueuedBufferEvent = ( + ViewConfigurationChangedEvent | + ViewLineMappingChangedEvent | + ViewLinesDeletedEvent | + ViewZonesChangedEvent +); + +export class FullFileRenderStrategy extends ViewEventHandler implements IGpuRenderStrategy { -export class FullFileRenderStrategy - extends ViewEventHandler - implements IGpuRenderStrategy -{ readonly wgsl: string = fullFileRenderStrategyWgsl; private readonly _glyphRasterizer: MandatoryMutableDisposable; @@ -74,10 +61,7 @@ export class FullFileRenderStrategy private _cellValueBuffers!: [ArrayBuffer, ArrayBuffer]; private _activeDoubleBufferIndex: 0 | 1 = 0; - private readonly _upToDateLines: [Set, Set] = [ - new Set(), - new Set(), - ]; + private readonly _upToDateLines: [Set, Set] = [new Set(), new Set()]; private _visibleObjectCount: number = 0; private _finalRenderedLine: number = 0; @@ -85,21 +69,12 @@ export class FullFileRenderStrategy private _scrollOffsetValueBuffer: Float32Array; private _scrollInitialized: boolean = false; - private readonly _queuedBufferUpdates: [ - QueuedBufferEvent[], - QueuedBufferEvent[], - ] = [[], []]; + private readonly _queuedBufferUpdates: [QueuedBufferEvent[], QueuedBufferEvent[]] = [[], []]; get bindGroupEntries(): GPUBindGroupEntry[] { return [ - { - binding: BindingId.Cells, - resource: { buffer: this._cellBindBuffer }, - }, - { - binding: BindingId.ScrollOffset, - resource: { buffer: this._scrollOffsetBindBuffer }, - }, + { binding: BindingId.Cells, resource: { buffer: this._cellBindBuffer } }, + { binding: BindingId.ScrollOffset, resource: { buffer: this._scrollOffsetBindBuffer } } ]; } @@ -112,48 +87,29 @@ export class FullFileRenderStrategy this._context.addEventHandler(this); - const fontFamily = this._context.configuration.options.get( - EditorOption.fontFamily, - ); - - const fontSize = this._context.configuration.options.get( - EditorOption.fontSize, - ); + const fontFamily = this._context.configuration.options.get(EditorOption.fontFamily); + const fontSize = this._context.configuration.options.get(EditorOption.fontSize); - this._glyphRasterizer = this._register( - new MandatoryMutableDisposable( - new GlyphRasterizer(fontSize, fontFamily), - ), - ); + this._glyphRasterizer = this._register(new MandatoryMutableDisposable(new GlyphRasterizer(fontSize, fontFamily, this._viewGpuContext.devicePixelRatio.get()))); - const bufferSize = - this._viewGpuContext.maxGpuLines * - this._viewGpuContext.maxGpuCols * - Constants.IndicesPerCell * - Float32Array.BYTES_PER_ELEMENT; - this._cellBindBuffer = this._register( - GPULifecycle.createBuffer(this._device, { - label: "Monaco full file cell buffer", - size: bufferSize, - usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, - }), - ).object; + const bufferSize = this._viewGpuContext.maxGpuLines * this._viewGpuContext.maxGpuCols * Constants.IndicesPerCell * Float32Array.BYTES_PER_ELEMENT; + this._cellBindBuffer = this._register(GPULifecycle.createBuffer(this._device, { + label: 'Monaco full file cell buffer', + size: bufferSize, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, + })).object; this._cellValueBuffers = [ new ArrayBuffer(bufferSize), new ArrayBuffer(bufferSize), ]; const scrollOffsetBufferSize = 2; - this._scrollOffsetBindBuffer = this._register( - GPULifecycle.createBuffer(this._device, { - label: "Monaco scroll offset buffer", - size: scrollOffsetBufferSize * Float32Array.BYTES_PER_ELEMENT, - usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, - }), - ).object; - this._scrollOffsetValueBuffer = new Float32Array( - scrollOffsetBufferSize, - ); + this._scrollOffsetBindBuffer = this._register(GPULifecycle.createBuffer(this._device, { + label: 'Monaco scroll offset buffer', + size: scrollOffsetBufferSize * Float32Array.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + })).object; + this._scrollOffsetValueBuffer = new Float32Array(scrollOffsetBufferSize); } // #region Event handlers @@ -166,39 +122,26 @@ export class FullFileRenderStrategy // there are lines that used to be visible but are no longer, so those ranges must be // cleared and uploaded to the GPU. - public override onConfigurationChanged( - e: ViewConfigurationChangedEvent, - ): boolean { + public override onConfigurationChanged(e: ViewConfigurationChangedEvent): boolean { this._invalidateAllLines(); this._queueBufferUpdate(e); - const fontFamily = this._context.configuration.options.get( - EditorOption.fontFamily, - ); - - const fontSize = this._context.configuration.options.get( - EditorOption.fontSize, - ); - + const fontFamily = this._context.configuration.options.get(EditorOption.fontFamily); + const fontSize = this._context.configuration.options.get(EditorOption.fontSize); + const devicePixelRatio = this._viewGpuContext.devicePixelRatio.get(); if ( this._glyphRasterizer.value.fontFamily !== fontFamily || - this._glyphRasterizer.value.fontSize !== fontSize + this._glyphRasterizer.value.fontSize !== fontSize || + this._glyphRasterizer.value.devicePixelRatio !== devicePixelRatio ) { - this._glyphRasterizer.value = new GlyphRasterizer( - fontSize, - fontFamily, - ); + this._glyphRasterizer.value = new GlyphRasterizer(fontSize, fontFamily, devicePixelRatio); } return true; } - public override onDecorationsChanged( - e: ViewDecorationsChangedEvent, - ): boolean { - // TODO: Don't clear all cells if we can avoid it + public override onDecorationsChanged(e: ViewDecorationsChangedEvent): boolean { this._invalidateAllLines(); - return true; } @@ -206,10 +149,7 @@ export class FullFileRenderStrategy // TODO: This currently fires for the entire viewport whenever scrolling stops // https://github.com/microsoft/vscode/issues/233942 for (const range of e.ranges) { - for (let i = range.fromLineNumber; i <= range.toLineNumber; i++) { - this._upToDateLines[0].delete(i); - this._upToDateLines[1].delete(i); - } + this._invalidateLineRange(range.fromLineNumber, range.toLineNumber); } return true; } @@ -219,12 +159,7 @@ export class FullFileRenderStrategy // line data up to retain some up to date lines // TODO: This does not invalidate lines that are no longer in the file this._invalidateLinesFrom(e.fromLineNumber); - - // Queue updates that need to happen on the active buffer, not just the cache. This is - // deferred since the active buffer could be locked by the GPU which would block the main - // thread. this._queueBufferUpdate(e); - return true; } @@ -232,41 +167,35 @@ export class FullFileRenderStrategy // TODO: This currently invalidates everything after the deleted line, it could shift the // line data up to retain some up to date lines this._invalidateLinesFrom(e.fromLineNumber); - return true; } public override onLinesChanged(e: ViewLinesChangedEvent): boolean { - for (let i = e.fromLineNumber; i < e.fromLineNumber + e.count; i++) { - this._upToDateLines[0].delete(i); - this._upToDateLines[1].delete(i); - } + this._invalidateLineRange(e.fromLineNumber, e.fromLineNumber + e.count); return true; } public override onScrollChanged(e?: ViewScrollChangedEvent): boolean { const dpr = getActiveWindow().devicePixelRatio; - this._scrollOffsetValueBuffer[0] = - (e?.scrollLeft ?? this._context.viewLayout.getCurrentScrollLeft()) * - dpr; - this._scrollOffsetValueBuffer[1] = - (e?.scrollTop ?? this._context.viewLayout.getCurrentScrollTop()) * - dpr; - this._device.queue.writeBuffer( - this._scrollOffsetBindBuffer, - 0, - this._scrollOffsetValueBuffer, - ); + this._scrollOffsetValueBuffer[0] = (e?.scrollLeft ?? this._context.viewLayout.getCurrentScrollLeft()) * dpr; + this._scrollOffsetValueBuffer[1] = (e?.scrollTop ?? this._context.viewLayout.getCurrentScrollTop()) * dpr; + this._device.queue.writeBuffer(this._scrollOffsetBindBuffer, 0, this._scrollOffsetValueBuffer); + return true; + } + public override onThemeChanged(e: ViewThemeChangedEvent): boolean { + this._invalidateAllLines(); return true; } - public override onZonesChanged(e: ViewZonesChangedEvent): boolean { + public override onLineMappingChanged(e: ViewLineMappingChangedEvent): boolean { this._invalidateAllLines(); + this._queueBufferUpdate(e); + return true; + } - // Queue updates that need to happen on the active buffer, not just the cache. This is - // deferred since the active buffer could be locked by the GPU which would block the main - // thread. + public override onZonesChanged(e: ViewZonesChangedEvent): boolean { + this._invalidateAllLines(); this._queueBufferUpdate(e); return true; @@ -282,7 +211,6 @@ export class FullFileRenderStrategy private _invalidateLinesFrom(lineNumber: number): void { for (const i of [0, 1]) { const upToDateLines = this._upToDateLines[i]; - for (const upToDateLine of upToDateLines) { if (upToDateLine >= lineNumber) { upToDateLines.delete(upToDateLine); @@ -291,89 +219,49 @@ export class FullFileRenderStrategy } } - // #region Event handlers - - public override onLinesDeleted(e: ViewLinesDeletedEvent): boolean { - this._queueBufferUpdate(e); - - return true; - } - - public override onScrollChanged(e?: ViewScrollChangedEvent): boolean { - const dpr = getActiveWindow().devicePixelRatio; - this._scrollOffsetValueBuffer[0] = - (e?.scrollLeft ?? this._context.viewLayout.getCurrentScrollLeft()) * - dpr; - this._scrollOffsetValueBuffer[1] = - (e?.scrollTop ?? this._context.viewLayout.getCurrentScrollTop()) * - dpr; - this._device.queue.writeBuffer( - this._scrollOffsetBindBuffer, - 0, - this._scrollOffsetValueBuffer, - ); - - return true; + private _invalidateLineRange(fromLineNumber: number, toLineNumber: number): void { + for (let i = fromLineNumber; i <= toLineNumber; i++) { + this._upToDateLines[0].delete(i); + this._upToDateLines[1].delete(i); + } } - // #endregion - reset() { this._invalidateAllLines(); for (const bufferIndex of [0, 1]) { // Zero out buffer and upload to GPU to prevent stale rows from rendering - const buffer = new Float32Array( - this._cellValueBuffers[bufferIndex], - ); + const buffer = new Float32Array(this._cellValueBuffers[bufferIndex]); buffer.fill(0, 0, buffer.length); - this._device.queue.writeBuffer( - this._cellBindBuffer, - 0, - buffer.buffer, - 0, - buffer.byteLength, - ); + this._device.queue.writeBuffer(this._cellBindBuffer, 0, buffer.buffer, 0, buffer.byteLength); } this._finalRenderedLine = 0; } - update( - viewportData: ViewportData, - viewLineOptions: ViewLineOptions, - ): number { + update(viewportData: ViewportData, viewLineOptions: ViewLineOptions): number { // IMPORTANT: This is a hot function. Variables are pre-allocated and shared within the // loop. This is done so we don't need to trust the JIT compiler to do this optimization to // avoid potential additional blocking time in garbage collector which is a common cause of // dropped frames. - let chars = ""; + let chars = ''; let y = 0; - let x = 0; - let absoluteOffsetX = 0; - let absoluteOffsetY = 0; - let xOffset = 0; - let glyph: Readonly; - let cellIndex = 0; let tokenStartIndex = 0; - let tokenEndIndex = 0; - let tokenMetadata = 0; let charMetadata = 0; let lineData: ViewLineRenderingData; let decoration: InlineDecoration; - let content: string = ""; + let content: string = ''; let fillStartIndex = 0; - let fillEndIndex = 0; let tokens: IViewLineTokens; @@ -386,107 +274,59 @@ export class FullFileRenderStrategy } // Update cell data - const cellBuffer = new Float32Array( - this._cellValueBuffers[this._activeDoubleBufferIndex], - ); - - const lineIndexCount = - this._viewGpuContext.maxGpuCols * Constants.IndicesPerCell; - - const upToDateLines = - this._upToDateLines[this._activeDoubleBufferIndex]; + const cellBuffer = new Float32Array(this._cellValueBuffers[this._activeDoubleBufferIndex]); + const lineIndexCount = this._viewGpuContext.maxGpuCols * Constants.IndicesPerCell; + const upToDateLines = this._upToDateLines[this._activeDoubleBufferIndex]; let dirtyLineStart = Number.MAX_SAFE_INTEGER; - let dirtyLineEnd = 0; // Handle any queued buffer updates - const queuedBufferUpdates = - this._queuedBufferUpdates[this._activeDoubleBufferIndex]; - + const queuedBufferUpdates = this._queuedBufferUpdates[this._activeDoubleBufferIndex]; while (queuedBufferUpdates.length) { const e = queuedBufferUpdates.shift()!; switch (e.type) { - case ViewEventType.ViewConfigurationChanged: { - // TODO: Refine the cases for when we throw away all the data + // TODO: Refine these cases so we're not throwing away everything + case ViewEventType.ViewConfigurationChanged: + case ViewEventType.ViewLineMappingChanged: + case ViewEventType.ViewZonesChanged: { cellBuffer.fill(0); dirtyLineStart = 1; - dirtyLineEnd = this._finalRenderedLine; + dirtyLineEnd = Math.max(dirtyLineEnd, this._finalRenderedLine); this._finalRenderedLine = 0; - break; } case ViewEventType.ViewLinesDeleted: { // Shift content below deleted line up - const deletedLineContentStartIndex = - (e.fromLineNumber - 1) * - this._viewGpuContext.maxGpuCols * - Constants.IndicesPerCell; - - const deletedLineContentEndIndex = - e.toLineNumber * - this._viewGpuContext.maxGpuCols * - Constants.IndicesPerCell; - - const nullContentStartIndex = - (this._finalRenderedLine - - (e.toLineNumber - e.fromLineNumber + 1)) * - this._viewGpuContext.maxGpuCols * - Constants.IndicesPerCell; - cellBuffer.set( - cellBuffer.subarray(deletedLineContentEndIndex), - deletedLineContentStartIndex, - ); + const deletedLineContentStartIndex = (e.fromLineNumber - 1) * this._viewGpuContext.maxGpuCols * Constants.IndicesPerCell; + const deletedLineContentEndIndex = (e.toLineNumber) * this._viewGpuContext.maxGpuCols * Constants.IndicesPerCell; + const nullContentStartIndex = (this._finalRenderedLine - (e.toLineNumber - e.fromLineNumber + 1)) * this._viewGpuContext.maxGpuCols * Constants.IndicesPerCell; + cellBuffer.set(cellBuffer.subarray(deletedLineContentEndIndex), deletedLineContentStartIndex); // Zero out content on lines that are no longer valid cellBuffer.fill(0, nullContentStartIndex); // Update dirty lines and final rendered line dirtyLineStart = Math.min(dirtyLineStart, e.fromLineNumber); - dirtyLineEnd = this._finalRenderedLine; - this._finalRenderedLine -= - e.toLineNumber - e.fromLineNumber + 1; - - break; - } - case ViewEventType.ViewZonesChanged: { - // TODO: We could retain render data if we know what view zones changed and how - // Zero out content on all lines - cellBuffer.fill(0); - - dirtyLineStart = 1; - dirtyLineEnd = this._finalRenderedLine; - this._finalRenderedLine = 0; - + dirtyLineEnd = Math.max(dirtyLineEnd, this._finalRenderedLine); + this._finalRenderedLine -= e.toLineNumber - e.fromLineNumber + 1; break; } } } - for ( - y = viewportData.startLineNumber; - y <= viewportData.endLineNumber; - y++ - ) { + for (y = viewportData.startLineNumber; y <= viewportData.endLineNumber; y++) { + // Only attempt to render lines that the GPU renderer can handle - if ( - !this._viewGpuContext.canRender( - viewLineOptions, - viewportData, - y, - ) - ) { - fillStartIndex = - (y - 1) * - this._viewGpuContext.maxGpuCols * - Constants.IndicesPerCell; - fillEndIndex = - y * - this._viewGpuContext.maxGpuCols * - Constants.IndicesPerCell; + if (!this._viewGpuContext.canRender(viewLineOptions, viewportData, y)) { + fillStartIndex = ((y - 1) * this._viewGpuContext.maxGpuCols) * Constants.IndicesPerCell; + fillEndIndex = (y * this._viewGpuContext.maxGpuCols) * Constants.IndicesPerCell; cellBuffer.fill(0, fillStartIndex, fillEndIndex); + dirtyLineStart = Math.min(dirtyLineStart, y); + dirtyLineEnd = Math.max(dirtyLineEnd, y); + continue; } @@ -505,14 +345,8 @@ export class FullFileRenderStrategy tokens = lineData.tokens; tokenStartIndex = lineData.minColumn - 1; tokenEndIndex = 0; - - for ( - let tokenIndex = 0, tokensLen = tokens.getCount(); - tokenIndex < tokensLen; - tokenIndex++ - ) { + for (let tokenIndex = 0, tokensLen = tokens.getCount(); tokenIndex < tokensLen; tokenIndex++) { tokenEndIndex = tokens.getEndOffset(tokenIndex); - if (tokenEndIndex <= tokenStartIndex) { // The faux indent part of the line should have no token type continue; @@ -533,193 +367,119 @@ export class FullFileRenderStrategy // This is Range.strictContainsPosition except it works at the cell level, // it's also inlined to avoid overhead. if ( - y < decoration.range.startLineNumber || - y > decoration.range.endLineNumber || - (y === decoration.range.startLineNumber && - x < decoration.range.startColumn - 1) || - (y === decoration.range.endLineNumber && - x >= decoration.range.endColumn - 1) + (y < decoration.range.startLineNumber || y > decoration.range.endLineNumber) || + (y === decoration.range.startLineNumber && x < decoration.range.startColumn - 1) || + (y === decoration.range.endLineNumber && x >= decoration.range.endColumn - 1) ) { continue; } - const rules = - ViewGpuContext.decorationCssRuleExtractor.getStyleRules( - this._viewGpuContext.canvas.domNode, - decoration.inlineClassName, - ); + const rules = ViewGpuContext.decorationCssRuleExtractor.getStyleRules(this._viewGpuContext.canvas.domNode, decoration.inlineClassName); for (const rule of rules) { for (const r of rule.style) { - const value = - rule.styleMap.get(r)?.toString() ?? ""; + const value = rule.styleMap.get(r)?.toString() ?? ''; switch (r) { - case "color": { + case 'color': { // TODO: This parsing and error handling should move into canRender so fallback // to DOM works - const parsedColor = - Color.Format.CSS.parse(value); + const parsedColor = Color.Format.CSS.parse(value); if (!parsedColor) { - throw new BugIndicatingError( - "Invalid color format " + value, - ); + throw new BugIndicatingError('Invalid color format ' + value); } - charMetadata = - parsedColor.toNumber24Bit(); + charMetadata = parsedColor.toNumber24Bit(); break; } - default: - throw new BugIndicatingError( - "Unexpected inline decoration style", - ); + default: throw new BugIndicatingError('Unexpected inline decoration style'); } } } } - if (chars === " " || chars === "\t") { + if (chars === ' ' || chars === '\t') { // Zero out glyph to ensure it doesn't get rendered - cellIndex = - ((y - 1) * this._viewGpuContext.maxGpuCols + x) * - Constants.IndicesPerCell; - cellBuffer.fill( - 0, - cellIndex, - cellIndex + CellBufferInfo.FloatsPerEntry, - ); + cellIndex = ((y - 1) * this._viewGpuContext.maxGpuCols + x) * Constants.IndicesPerCell; + cellBuffer.fill(0, cellIndex, cellIndex + CellBufferInfo.FloatsPerEntry); // Adjust xOffset for tab stops - if (chars === "\t") { - xOffset = - CursorColumns.nextRenderTabStop( - x + xOffset, - lineData.tabSize, - ) - - x - - 1; + if (chars === '\t') { + xOffset = CursorColumns.nextRenderTabStop(x + xOffset, lineData.tabSize) - x - 1; } continue; } - glyph = this._viewGpuContext.atlas.getGlyph( - this._glyphRasterizer.value, - chars, - tokenMetadata, - charMetadata, - ); + glyph = this._viewGpuContext.atlas.getGlyph(this._glyphRasterizer.value, chars, tokenMetadata, charMetadata); // TODO: Support non-standard character widths - absoluteOffsetX = Math.round( - (x + xOffset) * viewLineOptions.spaceWidth * dpr, - ); - absoluteOffsetY = Math.ceil( - // Top of line including line height - (viewportData.relativeVerticalOffset[ - y - viewportData.startLineNumber - ] + - // Delta to top of line after line height - Math.floor( - (viewportData.lineHeight - - this._context.configuration.options.get( - EditorOption.fontSize, - )) / - 2, - )) * - dpr, + absoluteOffsetX = Math.round((x + xOffset) * viewLineOptions.spaceWidth * dpr); + absoluteOffsetY = Math.round( + // Top of layout box (includes line height) + viewportData.relativeVerticalOffset[y - viewportData.startLineNumber] * dpr + + + // Delta from top of layout box (includes line height) to top of the inline box (no line height) + Math.floor((viewportData.lineHeight * dpr - (glyph.fontBoundingBoxAscent + glyph.fontBoundingBoxDescent)) / 2) + + + // Delta from top of inline box (no line height) to top of glyph origin. If the glyph was drawn + // with a top baseline for example, this ends up drawing the glyph correctly using the alphabetical + // baseline. + glyph.fontBoundingBoxAscent ); - // TODO: Support non-standard character widths - absoluteOffsetX = Math.round( - (x + xOffset) * viewLineOptions.spaceWidth * dpr, - ); - absoluteOffsetY = Math.ceil( - // Top of line including line height - (viewportData.relativeVerticalOffset[ - y - viewportData.startLineNumber - ] + - // Delta to top of line after line height - Math.floor( - (viewportData.lineHeight - - this._context.configuration.options.get( - EditorOption.fontSize, - )) / - 2, - )) * - dpr, - ); - - cellIndex = - ((y - 1) * this._viewGpuContext.maxGpuCols + x) * - Constants.IndicesPerCell; - cellBuffer[cellIndex + CellBufferInfo.Offset_X] = - absoluteOffsetX; - cellBuffer[cellIndex + CellBufferInfo.Offset_Y] = - absoluteOffsetY; - cellBuffer[cellIndex + CellBufferInfo.GlyphIndex] = - glyph.glyphIndex; - cellBuffer[cellIndex + CellBufferInfo.TextureIndex] = - glyph.pageIndex; + cellIndex = ((y - 1) * this._viewGpuContext.maxGpuCols + x) * Constants.IndicesPerCell; + cellBuffer[cellIndex + CellBufferInfo.Offset_X] = absoluteOffsetX; + cellBuffer[cellIndex + CellBufferInfo.Offset_Y] = absoluteOffsetY; + cellBuffer[cellIndex + CellBufferInfo.GlyphIndex] = glyph.glyphIndex; + cellBuffer[cellIndex + CellBufferInfo.TextureIndex] = glyph.pageIndex; } tokenStartIndex = tokenEndIndex; } // Clear to end of line - fillStartIndex = - ((y - 1) * this._viewGpuContext.maxGpuCols + tokenEndIndex) * - Constants.IndicesPerCell; - fillEndIndex = - y * this._viewGpuContext.maxGpuCols * Constants.IndicesPerCell; + fillStartIndex = ((y - 1) * this._viewGpuContext.maxGpuCols + tokenEndIndex) * Constants.IndicesPerCell; + fillEndIndex = (y * this._viewGpuContext.maxGpuCols) * Constants.IndicesPerCell; cellBuffer.fill(0, fillStartIndex, fillEndIndex); upToDateLines.add(y); } - const visibleObjectCount = - (viewportData.endLineNumber - viewportData.startLineNumber + 1) * - lineIndexCount; + const visibleObjectCount = (viewportData.endLineNumber - viewportData.startLineNumber + 1) * lineIndexCount; // Only write when there is changed data if (dirtyLineStart <= dirtyLineEnd) { // Write buffer and swap it out to unblock writes this._device.queue.writeBuffer( this._cellBindBuffer, - (dirtyLineStart - 1) * - lineIndexCount * - Float32Array.BYTES_PER_ELEMENT, + (dirtyLineStart - 1) * lineIndexCount * Float32Array.BYTES_PER_ELEMENT, cellBuffer.buffer, - (dirtyLineStart - 1) * - lineIndexCount * - Float32Array.BYTES_PER_ELEMENT, - (dirtyLineEnd - dirtyLineStart + 1) * - lineIndexCount * - Float32Array.BYTES_PER_ELEMENT, + (dirtyLineStart - 1) * lineIndexCount * Float32Array.BYTES_PER_ELEMENT, + (dirtyLineEnd - dirtyLineStart + 1) * lineIndexCount * Float32Array.BYTES_PER_ELEMENT ); } - this._finalRenderedLine = Math.max( - this._finalRenderedLine, - dirtyLineEnd, - ); + this._finalRenderedLine = Math.max(this._finalRenderedLine, dirtyLineEnd); this._activeDoubleBufferIndex = this._activeDoubleBufferIndex ? 0 : 1; this._visibleObjectCount = visibleObjectCount; - return visibleObjectCount; } draw(pass: GPURenderPassEncoder, viewportData: ViewportData): void { if (this._visibleObjectCount <= 0) { - throw new BugIndicatingError("Attempt to draw 0 objects"); + throw new BugIndicatingError('Attempt to draw 0 objects'); } pass.draw( quadVertices.length / 2, this._visibleObjectCount, undefined, - (viewportData.startLineNumber - 1) * - this._viewGpuContext.maxGpuCols, + (viewportData.startLineNumber - 1) * this._viewGpuContext.maxGpuCols ); } + /** + * Queue updates that need to happen on the active buffer, not just the cache. This will be + * deferred to when the actual cell buffer is changed since the active buffer could be locked by + * the GPU which would block the main thread. + */ private _queueBufferUpdate(e: QueuedBufferEvent) { this._queuedBufferUpdates[0].push(e); this._queuedBufferUpdates[1].push(e); diff --git a/Source/vs/editor/browser/gpu/fullFileRenderStrategy.wgsl.ts b/Source/vs/editor/browser/gpu/fullFileRenderStrategy.wgsl.ts index f1e6c05f04e66..7aab399457b34 100644 --- a/Source/vs/editor/browser/gpu/fullFileRenderStrategy.wgsl.ts +++ b/Source/vs/editor/browser/gpu/fullFileRenderStrategy.wgsl.ts @@ -2,7 +2,10 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BindingId } from "./gpu.js"; + +import { TextureAtlas } from './atlas/textureAtlas.js'; +import { TextureAtlasPage } from './atlas/textureAtlasPage.js'; +import { BindingId } from './gpu.js'; export const fullFileRenderStrategyWgsl = /*wgsl*/ ` struct GlyphInfo { @@ -44,8 +47,7 @@ struct VSOutput { @group(0) @binding(${BindingId.ScrollOffset}) var scrollOffset: ScrollOffset; // Storage buffers -@group(0) @binding(${BindingId.GlyphInfo0}) var glyphInfo0: array; -@group(0) @binding(${BindingId.GlyphInfo1}) var glyphInfo1: array; +@group(0) @binding(${BindingId.GlyphInfo}) var glyphInfo: array, ${TextureAtlas.maximumPageCount}>; @group(0) @binding(${BindingId.Cells}) var cells: array; @vertex fn vs( @@ -54,16 +56,7 @@ struct VSOutput { @builtin(vertex_index) vertexIndex : u32 ) -> VSOutput { let cell = cells[instanceIndex]; - // TODO: Is there a nicer way to init this? - var glyph = glyphInfo0[0]; - - let glyphIndex = u32(cell.glyphIndex); - - if (u32(cell.textureIndex) == 0) { - glyph = glyphInfo0[glyphIndex]; - } else { - glyph = glyphInfo1[glyphIndex]; - } + var glyph = glyphInfo[u32(cell.textureIndex)][u32(cell.glyphIndex)]; var vsOut: VSOutput; // Multiple vert.position by 2,-2 to get it into clipspace which ranged from -1 to 1 diff --git a/Source/vs/editor/browser/gpu/gpu.ts b/Source/vs/editor/browser/gpu/gpu.ts index cfd307769d212..f451017abdab9 100644 --- a/Source/vs/editor/browser/gpu/gpu.ts +++ b/Source/vs/editor/browser/gpu/gpu.ts @@ -15,8 +15,7 @@ import type { ViewportData } from "../../common/viewLayout/viewLinesViewportData import type { ViewLineOptions } from "../viewParts/viewLines/viewLineOptions.js"; export const enum BindingId { - GlyphInfo0, - GlyphInfo1, + GlyphInfo, Cells, TextureSampler, Texture, diff --git a/Source/vs/editor/browser/gpu/raster/glyphRasterizer.ts b/Source/vs/editor/browser/gpu/raster/glyphRasterizer.ts index 81f4bf368a1aa..b0af275ead60e 100644 --- a/Source/vs/editor/browser/gpu/raster/glyphRasterizer.ts +++ b/Source/vs/editor/browser/gpu/raster/glyphRasterizer.ts @@ -3,20 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getActiveWindow } from "../../../../base/browser/dom.js"; -import { memoize } from "../../../../base/common/decorators.js"; -import { Disposable } from "../../../../base/common/lifecycle.js"; -import { StringBuilder } from "../../../common/core/stringBuilder.js"; -import { - FontStyle, - TokenMetadata, -} from "../../../common/encodedTokenAttributes.js"; -import { ensureNonNullable } from "../gpuUtils.js"; -import { - type IBoundingBox, - type IGlyphRasterizer, - type IRasterizedGlyph, -} from "./raster.js"; +import { memoize } from '../../../../base/common/decorators.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { StringBuilder } from '../../../common/core/stringBuilder.js'; +import { FontStyle, TokenMetadata } from '../../../common/encodedTokenAttributes.js'; +import { ensureNonNullable } from '../gpuUtils.js'; +import { type IBoundingBox, type IGlyphRasterizer, type IRasterizedGlyph } from './raster.js'; let nextId = 0; @@ -31,6 +23,8 @@ export class GlyphRasterizer extends Disposable implements IGlyphRasterizer { private _canvas: OffscreenCanvas; private _ctx: OffscreenCanvasRenderingContext2D; + private readonly _textMetrics: TextMetrics; + private _workGlyph: IRasterizedGlyph = { source: null!, boundingBox: { @@ -43,6 +37,8 @@ export class GlyphRasterizer extends Disposable implements IGlyphRasterizer { x: 0, y: 0, }, + fontBoundingBoxAscent: 0, + fontBoundingBoxDescent: 0, }; private _workGlyphConfig: { chars: string | undefined; @@ -53,23 +49,19 @@ export class GlyphRasterizer extends Disposable implements IGlyphRasterizer { constructor( readonly fontSize: number, readonly fontFamily: string, + readonly devicePixelRatio: number ) { super(); - const devicePixelFontSize = Math.ceil( - this.fontSize * getActiveWindow().devicePixelRatio, - ); - this._canvas = new OffscreenCanvas( - devicePixelFontSize * 3, - devicePixelFontSize * 3, - ); - this._ctx = ensureNonNullable( - this._canvas.getContext("2d", { - willReadFrequently: true, - }), - ); - this._ctx.textBaseline = "top"; - this._ctx.fillStyle = "#FFFFFF"; + const devicePixelFontSize = Math.ceil(this.fontSize * devicePixelRatio); + this._canvas = new OffscreenCanvas(devicePixelFontSize * 3, devicePixelFontSize * 3); + this._ctx = ensureNonNullable(this._canvas.getContext('2d', { + willReadFrequently: true + })); + this._ctx.textBaseline = 'top'; + this._ctx.fillStyle = '#FFFFFF'; + this._ctx.font = `${devicePixelFontSize}px ${this.fontFamily}`; + this._textMetrics = this._ctx.measureText('A'); } // TODO: Support drawing multiple fonts and sizes @@ -88,6 +80,8 @@ export class GlyphRasterizer extends Disposable implements IGlyphRasterizer { source: this._canvas, boundingBox: { top: 0, left: 0, bottom: -1, right: -1 }, originOffset: { x: 0, y: 0 }, + fontBoundingBoxAscent: 0, + fontBoundingBoxDescent: 0, }; } // Check if the last glyph matches the config, reuse if so. This helps avoid unnecessary @@ -117,10 +111,7 @@ export class GlyphRasterizer extends Disposable implements IGlyphRasterizer { charMetadata: number, colorMap: string[], ): Readonly { - const devicePixelFontSize = Math.ceil( - this.fontSize * getActiveWindow().devicePixelRatio, - ); - + const devicePixelFontSize = Math.ceil(this.fontSize * this.devicePixelRatio); const canvasDim = devicePixelFontSize * 3; if (this._canvas.width !== canvasDim) { @@ -157,9 +148,8 @@ export class GlyphRasterizer extends Disposable implements IGlyphRasterizer { this._ctx.fillStyle = colorMap[TokenMetadata.getForeground(metadata)]; } - // TODO: This might actually be slower - // const textMetrics = this._ctx.measureText(chars); - this._ctx.textBaseline = "top"; + this._ctx.textBaseline = 'top'; + this._ctx.fillText(chars, originX, originY); const imageData = this._ctx.getImageData( @@ -181,10 +171,11 @@ export class GlyphRasterizer extends Disposable implements IGlyphRasterizer { // }; // console.log(`${chars}_${fg}`, textMetrics, boundingBox, originX, originY, { width: boundingBox.right - boundingBox.left, height: boundingBox.bottom - boundingBox.top }); this._workGlyph.source = this._canvas; - this._workGlyph.originOffset.x = - this._workGlyph.boundingBox.left - originX; - this._workGlyph.originOffset.y = - this._workGlyph.boundingBox.top - originY; + this._workGlyph.originOffset.x = this._workGlyph.boundingBox.left - originX; + this._workGlyph.originOffset.y = this._workGlyph.boundingBox.top - originY; + this._workGlyph.fontBoundingBoxAscent = this._textMetrics.fontBoundingBoxAscent; + this._workGlyph.fontBoundingBoxDescent = this._textMetrics.fontBoundingBoxDescent; + // const result2: IRasterizedGlyph = { // source: this._canvas, // boundingBox: { diff --git a/Source/vs/editor/browser/gpu/raster/raster.ts b/Source/vs/editor/browser/gpu/raster/raster.ts index 170a2b386f0bc..b72b9e7c04312 100644 --- a/Source/vs/editor/browser/gpu/raster/raster.ts +++ b/Source/vs/editor/browser/gpu/raster/raster.ts @@ -58,8 +58,19 @@ export interface IRasterizedGlyph { /** * The offset to the glyph's origin (where it should be drawn to). */ - originOffset: { - x: number; - y: number; - }; + originOffset: { x: number; y: number }; + /** + * The distance from the the glyph baseline to the top of the highest bounding rectangle of all + * fonts used to render the text. + * + * @see {@link TextMetrics.fontBoundingBoxAscent} + */ + fontBoundingBoxAscent: number; + /** + * The distance from the the glyph baseline to the bottom of the bounding rectangle of all fonts + * used to render the text. + * + * @see {@link TextMetrics.fontBoundingBoxDescent} + */ + fontBoundingBoxDescent: number; } diff --git a/Source/vs/editor/browser/gpu/viewGpuContext.ts b/Source/vs/editor/browser/gpu/viewGpuContext.ts index 0da72cb9c2b5f..54e4a4892bd87 100644 --- a/Source/vs/editor/browser/gpu/viewGpuContext.ts +++ b/Source/vs/editor/browser/gpu/viewGpuContext.ts @@ -3,39 +3,26 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { - addDisposableListener, - getActiveWindow, -} from "../../../base/browser/dom.js"; -import { - createFastDomNode, - type FastDomNode, -} from "../../../base/browser/fastDomNode.js"; -import { BugIndicatingError } from "../../../base/common/errors.js"; -import { Event } from "../../../base/common/event.js"; -import { Disposable } from "../../../base/common/lifecycle.js"; -import { - observableValue, - runOnChange, - type IObservable, -} from "../../../base/common/observable.js"; -import * as nls from "../../../nls.js"; -import { IConfigurationService } from "../../../platform/configuration/common/configuration.js"; -import { IInstantiationService } from "../../../platform/instantiation/common/instantiation.js"; -import { - INotificationService, - IPromptChoice, - Severity, -} from "../../../platform/notification/common/notification.js"; -import type { IEditorOptions } from "../../common/config/editorOptions.js"; -import type { ViewportData } from "../../common/viewLayout/viewLinesViewportData.js"; -import type { ViewContext } from "../../common/viewModel/viewContext.js"; -import type { ViewLineOptions } from "../viewParts/viewLines/viewLineOptions.js"; -import { TextureAtlas } from "./atlas/textureAtlas.js"; -import { DecorationCssRuleExtractor } from "./decorationCssRuleExtractor.js"; -import { GPULifecycle } from "./gpuDisposable.js"; -import { ensureNonNullable, observeDevicePixelDimensions } from "./gpuUtils.js"; -import { RectangleRenderer } from "./rectangleRenderer.js"; +import * as nls from '../../../nls.js'; +import { addDisposableListener, getActiveWindow } from '../../../base/browser/dom.js'; +import { createFastDomNode, type FastDomNode } from '../../../base/browser/fastDomNode.js'; +import { BugIndicatingError } from '../../../base/common/errors.js'; +import { Disposable } from '../../../base/common/lifecycle.js'; +import type { ViewportData } from '../../common/viewLayout/viewLinesViewportData.js'; +import type { ViewLineOptions } from '../viewParts/viewLines/viewLineOptions.js'; +import { observableValue, runOnChange, type IObservable } from '../../../base/common/observable.js'; +import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js'; +import { TextureAtlas } from './atlas/textureAtlas.js'; +import { IConfigurationService } from '../../../platform/configuration/common/configuration.js'; +import { INotificationService, IPromptChoice, Severity } from '../../../platform/notification/common/notification.js'; +import { GPULifecycle } from './gpuDisposable.js'; +import { ensureNonNullable, observeDevicePixelDimensions } from './gpuUtils.js'; +import { RectangleRenderer } from './rectangleRenderer.js'; +import type { ViewContext } from '../../common/viewModel/viewContext.js'; +import { DecorationCssRuleExtractor } from './decorationCssRuleExtractor.js'; +import { Event } from '../../../base/common/event.js'; +import type { IEditorOptions } from '../../common/config/editorOptions.js'; +import { InlineDecorationType } from '../../common/viewModel.js'; const enum GpuRenderLimits { maxGpuLines = 3000, @@ -62,8 +49,7 @@ export class ViewGpuContext extends Disposable { readonly rectangleRenderer: RectangleRenderer; - private static readonly _decorationCssRuleExtractor = - new DecorationCssRuleExtractor(); + private static readonly _decorationCssRuleExtractor = new DecorationCssRuleExtractor(); static get decorationCssRuleExtractor(): DecorationCssRuleExtractor { return ViewGpuContext._decorationCssRuleExtractor; } @@ -77,9 +63,7 @@ export class ViewGpuContext extends Disposable { */ static get atlas(): TextureAtlas { if (!ViewGpuContext._atlas) { - throw new BugIndicatingError( - "Cannot call ViewGpuContext.textureAtlas before device is resolved", - ); + throw new BugIndicatingError('Cannot call ViewGpuContext.textureAtlas before device is resolved'); } return ViewGpuContext._atlas; } @@ -93,123 +77,63 @@ export class ViewGpuContext extends Disposable { return ViewGpuContext.atlas; } - readonly canvasDevicePixelDimensions: IObservable<{ - width: number; - height: number; - }>; + readonly canvasDevicePixelDimensions: IObservable<{ width: number; height: number }>; readonly devicePixelRatio: IObservable; constructor( context: ViewContext, - @IInstantiationService - private readonly _instantiationService: IInstantiationService, - @INotificationService - private readonly _notificationService: INotificationService, - @IConfigurationService - private readonly configurationService: IConfigurationService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @INotificationService private readonly _notificationService: INotificationService, + @IConfigurationService private readonly configurationService: IConfigurationService, ) { super(); - this.canvas = createFastDomNode(document.createElement("canvas")); - this.canvas.setClassName("editorCanvas"); + this.canvas = createFastDomNode(document.createElement('canvas')); + this.canvas.setClassName('editorCanvas'); // Adjust the canvas size to avoid drawing under the scroll bar - this._register( - Event.runAndSubscribe( - configurationService.onDidChangeConfiguration, - (e) => { - if ( - !e || - e.affectsConfiguration( - "editor.scrollbar.verticalScrollbarSize", - ) - ) { - const verticalScrollbarSize = - configurationService.getValue( - "editor", - ).scrollbar?.verticalScrollbarSize ?? 14; - this.canvas.domNode.style.boxSizing = "border-box"; - this.canvas.domNode.style.paddingRight = `${verticalScrollbarSize}px`; - } - }, - ), - ); + this._register(Event.runAndSubscribe(configurationService.onDidChangeConfiguration, e => { + if (!e || e.affectsConfiguration('editor.scrollbar.verticalScrollbarSize')) { + const verticalScrollbarSize = configurationService.getValue('editor').scrollbar?.verticalScrollbarSize ?? 14; + this.canvas.domNode.style.boxSizing = 'border-box'; + this.canvas.domNode.style.paddingRight = `${verticalScrollbarSize}px`; + } + })); - this.ctx = ensureNonNullable(this.canvas.domNode.getContext("webgpu")); + this.ctx = ensureNonNullable(this.canvas.domNode.getContext('webgpu')); this.device = GPULifecycle.requestDevice((message) => { - const choices: IPromptChoice[] = [ - { - label: nls.localize( - "editor.dom.render", - "Use DOM-based rendering", - ), - run: () => - this.configurationService.updateValue( - "editor.experimentalGpuAcceleration", - "off", - ), - }, - ]; - this._notificationService.prompt( - Severity.Warning, - message, - choices, - ); - }).then((ref) => this._register(ref).object); - this.device.then((device) => { + const choices: IPromptChoice[] = [{ + label: nls.localize('editor.dom.render', "Use DOM-based rendering"), + run: () => this.configurationService.updateValue('editor.experimentalGpuAcceleration', 'off'), + }]; + this._notificationService.prompt(Severity.Warning, message, choices); + }).then(ref => this._register(ref).object); + this.device.then(device => { if (!ViewGpuContext._atlas) { - ViewGpuContext._atlas = - this._instantiationService.createInstance( - TextureAtlas, - device.limits.maxTextureDimension2D, - undefined, - ); + ViewGpuContext._atlas = this._instantiationService.createInstance(TextureAtlas, device.limits.maxTextureDimension2D, undefined); } }); - this.rectangleRenderer = this._instantiationService.createInstance( - RectangleRenderer, - context, - this.canvas.domNode, - this.ctx, - this.device, - ); + this.rectangleRenderer = this._instantiationService.createInstance(RectangleRenderer, context, this.canvas.domNode, this.ctx, this.device); - const dprObs = observableValue( - this, - getActiveWindow().devicePixelRatio, - ); - this._register( - addDisposableListener(getActiveWindow(), "resize", () => { - dprObs.set(getActiveWindow().devicePixelRatio, undefined); - }), - ); + const dprObs = observableValue(this, getActiveWindow().devicePixelRatio); + this._register(addDisposableListener(getActiveWindow(), 'resize', () => { + dprObs.set(getActiveWindow().devicePixelRatio, undefined); + })); this.devicePixelRatio = dprObs; - this._register( - runOnChange(this.devicePixelRatio, () => - ViewGpuContext.atlas?.clear(), - ), - ); + this._register(runOnChange(this.devicePixelRatio, () => ViewGpuContext.atlas?.clear())); - const canvasDevicePixelDimensions = observableValue(this, { - width: this.canvas.domNode.width, - height: this.canvas.domNode.height, - }); - this._register( - observeDevicePixelDimensions( - this.canvas.domNode, - getActiveWindow(), - (width, height) => { - this.canvas.domNode.width = width; - this.canvas.domNode.height = height; - canvasDevicePixelDimensions.set( - { width, height }, - undefined, - ); - }, - ), - ); + const canvasDevicePixelDimensions = observableValue(this, { width: this.canvas.domNode.width, height: this.canvas.domNode.height }); + this._register(observeDevicePixelDimensions( + this.canvas.domNode, + getActiveWindow(), + (width, height) => { + this.canvas.domNode.width = width; + this.canvas.domNode.height = height; + canvasDevicePixelDimensions.set({ width, height }, undefined); + } + )); this.canvasDevicePixelDimensions = canvasDevicePixelDimensions; } @@ -218,11 +142,7 @@ export class ViewGpuContext extends Disposable { * renderer. Eventually this should trend all lines, except maybe exceptional cases like * decorations that use class names. */ - public canRender( - options: ViewLineOptions, - viewportData: ViewportData, - lineNumber: number, - ): boolean { + public canRender(options: ViewLineOptions, viewportData: ViewportData, lineNumber: number): boolean { const data = viewportData.getViewLineRenderingData(lineNumber); // Check if the line has simple attributes that aren't supported @@ -239,14 +159,14 @@ export class ViewGpuContext extends Disposable { if (data.inlineDecorations.length > 0) { let supported = true; for (const decoration of data.inlineDecorations) { - const styleRules = - ViewGpuContext._decorationCssRuleExtractor.getStyleRules( - this.canvas.domNode, - decoration.inlineClassName, - ); - supported &&= styleRules.every((rule) => { + if (decoration.type !== InlineDecorationType.Regular) { + supported = false; + break; + } + const styleRules = ViewGpuContext._decorationCssRuleExtractor.getStyleRules(this.canvas.domNode, decoration.inlineClassName); + supported &&= styleRules.every(rule => { // Pseudo classes aren't supported currently - if (rule.selectorText.includes(":")) { + if (rule.selectorText.includes(':')) { return false; } for (const r of rule.style) { @@ -269,35 +189,33 @@ export class ViewGpuContext extends Disposable { /** * Like {@link canRender} but returns detailed information about why the line cannot be rendered. */ - public canRenderDetailed( - options: ViewLineOptions, - viewportData: ViewportData, - lineNumber: number, - ): string[] { + public canRenderDetailed(options: ViewLineOptions, viewportData: ViewportData, lineNumber: number): string[] { const data = viewportData.getViewLineRenderingData(lineNumber); const reasons: string[] = []; if (data.containsRTL) { - reasons.push("containsRTL"); + reasons.push('containsRTL'); } if (data.maxColumn > GpuRenderLimits.maxGpuCols) { - reasons.push("maxColumn > maxGpuCols"); + reasons.push('maxColumn > maxGpuCols'); } if (data.continuesWithWrappedLine) { - reasons.push("continuesWithWrappedLine"); + reasons.push('continuesWithWrappedLine'); } if (data.inlineDecorations.length > 0) { let supported = true; + const problemTypes: InlineDecorationType[] = []; const problemSelectors: string[] = []; const problemRules: string[] = []; for (const decoration of data.inlineDecorations) { - const styleRules = - ViewGpuContext._decorationCssRuleExtractor.getStyleRules( - this.canvas.domNode, - decoration.inlineClassName, - ); - supported &&= styleRules.every((rule) => { + if (decoration.type !== InlineDecorationType.Regular) { + problemTypes.push(decoration.type); + supported = false; + continue; + } + const styleRules = ViewGpuContext._decorationCssRuleExtractor.getStyleRules(this.canvas.domNode, decoration.inlineClassName); + supported &&= styleRules.every(rule => { // Pseudo classes aren't supported currently - if (rule.selectorText.includes(":")) { + if (rule.selectorText.includes(':')) { problemSelectors.push(rule.selectorText); return false; } @@ -310,22 +228,21 @@ export class ViewGpuContext extends Disposable { return true; }); if (!supported) { - break; + continue; } } + if (problemTypes.length > 0) { + reasons.push(`inlineDecorations with unsupported types (${problemTypes.map(e => `\`${e}\``).join(', ')})`); + } if (problemRules.length > 0) { - reasons.push( - `inlineDecorations with unsupported CSS rules (\`${problemRules.join(", ")}\`)`, - ); + reasons.push(`inlineDecorations with unsupported CSS rules (${problemRules.map(e => `\`${e}\``).join(', ')})`); } if (problemSelectors.length > 0) { - reasons.push( - `inlineDecorations with unsupported CSS selectors (\`${problemSelectors.join(", ")}\`)`, - ); + reasons.push(`inlineDecorations with unsupported CSS selectors (${problemSelectors.map(e => `\`${e}\``).join(', ')})`); } } if (lineNumber >= GpuRenderLimits.maxGpuLines) { - reasons.push("lineNumber >= maxGpuLines"); + reasons.push('lineNumber >= maxGpuLines'); } return reasons; } @@ -334,4 +251,6 @@ export class ViewGpuContext extends Disposable { /** * A list of fully supported decoration CSS rules that can be used in the GPU renderer. */ -const gpuSupportedDecorationCssRules = ["color"]; +const gpuSupportedDecorationCssRules = [ + 'color', +]; diff --git a/Source/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts b/Source/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts index 98dbb6a673920..678dec6ab99ee 100644 --- a/Source/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts +++ b/Source/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts @@ -3,39 +3,28 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getActiveWindow } from "../../../../base/browser/dom.js"; -import { BugIndicatingError } from "../../../../base/common/errors.js"; -import { - autorun, - observableValue, - runOnChange, -} from "../../../../base/common/observable.js"; -import { IInstantiationService } from "../../../../platform/instantiation/common/instantiation.js"; -import { ILogService } from "../../../../platform/log/common/log.js"; -import { EditorOption } from "../../../common/config/editorOptions.js"; -import { Position } from "../../../common/core/position.js"; -import { Range } from "../../../common/core/range.js"; -import type * as viewEvents from "../../../common/viewEvents.js"; -import type { ViewportData } from "../../../common/viewLayout/viewLinesViewportData.js"; -import type { ViewContext } from "../../../common/viewModel/viewContext.js"; -import { TextureAtlasPage } from "../../gpu/atlas/textureAtlasPage.js"; -import { FullFileRenderStrategy } from "../../gpu/fullFileRenderStrategy.js"; -import { BindingId, type IGpuRenderStrategy } from "../../gpu/gpu.js"; -import { GPULifecycle } from "../../gpu/gpuDisposable.js"; -import { quadVertices } from "../../gpu/gpuUtils.js"; -import { ViewGpuContext } from "../../gpu/viewGpuContext.js"; -import { - FloatHorizontalRange, - HorizontalPosition, - HorizontalRange, - IViewLines, - LineVisibleRanges, - RenderingContext, - RestrictedRenderingContext, - VisibleRanges, -} from "../../view/renderingContext.js"; -import { ViewPart } from "../../view/viewPart.js"; -import { ViewLineOptions } from "../viewLines/viewLineOptions.js"; +import { getActiveWindow } from '../../../../base/browser/dom.js'; +import { BugIndicatingError } from '../../../../base/common/errors.js'; +import { autorun, observableValue, runOnChange } from '../../../../base/common/observable.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; +import { EditorOption } from '../../../common/config/editorOptions.js'; +import { Position } from '../../../common/core/position.js'; +import { Range } from '../../../common/core/range.js'; +import type { ViewportData } from '../../../common/viewLayout/viewLinesViewportData.js'; +import type { ViewContext } from '../../../common/viewModel/viewContext.js'; +import { TextureAtlasPage } from '../../gpu/atlas/textureAtlasPage.js'; +import { FullFileRenderStrategy } from '../../gpu/fullFileRenderStrategy.js'; +import { BindingId, type IGpuRenderStrategy } from '../../gpu/gpu.js'; +import { GPULifecycle } from '../../gpu/gpuDisposable.js'; +import { quadVertices } from '../../gpu/gpuUtils.js'; +import { ViewGpuContext } from '../../gpu/viewGpuContext.js'; +import { FloatHorizontalRange, HorizontalPosition, HorizontalRange, IViewLines, LineVisibleRanges, RenderingContext, RestrictedRenderingContext, VisibleRanges } from '../../view/renderingContext.js'; +import { ViewPart } from '../../view/viewPart.js'; +import { ViewLineOptions } from '../viewLines/viewLineOptions.js'; +import type * as viewEvents from '../../../common/viewEvents.js'; +import { CursorColumns } from '../../../common/core/cursorColumns.js'; +import { TextureAtlas } from '../../gpu/atlas/textureAtlas.js'; const enum GlyphStorageBufferInfo { FloatsPerEntry = 2 + 2 + 2, @@ -49,6 +38,7 @@ const enum GlyphStorageBufferInfo { * The GPU implementation of the ViewLines part. */ export class ViewLinesGpu extends ViewPart implements IViewLines { + private readonly canvas: HTMLCanvasElement; private _initViewportData?: ViewportData[]; @@ -63,7 +53,7 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { private _vertexBuffer!: GPUBuffer; - private readonly _glyphStorageBuffer: GPUBuffer[] = []; + private _glyphStorageBuffer!: GPUBuffer; private _atlasGpuTexture!: GPUTexture; private readonly _atlasGpuTextureVersions: number[] = []; @@ -71,13 +61,12 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { private _renderStrategy!: IGpuRenderStrategy; - private _contentLeftObs = observableValue("contentLeft", 0); + private _contentLeftObs = observableValue('contentLeft', 0); constructor( context: ViewContext, private readonly _viewGpuContext: ViewGpuContext, - @IInstantiationService - private readonly _instantiationService: IInstantiationService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, @ILogService private readonly _logService: ILogService, ) { super(context); @@ -86,19 +75,17 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { // Re-render the following frame after canvas device pixel dimensions change, provided a // new render does not occur. - this._register( - autorun((reader) => { - this._viewGpuContext.canvasDevicePixelDimensions.read(reader); - const lastViewportData = this._lastViewportData; - if (lastViewportData) { - setTimeout(() => { - if (lastViewportData === this._lastViewportData) { - this.renderText(lastViewportData); - } - }); - } - }), - ); + this._register(autorun(reader => { + this._viewGpuContext.canvasDevicePixelDimensions.read(reader); + const lastViewportData = this._lastViewportData; + if (lastViewportData) { + setTimeout(() => { + if (lastViewportData === this._lastViewportData) { + this.renderText(lastViewportData); + } + }); + } + })); this.initWebgpu(); } @@ -115,29 +102,27 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { const atlas = ViewGpuContext.atlas; // Rerender when the texture atlas deletes glyphs - this._register( - atlas.onDidDeleteGlyphs(() => { - this._atlasGpuTextureVersions.length = 0; - this._atlasGpuTextureVersions[0] = 0; - this._atlasGpuTextureVersions[1] = 0; - this._renderStrategy.reset(); - }), - ); + this._register(atlas.onDidDeleteGlyphs(() => { + this._atlasGpuTextureVersions.length = 0; + this._atlasGpuTextureVersions[0] = 0; + this._atlasGpuTextureVersions[1] = 0; + this._renderStrategy.reset(); + })); const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); this._viewGpuContext.ctx.configure({ device: this._device, format: presentationFormat, - alphaMode: "premultiplied", + alphaMode: 'premultiplied', }); this._renderPassColorAttachment = { view: null!, // Will be filled at render time - loadOp: "load", - storeOp: "store", + loadOp: 'load', + storeOp: 'store', }; this._renderPassDescriptor = { - label: "Monaco render pass", + label: 'Monaco render pass', colorAttachments: [this._renderPassColorAttachment], }; @@ -158,60 +143,26 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { Offset_ViewportHeight_ = 5, } const bufferValues = new Float32Array(Info.FloatsPerEntry); - const updateBufferValues = ( - canvasDevicePixelWidth: number = this.canvas.width, - canvasDevicePixelHeight: number = this.canvas.height, - ) => { - bufferValues[Info.Offset_CanvasWidth____] = - canvasDevicePixelWidth; - bufferValues[Info.Offset_CanvasHeight___] = - canvasDevicePixelHeight; - bufferValues[Info.Offset_ViewportOffsetX] = Math.ceil( - this._context.configuration.options.get( - EditorOption.layoutInfo, - ).contentLeft * getActiveWindow().devicePixelRatio, - ); + const updateBufferValues = (canvasDevicePixelWidth: number = this.canvas.width, canvasDevicePixelHeight: number = this.canvas.height) => { + bufferValues[Info.Offset_CanvasWidth____] = canvasDevicePixelWidth; + bufferValues[Info.Offset_CanvasHeight___] = canvasDevicePixelHeight; + bufferValues[Info.Offset_ViewportOffsetX] = Math.ceil(this._context.configuration.options.get(EditorOption.layoutInfo).contentLeft * getActiveWindow().devicePixelRatio); bufferValues[Info.Offset_ViewportOffsetY] = 0; - bufferValues[Info.Offset_ViewportWidth__] = - bufferValues[Info.Offset_CanvasWidth____] - - bufferValues[Info.Offset_ViewportOffsetX]; - bufferValues[Info.Offset_ViewportHeight_] = - bufferValues[Info.Offset_CanvasHeight___] - - bufferValues[Info.Offset_ViewportOffsetY]; + bufferValues[Info.Offset_ViewportWidth__] = bufferValues[Info.Offset_CanvasWidth____] - bufferValues[Info.Offset_ViewportOffsetX]; + bufferValues[Info.Offset_ViewportHeight_] = bufferValues[Info.Offset_CanvasHeight___] - bufferValues[Info.Offset_ViewportOffsetY]; return bufferValues; }; - layoutInfoUniformBuffer = this._register( - GPULifecycle.createBuffer( - this._device, - { - label: "Monaco uniform buffer", - size: Info.BytesPerEntry, - usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, - }, - () => updateBufferValues(), - ), - ).object; - this._register( - runOnChange( - this._viewGpuContext.canvasDevicePixelDimensions, - ({ width, height }) => { - this._device.queue.writeBuffer( - layoutInfoUniformBuffer, - 0, - updateBufferValues(width, height), - ); - }, - ), - ); - this._register( - runOnChange(this._contentLeftObs, () => { - this._device.queue.writeBuffer( - layoutInfoUniformBuffer, - 0, - updateBufferValues(), - ); - }), - ); + layoutInfoUniformBuffer = this._register(GPULifecycle.createBuffer(this._device, { + label: 'Monaco uniform buffer', + size: Info.BytesPerEntry, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + }, () => updateBufferValues())).object; + this._register(runOnChange(this._viewGpuContext.canvasDevicePixelDimensions, ({ width, height }) => { + this._device.queue.writeBuffer(layoutInfoUniformBuffer, 0, updateBufferValues(width, height)); + })); + this._register(runOnChange(this._contentLeftObs, () => { + this._device.queue.writeBuffer(layoutInfoUniformBuffer, 0, updateBufferValues()); + })); } let atlasInfoUniformBuffer: GPUBuffer; @@ -222,74 +173,40 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { Offset_Width_ = 0, Offset_Height = 1, } - atlasInfoUniformBuffer = this._register( - GPULifecycle.createBuffer( - this._device, - { - label: "Monaco atlas info uniform buffer", - size: Info.BytesPerEntry, - usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, - }, - () => { - const values = new Float32Array(Info.FloatsPerEntry); - values[Info.Offset_Width_] = atlas.pageSize; - values[Info.Offset_Height] = atlas.pageSize; - return values; - }, - ), - ).object; + atlasInfoUniformBuffer = this._register(GPULifecycle.createBuffer(this._device, { + label: 'Monaco atlas info uniform buffer', + size: Info.BytesPerEntry, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + }, () => { + const values = new Float32Array(Info.FloatsPerEntry); + values[Info.Offset_Width_] = atlas.pageSize; + values[Info.Offset_Height] = atlas.pageSize; + return values; + })).object; } // #endregion Uniforms // #region Storage buffers - this._renderStrategy = this._register( - this._instantiationService.createInstance( - FullFileRenderStrategy, - this._context, - this._viewGpuContext, - this._device, - ), - ); - - this._glyphStorageBuffer[0] = this._register( - GPULifecycle.createBuffer(this._device, { - label: "Monaco glyph storage buffer [0]", - size: - GlyphStorageBufferInfo.BytesPerEntry * - TextureAtlasPage.maximumGlyphCount, - usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, - }), - ).object; - this._glyphStorageBuffer[1] = this._register( - GPULifecycle.createBuffer(this._device, { - label: "Monaco glyph storage buffer [1]", - size: - GlyphStorageBufferInfo.BytesPerEntry * - TextureAtlasPage.maximumGlyphCount, - usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, - }), - ).object; + this._renderStrategy = this._register(this._instantiationService.createInstance(FullFileRenderStrategy, this._context, this._viewGpuContext, this._device)); + + this._glyphStorageBuffer = this._register(GPULifecycle.createBuffer(this._device, { + label: 'Monaco glyph storage buffer', + size: TextureAtlas.maximumPageCount * (TextureAtlasPage.maximumGlyphCount * GlyphStorageBufferInfo.BytesPerEntry), + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, + })).object; this._atlasGpuTextureVersions[0] = 0; this._atlasGpuTextureVersions[1] = 0; - this._atlasGpuTexture = this._register( - GPULifecycle.createTexture(this._device, { - label: "Monaco atlas texture", - format: "rgba8unorm", - // TODO: Dynamically grow/shrink layer count - size: { - width: atlas.pageSize, - height: atlas.pageSize, - depthOrArrayLayers: 2, - }, - dimension: "2d", - usage: - GPUTextureUsage.TEXTURE_BINDING | - GPUTextureUsage.COPY_DST | - GPUTextureUsage.RENDER_ATTACHMENT, - }), - ).object; + this._atlasGpuTexture = this._register(GPULifecycle.createTexture(this._device, { + label: 'Monaco atlas texture', + format: 'rgba8unorm', + size: { width: atlas.pageSize, height: atlas.pageSize, depthOrArrayLayers: TextureAtlas.maximumPageCount }, + dimension: '2d', + usage: GPUTextureUsage.TEXTURE_BINDING | + GPUTextureUsage.COPY_DST | + GPUTextureUsage.RENDER_ATTACHMENT, + })).object; this._updateAtlasStorageBufferAndTexture(); @@ -297,24 +214,18 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { // #region Vertex buffer - this._vertexBuffer = this._register( - GPULifecycle.createBuffer( - this._device, - { - label: "Monaco vertex buffer", - size: quadVertices.byteLength, - usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, - }, - quadVertices, - ), - ).object; + this._vertexBuffer = this._register(GPULifecycle.createBuffer(this._device, { + label: 'Monaco vertex buffer', + size: quadVertices.byteLength, + usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, + }, quadVertices)).object; // #endregion Vertex buffer // #region Shader module const module = this._device.createShaderModule({ - label: "Monaco shader module", + label: 'Monaco shader module', code: this._renderStrategy.wgsl, }); @@ -323,22 +234,18 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { // #region Pipeline this._pipeline = this._device.createRenderPipeline({ - label: "Monaco render pipeline", - layout: "auto", + label: 'Monaco render pipeline', + layout: 'auto', vertex: { module, buffers: [ { arrayStride: 2 * Float32Array.BYTES_PER_ELEMENT, // 2 floats, 4 bytes each attributes: [ - { - shaderLocation: 0, - offset: 0, - format: "float32x2", - }, // position + { shaderLocation: 0, offset: 0, format: 'float32x2' }, // position ], - }, - ], + } + ] }, fragment: { module, @@ -347,15 +254,15 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { format: presentationFormat, blend: { color: { - srcFactor: "src-alpha", - dstFactor: "one-minus-src-alpha", + srcFactor: 'src-alpha', + dstFactor: 'one-minus-src-alpha' }, alpha: { - srcFactor: "src-alpha", - dstFactor: "one-minus-src-alpha", + srcFactor: 'src-alpha', + dstFactor: 'one-minus-src-alpha' }, }, - }, + } ], }, }); @@ -365,39 +272,22 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { // #region Bind group this._bindGroup = this._device.createBindGroup({ - label: "Monaco bind group", + label: 'Monaco bind group', layout: this._pipeline.getBindGroupLayout(0), entries: [ // TODO: Pass in generically as array? + { binding: BindingId.GlyphInfo, resource: { buffer: this._glyphStorageBuffer } }, { - binding: BindingId.GlyphInfo0, - resource: { buffer: this._glyphStorageBuffer[0] }, - }, - { - binding: BindingId.GlyphInfo1, - resource: { buffer: this._glyphStorageBuffer[1] }, - }, - { - binding: BindingId.TextureSampler, - resource: this._device.createSampler({ - label: "Monaco atlas sampler", - magFilter: "nearest", - minFilter: "nearest", - }), - }, - { - binding: BindingId.Texture, - resource: this._atlasGpuTexture.createView(), + binding: BindingId.TextureSampler, resource: this._device.createSampler({ + label: 'Monaco atlas sampler', + magFilter: 'nearest', + minFilter: 'nearest', + }) }, - { - binding: BindingId.LayoutInfoUniform, - resource: { buffer: layoutInfoUniformBuffer }, - }, - { - binding: BindingId.AtlasDimensionsUniform, - resource: { buffer: atlasInfoUniformBuffer }, - }, - ...this._renderStrategy.bindGroupEntries, + { binding: BindingId.Texture, resource: this._atlasGpuTexture.createView() }, + { binding: BindingId.LayoutInfoUniform, resource: { buffer: layoutInfoUniformBuffer } }, + { binding: BindingId.AtlasDimensionsUniform, resource: { buffer: atlasInfoUniformBuffer } }, + ...this._renderStrategy.bindGroupEntries ], }); @@ -418,11 +308,8 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { private _updateAtlasStorageBufferAndTexture() { for (const [layerIndex, page] of ViewGpuContext.atlas.pages.entries()) { - if (layerIndex >= 2) { - // TODO: Support arbitrary number of layers - console.log( - `Attempt to upload atlas page [${layerIndex}], only 2 are supported currently`, - ); + if (layerIndex >= TextureAtlas.maximumPageCount) { + console.log(`Attempt to upload atlas page [${layerIndex}], only ${TextureAtlas.maximumPageCount} are supported currently`); continue; } @@ -431,64 +318,31 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { continue; } - this._logService.trace( - "Updating atlas page[", - layerIndex, - "] from version ", - this._atlasGpuTextureVersions[layerIndex], - " to version ", - page.version, - ); + this._logService.trace('Updating atlas page[', layerIndex, '] from version ', this._atlasGpuTextureVersions[layerIndex], ' to version ', page.version); - // TODO: Reuse buffer instead of reconstructing each time - // TODO: Dynamically set buffer size - const values = new Float32Array( - GlyphStorageBufferInfo.FloatsPerEntry * - TextureAtlasPage.maximumGlyphCount, - ); + const entryCount = GlyphStorageBufferInfo.FloatsPerEntry * TextureAtlasPage.maximumGlyphCount; + const values = new Float32Array(entryCount); let entryOffset = 0; for (const glyph of page.glyphs) { - values[ - entryOffset + GlyphStorageBufferInfo.Offset_TexturePosition - ] = glyph.x; - values[ - entryOffset + - GlyphStorageBufferInfo.Offset_TexturePosition + - 1 - ] = glyph.y; - values[ - entryOffset + GlyphStorageBufferInfo.Offset_TextureSize - ] = glyph.w; - values[ - entryOffset + GlyphStorageBufferInfo.Offset_TextureSize + 1 - ] = glyph.h; - values[ - entryOffset + GlyphStorageBufferInfo.Offset_OriginPosition - ] = glyph.originOffsetX; - values[ - entryOffset + - GlyphStorageBufferInfo.Offset_OriginPosition + - 1 - ] = glyph.originOffsetY; + values[entryOffset + GlyphStorageBufferInfo.Offset_TexturePosition] = glyph.x; + values[entryOffset + GlyphStorageBufferInfo.Offset_TexturePosition + 1] = glyph.y; + values[entryOffset + GlyphStorageBufferInfo.Offset_TextureSize] = glyph.w; + values[entryOffset + GlyphStorageBufferInfo.Offset_TextureSize + 1] = glyph.h; + values[entryOffset + GlyphStorageBufferInfo.Offset_OriginPosition] = glyph.originOffsetX; + values[entryOffset + GlyphStorageBufferInfo.Offset_OriginPosition + 1] = glyph.originOffsetY; entryOffset += GlyphStorageBufferInfo.FloatsPerEntry; } - if ( - entryOffset / GlyphStorageBufferInfo.FloatsPerEntry > - TextureAtlasPage.maximumGlyphCount - ) { - throw new Error( - `Attempting to write more glyphs (${entryOffset / GlyphStorageBufferInfo.FloatsPerEntry}) than the GPUBuffer can hold (${TextureAtlasPage.maximumGlyphCount})`, - ); + if (entryOffset / GlyphStorageBufferInfo.FloatsPerEntry > TextureAtlasPage.maximumGlyphCount) { + throw new Error(`Attempting to write more glyphs (${entryOffset / GlyphStorageBufferInfo.FloatsPerEntry}) than the GPUBuffer can hold (${TextureAtlasPage.maximumGlyphCount})`); } this._device.queue.writeBuffer( - this._glyphStorageBuffer[layerIndex], - 0, + this._glyphStorageBuffer, + layerIndex * GlyphStorageBufferInfo.FloatsPerEntry * TextureAtlasPage.maximumGlyphCount * Float32Array.BYTES_PER_ELEMENT, values, + 0, + GlyphStorageBufferInfo.FloatsPerEntry * TextureAtlasPage.maximumGlyphCount ); - if ( - page.usedArea.right - page.usedArea.left > 0 && - page.usedArea.bottom - page.usedArea.top > 0 - ) { + if (page.usedArea.right - page.usedArea.left > 0 && page.usedArea.bottom - page.usedArea.top > 0) { this._device.queue.copyExternalImageToTexture( { source: page.source }, { @@ -496,12 +350,12 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { origin: { x: page.usedArea.left, y: page.usedArea.top, - z: layerIndex, - }, + z: layerIndex + } }, { width: page.usedArea.right - page.usedArea.left + 1, - height: page.usedArea.bottom - page.usedArea.top + 1, + height: page.usedArea.bottom - page.usedArea.top + 1 }, ); } @@ -510,11 +364,11 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { } public prepareRender(ctx: RenderingContext): void { - throw new BugIndicatingError("Should not be called"); + throw new BugIndicatingError('Should not be called'); } public override render(ctx: RestrictedRenderingContext): void { - throw new BugIndicatingError("Should not be called"); + throw new BugIndicatingError('Should not be called'); } // #region Event handlers @@ -527,51 +381,21 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { // from that side. Luckily rendering is cheap, it's only when uploaded data changes does it // start to cost. - override onCursorStateChanged( - e: viewEvents.ViewCursorStateChangedEvent, - ): boolean { - return true; - } - override onDecorationsChanged( - e: viewEvents.ViewDecorationsChangedEvent, - ): boolean { - return true; - } - override onFlushed(e: viewEvents.ViewFlushedEvent): boolean { - return true; - } - override onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { - return true; - } - override onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { - return true; - } - override onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { - return true; - } - override onRevealRangeRequest( - e: viewEvents.ViewRevealRangeRequestEvent, - ): boolean { - return true; - } - override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { - return true; - } - override onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { - return true; - } - override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { - return true; - } - - override onConfigurationChanged( - e: viewEvents.ViewConfigurationChangedEvent, - ): boolean { - this._contentLeftObs.set( - this._context.configuration.options.get(EditorOption.layoutInfo) - .contentLeft, - undefined, - ); + override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { return true; } + override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { return true; } + override onFlushed(e: viewEvents.ViewFlushedEvent): boolean { return true; } + + override onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { return true; } + override onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { return true; } + override onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { return true; } + override onLineMappingChanged(e: viewEvents.ViewLineMappingChangedEvent): boolean { return true; } + override onRevealRangeRequest(e: viewEvents.ViewRevealRangeRequestEvent): boolean { return true; } + override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { return true; } + override onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { return true; } + override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { return true; } + + override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + this._contentLeftObs.set(this._context.configuration.options.get(EditorOption.layoutInfo).contentLeft, undefined); return true; } @@ -589,40 +413,22 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { private _renderText(viewportData: ViewportData): void { this._viewGpuContext.rectangleRenderer.draw(viewportData); - const options = new ViewLineOptions( - this._context.configuration, - this._context.theme.type, - ); + const options = new ViewLineOptions(this._context.configuration, this._context.theme.type); - const visibleObjectCount = this._renderStrategy.update( - viewportData, - options, - ); + const visibleObjectCount = this._renderStrategy.update(viewportData, options); this._updateAtlasStorageBufferAndTexture(); - const encoder = this._device.createCommandEncoder({ - label: "Monaco command encoder", - }); + const encoder = this._device.createCommandEncoder({ label: 'Monaco command encoder' }); - this._renderPassColorAttachment.view = this._viewGpuContext.ctx - .getCurrentTexture() - .createView({ label: "Monaco canvas texture view" }); + this._renderPassColorAttachment.view = this._viewGpuContext.ctx.getCurrentTexture().createView({ label: 'Monaco canvas texture view' }); const pass = encoder.beginRenderPass(this._renderPassDescriptor); pass.setPipeline(this._pipeline); pass.setVertexBuffer(0, this._vertexBuffer); // Only draw the content area - const contentLeft = Math.ceil( - this._contentLeftObs.get() * - this._viewGpuContext.devicePixelRatio.get(), - ); - pass.setScissorRect( - contentLeft, - 0, - this.canvas.width - contentLeft, - this.canvas.height, - ); + const contentLeft = Math.ceil(this._contentLeftObs.get() * this._viewGpuContext.devicePixelRatio.get()); + pass.setScissorRect(contentLeft, 0, this.canvas.width - contentLeft, this.canvas.height); pass.setBindGroup(0, this._bindGroup); @@ -642,18 +448,12 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { this._lastViewLineOptions = options; } - linesVisibleRangesForRange( - _range: Range, - includeNewLines: boolean, - ): LineVisibleRanges[] | null { + linesVisibleRangesForRange(_range: Range, includeNewLines: boolean): LineVisibleRanges[] | null { if (!this._lastViewportData) { return null; } const originalEndLineNumber = _range.endLineNumber; - const range = Range.intersectRanges( - _range, - this._lastViewportData.visibleRange, - ); + const range = Range.intersectRanges(_range, this._lastViewportData.visibleRange); if (!range) { return null; } @@ -672,35 +472,19 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { let nextLineModelLineNumber: number = 0; if (includeNewLines) { - nextLineModelLineNumber = - this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition( - new Position(range.startLineNumber, 1), - ).lineNumber; + nextLineModelLineNumber = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(range.startLineNumber, 1)).lineNumber; } - for ( - let lineNumber = range.startLineNumber; - lineNumber <= range.endLineNumber; - lineNumber++ - ) { - if ( - lineNumber < rendStartLineNumber || - lineNumber > rendEndLineNumber - ) { + for (let lineNumber = range.startLineNumber; lineNumber <= range.endLineNumber; lineNumber++) { + + if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) { continue; } - const startColumn = - lineNumber === range.startLineNumber ? range.startColumn : 1; + const startColumn = lineNumber === range.startLineNumber ? range.startColumn : 1; const continuesInNextLine = lineNumber !== range.endLineNumber; - const endColumn = continuesInNextLine - ? this._context.viewModel.getLineMaxColumn(lineNumber) - : range.endColumn; - - const visibleRangesForLine = this._visibleRangesForLineRange( - lineNumber, - startColumn, - endColumn, - ); + const endColumn = continuesInNextLine ? this._context.viewModel.getLineMaxColumn(lineNumber) : range.endColumn; + + const visibleRangesForLine = this._visibleRangesForLineRange(lineNumber, startColumn, endColumn); if (!visibleRangesForLine) { continue; @@ -708,26 +492,14 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { if (includeNewLines && lineNumber < originalEndLineNumber) { const currentLineModelLineNumber = nextLineModelLineNumber; - nextLineModelLineNumber = - this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition( - new Position(lineNumber + 1, 1), - ).lineNumber; + nextLineModelLineNumber = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(lineNumber + 1, 1)).lineNumber; if (currentLineModelLineNumber !== nextLineModelLineNumber) { - visibleRangesForLine.ranges[ - visibleRangesForLine.ranges.length - 1 - ].width += viewLineOptions.spaceWidth; + visibleRangesForLine.ranges[visibleRangesForLine.ranges.length - 1].width += viewLineOptions.spaceWidth; } } - visibleRanges.push( - new LineVisibleRanges( - visibleRangesForLine.outsideRenderedLine, - lineNumber, - HorizontalRange.from(visibleRangesForLine.ranges), - continuesInNextLine, - ), - ); + visibleRanges.push(new LineVisibleRanges(visibleRangesForLine.outsideRenderedLine, lineNumber, HorizontalRange.from(visibleRangesForLine.ranges), continuesInNextLine)); } if (visibleRanges.length === 0) { @@ -737,11 +509,7 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { return visibleRanges; } - private _visibleRangesForLineRange( - lineNumber: number, - startColumn: number, - endColumn: number, - ): VisibleRanges | null { + private _visibleRangesForLineRange(lineNumber: number, startColumn: number, endColumn: number): VisibleRanges | null { if (this.shouldRender()) { // Cannot read from the DOM because it is dirty // i.e. the model & the dom are out of sync, so I'd be reading something stale @@ -751,75 +519,57 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { const viewportData = this._lastViewportData; const viewLineOptions = this._lastViewLineOptions; - if ( - !viewportData || - !viewLineOptions || - lineNumber < viewportData.startLineNumber || - lineNumber > viewportData.endLineNumber - ) { + if (!viewportData || !viewLineOptions || lineNumber < viewportData.startLineNumber || lineNumber > viewportData.endLineNumber) { return null; } // Resolve tab widths for this line const lineData = viewportData.getViewLineRenderingData(lineNumber); const content = lineData.content; - let resolvedStartColumnLeft = 0; + let resolvedStartColumn = 0; for (let x = 0; x < startColumn - 1; x++) { - resolvedStartColumnLeft += - content[x] === "\t" ? lineData.tabSize : 1; + if (content[x] === '\t') { + resolvedStartColumn = CursorColumns.nextRenderTabStop(resolvedStartColumn, lineData.tabSize); + } else { + resolvedStartColumn++; + } } - let resolvedRangeWidth = 0; + let resolvedEndColumn = resolvedStartColumn; for (let x = startColumn - 1; x < endColumn - 1; x++) { - resolvedRangeWidth += content[x] === "\t" ? lineData.tabSize : 1; + if (content[x] === '\t') { + resolvedEndColumn = CursorColumns.nextRenderTabStop(resolvedEndColumn, lineData.tabSize); + } else { + resolvedEndColumn++; + } } // Visible horizontal range in _scaled_ pixels - const result = new VisibleRanges(false, [ - new FloatHorizontalRange( - resolvedStartColumnLeft * viewLineOptions.spaceWidth, - resolvedRangeWidth * viewLineOptions.spaceWidth, - ), + const result = new VisibleRanges(false, [new FloatHorizontalRange( + resolvedStartColumn * viewLineOptions.spaceWidth, + (resolvedEndColumn - resolvedStartColumn) * viewLineOptions.spaceWidth) ]); return result; } visibleRangeForPosition(position: Position): HorizontalPosition | null { - const visibleRanges = this._visibleRangesForLineRange( - position.lineNumber, - position.column, - position.column, - ); + const visibleRanges = this._visibleRangesForLineRange(position.lineNumber, position.column, position.column); if (!visibleRanges) { return null; } - return new HorizontalPosition( - visibleRanges.outsideRenderedLine, - visibleRanges.ranges[0].left, - ); + return new HorizontalPosition(visibleRanges.outsideRenderedLine, visibleRanges.ranges[0].left); } getLineWidth(lineNumber: number): number | undefined { if (!this._lastViewportData || !this._lastViewLineOptions) { return undefined; } - if ( - !this._viewGpuContext.canRender( - this._lastViewLineOptions, - this._lastViewportData, - lineNumber, - ) - ) { + if (!this._viewGpuContext.canRender(this._lastViewLineOptions, this._lastViewportData, lineNumber)) { return undefined; } - const lineData = - this._lastViewportData.getViewLineRenderingData(lineNumber); - const lineRange = this._visibleRangesForLineRange( - lineNumber, - 1, - lineData.maxColumn, - ); + const lineData = this._lastViewportData.getViewLineRenderingData(lineNumber); + const lineRange = this._visibleRangesForLineRange(lineNumber, 1, lineData.maxColumn); const lastRange = lineRange?.ranges.at(-1); if (lastRange) { return lastRange.width; @@ -828,41 +578,33 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { return undefined; } - getPositionAtCoordinate( - lineNumber: number, - mouseContentHorizontalOffset: number, - ): Position | undefined { + getPositionAtCoordinate(lineNumber: number, mouseContentHorizontalOffset: number): Position | undefined { if (!this._lastViewportData || !this._lastViewLineOptions) { return undefined; } - if ( - !this._viewGpuContext.canRender( - this._lastViewLineOptions, - this._lastViewportData, - lineNumber, - ) - ) { + if (!this._viewGpuContext.canRender(this._lastViewLineOptions, this._lastViewportData, lineNumber)) { return undefined; } - const lineData = - this._lastViewportData.getViewLineRenderingData(lineNumber); + const lineData = this._lastViewportData.getViewLineRenderingData(lineNumber); const content = lineData.content; - let visualColumn = Math.ceil( - mouseContentHorizontalOffset / this._lastViewLineOptions.spaceWidth, - ); + let visualColumnTarget = Math.round(mouseContentHorizontalOffset / this._lastViewLineOptions.spaceWidth); let contentColumn = 0; - while (visualColumn > 0) { - if ( - visualColumn - - (content[contentColumn] === "\t" ? lineData.tabSize : 1) < - 0 - ) { + let contentColumnWithTabStops = 0; + while (visualColumnTarget > 0) { + let columnWithTabStopsSize = 0; + if (content[contentColumn] === '\t') { + const tabStop = CursorColumns.nextRenderTabStop(contentColumnWithTabStops, lineData.tabSize); + columnWithTabStopsSize = tabStop - contentColumnWithTabStops; + } else { + columnWithTabStopsSize = 1; + } + if (visualColumnTarget - columnWithTabStopsSize / 2 < 0) { break; } - visualColumn -= - content[contentColumn] === "\t" ? lineData.tabSize : 1; + visualColumnTarget -= columnWithTabStopsSize; contentColumn++; + contentColumnWithTabStops += columnWithTabStopsSize; } - return new Position(lineNumber, contentColumn); + return new Position(lineNumber, Math.floor(contentColumn) + 1); } } diff --git a/Source/vs/editor/common/config/editorOptions.ts b/Source/vs/editor/common/config/editorOptions.ts index 26e920e6c26fc..17e0805cbdb5f 100644 --- a/Source/vs/editor/common/config/editorOptions.ts +++ b/Source/vs/editor/common/config/editorOptions.ts @@ -3,45 +3,37 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as arrays from "../../../base/common/arrays.js"; -import { IMarkdownString } from "../../../base/common/htmlContent.js"; -import { IJSONSchema } from "../../../base/common/jsonSchema.js"; -import * as objects from "../../../base/common/objects.js"; -import * as platform from "../../../base/common/platform.js"; -import { ScrollbarVisibility } from "../../../base/common/scrollable.js"; -import { Constants } from "../../../base/common/uint.js"; -import * as nls from "../../../nls.js"; -import { AccessibilitySupport } from "../../../platform/accessibility/common/accessibility.js"; -import { IConfigurationPropertySchema } from "../../../platform/configuration/common/configurationRegistry.js"; -import product from "../../../platform/product/common/product.js"; -import { EDITOR_MODEL_DEFAULTS } from "../core/textModelDefaults.js"; -import { USUAL_WORD_SEPARATORS } from "../core/wordHelper.js"; -import { FontInfo } from "./fontInfo.js"; +import * as arrays from '../../../base/common/arrays.js'; +import { IMarkdownString } from '../../../base/common/htmlContent.js'; +import { IJSONSchema } from '../../../base/common/jsonSchema.js'; +import * as objects from '../../../base/common/objects.js'; +import * as platform from '../../../base/common/platform.js'; +import { ScrollbarVisibility } from '../../../base/common/scrollable.js'; +import { Constants } from '../../../base/common/uint.js'; +import { FontInfo } from './fontInfo.js'; +import { EDITOR_MODEL_DEFAULTS } from '../core/textModelDefaults.js'; +import { USUAL_WORD_SEPARATORS } from '../core/wordHelper.js'; +import * as nls from '../../../nls.js'; +import { AccessibilitySupport } from '../../../platform/accessibility/common/accessibility.js'; +import { IConfigurationPropertySchema } from '../../../platform/configuration/common/configurationRegistry.js'; +import product from '../../../platform/product/common/product.js'; //#region typed options /** * Configuration options for auto closing quotes and brackets */ -export type EditorAutoClosingStrategy = - | "always" - | "languageDefined" - | "beforeWhitespace" - | "never"; +export type EditorAutoClosingStrategy = 'always' | 'languageDefined' | 'beforeWhitespace' | 'never'; /** * Configuration options for auto wrapping quotes and brackets */ -export type EditorAutoSurroundStrategy = - | "languageDefined" - | "quotes" - | "brackets" - | "never"; +export type EditorAutoSurroundStrategy = 'languageDefined' | 'quotes' | 'brackets' | 'never'; /** * Configuration options for typing over closing quotes or brackets */ -export type EditorAutoClosingEditStrategy = "always" | "auto" | "never"; +export type EditorAutoClosingEditStrategy = 'always' | 'auto' | 'never'; /** * Configuration options for auto indentation in the editor @@ -51,7 +43,7 @@ export const enum EditorAutoIndentStrategy { Keep = 1, Brackets = 2, Advanced = 3, - Full = 4, + Full = 4 } /** @@ -112,24 +104,24 @@ export interface IEditorOptions { /** * Controls the minimal number of visible leading and trailing lines surrounding the cursor. * Defaults to 0. - */ + */ cursorSurroundingLines?: number; /** * Controls when `cursorSurroundingLines` should be enforced * Defaults to `default`, `cursorSurroundingLines` is not enforced when cursor position is changed * by mouse. - */ - cursorSurroundingLinesStyle?: "default" | "all"; + */ + cursorSurroundingLinesStyle?: 'default' | 'all'; /** * Render last line number when the file ends with a newline. * Defaults to 'on' for Windows and macOS and 'dimmed' for Linux. - */ - renderFinalNewline?: "on" | "off" | "dimmed"; + */ + renderFinalNewline?: 'on' | 'off' | 'dimmed'; /** * Remove unusual line terminators like LINE SEPARATOR (LS), PARAGRAPH SEPARATOR (PS). * Defaults to 'prompt'. */ - unusualLineTerminators?: "auto" | "off" | "prompt"; + unusualLineTerminators?: 'auto' | 'off' | 'prompt'; /** * Should the corresponding line be selected when clicking on the line number? * Defaults to true. @@ -194,7 +186,7 @@ export interface IEditorOptions { * Should the editor render validation decorations. * Defaults to editable. */ - renderValidationDecorations?: "editable" | "on" | "off"; + renderValidationDecorations?: 'editable' | 'on' | 'off'; /** * Control the behavior and rendering of the scrollbars. */ @@ -230,7 +222,7 @@ export interface IEditorOptions { * Control the cursor animation style, possible values are 'blink', 'smooth', 'phase', 'expand' and 'solid'. * Defaults to 'blink'. */ - cursorBlinking?: "blink" | "smooth" | "phase" | "expand" | "solid"; + cursorBlinking?: 'blink' | 'smooth' | 'phase' | 'expand' | 'solid'; /** * Zoom the font in the editor when using the mouse wheel in combination with holding Ctrl. * Defaults to false. @@ -240,23 +232,17 @@ export interface IEditorOptions { * Control the mouse pointer style, either 'text' or 'default' or 'copy' * Defaults to 'text' */ - mouseStyle?: "text" | "default" | "copy"; + mouseStyle?: 'text' | 'default' | 'copy'; /** * Enable smooth caret animation. * Defaults to 'off'. */ - cursorSmoothCaretAnimation?: "off" | "explicit" | "on"; + cursorSmoothCaretAnimation?: 'off' | 'explicit' | 'on'; /** * Control the cursor style, either 'block' or 'line'. * Defaults to 'line'. */ - cursorStyle?: - | "line" - | "block" - | "underline" - | "line-thin" - | "block-outline" - | "underline-thin"; + cursorStyle?: 'line' | 'block' | 'underline' | 'line-thin' | 'block-outline' | 'underline-thin'; /** * Control the width of the cursor when cursorStyle is set to 'line' */ @@ -319,15 +305,15 @@ export interface IEditorOptions { * When `wordWrap` = "bounded", the lines will wrap at min(viewport width, wordWrapColumn). * Defaults to "off". */ - wordWrap?: "off" | "on" | "wordWrapColumn" | "bounded"; + wordWrap?: 'off' | 'on' | 'wordWrapColumn' | 'bounded'; /** * Override the `wordWrap` setting. */ - wordWrapOverride1?: "off" | "on" | "inherit"; + wordWrapOverride1?: 'off' | 'on' | 'inherit'; /** * Override the `wordWrapOverride1` setting. */ - wordWrapOverride2?: "off" | "on" | "inherit"; + wordWrapOverride2?: 'off' | 'on' | 'inherit'; /** * Control the wrapping of the editor. * When `wordWrap` = "off", the lines will never wrap. @@ -341,12 +327,12 @@ export interface IEditorOptions { * Control indentation of wrapped lines. Can be: 'none', 'same', 'indent' or 'deepIndent'. * Defaults to 'same' in vscode and to 'none' in monaco-editor. */ - wrappingIndent?: "none" | "same" | "indent" | "deepIndent"; + wrappingIndent?: 'none' | 'same' | 'indent' | 'deepIndent'; /** * Controls the wrapping strategy to use. * Defaults to 'simple'. */ - wrappingStrategy?: "simple" | "advanced"; + wrappingStrategy?: 'simple' | 'advanced'; /** * Configure word wrapping characters. A break will be introduced before these characters. */ @@ -360,7 +346,7 @@ export interface IEditorOptions { * When wordBreak = 'normal', Use the default line break rule. * When wordBreak = 'keepAll', Word breaks should not be used for Chinese/Japanese/Korean (CJK) text. Non-CJK text behavior is the same as for normal. */ - wordBreak?: "normal" | "keepAll"; + wordBreak?: 'normal' | 'keepAll'; /** * Performance guard: Stop rendering a line after x characters. * Defaults to 10000. @@ -383,7 +369,7 @@ export interface IEditorOptions { /** * Controls what is the condition to spawn a color picker from a color dectorator */ - colorDecoratorsActivatedOn?: "clickAndHover" | "click" | "hover"; + colorDecoratorsActivatedOn?: 'clickAndHover' | 'click' | 'hover'; /** * Controls the max number of color decorators that can be rendered in an editor at once. */ @@ -421,7 +407,7 @@ export interface IEditorOptions { * The modifier to be used to add multiple cursors with the mouse. * Defaults to 'alt' */ - multiCursorModifier?: "ctrlCmd" | "alt"; + multiCursorModifier?: 'ctrlCmd' | 'alt'; /** * Merge overlapping selections. * Defaults to true @@ -431,7 +417,7 @@ export interface IEditorOptions { * Configure the behaviour when pasting a text with the line count equal to the cursor count. * Defaults to 'spread'. */ - multiCursorPaste?: "spread" | "full"; + multiCursorPaste?: 'spread' | 'full'; /** * Controls the max number of text cursors that can be in an active editor at once. */ @@ -440,7 +426,7 @@ export interface IEditorOptions { * Configure the editor's accessibility support. * Defaults to 'auto'. It is best to leave this to 'auto'. */ - accessibilitySupport?: "auto" | "off" | "on"; + accessibilitySupport?: 'auto' | 'off' | 'on'; /** * Controls the number of lines in the editor that can be read out by a screen reader */ @@ -508,7 +494,7 @@ export interface IEditorOptions { * Controls whether the editor should automatically adjust the indentation when users type, paste, move or indent lines. * Defaults to advanced. */ - autoIndent?: "none" | "keep" | "brackets" | "advanced" | "full"; + autoIndent?: 'none' | 'keep' | 'brackets' | 'advanced' | 'full'; /** * Emulate selection behaviour of tab characters when using spaces for indentation. * This means selection will stick to tab stops. @@ -538,7 +524,7 @@ export interface IEditorOptions { * Accept suggestions on ENTER. * Defaults to 'on'. */ - acceptSuggestionOnEnter?: "on" | "smart" | "off"; + acceptSuggestionOnEnter?: 'on' | 'smart' | 'off'; /** * Accept suggestions on provider defined characters. * Defaults to true. @@ -547,7 +533,7 @@ export interface IEditorOptions { /** * Enable snippet suggestions. Default to 'true'. */ - snippetSuggestions?: "top" | "bottom" | "inline" | "none"; + snippetSuggestions?: 'top' | 'bottom' | 'inline' | 'none'; /** * Copying without a selection copies the current line. */ @@ -559,7 +545,7 @@ export interface IEditorOptions { /** * The history mode for suggestions. */ - suggestSelection?: "first" | "recentlyUsed" | "recentlyUsedByPrefix"; + suggestSelection?: 'first' | 'recentlyUsed' | 'recentlyUsedByPrefix'; /** * The font size for the suggest widget. * Defaults to the editor font size. @@ -573,7 +559,7 @@ export interface IEditorOptions { /** * Enable tab completion. */ - tabCompletion?: "on" | "off" | "onlySnippets"; + tabCompletion?: 'on' | 'off' | 'onlySnippets'; /** * Enable selection highlight. * Defaults to true. @@ -586,7 +572,7 @@ export interface IEditorOptions { * 'singleFile' triggers occurrence highlighting in the current document * 'multiFile' triggers occurrence highlighting across valid open documents */ - occurrencesHighlight?: "off" | "singleFile" | "multiFile"; + occurrencesHighlight?: 'off' | 'singleFile' | 'multiFile'; /** * Controls delay for occurrences highlighting * Defaults to 250. @@ -624,7 +610,7 @@ export interface IEditorOptions { * Selects the folding strategy. 'auto' uses the strategies contributed for the current document, 'indentation' uses the indentation based folding strategy. * Defaults to 'auto'. */ - foldingStrategy?: "auto" | "indentation"; + foldingStrategy?: 'auto' | 'indentation'; /** * Enable highlight for folded regions. * Defaults to true. @@ -644,7 +630,7 @@ export interface IEditorOptions { * Controls whether the fold actions in the gutter stay always visible or hide unless the mouse is over the gutter. * Defaults to 'mouseover'. */ - showFoldingControls?: "always" | "never" | "mouseover"; + showFoldingControls?: 'always' | 'never' | 'mouseover'; /** * Controls whether clicking on the empty content after a folded line will unfold the line. * Defaults to false. @@ -654,22 +640,22 @@ export interface IEditorOptions { * Enable highlighting of matching brackets. * Defaults to 'always'. */ - matchBrackets?: "never" | "near" | "always"; + matchBrackets?: 'never' | 'near' | 'always'; /** * Enable experimental rendering using WebGPU. * Defaults to 'off'. */ - experimentalGpuAcceleration?: "on" | "off"; + experimentalGpuAcceleration?: 'on' | 'off'; /** * Enable experimental whitespace rendering. * Defaults to 'svg'. */ - experimentalWhitespaceRendering?: "svg" | "font" | "off"; + experimentalWhitespaceRendering?: 'svg' | 'font' | 'off'; /** * Enable rendering of whitespace. * Defaults to 'selection'. */ - renderWhitespace?: "none" | "boundary" | "selection" | "trailing" | "all"; + renderWhitespace?: 'none' | 'boundary' | 'selection' | 'trailing' | 'all'; /** * Enable rendering of control characters. * Defaults to true. @@ -679,7 +665,7 @@ export interface IEditorOptions { * Enable rendering of current line highlight. * Defaults to all. */ - renderLineHighlight?: "none" | "gutter" | "line" | "all"; + renderLineHighlight?: 'none' | 'gutter' | 'line' | 'all'; /** * Control if the current line highlight should be rendered only the editor is focused. * Defaults to false. @@ -717,12 +703,12 @@ export interface IEditorOptions { * Controls whether to focus the inline editor in the peek widget by default. * Defaults to false. */ - peekWidgetDefaultFocus?: "tree" | "editor"; + peekWidgetDefaultFocus?: 'tree' | 'editor'; /** * Sets a placeholder for the editor. * If set, the placeholder is shown if the editor is empty. - */ + */ placeholder?: string | undefined; /** @@ -748,7 +734,7 @@ export interface IEditorOptions { useShadowDOM?: boolean; /** * Controls the behavior of editor guides. - */ + */ guides?: IGuidesOptions; /** @@ -759,7 +745,7 @@ export interface IEditorOptions { /** * Configures bracket pair colorization (disabled by default). - */ + */ bracketPairColorization?: IBracketPairColorizationOptions; /** @@ -831,7 +817,7 @@ export interface IDiffEditorBaseOptions { /** * If set, the diff editor is optimized for small views. * Defaults to `false`. - */ + */ compactMode?: boolean; /** @@ -866,7 +852,7 @@ export interface IDiffEditorBaseOptions { /** * Indicates if the gutter menu should be rendered. - */ + */ renderGutterMenu?: boolean; /** @@ -890,12 +876,12 @@ export interface IDiffEditorBaseOptions { /** * Control the wrapping of the diff editor. */ - diffWordWrap?: "off" | "on" | "inherit"; + diffWordWrap?: 'off' | 'on' | 'inherit'; /** * Diff Algorithm - */ - diffAlgorithm?: "legacy" | "advanced"; + */ + diffAlgorithm?: 'legacy' | 'advanced'; /** * Whether the diff editor aria label should be verbose. @@ -912,7 +898,7 @@ export interface IDiffEditorBaseOptions { /** * Only applies when `renderSideBySide` is set to false. - */ + */ useTrueInlineView?: boolean; }; @@ -938,16 +924,13 @@ export interface IDiffEditorBaseOptions { /** * Configuration options for the diff editor. */ -export interface IDiffEditorOptions - extends IEditorOptions, - IDiffEditorBaseOptions {} +export interface IDiffEditorOptions extends IEditorOptions, IDiffEditorBaseOptions { +} /** * @internal */ -export type ValidDiffEditorBaseOptions = Readonly< - Required ->; +export type ValidDiffEditorBaseOptions = Readonly>; //#endregion @@ -999,6 +982,7 @@ export interface IEnvironmentalOptions { * @internal */ export class ComputeOptionsMemory { + public stableMinimapLayoutInput: IMinimapLayoutInput | null; public stableFitMaxMinimapScale: number; public stableFitRemainingWidth: number; @@ -1017,10 +1001,7 @@ export interface IEditorOption { /** * @internal */ - readonly schema: - | IConfigurationPropertySchema - | { [path: string]: IConfigurationPropertySchema } - | undefined; + readonly schema: IConfigurationPropertySchema | { [path: string]: IConfigurationPropertySchema } | undefined; /** * @internal */ @@ -1028,26 +1009,18 @@ export interface IEditorOption { /** * @internal */ - compute( - env: IEnvironmentalOptions, - options: IComputedEditorOptions, - value: V, - ): V; + compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: V): V; /** * Might modify `value`. - */ + */ applyUpdate(value: V | undefined, update: V): ApplyUpdateResult; } /** * @internal */ -type PossibleKeyName0 = { - [K in keyof IEditorOptions]: IEditorOptions[K] extends V | undefined - ? K - : never; -}[keyof IEditorOptions]; +type PossibleKeyName0 = { [K in keyof IEditorOptions]: IEditorOptions[K] extends V | undefined ? K : never }[keyof IEditorOptions]; /** * @internal */ @@ -1056,25 +1029,14 @@ type PossibleKeyName = NonNullable>; /** * @internal */ -abstract class BaseEditorOption - implements IEditorOption -{ +abstract class BaseEditorOption implements IEditorOption { + public readonly id: K; public readonly name: string; public readonly defaultValue: V; - public readonly schema: - | IConfigurationPropertySchema - | { [path: string]: IConfigurationPropertySchema } - | undefined; + public readonly schema: IConfigurationPropertySchema | { [path: string]: IConfigurationPropertySchema } | undefined; - constructor( - id: K, - name: PossibleKeyName, - defaultValue: V, - schema?: - | IConfigurationPropertySchema - | { [path: string]: IConfigurationPropertySchema }, - ) { + constructor(id: K, name: PossibleKeyName, defaultValue: V, schema?: IConfigurationPropertySchema | { [path: string]: IConfigurationPropertySchema }) { this.id = id; this.name = name; this.defaultValue = defaultValue; @@ -1087,11 +1049,7 @@ abstract class BaseEditorOption public abstract validate(input: any): V; - public compute( - env: IEnvironmentalOptions, - options: IComputedEditorOptions, - value: V, - ): V { + public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: V): V { return value; } } @@ -1099,24 +1057,16 @@ abstract class BaseEditorOption export class ApplyUpdateResult { constructor( public readonly newValue: T, - public readonly didChange: boolean, - ) {} + public readonly didChange: boolean + ) { } } function applyUpdate(value: T | undefined, update: T): ApplyUpdateResult { - if ( - typeof value !== "object" || - typeof update !== "object" || - !value || - !update - ) { + if (typeof value !== 'object' || typeof update !== 'object' || !value || !update) { return new ApplyUpdateResult(update, value !== update); } if (Array.isArray(value) || Array.isArray(update)) { - const arrayEquals = - Array.isArray(value) && - Array.isArray(update) && - arrays.equals(value, update); + const arrayEquals = Array.isArray(value) && Array.isArray(update) && arrays.equals(value, update); return new ApplyUpdateResult(update, !arrayEquals); } let didChange = false; @@ -1135,18 +1085,16 @@ function applyUpdate(value: T | undefined, update: T): ApplyUpdateResult { /** * @internal */ -abstract class ComputedEditorOption - implements IEditorOption -{ +abstract class ComputedEditorOption implements IEditorOption { + public readonly id: K; - public readonly name: "_never_"; + public readonly name: '_never_'; public readonly defaultValue: V; - public readonly schema: IConfigurationPropertySchema | undefined = - undefined; + public readonly schema: IConfigurationPropertySchema | undefined = undefined; constructor(id: K) { this.id = id; - this.name = "_never_"; + this.name = '_never_'; this.defaultValue = undefined; } @@ -1158,27 +1106,17 @@ abstract class ComputedEditorOption return this.defaultValue; } - public abstract compute( - env: IEnvironmentalOptions, - options: IComputedEditorOptions, - value: V, - ): V; + public abstract compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: V): V; } -class SimpleEditorOption - implements IEditorOption -{ +class SimpleEditorOption implements IEditorOption { + public readonly id: K; public readonly name: PossibleKeyName; public readonly defaultValue: V; public readonly schema: IConfigurationPropertySchema | undefined; - constructor( - id: K, - name: PossibleKeyName, - defaultValue: V, - schema?: IConfigurationPropertySchema, - ) { + constructor(id: K, name: PossibleKeyName, defaultValue: V, schema?: IConfigurationPropertySchema) { this.id = id; this.name = name; this.defaultValue = defaultValue; @@ -1190,17 +1128,13 @@ class SimpleEditorOption } public validate(input: any): V { - if (typeof input === "undefined") { + if (typeof input === 'undefined') { return this.defaultValue; } return input as any; } - public compute( - env: IEnvironmentalOptions, - options: IComputedEditorOptions, - value: V, - ): V { + public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: V): V { return value; } } @@ -1209,28 +1143,21 @@ class SimpleEditorOption * @internal */ export function boolean(value: any, defaultValue: boolean): boolean { - if (typeof value === "undefined") { + if (typeof value === 'undefined') { return defaultValue; } - if (value === "false") { + if (value === 'false') { // treat the string 'false' as false return false; } return Boolean(value); } -class EditorBooleanOption extends SimpleEditorOption< - K, - boolean -> { - constructor( - id: K, - name: PossibleKeyName, - defaultValue: boolean, - schema: IConfigurationPropertySchema | undefined = undefined, - ) { - if (typeof schema !== "undefined") { - schema.type = "boolean"; +class EditorBooleanOption extends SimpleEditorOption { + + constructor(id: K, name: PossibleKeyName, defaultValue: boolean, schema: IConfigurationPropertySchema | undefined = undefined) { + if (typeof schema !== 'undefined') { + schema.type = 'boolean'; schema.default = defaultValue; } super(id, name, defaultValue, schema); @@ -1244,13 +1171,8 @@ class EditorBooleanOption extends SimpleEditorOption< /** * @internal */ -export function clampedInt( - value: any, - defaultValue: T, - minimum: number, - maximum: number, -): number | T { - if (typeof value === "undefined") { +export function clampedInt(value: any, defaultValue: T, minimum: number, maximum: number): number | T { + if (typeof value === 'undefined') { return defaultValue; } let r = parseInt(value, 10); @@ -1262,32 +1184,18 @@ export function clampedInt( return r | 0; } -class EditorIntOption extends SimpleEditorOption< - K, - number -> { - public static clampedInt( - value: any, - defaultValue: T, - minimum: number, - maximum: number, - ): number | T { +class EditorIntOption extends SimpleEditorOption { + + public static clampedInt(value: any, defaultValue: T, minimum: number, maximum: number): number | T { return clampedInt(value, defaultValue, minimum, maximum); } public readonly minimum: number; public readonly maximum: number; - constructor( - id: K, - name: PossibleKeyName, - defaultValue: number, - minimum: number, - maximum: number, - schema: IConfigurationPropertySchema | undefined = undefined, - ) { - if (typeof schema !== "undefined") { - schema.type = "integer"; + constructor(id: K, name: PossibleKeyName, defaultValue: number, minimum: number, maximum: number, schema: IConfigurationPropertySchema | undefined = undefined) { + if (typeof schema !== 'undefined') { + schema.type = 'integer'; schema.default = defaultValue; schema.minimum = minimum; schema.maximum = maximum; @@ -1298,34 +1206,22 @@ class EditorIntOption extends SimpleEditorOption< } public override validate(input: any): number { - return EditorIntOption.clampedInt( - input, - this.defaultValue, - this.minimum, - this.maximum, - ); + return EditorIntOption.clampedInt(input, this.defaultValue, this.minimum, this.maximum); } } /** * @internal */ -export function clampedFloat( - value: any, - defaultValue: T, - minimum: number, - maximum: number, -): number | T { - if (typeof value === "undefined") { +export function clampedFloat(value: any, defaultValue: T, minimum: number, maximum: number): number | T { + if (typeof value === 'undefined') { return defaultValue; } const r = EditorFloatOption.float(value, defaultValue); return EditorFloatOption.clamp(r, minimum, maximum); } -class EditorFloatOption extends SimpleEditorOption< - K, - number -> { +class EditorFloatOption extends SimpleEditorOption { + public static clamp(n: number, min: number, max: number): number { if (n < min) { return min; @@ -1337,27 +1233,21 @@ class EditorFloatOption extends SimpleEditorOption< } public static float(value: any, defaultValue: number): number { - if (typeof value === "number") { + if (typeof value === 'number') { return value; } - if (typeof value === "undefined") { + if (typeof value === 'undefined') { return defaultValue; } const r = parseFloat(value); - return isNaN(r) ? defaultValue : r; + return (isNaN(r) ? defaultValue : r); } public readonly validationFn: (value: number) => number; - constructor( - id: K, - name: PossibleKeyName, - defaultValue: number, - validationFn: (value: number) => number, - schema?: IConfigurationPropertySchema, - ) { - if (typeof schema !== "undefined") { - schema.type = "number"; + constructor(id: K, name: PossibleKeyName, defaultValue: number, validationFn: (value: number) => number, schema?: IConfigurationPropertySchema) { + if (typeof schema !== 'undefined') { + schema.type = 'number'; schema.default = defaultValue; } super(id, name, defaultValue, schema); @@ -1365,31 +1255,22 @@ class EditorFloatOption extends SimpleEditorOption< } public override validate(input: any): number { - return this.validationFn( - EditorFloatOption.float(input, this.defaultValue), - ); + return this.validationFn(EditorFloatOption.float(input, this.defaultValue)); } } -class EditorStringOption extends SimpleEditorOption< - K, - string -> { +class EditorStringOption extends SimpleEditorOption { + public static string(value: any, defaultValue: string): string { - if (typeof value !== "string") { + if (typeof value !== 'string') { return defaultValue; } return value; } - constructor( - id: K, - name: PossibleKeyName, - defaultValue: string, - schema: IConfigurationPropertySchema | undefined = undefined, - ) { - if (typeof schema !== "undefined") { - schema.type = "string"; + constructor(id: K, name: PossibleKeyName, defaultValue: string, schema: IConfigurationPropertySchema | undefined = undefined) { + if (typeof schema !== 'undefined') { + schema.type = 'string'; schema.default = defaultValue; } super(id, name, defaultValue, schema); @@ -1403,13 +1284,8 @@ class EditorStringOption extends SimpleEditorOption< /** * @internal */ -export function stringSet( - value: T | undefined, - defaultValue: T, - allowedValues: ReadonlyArray, - renamedValues?: Record, -): T { - if (typeof value !== "string") { +export function stringSet(value: T | undefined, defaultValue: T, allowedValues: ReadonlyArray, renamedValues?: Record): T { + if (typeof value !== 'string') { return defaultValue; } if (renamedValues && value in renamedValues) { @@ -1421,21 +1297,13 @@ export function stringSet( return value; } -class EditorStringEnumOption< - K extends EditorOption, - V extends string, -> extends SimpleEditorOption { +class EditorStringEnumOption extends SimpleEditorOption { + private readonly _allowedValues: ReadonlyArray; - constructor( - id: K, - name: PossibleKeyName, - defaultValue: V, - allowedValues: ReadonlyArray, - schema: IConfigurationPropertySchema | undefined = undefined, - ) { - if (typeof schema !== "undefined") { - schema.type = "string"; + constructor(id: K, name: PossibleKeyName, defaultValue: V, allowedValues: ReadonlyArray, schema: IConfigurationPropertySchema | undefined = undefined) { + if (typeof schema !== 'undefined') { + schema.type = 'string'; schema.enum = allowedValues; schema.default = defaultValue; } @@ -1448,25 +1316,14 @@ class EditorStringEnumOption< } } -class EditorEnumOption< - K extends EditorOption, - T extends string, - V, -> extends BaseEditorOption { +class EditorEnumOption extends BaseEditorOption { + private readonly _allowedValues: T[]; private readonly _convert: (value: T) => V; - constructor( - id: K, - name: PossibleKeyName, - defaultValue: V, - defaultStringValue: string, - allowedValues: T[], - convert: (value: T) => V, - schema: IConfigurationPropertySchema | undefined = undefined, - ) { - if (typeof schema !== "undefined") { - schema.type = "string"; + constructor(id: K, name: PossibleKeyName, defaultValue: V, defaultStringValue: string, allowedValues: T[], convert: (value: T) => V, schema: IConfigurationPropertySchema | undefined = undefined) { + if (typeof schema !== 'undefined') { + schema.type = 'string'; schema.enum = allowedValues; schema.default = defaultStringValue; } @@ -1476,7 +1333,7 @@ class EditorEnumOption< } public validate(input: any): V { - if (typeof input !== "string") { + if (typeof input !== 'string') { return this.defaultValue; } if (this._allowedValues.indexOf(input) === -1) { @@ -1490,20 +1347,13 @@ class EditorEnumOption< //#region autoIndent -function _autoIndentFromString( - autoIndent: "none" | "keep" | "brackets" | "advanced" | "full", -): EditorAutoIndentStrategy { +function _autoIndentFromString(autoIndent: 'none' | 'keep' | 'brackets' | 'advanced' | 'full'): EditorAutoIndentStrategy { switch (autoIndent) { - case "none": - return EditorAutoIndentStrategy.None; - case "keep": - return EditorAutoIndentStrategy.Keep; - case "brackets": - return EditorAutoIndentStrategy.Brackets; - case "advanced": - return EditorAutoIndentStrategy.Advanced; - case "full": - return EditorAutoIndentStrategy.Full; + case 'none': return EditorAutoIndentStrategy.None; + case 'keep': return EditorAutoIndentStrategy.Keep; + case 'brackets': return EditorAutoIndentStrategy.Brackets; + case 'advanced': return EditorAutoIndentStrategy.Advanced; + case 'full': return EditorAutoIndentStrategy.Full; } } @@ -1511,60 +1361,36 @@ function _autoIndentFromString( //#region accessibilitySupport -class EditorAccessibilitySupport extends BaseEditorOption< - EditorOption.accessibilitySupport, - "auto" | "off" | "on", - AccessibilitySupport -> { +class EditorAccessibilitySupport extends BaseEditorOption { + constructor() { super( - EditorOption.accessibilitySupport, - "accessibilitySupport", - AccessibilitySupport.Unknown, + EditorOption.accessibilitySupport, 'accessibilitySupport', AccessibilitySupport.Unknown, { - type: "string", - enum: ["auto", "on", "off"], + type: 'string', + enum: ['auto', 'on', 'off'], enumDescriptions: [ - nls.localize( - "accessibilitySupport.auto", - "Use platform APIs to detect when a Screen Reader is attached.", - ), - nls.localize( - "accessibilitySupport.on", - "Optimize for usage with a Screen Reader.", - ), - nls.localize( - "accessibilitySupport.off", - "Assume a screen reader is not attached.", - ), + nls.localize('accessibilitySupport.auto', "Use platform APIs to detect when a Screen Reader is attached."), + nls.localize('accessibilitySupport.on', "Optimize for usage with a Screen Reader."), + nls.localize('accessibilitySupport.off', "Assume a screen reader is not attached."), ], - default: "auto", - tags: ["accessibility"], - description: nls.localize( - "accessibilitySupport", - "Controls if the UI should run in a mode where it is optimized for screen readers.", - ), - }, + default: 'auto', + tags: ['accessibility'], + description: nls.localize('accessibilitySupport', "Controls if the UI should run in a mode where it is optimized for screen readers.") + } ); } public validate(input: any): AccessibilitySupport { switch (input) { - case "auto": - return AccessibilitySupport.Unknown; - case "off": - return AccessibilitySupport.Disabled; - case "on": - return AccessibilitySupport.Enabled; + case 'auto': return AccessibilitySupport.Unknown; + case 'off': return AccessibilitySupport.Disabled; + case 'on': return AccessibilitySupport.Enabled; } return this.defaultValue; } - public override compute( - env: IEnvironmentalOptions, - options: IComputedEditorOptions, - value: AccessibilitySupport, - ): AccessibilitySupport { + public override compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: AccessibilitySupport): AccessibilitySupport { if (value === AccessibilitySupport.Unknown) { // The editor reads the `accessibilitySupport` from the environment return env.accessibilitySupport; @@ -1598,50 +1424,38 @@ export interface IEditorCommentsOptions { */ export type EditorCommentsOptions = Readonly>; -class EditorComments extends BaseEditorOption< - EditorOption.comments, - IEditorCommentsOptions, - EditorCommentsOptions -> { +class EditorComments extends BaseEditorOption { + constructor() { const defaults: EditorCommentsOptions = { insertSpace: true, ignoreEmptyLines: true, }; - super(EditorOption.comments, "comments", defaults, { - "editor.comments.insertSpace": { - type: "boolean", - default: defaults.insertSpace, - description: nls.localize( - "comments.insertSpace", - "Controls whether a space character is inserted when commenting.", - ), - }, - "editor.comments.ignoreEmptyLines": { - type: "boolean", - default: defaults.ignoreEmptyLines, - description: nls.localize( - "comments.ignoreEmptyLines", - "Controls if empty lines should be ignored with toggle, add or remove actions for line comments.", - ), - }, - }); + super( + EditorOption.comments, 'comments', defaults, + { + 'editor.comments.insertSpace': { + type: 'boolean', + default: defaults.insertSpace, + description: nls.localize('comments.insertSpace', "Controls whether a space character is inserted when commenting.") + }, + 'editor.comments.ignoreEmptyLines': { + type: 'boolean', + default: defaults.ignoreEmptyLines, + description: nls.localize('comments.ignoreEmptyLines', 'Controls if empty lines should be ignored with toggle, add or remove actions for line comments.') + }, + } + ); } public validate(_input: any): EditorCommentsOptions { - if (!_input || typeof _input !== "object") { + if (!_input || typeof _input !== 'object') { return this.defaultValue; } const input = _input as IEditorCommentsOptions; return { - insertSpace: boolean( - input.insertSpace, - this.defaultValue.insertSpace, - ), - ignoreEmptyLines: boolean( - input.ignoreEmptyLines, - this.defaultValue.ignoreEmptyLines, - ), + insertSpace: boolean(input.insertSpace, this.defaultValue.insertSpace), + ignoreEmptyLines: boolean(input.ignoreEmptyLines, this.defaultValue.ignoreEmptyLines), }; } } @@ -1677,26 +1491,19 @@ export const enum TextEditorCursorBlinkingStyle { /** * No-Blinking */ - Solid = 5, + Solid = 5 } /** * @internal */ -export function cursorBlinkingStyleFromString( - cursorBlinkingStyle: "blink" | "smooth" | "phase" | "expand" | "solid", -): TextEditorCursorBlinkingStyle { +export function cursorBlinkingStyleFromString(cursorBlinkingStyle: 'blink' | 'smooth' | 'phase' | 'expand' | 'solid'): TextEditorCursorBlinkingStyle { switch (cursorBlinkingStyle) { - case "blink": - return TextEditorCursorBlinkingStyle.Blink; - case "smooth": - return TextEditorCursorBlinkingStyle.Smooth; - case "phase": - return TextEditorCursorBlinkingStyle.Phase; - case "expand": - return TextEditorCursorBlinkingStyle.Expand; - case "solid": - return TextEditorCursorBlinkingStyle.Solid; + case 'blink': return TextEditorCursorBlinkingStyle.Blink; + case 'smooth': return TextEditorCursorBlinkingStyle.Smooth; + case 'phase': return TextEditorCursorBlinkingStyle.Phase; + case 'expand': return TextEditorCursorBlinkingStyle.Expand; + case 'solid': return TextEditorCursorBlinkingStyle.Solid; } } @@ -1731,62 +1538,34 @@ export enum TextEditorCursorStyle { /** * As a thin horizontal line (sitting under a character). */ - UnderlineThin = 6, + UnderlineThin = 6 } /** * @internal */ -export function cursorStyleToString( - cursorStyle: TextEditorCursorStyle, -): - | "line" - | "block" - | "underline" - | "line-thin" - | "block-outline" - | "underline-thin" { +export function cursorStyleToString(cursorStyle: TextEditorCursorStyle): 'line' | 'block' | 'underline' | 'line-thin' | 'block-outline' | 'underline-thin' { switch (cursorStyle) { - case TextEditorCursorStyle.Line: - return "line"; - case TextEditorCursorStyle.Block: - return "block"; - case TextEditorCursorStyle.Underline: - return "underline"; - case TextEditorCursorStyle.LineThin: - return "line-thin"; - case TextEditorCursorStyle.BlockOutline: - return "block-outline"; - case TextEditorCursorStyle.UnderlineThin: - return "underline-thin"; + case TextEditorCursorStyle.Line: return 'line'; + case TextEditorCursorStyle.Block: return 'block'; + case TextEditorCursorStyle.Underline: return 'underline'; + case TextEditorCursorStyle.LineThin: return 'line-thin'; + case TextEditorCursorStyle.BlockOutline: return 'block-outline'; + case TextEditorCursorStyle.UnderlineThin: return 'underline-thin'; } } /** * @internal */ -export function cursorStyleFromString( - cursorStyle: - | "line" - | "block" - | "underline" - | "line-thin" - | "block-outline" - | "underline-thin", -): TextEditorCursorStyle { +export function cursorStyleFromString(cursorStyle: 'line' | 'block' | 'underline' | 'line-thin' | 'block-outline' | 'underline-thin'): TextEditorCursorStyle { switch (cursorStyle) { - case "line": - return TextEditorCursorStyle.Line; - case "block": - return TextEditorCursorStyle.Block; - case "underline": - return TextEditorCursorStyle.Underline; - case "line-thin": - return TextEditorCursorStyle.LineThin; - case "block-outline": - return TextEditorCursorStyle.BlockOutline; - case "underline-thin": - return TextEditorCursorStyle.UnderlineThin; + case 'line': return TextEditorCursorStyle.Line; + case 'block': return TextEditorCursorStyle.Block; + case 'underline': return TextEditorCursorStyle.Underline; + case 'line-thin': return TextEditorCursorStyle.LineThin; + case 'block-outline': return TextEditorCursorStyle.BlockOutline; + case 'underline-thin': return TextEditorCursorStyle.UnderlineThin; } } @@ -1794,41 +1573,35 @@ export function cursorStyleFromString( //#region editorClassName -class EditorClassName extends ComputedEditorOption< - EditorOption.editorClassName, - string -> { +class EditorClassName extends ComputedEditorOption { + constructor() { super(EditorOption.editorClassName); } - public compute( - env: IEnvironmentalOptions, - options: IComputedEditorOptions, - _: string, - ): string { - const classNames = ["monaco-editor"]; + public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, _: string): string { + const classNames = ['monaco-editor']; if (options.get(EditorOption.extraEditorClassName)) { classNames.push(options.get(EditorOption.extraEditorClassName)); } if (env.extraEditorClassName) { classNames.push(env.extraEditorClassName); } - if (options.get(EditorOption.mouseStyle) === "default") { - classNames.push("mouse-default"); - } else if (options.get(EditorOption.mouseStyle) === "copy") { - classNames.push("mouse-copy"); + if (options.get(EditorOption.mouseStyle) === 'default') { + classNames.push('mouse-default'); + } else if (options.get(EditorOption.mouseStyle) === 'copy') { + classNames.push('mouse-copy'); } if (options.get(EditorOption.showUnused)) { - classNames.push("showUnused"); + classNames.push('showUnused'); } if (options.get(EditorOption.showDeprecated)) { - classNames.push("showDeprecated"); + classNames.push('showDeprecated'); } - return classNames.join(" "); + return classNames.join(' '); } } @@ -1837,25 +1610,15 @@ class EditorClassName extends ComputedEditorOption< //#region emptySelectionClipboard class EditorEmptySelectionClipboard extends EditorBooleanOption { + constructor() { super( - EditorOption.emptySelectionClipboard, - "emptySelectionClipboard", - true, - { - description: nls.localize( - "emptySelectionClipboard", - "Controls whether copying without a selection copies the current line.", - ), - }, + EditorOption.emptySelectionClipboard, 'emptySelectionClipboard', true, + { description: nls.localize('emptySelectionClipboard', "Controls whether copying without a selection copies the current line.") } ); } - public override compute( - env: IEnvironmentalOptions, - options: IComputedEditorOptions, - value: boolean, - ): boolean { + public override compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: boolean): boolean { return value && env.emptySelectionClipboard; } } @@ -1869,17 +1632,17 @@ class EditorEmptySelectionClipboard extends EditorBooleanOption>; -class EditorFind extends BaseEditorOption< - EditorOption.find, - IEditorFindOptions, - EditorFindOptions -> { +class EditorFind extends BaseEditorOption { + constructor() { const defaults: EditorFindOptions = { cursorMoveOnType: true, - seedSearchStringFromSelection: "always", - autoFindInSelection: "never", + seedSearchStringFromSelection: 'always', + autoFindInSelection: 'never', globalFindClipboard: false, addExtraSpaceOnTop: true, loop: true, - findSearchHistory: "never", + findSearchHistory: 'never', }; - super(EditorOption.find, "find", defaults, { - "editor.find.cursorMoveOnType": { - type: "boolean", - default: defaults.cursorMoveOnType, - description: nls.localize( - "find.cursorMoveOnType", - "Controls whether the cursor should jump to find matches while typing.", - ), - }, - "editor.find.seedSearchStringFromSelection": { - type: "string", - enum: ["never", "always", "selection"], - default: defaults.seedSearchStringFromSelection, - enumDescriptions: [ - nls.localize( - "editor.find.seedSearchStringFromSelection.never", - "Never seed search string from the editor selection.", - ), - nls.localize( - "editor.find.seedSearchStringFromSelection.always", - "Always seed search string from the editor selection, including word at cursor position.", - ), - nls.localize( - "editor.find.seedSearchStringFromSelection.selection", - "Only seed search string from the editor selection.", - ), - ], - description: nls.localize( - "find.seedSearchStringFromSelection", - "Controls whether the search string in the Find Widget is seeded from the editor selection.", - ), - }, - "editor.find.autoFindInSelection": { - type: "string", - enum: ["never", "always", "multiline"], - default: defaults.autoFindInSelection, - enumDescriptions: [ - nls.localize( - "editor.find.autoFindInSelection.never", - "Never turn on Find in Selection automatically (default).", - ), - nls.localize( - "editor.find.autoFindInSelection.always", - "Always turn on Find in Selection automatically.", - ), - nls.localize( - "editor.find.autoFindInSelection.multiline", - "Turn on Find in Selection automatically when multiple lines of content are selected.", - ), - ], - description: nls.localize( - "find.autoFindInSelection", - "Controls the condition for turning on Find in Selection automatically.", - ), - }, - "editor.find.globalFindClipboard": { - type: "boolean", - default: defaults.globalFindClipboard, - description: nls.localize( - "find.globalFindClipboard", - "Controls whether the Find Widget should read or modify the shared find clipboard on macOS.", - ), - included: platform.isMacintosh, - }, - "editor.find.addExtraSpaceOnTop": { - type: "boolean", - default: defaults.addExtraSpaceOnTop, - description: nls.localize( - "find.addExtraSpaceOnTop", - "Controls whether the Find Widget should add extra lines on top of the editor. When true, you can scroll beyond the first line when the Find Widget is visible.", - ), - }, - "editor.find.loop": { - type: "boolean", - default: defaults.loop, - description: nls.localize( - "find.loop", - "Controls whether the search automatically restarts from the beginning (or the end) when no further matches can be found.", - ), - }, - "editor.find.history": { - type: "string", - enum: ["never", "workspace"], - default: - typeof product.quality === "string" && - product.quality !== "stable" - ? "workspace" - : "none", - enumDescriptions: [ - nls.localize( - "editor.find.history.never", - "Do not store search history from the find widget.", - ), - nls.localize( - "editor.find.history.workspace", - "Store search history across the active workspace", - ), - ], - description: nls.localize( - "find.history", - "Controls how the find widget history should be stored", - ), - }, - }); + super( + EditorOption.find, 'find', defaults, + { + 'editor.find.cursorMoveOnType': { + type: 'boolean', + default: defaults.cursorMoveOnType, + description: nls.localize('find.cursorMoveOnType', "Controls whether the cursor should jump to find matches while typing.") + }, + 'editor.find.seedSearchStringFromSelection': { + type: 'string', + enum: ['never', 'always', 'selection'], + default: defaults.seedSearchStringFromSelection, + enumDescriptions: [ + nls.localize('editor.find.seedSearchStringFromSelection.never', 'Never seed search string from the editor selection.'), + nls.localize('editor.find.seedSearchStringFromSelection.always', 'Always seed search string from the editor selection, including word at cursor position.'), + nls.localize('editor.find.seedSearchStringFromSelection.selection', 'Only seed search string from the editor selection.') + ], + description: nls.localize('find.seedSearchStringFromSelection', "Controls whether the search string in the Find Widget is seeded from the editor selection.") + }, + 'editor.find.autoFindInSelection': { + type: 'string', + enum: ['never', 'always', 'multiline'], + default: defaults.autoFindInSelection, + enumDescriptions: [ + nls.localize('editor.find.autoFindInSelection.never', 'Never turn on Find in Selection automatically (default).'), + nls.localize('editor.find.autoFindInSelection.always', 'Always turn on Find in Selection automatically.'), + nls.localize('editor.find.autoFindInSelection.multiline', 'Turn on Find in Selection automatically when multiple lines of content are selected.') + ], + description: nls.localize('find.autoFindInSelection', "Controls the condition for turning on Find in Selection automatically.") + }, + 'editor.find.globalFindClipboard': { + type: 'boolean', + default: defaults.globalFindClipboard, + description: nls.localize('find.globalFindClipboard', "Controls whether the Find Widget should read or modify the shared find clipboard on macOS."), + included: platform.isMacintosh + }, + 'editor.find.addExtraSpaceOnTop': { + type: 'boolean', + default: defaults.addExtraSpaceOnTop, + description: nls.localize('find.addExtraSpaceOnTop', "Controls whether the Find Widget should add extra lines on top of the editor. When true, you can scroll beyond the first line when the Find Widget is visible.") + }, + 'editor.find.loop': { + type: 'boolean', + default: defaults.loop, + description: nls.localize('find.loop', "Controls whether the search automatically restarts from the beginning (or the end) when no further matches can be found.") + }, + 'editor.find.history': { + type: 'string', + enum: ['never', 'workspace'], + default: typeof product.quality === 'string' && product.quality !== 'stable' ? 'workspace' : 'none', + enumDescriptions: [ + nls.localize('editor.find.history.never', 'Do not store search history from the find widget.'), + nls.localize('editor.find.history.workspace', 'Store search history across the active workspace'), + ], + description: nls.localize('find.history', "Controls how the find widget history should be stored") + } + } + ); } public validate(_input: any): EditorFindOptions { - if (!_input || typeof _input !== "object") { + if (!_input || typeof _input !== 'object') { return this.defaultValue; } const input = _input as IEditorFindOptions; return { - cursorMoveOnType: boolean( - input.cursorMoveOnType, - this.defaultValue.cursorMoveOnType, - ), - seedSearchStringFromSelection: - typeof _input.seedSearchStringFromSelection === "boolean" - ? _input.seedSearchStringFromSelection - ? "always" - : "never" - : stringSet<"never" | "always" | "selection">( - input.seedSearchStringFromSelection, - this.defaultValue.seedSearchStringFromSelection, - ["never", "always", "selection"], - ), - autoFindInSelection: - typeof _input.autoFindInSelection === "boolean" - ? _input.autoFindInSelection - ? "always" - : "never" - : stringSet<"never" | "always" | "multiline">( - input.autoFindInSelection, - this.defaultValue.autoFindInSelection, - ["never", "always", "multiline"], - ), - globalFindClipboard: boolean( - input.globalFindClipboard, - this.defaultValue.globalFindClipboard, - ), - addExtraSpaceOnTop: boolean( - input.addExtraSpaceOnTop, - this.defaultValue.addExtraSpaceOnTop, - ), + cursorMoveOnType: boolean(input.cursorMoveOnType, this.defaultValue.cursorMoveOnType), + seedSearchStringFromSelection: typeof _input.seedSearchStringFromSelection === 'boolean' + ? (_input.seedSearchStringFromSelection ? 'always' : 'never') + : stringSet<'never' | 'always' | 'selection'>(input.seedSearchStringFromSelection, this.defaultValue.seedSearchStringFromSelection, ['never', 'always', 'selection']), + autoFindInSelection: typeof _input.autoFindInSelection === 'boolean' + ? (_input.autoFindInSelection ? 'always' : 'never') + : stringSet<'never' | 'always' | 'multiline'>(input.autoFindInSelection, this.defaultValue.autoFindInSelection, ['never', 'always', 'multiline']), + globalFindClipboard: boolean(input.globalFindClipboard, this.defaultValue.globalFindClipboard), + addExtraSpaceOnTop: boolean(input.addExtraSpaceOnTop, this.defaultValue.addExtraSpaceOnTop), loop: boolean(input.loop, this.defaultValue.loop), - findSearchHistory: stringSet<"never" | "workspace">( - input.findSearchHistory, - this.defaultValue.findSearchHistory, - ["never", "workspace"], - ), + findSearchHistory: stringSet<'never' | 'workspace'>(input.findSearchHistory, this.defaultValue.findSearchHistory, ['never', 'workspace']), }; } } @@ -2080,54 +1767,40 @@ class EditorFind extends BaseEditorOption< /** * @internal */ -export class EditorFontLigatures extends BaseEditorOption< - EditorOption.fontLigatures, - boolean | string, - string -> { +export class EditorFontLigatures extends BaseEditorOption { + public static OFF = '"liga" off, "calt" off'; public static ON = '"liga" on, "calt" on'; constructor() { super( - EditorOption.fontLigatures, - "fontLigatures", - EditorFontLigatures.OFF, + EditorOption.fontLigatures, 'fontLigatures', EditorFontLigatures.OFF, { anyOf: [ { - type: "boolean", - description: nls.localize( - "fontLigatures", - "Enables/Disables font ligatures ('calt' and 'liga' font features). Change this to a string for fine-grained control of the 'font-feature-settings' CSS property.", - ), + type: 'boolean', + description: nls.localize('fontLigatures', "Enables/Disables font ligatures ('calt' and 'liga' font features). Change this to a string for fine-grained control of the 'font-feature-settings' CSS property."), }, { - type: "string", - description: nls.localize( - "fontFeatureSettings", - "Explicit 'font-feature-settings' CSS property. A boolean can be passed instead if one only needs to turn on/off ligatures.", - ), - }, + type: 'string', + description: nls.localize('fontFeatureSettings', "Explicit 'font-feature-settings' CSS property. A boolean can be passed instead if one only needs to turn on/off ligatures.") + } ], - description: nls.localize( - "fontLigaturesGeneral", - "Configures font ligatures or font features. Can be either a boolean to enable/disable ligatures or a string for the value of the CSS 'font-feature-settings' property.", - ), - default: false, - }, + description: nls.localize('fontLigaturesGeneral', "Configures font ligatures or font features. Can be either a boolean to enable/disable ligatures or a string for the value of the CSS 'font-feature-settings' property."), + default: false + } ); } public validate(input: any): string { - if (typeof input === "undefined") { + if (typeof input === 'undefined') { return this.defaultValue; } - if (typeof input === "string") { - if (input === "false" || input.length === 0) { + if (typeof input === 'string') { + if (input === 'false' || input.length === 0) { return EditorFontLigatures.OFF; } - if (input === "true") { + if (input === 'true') { return EditorFontLigatures.ON; } return input; @@ -2146,57 +1819,42 @@ export class EditorFontLigatures extends BaseEditorOption< /** * @internal */ -export class EditorFontVariations extends BaseEditorOption< - EditorOption.fontVariations, - boolean | string, - string -> { +export class EditorFontVariations extends BaseEditorOption { // Text is laid out using default settings. - public static OFF = "normal"; + public static OFF = 'normal'; // Translate `fontWeight` config to the `font-variation-settings` CSS property. - public static TRANSLATE = "translate"; + public static TRANSLATE = 'translate'; constructor() { super( - EditorOption.fontVariations, - "fontVariations", - EditorFontVariations.OFF, + EditorOption.fontVariations, 'fontVariations', EditorFontVariations.OFF, { anyOf: [ { - type: "boolean", - description: nls.localize( - "fontVariations", - "Enables/Disables the translation from font-weight to font-variation-settings. Change this to a string for fine-grained control of the 'font-variation-settings' CSS property.", - ), + type: 'boolean', + description: nls.localize('fontVariations', "Enables/Disables the translation from font-weight to font-variation-settings. Change this to a string for fine-grained control of the 'font-variation-settings' CSS property."), }, { - type: "string", - description: nls.localize( - "fontVariationSettings", - "Explicit 'font-variation-settings' CSS property. A boolean can be passed instead if one only needs to translate font-weight to font-variation-settings.", - ), - }, + type: 'string', + description: nls.localize('fontVariationSettings', "Explicit 'font-variation-settings' CSS property. A boolean can be passed instead if one only needs to translate font-weight to font-variation-settings.") + } ], - description: nls.localize( - "fontVariationsGeneral", - "Configures font variations. Can be either a boolean to enable/disable the translation from font-weight to font-variation-settings or a string for the value of the CSS 'font-variation-settings' property.", - ), - default: false, - }, + description: nls.localize('fontVariationsGeneral', "Configures font variations. Can be either a boolean to enable/disable the translation from font-weight to font-variation-settings or a string for the value of the CSS 'font-variation-settings' property."), + default: false + } ); } public validate(input: any): string { - if (typeof input === "undefined") { + if (typeof input === 'undefined') { return this.defaultValue; } - if (typeof input === "string") { - if (input === "false") { + if (typeof input === 'string') { + if (input === 'false') { return EditorFontVariations.OFF; } - if (input === "true") { + if (input === 'true') { return EditorFontVariations.TRANSLATE; } return input; @@ -2207,11 +1865,7 @@ export class EditorFontVariations extends BaseEditorOption< return EditorFontVariations.OFF; } - public override compute( - env: IEnvironmentalOptions, - options: IComputedEditorOptions, - value: string, - ): string { + public override compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: string): string { // The value is computed from the fontWeight if it is true. // So take the result from env.fontInfo return env.fontInfo.fontVariationSettings; @@ -2222,19 +1876,13 @@ export class EditorFontVariations extends BaseEditorOption< //#region fontInfo -class EditorFontInfo extends ComputedEditorOption< - EditorOption.fontInfo, - FontInfo -> { +class EditorFontInfo extends ComputedEditorOption { + constructor() { super(EditorOption.fontInfo); } - public compute( - env: IEnvironmentalOptions, - options: IComputedEditorOptions, - _: FontInfo, - ): FontInfo { + public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, _: FontInfo): FontInfo { return env.fontInfo; } } @@ -2244,21 +1892,17 @@ class EditorFontInfo extends ComputedEditorOption< //#region fontSize class EditorFontSize extends SimpleEditorOption { + constructor() { super( - EditorOption.fontSize, - "fontSize", - EDITOR_FONT_DEFAULTS.fontSize, + EditorOption.fontSize, 'fontSize', EDITOR_FONT_DEFAULTS.fontSize, { - type: "number", + type: 'number', minimum: 6, maximum: 100, default: EDITOR_FONT_DEFAULTS.fontSize, - description: nls.localize( - "fontSize", - "Controls the font size in pixels.", - ), - }, + description: nls.localize('fontSize', "Controls the font size in pixels.") + } ); } @@ -2269,11 +1913,7 @@ class EditorFontSize extends SimpleEditorOption { } return EditorFloatOption.clamp(r, 6, 100); } - public override compute( - env: IEnvironmentalOptions, - options: IComputedEditorOptions, - value: number, - ): number { + public override compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: number): number { // The final fontSize respects the editor zoom level. // So take the result from env.fontInfo return env.fontInfo.fontSize; @@ -2284,72 +1924,41 @@ class EditorFontSize extends SimpleEditorOption { //#region fontWeight -class EditorFontWeight extends BaseEditorOption< - EditorOption.fontWeight, - string, - string -> { - private static SUGGESTION_VALUES = [ - "normal", - "bold", - "100", - "200", - "300", - "400", - "500", - "600", - "700", - "800", - "900", - ]; +class EditorFontWeight extends BaseEditorOption { + private static SUGGESTION_VALUES = ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900']; private static MINIMUM_VALUE = 1; private static MAXIMUM_VALUE = 1000; constructor() { super( - EditorOption.fontWeight, - "fontWeight", - EDITOR_FONT_DEFAULTS.fontWeight, + EditorOption.fontWeight, 'fontWeight', EDITOR_FONT_DEFAULTS.fontWeight, { anyOf: [ { - type: "number", + type: 'number', minimum: EditorFontWeight.MINIMUM_VALUE, maximum: EditorFontWeight.MAXIMUM_VALUE, - errorMessage: nls.localize( - "fontWeightErrorMessage", - 'Only "normal" and "bold" keywords or numbers between 1 and 1000 are allowed.', - ), + errorMessage: nls.localize('fontWeightErrorMessage', "Only \"normal\" and \"bold\" keywords or numbers between 1 and 1000 are allowed.") }, { - type: "string", - pattern: "^(normal|bold|1000|[1-9][0-9]{0,2})$", + type: 'string', + pattern: '^(normal|bold|1000|[1-9][0-9]{0,2})$' }, { - enum: EditorFontWeight.SUGGESTION_VALUES, - }, + enum: EditorFontWeight.SUGGESTION_VALUES + } ], default: EDITOR_FONT_DEFAULTS.fontWeight, - description: nls.localize( - "fontWeight", - 'Controls the font weight. Accepts "normal" and "bold" keywords or numbers between 1 and 1000.', - ), - }, + description: nls.localize('fontWeight', "Controls the font weight. Accepts \"normal\" and \"bold\" keywords or numbers between 1 and 1000.") + } ); } public validate(input: any): string { - if (input === "normal" || input === "bold") { + if (input === 'normal' || input === 'bold') { return input; } - return String( - EditorIntOption.clampedInt( - input, - EDITOR_FONT_DEFAULTS.fontWeight, - EditorFontWeight.MINIMUM_VALUE, - EditorFontWeight.MAXIMUM_VALUE, - ), - ); + return String(EditorIntOption.clampedInt(input, EDITOR_FONT_DEFAULTS.fontWeight, EditorFontWeight.MINIMUM_VALUE, EditorFontWeight.MAXIMUM_VALUE)); } } @@ -2357,12 +1966,13 @@ class EditorFontWeight extends BaseEditorOption< //#region gotoLocation -export type GoToLocationValues = "peek" | "gotoAndPeek" | "goto"; +export type GoToLocationValues = 'peek' | 'gotoAndPeek' | 'goto'; /** * Configuration options for go to location */ export interface IGotoLocationOptions { + multiple?: GoToLocationValues; multipleDefinitions?: GoToLocationValues; @@ -2385,227 +1995,114 @@ export interface IGotoLocationOptions { */ export type GoToLocationOptions = Readonly>; -class EditorGoToLocation extends BaseEditorOption< - EditorOption.gotoLocation, - IGotoLocationOptions, - GoToLocationOptions -> { +class EditorGoToLocation extends BaseEditorOption { + constructor() { const defaults: GoToLocationOptions = { - multiple: "peek", - multipleDefinitions: "peek", - multipleTypeDefinitions: "peek", - multipleDeclarations: "peek", - multipleImplementations: "peek", - multipleReferences: "peek", - multipleTests: "peek", - alternativeDefinitionCommand: "editor.action.goToReferences", - alternativeTypeDefinitionCommand: "editor.action.goToReferences", - alternativeDeclarationCommand: "editor.action.goToReferences", - alternativeImplementationCommand: "", - alternativeReferenceCommand: "", - alternativeTestsCommand: "", + multiple: 'peek', + multipleDefinitions: 'peek', + multipleTypeDefinitions: 'peek', + multipleDeclarations: 'peek', + multipleImplementations: 'peek', + multipleReferences: 'peek', + multipleTests: 'peek', + alternativeDefinitionCommand: 'editor.action.goToReferences', + alternativeTypeDefinitionCommand: 'editor.action.goToReferences', + alternativeDeclarationCommand: 'editor.action.goToReferences', + alternativeImplementationCommand: '', + alternativeReferenceCommand: '', + alternativeTestsCommand: '', }; const jsonSubset: IJSONSchema = { - type: "string", - enum: ["peek", "gotoAndPeek", "goto"], + type: 'string', + enum: ['peek', 'gotoAndPeek', 'goto'], default: defaults.multiple, enumDescriptions: [ - nls.localize( - "editor.gotoLocation.multiple.peek", - "Show Peek view of the results (default)", - ), - nls.localize( - "editor.gotoLocation.multiple.gotoAndPeek", - "Go to the primary result and show a Peek view", - ), - nls.localize( - "editor.gotoLocation.multiple.goto", - "Go to the primary result and enable Peek-less navigation to others", - ), - ], + nls.localize('editor.gotoLocation.multiple.peek', 'Show Peek view of the results (default)'), + nls.localize('editor.gotoLocation.multiple.gotoAndPeek', 'Go to the primary result and show a Peek view'), + nls.localize('editor.gotoLocation.multiple.goto', 'Go to the primary result and enable Peek-less navigation to others') + ] }; - const alternativeCommandOptions = [ - "", - "editor.action.referenceSearch.trigger", - "editor.action.goToReferences", - "editor.action.peekImplementation", - "editor.action.goToImplementation", - "editor.action.peekTypeDefinition", - "editor.action.goToTypeDefinition", - "editor.action.peekDeclaration", - "editor.action.revealDeclaration", - "editor.action.peekDefinition", - "editor.action.revealDefinitionAside", - "editor.action.revealDefinition", - ]; - super(EditorOption.gotoLocation, "gotoLocation", defaults, { - "editor.gotoLocation.multiple": { - deprecationMessage: nls.localize( - "editor.gotoLocation.multiple.deprecated", - "This setting is deprecated, please use separate settings like 'editor.editor.gotoLocation.multipleDefinitions' or 'editor.editor.gotoLocation.multipleImplementations' instead.", - ), - }, - "editor.gotoLocation.multipleDefinitions": { - description: nls.localize( - "editor.editor.gotoLocation.multipleDefinitions", - "Controls the behavior the 'Go to Definition'-command when multiple target locations exist.", - ), - ...jsonSubset, - }, - "editor.gotoLocation.multipleTypeDefinitions": { - description: nls.localize( - "editor.editor.gotoLocation.multipleTypeDefinitions", - "Controls the behavior the 'Go to Type Definition'-command when multiple target locations exist.", - ), - ...jsonSubset, - }, - "editor.gotoLocation.multipleDeclarations": { - description: nls.localize( - "editor.editor.gotoLocation.multipleDeclarations", - "Controls the behavior the 'Go to Declaration'-command when multiple target locations exist.", - ), - ...jsonSubset, - }, - "editor.gotoLocation.multipleImplementations": { - description: nls.localize( - "editor.editor.gotoLocation.multipleImplemenattions", - "Controls the behavior the 'Go to Implementations'-command when multiple target locations exist.", - ), - ...jsonSubset, - }, - "editor.gotoLocation.multipleReferences": { - description: nls.localize( - "editor.editor.gotoLocation.multipleReferences", - "Controls the behavior the 'Go to References'-command when multiple target locations exist.", - ), - ...jsonSubset, - }, - "editor.gotoLocation.alternativeDefinitionCommand": { - type: "string", - default: defaults.alternativeDefinitionCommand, - enum: alternativeCommandOptions, - description: nls.localize( - "alternativeDefinitionCommand", - "Alternative command id that is being executed when the result of 'Go to Definition' is the current location.", - ), - }, - "editor.gotoLocation.alternativeTypeDefinitionCommand": { - type: "string", - default: defaults.alternativeTypeDefinitionCommand, - enum: alternativeCommandOptions, - description: nls.localize( - "alternativeTypeDefinitionCommand", - "Alternative command id that is being executed when the result of 'Go to Type Definition' is the current location.", - ), - }, - "editor.gotoLocation.alternativeDeclarationCommand": { - type: "string", - default: defaults.alternativeDeclarationCommand, - enum: alternativeCommandOptions, - description: nls.localize( - "alternativeDeclarationCommand", - "Alternative command id that is being executed when the result of 'Go to Declaration' is the current location.", - ), - }, - "editor.gotoLocation.alternativeImplementationCommand": { - type: "string", - default: defaults.alternativeImplementationCommand, - enum: alternativeCommandOptions, - description: nls.localize( - "alternativeImplementationCommand", - "Alternative command id that is being executed when the result of 'Go to Implementation' is the current location.", - ), - }, - "editor.gotoLocation.alternativeReferenceCommand": { - type: "string", - default: defaults.alternativeReferenceCommand, - enum: alternativeCommandOptions, - description: nls.localize( - "alternativeReferenceCommand", - "Alternative command id that is being executed when the result of 'Go to Reference' is the current location.", - ), - }, - }); + const alternativeCommandOptions = ['', 'editor.action.referenceSearch.trigger', 'editor.action.goToReferences', 'editor.action.peekImplementation', 'editor.action.goToImplementation', 'editor.action.peekTypeDefinition', 'editor.action.goToTypeDefinition', 'editor.action.peekDeclaration', 'editor.action.revealDeclaration', 'editor.action.peekDefinition', 'editor.action.revealDefinitionAside', 'editor.action.revealDefinition']; + super( + EditorOption.gotoLocation, 'gotoLocation', defaults, + { + 'editor.gotoLocation.multiple': { + deprecationMessage: nls.localize('editor.gotoLocation.multiple.deprecated', "This setting is deprecated, please use separate settings like 'editor.editor.gotoLocation.multipleDefinitions' or 'editor.editor.gotoLocation.multipleImplementations' instead."), + }, + 'editor.gotoLocation.multipleDefinitions': { + description: nls.localize('editor.editor.gotoLocation.multipleDefinitions', "Controls the behavior the 'Go to Definition'-command when multiple target locations exist."), + ...jsonSubset, + }, + 'editor.gotoLocation.multipleTypeDefinitions': { + description: nls.localize('editor.editor.gotoLocation.multipleTypeDefinitions', "Controls the behavior the 'Go to Type Definition'-command when multiple target locations exist."), + ...jsonSubset, + }, + 'editor.gotoLocation.multipleDeclarations': { + description: nls.localize('editor.editor.gotoLocation.multipleDeclarations', "Controls the behavior the 'Go to Declaration'-command when multiple target locations exist."), + ...jsonSubset, + }, + 'editor.gotoLocation.multipleImplementations': { + description: nls.localize('editor.editor.gotoLocation.multipleImplemenattions', "Controls the behavior the 'Go to Implementations'-command when multiple target locations exist."), + ...jsonSubset, + }, + 'editor.gotoLocation.multipleReferences': { + description: nls.localize('editor.editor.gotoLocation.multipleReferences', "Controls the behavior the 'Go to References'-command when multiple target locations exist."), + ...jsonSubset, + }, + 'editor.gotoLocation.alternativeDefinitionCommand': { + type: 'string', + default: defaults.alternativeDefinitionCommand, + enum: alternativeCommandOptions, + description: nls.localize('alternativeDefinitionCommand', "Alternative command id that is being executed when the result of 'Go to Definition' is the current location.") + }, + 'editor.gotoLocation.alternativeTypeDefinitionCommand': { + type: 'string', + default: defaults.alternativeTypeDefinitionCommand, + enum: alternativeCommandOptions, + description: nls.localize('alternativeTypeDefinitionCommand', "Alternative command id that is being executed when the result of 'Go to Type Definition' is the current location.") + }, + 'editor.gotoLocation.alternativeDeclarationCommand': { + type: 'string', + default: defaults.alternativeDeclarationCommand, + enum: alternativeCommandOptions, + description: nls.localize('alternativeDeclarationCommand', "Alternative command id that is being executed when the result of 'Go to Declaration' is the current location.") + }, + 'editor.gotoLocation.alternativeImplementationCommand': { + type: 'string', + default: defaults.alternativeImplementationCommand, + enum: alternativeCommandOptions, + description: nls.localize('alternativeImplementationCommand', "Alternative command id that is being executed when the result of 'Go to Implementation' is the current location.") + }, + 'editor.gotoLocation.alternativeReferenceCommand': { + type: 'string', + default: defaults.alternativeReferenceCommand, + enum: alternativeCommandOptions, + description: nls.localize('alternativeReferenceCommand', "Alternative command id that is being executed when the result of 'Go to Reference' is the current location.") + }, + } + ); } public validate(_input: any): GoToLocationOptions { - if (!_input || typeof _input !== "object") { + if (!_input || typeof _input !== 'object') { return this.defaultValue; } const input = _input as IGotoLocationOptions; return { - multiple: stringSet( - input.multiple, - this.defaultValue.multiple, - ["peek", "gotoAndPeek", "goto"], - ), - multipleDefinitions: - input.multipleDefinitions ?? - stringSet( - input.multipleDefinitions, - "peek", - ["peek", "gotoAndPeek", "goto"], - ), - multipleTypeDefinitions: - input.multipleTypeDefinitions ?? - stringSet( - input.multipleTypeDefinitions, - "peek", - ["peek", "gotoAndPeek", "goto"], - ), - multipleDeclarations: - input.multipleDeclarations ?? - stringSet( - input.multipleDeclarations, - "peek", - ["peek", "gotoAndPeek", "goto"], - ), - multipleImplementations: - input.multipleImplementations ?? - stringSet( - input.multipleImplementations, - "peek", - ["peek", "gotoAndPeek", "goto"], - ), - multipleReferences: - input.multipleReferences ?? - stringSet( - input.multipleReferences, - "peek", - ["peek", "gotoAndPeek", "goto"], - ), - multipleTests: - input.multipleTests ?? - stringSet(input.multipleTests, "peek", [ - "peek", - "gotoAndPeek", - "goto", - ]), - alternativeDefinitionCommand: EditorStringOption.string( - input.alternativeDefinitionCommand, - this.defaultValue.alternativeDefinitionCommand, - ), - alternativeTypeDefinitionCommand: EditorStringOption.string( - input.alternativeTypeDefinitionCommand, - this.defaultValue.alternativeTypeDefinitionCommand, - ), - alternativeDeclarationCommand: EditorStringOption.string( - input.alternativeDeclarationCommand, - this.defaultValue.alternativeDeclarationCommand, - ), - alternativeImplementationCommand: EditorStringOption.string( - input.alternativeImplementationCommand, - this.defaultValue.alternativeImplementationCommand, - ), - alternativeReferenceCommand: EditorStringOption.string( - input.alternativeReferenceCommand, - this.defaultValue.alternativeReferenceCommand, - ), - alternativeTestsCommand: EditorStringOption.string( - input.alternativeTestsCommand, - this.defaultValue.alternativeTestsCommand, - ), + multiple: stringSet(input.multiple, this.defaultValue.multiple, ['peek', 'gotoAndPeek', 'goto']), + multipleDefinitions: input.multipleDefinitions ?? stringSet(input.multipleDefinitions, 'peek', ['peek', 'gotoAndPeek', 'goto']), + multipleTypeDefinitions: input.multipleTypeDefinitions ?? stringSet(input.multipleTypeDefinitions, 'peek', ['peek', 'gotoAndPeek', 'goto']), + multipleDeclarations: input.multipleDeclarations ?? stringSet(input.multipleDeclarations, 'peek', ['peek', 'gotoAndPeek', 'goto']), + multipleImplementations: input.multipleImplementations ?? stringSet(input.multipleImplementations, 'peek', ['peek', 'gotoAndPeek', 'goto']), + multipleReferences: input.multipleReferences ?? stringSet(input.multipleReferences, 'peek', ['peek', 'gotoAndPeek', 'goto']), + multipleTests: input.multipleTests ?? stringSet(input.multipleTests, 'peek', ['peek', 'gotoAndPeek', 'goto']), + alternativeDefinitionCommand: EditorStringOption.string(input.alternativeDefinitionCommand, this.defaultValue.alternativeDefinitionCommand), + alternativeTypeDefinitionCommand: EditorStringOption.string(input.alternativeTypeDefinitionCommand, this.defaultValue.alternativeTypeDefinitionCommand), + alternativeDeclarationCommand: EditorStringOption.string(input.alternativeDeclarationCommand, this.defaultValue.alternativeDeclarationCommand), + alternativeImplementationCommand: EditorStringOption.string(input.alternativeImplementationCommand, this.defaultValue.alternativeImplementationCommand), + alternativeReferenceCommand: EditorStringOption.string(input.alternativeReferenceCommand, this.defaultValue.alternativeReferenceCommand), + alternativeTestsCommand: EditorStringOption.string(input.alternativeTestsCommand, this.defaultValue.alternativeTestsCommand), }; } } @@ -2650,11 +2147,8 @@ export interface IEditorHoverOptions { */ export type EditorHoverOptions = Readonly>; -class EditorHover extends BaseEditorOption< - EditorOption.hover, - IEditorHoverOptions, - EditorHoverOptions -> { +class EditorHover extends BaseEditorOption { + constructor() { const defaults: EditorHoverOptions = { enabled: true, @@ -2663,73 +2157,51 @@ class EditorHover extends BaseEditorOption< sticky: true, above: true, }; - super(EditorOption.hover, "hover", defaults, { - "editor.hover.enabled": { - type: "boolean", - default: defaults.enabled, - description: nls.localize( - "hover.enabled", - "Controls whether the hover is shown.", - ), - }, - "editor.hover.delay": { - type: "number", - default: defaults.delay, - minimum: 0, - maximum: 10000, - description: nls.localize( - "hover.delay", - "Controls the delay in milliseconds after which the hover is shown.", - ), - }, - "editor.hover.sticky": { - type: "boolean", - default: defaults.sticky, - description: nls.localize( - "hover.sticky", - "Controls whether the hover should remain visible when mouse is moved over it.", - ), - }, - "editor.hover.hidingDelay": { - type: "integer", - minimum: 0, - default: defaults.hidingDelay, - description: nls.localize( - "hover.hidingDelay", - "Controls the delay in milliseconds after which the hover is hidden. Requires `editor.hover.sticky` to be enabled.", - ), - }, - "editor.hover.above": { - type: "boolean", - default: defaults.above, - description: nls.localize( - "hover.above", - "Prefer showing hovers above the line, if there's space.", - ), - }, - }); + super( + EditorOption.hover, 'hover', defaults, + { + 'editor.hover.enabled': { + type: 'boolean', + default: defaults.enabled, + description: nls.localize('hover.enabled', "Controls whether the hover is shown.") + }, + 'editor.hover.delay': { + type: 'number', + default: defaults.delay, + minimum: 0, + maximum: 10000, + description: nls.localize('hover.delay', "Controls the delay in milliseconds after which the hover is shown.") + }, + 'editor.hover.sticky': { + type: 'boolean', + default: defaults.sticky, + description: nls.localize('hover.sticky', "Controls whether the hover should remain visible when mouse is moved over it.") + }, + 'editor.hover.hidingDelay': { + type: 'integer', + minimum: 0, + default: defaults.hidingDelay, + description: nls.localize('hover.hidingDelay', "Controls the delay in milliseconds after which the hover is hidden. Requires `editor.hover.sticky` to be enabled.") + }, + 'editor.hover.above': { + type: 'boolean', + default: defaults.above, + description: nls.localize('hover.above', "Prefer showing hovers above the line, if there's space.") + }, + } + ); } public validate(_input: any): EditorHoverOptions { - if (!_input || typeof _input !== "object") { + if (!_input || typeof _input !== 'object') { return this.defaultValue; } const input = _input as IEditorHoverOptions; return { enabled: boolean(input.enabled, this.defaultValue.enabled), - delay: EditorIntOption.clampedInt( - input.delay, - this.defaultValue.delay, - 0, - 10000, - ), + delay: EditorIntOption.clampedInt(input.delay, this.defaultValue.delay, 0, 10000), sticky: boolean(input.sticky, this.defaultValue.sticky), - hidingDelay: EditorIntOption.clampedInt( - input.hidingDelay, - this.defaultValue.hidingDelay, - 0, - 600000, - ), + hidingDelay: EditorIntOption.clampedInt(input.hidingDelay, this.defaultValue.hidingDelay, 0, 600000), above: boolean(input.above, this.defaultValue.above), }; } @@ -2771,6 +2243,7 @@ export const enum RenderMinimap { * The internal layout details of the editor. */ export interface EditorLayoutInfo { + /** * Full editor width. */ @@ -2904,7 +2377,7 @@ export interface IEditorLayoutComputerInput { readonly lineNumbers: InternalEditorRenderLineNumbersOptions; readonly lineNumbersMinChars: number; readonly scrollBeyondLastLine: boolean; - readonly wordWrap: "wordWrapColumn" | "on" | "off" | "bounded"; + readonly wordWrap: 'wordWrapColumn' | 'on' | 'off' | 'bounded'; readonly wordWrapColumn: number; readonly wordWrapMinified: boolean; readonly accessibilitySupport: AccessibilitySupport; @@ -2932,19 +2405,13 @@ export interface IMinimapLayoutInput { /** * @internal */ -export class EditorLayoutInfoComputer extends ComputedEditorOption< - EditorOption.layoutInfo, - EditorLayoutInfo -> { +export class EditorLayoutInfoComputer extends ComputedEditorOption { + constructor() { super(EditorOption.layoutInfo); } - public compute( - env: IEnvironmentalOptions, - options: IComputedEditorOptions, - _: EditorLayoutInfo, - ): EditorLayoutInfo { + public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, _: EditorLayoutInfo): EditorLayoutInfo { return EditorLayoutInfoComputer.computeLayout(options, { memory: env.memory, outerWidth: env.outerWidth, @@ -2953,11 +2420,10 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption< lineHeight: env.fontInfo.lineHeight, viewLineCount: env.viewLineCount, lineNumbersDigitCount: env.lineNumbersDigitCount, - typicalHalfwidthCharacterWidth: - env.fontInfo.typicalHalfwidthCharacterWidth, + typicalHalfwidthCharacterWidth: env.fontInfo.typicalHalfwidthCharacterWidth, maxDigitWidth: env.fontInfo.maxDigitWidth, pixelRatio: env.pixelRatio, - glyphMarginDecorationLaneCount: env.glyphMarginDecorationLaneCount, + glyphMarginDecorationLaneCount: env.glyphMarginDecorationLaneCount }); } @@ -2969,45 +2435,19 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption< height: number; lineHeight: number; pixelRatio: number; - }): { - typicalViewportLineCount: number; - extraLinesBeforeFirstLine: number; - extraLinesBeyondLastLine: number; - desiredRatio: number; - minimapLineCount: number; - } { + }): { typicalViewportLineCount: number; extraLinesBeforeFirstLine: number; extraLinesBeyondLastLine: number; desiredRatio: number; minimapLineCount: number } { const typicalViewportLineCount = input.height / input.lineHeight; - const extraLinesBeforeFirstLine = Math.floor( - input.paddingTop / input.lineHeight, - ); - let extraLinesBeyondLastLine = Math.floor( - input.paddingBottom / input.lineHeight, - ); + const extraLinesBeforeFirstLine = Math.floor(input.paddingTop / input.lineHeight); + let extraLinesBeyondLastLine = Math.floor(input.paddingBottom / input.lineHeight); if (input.scrollBeyondLastLine) { - extraLinesBeyondLastLine = Math.max( - extraLinesBeyondLastLine, - typicalViewportLineCount - 1, - ); + extraLinesBeyondLastLine = Math.max(extraLinesBeyondLastLine, typicalViewportLineCount - 1); } - const desiredRatio = - (extraLinesBeforeFirstLine + - input.viewLineCount + - extraLinesBeyondLastLine) / - (input.pixelRatio * input.height); + const desiredRatio = (extraLinesBeforeFirstLine + input.viewLineCount + extraLinesBeyondLastLine) / (input.pixelRatio * input.height); const minimapLineCount = Math.floor(input.viewLineCount / desiredRatio); - return { - typicalViewportLineCount, - extraLinesBeforeFirstLine, - extraLinesBeyondLastLine, - desiredRatio, - minimapLineCount, - }; + return { typicalViewportLineCount, extraLinesBeforeFirstLine, extraLinesBeyondLastLine, desiredRatio, minimapLineCount }; } - private static _computeMinimapLayout( - input: IMinimapLayoutInput, - memory: ComputeOptionsMemory, - ): EditorMinimapLayoutInfo { + private static _computeMinimapLayout(input: IMinimapLayoutInput, memory: ComputeOptionsMemory): EditorMinimapLayoutInfo { const outerWidth = input.outerWidth; const outerHeight = input.outerHeight; const pixelRatio = input.pixelRatio; @@ -3030,45 +2470,34 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption< // Can use memory if only the `viewLineCount` and `remainingWidth` have changed const stableMinimapLayoutInput = memory.stableMinimapLayoutInput; - const couldUseMemory = - stableMinimapLayoutInput && + const couldUseMemory = ( + stableMinimapLayoutInput // && input.outerWidth === lastMinimapLayoutInput.outerWidth !!! INTENTIONAL OMITTED - input.outerHeight === stableMinimapLayoutInput.outerHeight && - input.lineHeight === stableMinimapLayoutInput.lineHeight && - input.typicalHalfwidthCharacterWidth === - stableMinimapLayoutInput.typicalHalfwidthCharacterWidth && - input.pixelRatio === stableMinimapLayoutInput.pixelRatio && - input.scrollBeyondLastLine === - stableMinimapLayoutInput.scrollBeyondLastLine && - input.paddingTop === stableMinimapLayoutInput.paddingTop && - input.paddingBottom === stableMinimapLayoutInput.paddingBottom && - input.minimap.enabled === - stableMinimapLayoutInput.minimap.enabled && - input.minimap.side === stableMinimapLayoutInput.minimap.side && - input.minimap.size === stableMinimapLayoutInput.minimap.size && - input.minimap.showSlider === - stableMinimapLayoutInput.minimap.showSlider && - input.minimap.renderCharacters === - stableMinimapLayoutInput.minimap.renderCharacters && - input.minimap.maxColumn === - stableMinimapLayoutInput.minimap.maxColumn && - input.minimap.scale === stableMinimapLayoutInput.minimap.scale && - input.verticalScrollbarWidth === - stableMinimapLayoutInput.verticalScrollbarWidth && + && input.outerHeight === stableMinimapLayoutInput.outerHeight + && input.lineHeight === stableMinimapLayoutInput.lineHeight + && input.typicalHalfwidthCharacterWidth === stableMinimapLayoutInput.typicalHalfwidthCharacterWidth + && input.pixelRatio === stableMinimapLayoutInput.pixelRatio + && input.scrollBeyondLastLine === stableMinimapLayoutInput.scrollBeyondLastLine + && input.paddingTop === stableMinimapLayoutInput.paddingTop + && input.paddingBottom === stableMinimapLayoutInput.paddingBottom + && input.minimap.enabled === stableMinimapLayoutInput.minimap.enabled + && input.minimap.side === stableMinimapLayoutInput.minimap.side + && input.minimap.size === stableMinimapLayoutInput.minimap.size + && input.minimap.showSlider === stableMinimapLayoutInput.minimap.showSlider + && input.minimap.renderCharacters === stableMinimapLayoutInput.minimap.renderCharacters + && input.minimap.maxColumn === stableMinimapLayoutInput.minimap.maxColumn + && input.minimap.scale === stableMinimapLayoutInput.minimap.scale + && input.verticalScrollbarWidth === stableMinimapLayoutInput.verticalScrollbarWidth // && input.viewLineCount === lastMinimapLayoutInput.viewLineCount !!! INTENTIONAL OMITTED // && input.remainingWidth === lastMinimapLayoutInput.remainingWidth !!! INTENTIONAL OMITTED - input.isViewportWrapping === - stableMinimapLayoutInput.isViewportWrapping; + && input.isViewportWrapping === stableMinimapLayoutInput.isViewportWrapping + ); const lineHeight = input.lineHeight; - const typicalHalfwidthCharacterWidth = - input.typicalHalfwidthCharacterWidth; + const typicalHalfwidthCharacterWidth = input.typicalHalfwidthCharacterWidth; const scrollBeyondLastLine = input.scrollBeyondLastLine; const minimapRenderCharacters = input.minimap.renderCharacters; - let minimapScale = - pixelRatio >= 2 - ? Math.round(input.minimap.scale * 2) - : input.minimap.scale; + let minimapScale = (pixelRatio >= 2 ? Math.round(input.minimap.scale * 2) : input.minimap.scale); const minimapMaxColumn = input.minimap.maxColumn; const minimapSize = input.minimap.size; const minimapSide = input.minimap.side; @@ -3086,21 +2515,15 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption< let minimapCharWidth = minimapScale / pixelRatio; let minimapWidthMultiplier: number = 1; - if (minimapSize === "fill" || minimapSize === "fit") { - const { - typicalViewportLineCount, - extraLinesBeforeFirstLine, - extraLinesBeyondLastLine, - desiredRatio, - minimapLineCount, - } = EditorLayoutInfoComputer.computeContainedMinimapLineCount({ + if (minimapSize === 'fill' || minimapSize === 'fit') { + const { typicalViewportLineCount, extraLinesBeforeFirstLine, extraLinesBeyondLastLine, desiredRatio, minimapLineCount } = EditorLayoutInfoComputer.computeContainedMinimapLineCount({ viewLineCount: viewLineCount, scrollBeyondLastLine: scrollBeyondLastLine, paddingTop: input.paddingTop, paddingBottom: input.paddingBottom, height: outerHeight, lineHeight: lineHeight, - pixelRatio: pixelRatio, + pixelRatio: pixelRatio }); // ratio is intentionally not part of the layout to avoid the layout changing all the time // when doing sampling @@ -3116,18 +2539,9 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption< let fitBecomesFill = false; let maxMinimapScale = minimapScale + 1; - if (minimapSize === "fit") { - const effectiveMinimapHeight = Math.ceil( - (extraLinesBeforeFirstLine + - viewLineCount + - extraLinesBeyondLastLine) * - minimapLineHeight, - ); - if ( - isViewportWrapping && - couldUseMemory && - remainingWidth <= memory.stableFitRemainingWidth - ) { + if (minimapSize === 'fit') { + const effectiveMinimapHeight = Math.ceil((extraLinesBeforeFirstLine + viewLineCount + extraLinesBeyondLastLine) * minimapLineHeight); + if (isViewportWrapping && couldUseMemory && remainingWidth <= memory.stableFitRemainingWidth) { // There is a loop when using `fit` and viewport wrapping: // - view line count impacts minimap layout // - minimap layout impacts viewport width @@ -3136,23 +2550,15 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption< fitBecomesFill = true; maxMinimapScale = memory.stableFitMaxMinimapScale; } else { - fitBecomesFill = - effectiveMinimapHeight > minimapCanvasInnerHeight; + fitBecomesFill = (effectiveMinimapHeight > minimapCanvasInnerHeight); } } - if (minimapSize === "fill" || fitBecomesFill) { + if (minimapSize === 'fill' || fitBecomesFill) { minimapHeightIsEditorHeight = true; const configuredMinimapScale = minimapScale; - minimapLineHeight = Math.min( - lineHeight * pixelRatio, - Math.max(1, Math.floor(1 / desiredRatio)), - ); - if ( - isViewportWrapping && - couldUseMemory && - remainingWidth <= memory.stableFitRemainingWidth - ) { + minimapLineHeight = Math.min(lineHeight * pixelRatio, Math.max(1, Math.floor(1 / desiredRatio))); + if (isViewportWrapping && couldUseMemory && remainingWidth <= memory.stableFitRemainingWidth) { // There is a loop when using `fill` and viewport wrapping: // - view line count impacts minimap layout // - minimap layout impacts viewport width @@ -3160,29 +2566,12 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption< // To break the loop, once we go to a smaller minimap scale, we try to stick with it. maxMinimapScale = memory.stableFitMaxMinimapScale; } - minimapScale = Math.min( - maxMinimapScale, - Math.max( - 1, - Math.floor(minimapLineHeight / baseCharHeight), - ), - ); + minimapScale = Math.min(maxMinimapScale, Math.max(1, Math.floor(minimapLineHeight / baseCharHeight))); if (minimapScale > configuredMinimapScale) { - minimapWidthMultiplier = Math.min( - 2, - minimapScale / configuredMinimapScale, - ); + minimapWidthMultiplier = Math.min(2, minimapScale / configuredMinimapScale); } - minimapCharWidth = - minimapScale / pixelRatio / minimapWidthMultiplier; - minimapCanvasInnerHeight = Math.ceil( - Math.max( - typicalViewportLineCount, - extraLinesBeforeFirstLine + - viewLineCount + - extraLinesBeyondLastLine, - ) * minimapLineHeight, - ); + minimapCharWidth = minimapScale / pixelRatio / minimapWidthMultiplier; + minimapCanvasInnerHeight = Math.ceil((Math.max(typicalViewportLineCount, extraLinesBeforeFirstLine + viewLineCount + extraLinesBeyondLastLine)) * minimapLineHeight); if (isViewportWrapping) { // remember for next time memory.stableMinimapLayoutInput = input; @@ -3210,31 +2599,14 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption< // minimapWidth = ((remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth) const minimapMaxWidth = Math.floor(minimapMaxColumn * minimapCharWidth); - const minimapWidth = Math.min( - minimapMaxWidth, - Math.max( - 0, - Math.floor( - ((remainingWidth - verticalScrollbarWidth - 2) * - minimapCharWidth) / - (typicalHalfwidthCharacterWidth + minimapCharWidth), - ), - ) + MINIMAP_GUTTER_WIDTH, - ); + const minimapWidth = Math.min(minimapMaxWidth, Math.max(0, Math.floor(((remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth))) + MINIMAP_GUTTER_WIDTH); let minimapCanvasInnerWidth = Math.floor(pixelRatio * minimapWidth); const minimapCanvasOuterWidth = minimapCanvasInnerWidth / pixelRatio; - minimapCanvasInnerWidth = Math.floor( - minimapCanvasInnerWidth * minimapWidthMultiplier, - ); + minimapCanvasInnerWidth = Math.floor(minimapCanvasInnerWidth * minimapWidthMultiplier); - const renderMinimap = minimapRenderCharacters - ? RenderMinimap.Text - : RenderMinimap.Blocks; - const minimapLeft = - minimapSide === "left" - ? 0 - : outerWidth - minimapWidth - verticalScrollbarWidth; + const renderMinimap = (minimapRenderCharacters ? RenderMinimap.Text : RenderMinimap.Blocks); + const minimapLeft = (minimapSide === 'left' ? 0 : (outerWidth - minimapWidth - verticalScrollbarWidth)); return { renderMinimap, @@ -3251,43 +2623,27 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption< }; } - public static computeLayout( - options: IComputedEditorOptions, - env: EditorLayoutInfoComputerEnv, - ): EditorLayoutInfo { + public static computeLayout(options: IComputedEditorOptions, env: EditorLayoutInfoComputerEnv): EditorLayoutInfo { const outerWidth = env.outerWidth | 0; const outerHeight = env.outerHeight | 0; const lineHeight = env.lineHeight | 0; const lineNumbersDigitCount = env.lineNumbersDigitCount | 0; - const typicalHalfwidthCharacterWidth = - env.typicalHalfwidthCharacterWidth; + const typicalHalfwidthCharacterWidth = env.typicalHalfwidthCharacterWidth; const maxDigitWidth = env.maxDigitWidth; const pixelRatio = env.pixelRatio; const viewLineCount = env.viewLineCount; const wordWrapOverride2 = options.get(EditorOption.wordWrapOverride2); - const wordWrapOverride1 = - wordWrapOverride2 === "inherit" - ? options.get(EditorOption.wordWrapOverride1) - : wordWrapOverride2; - const wordWrap = - wordWrapOverride1 === "inherit" - ? options.get(EditorOption.wordWrap) - : wordWrapOverride1; + const wordWrapOverride1 = (wordWrapOverride2 === 'inherit' ? options.get(EditorOption.wordWrapOverride1) : wordWrapOverride2); + const wordWrap = (wordWrapOverride1 === 'inherit' ? options.get(EditorOption.wordWrap) : wordWrapOverride1); const wordWrapColumn = options.get(EditorOption.wordWrapColumn); const isDominatedByLongLines = env.isDominatedByLongLines; const showGlyphMargin = options.get(EditorOption.glyphMargin); - const showLineNumbers = - options.get(EditorOption.lineNumbers).renderType !== - RenderLineNumbersType.Off; - const lineNumbersMinChars = options.get( - EditorOption.lineNumbersMinChars, - ); - const scrollBeyondLastLine = options.get( - EditorOption.scrollBeyondLastLine, - ); + const showLineNumbers = (options.get(EditorOption.lineNumbers).renderType !== RenderLineNumbersType.Off); + const lineNumbersMinChars = options.get(EditorOption.lineNumbersMinChars); + const scrollBeyondLastLine = options.get(EditorOption.scrollBeyondLastLine); const padding = options.get(EditorOption.padding); const minimap = options.get(EditorOption.minimap); @@ -3298,22 +2654,16 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption< const horizontalScrollbarHeight = scrollbar.horizontalScrollbarSize; const folding = options.get(EditorOption.folding); - const showFoldingDecoration = - options.get(EditorOption.showFoldingControls) !== "never"; + const showFoldingDecoration = options.get(EditorOption.showFoldingControls) !== 'never'; - let lineDecorationsWidth = options.get( - EditorOption.lineDecorationsWidth, - ); + let lineDecorationsWidth = options.get(EditorOption.lineDecorationsWidth); if (folding && showFoldingDecoration) { lineDecorationsWidth += 16; } let lineNumbersWidth = 0; if (showLineNumbers) { - const digitCount = Math.max( - lineNumbersDigitCount, - lineNumbersMinChars, - ); + const digitCount = Math.max(lineNumbersDigitCount, lineNumbersMinChars); lineNumbersWidth = Math.round(digitCount * maxDigitWidth); } @@ -3327,49 +2677,39 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption< let decorationsLeft = lineNumbersLeft + lineNumbersWidth; let contentLeft = decorationsLeft + lineDecorationsWidth; - const remainingWidth = - outerWidth - - glyphMarginWidth - - lineNumbersWidth - - lineDecorationsWidth; + const remainingWidth = outerWidth - glyphMarginWidth - lineNumbersWidth - lineDecorationsWidth; let isWordWrapMinified = false; let isViewportWrapping = false; let wrappingColumn = -1; - if (wordWrapOverride1 === "inherit" && isDominatedByLongLines) { + if (wordWrapOverride1 === 'inherit' && isDominatedByLongLines) { // Force viewport width wrapping if model is dominated by long lines isWordWrapMinified = true; isViewportWrapping = true; - } else if (wordWrap === "on" || wordWrap === "bounded") { + } else if (wordWrap === 'on' || wordWrap === 'bounded') { isViewportWrapping = true; - } else if (wordWrap === "wordWrapColumn") { + } else if (wordWrap === 'wordWrapColumn') { wrappingColumn = wordWrapColumn; } - const minimapLayout = EditorLayoutInfoComputer._computeMinimapLayout( - { - outerWidth: outerWidth, - outerHeight: outerHeight, - lineHeight: lineHeight, - typicalHalfwidthCharacterWidth: typicalHalfwidthCharacterWidth, - pixelRatio: pixelRatio, - scrollBeyondLastLine: scrollBeyondLastLine, - paddingTop: padding.top, - paddingBottom: padding.bottom, - minimap: minimap, - verticalScrollbarWidth: verticalScrollbarWidth, - viewLineCount: viewLineCount, - remainingWidth: remainingWidth, - isViewportWrapping: isViewportWrapping, - }, - env.memory || new ComputeOptionsMemory(), - ); + const minimapLayout = EditorLayoutInfoComputer._computeMinimapLayout({ + outerWidth: outerWidth, + outerHeight: outerHeight, + lineHeight: lineHeight, + typicalHalfwidthCharacterWidth: typicalHalfwidthCharacterWidth, + pixelRatio: pixelRatio, + scrollBeyondLastLine: scrollBeyondLastLine, + paddingTop: padding.top, + paddingBottom: padding.bottom, + minimap: minimap, + verticalScrollbarWidth: verticalScrollbarWidth, + viewLineCount: viewLineCount, + remainingWidth: remainingWidth, + isViewportWrapping: isViewportWrapping, + }, env.memory || new ComputeOptionsMemory()); - if ( - minimapLayout.renderMinimap !== RenderMinimap.None && - minimapLayout.minimapLeft === 0 - ) { + if (minimapLayout.renderMinimap !== RenderMinimap.None && minimapLayout.minimapLeft === 0) { // the minimap is rendered to the left, so move everything to the right glyphMarginLeft += minimapLayout.minimapWidth; lineNumbersLeft += minimapLayout.minimapWidth; @@ -3379,22 +2719,14 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption< const contentWidth = remainingWidth - minimapLayout.minimapWidth; // (leaving 2px for the cursor to have space after the last character) - const viewportColumn = Math.max( - 1, - Math.floor( - (contentWidth - verticalScrollbarWidth - 2) / - typicalHalfwidthCharacterWidth, - ), - ); + const viewportColumn = Math.max(1, Math.floor((contentWidth - verticalScrollbarWidth - 2) / typicalHalfwidthCharacterWidth)); - const verticalArrowSize = verticalScrollbarHasArrows - ? scrollbarArrowSize - : 0; + const verticalArrowSize = (verticalScrollbarHasArrows ? scrollbarArrowSize : 0); if (isViewportWrapping) { // compute the actual wrappingColumn wrappingColumn = Math.max(1, viewportColumn); - if (wordWrap === "bounded") { + if (wordWrap === 'bounded') { wrappingColumn = Math.min(wrappingColumn, wordWrapColumn); } } @@ -3430,9 +2762,9 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption< overviewRuler: { top: verticalArrowSize, width: verticalScrollbarWidth, - height: outerHeight - 2 * verticalArrowSize, - right: 0, - }, + height: (outerHeight - 2 * verticalArrowSize), + right: 0 + } }; } } @@ -3440,54 +2772,35 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption< //#endregion //#region WrappingStrategy -class WrappingStrategy extends BaseEditorOption< - EditorOption.wrappingStrategy, - "simple" | "advanced", - "simple" | "advanced" -> { +class WrappingStrategy extends BaseEditorOption { + constructor() { - super(EditorOption.wrappingStrategy, "wrappingStrategy", "simple", { - "editor.wrappingStrategy": { - enumDescriptions: [ - nls.localize( - "wrappingStrategy.simple", - "Assumes that all characters are of the same width. This is a fast algorithm that works correctly for monospace fonts and certain scripts (like Latin characters) where glyphs are of equal width.", - ), - nls.localize( - "wrappingStrategy.advanced", - "Delegates wrapping points computation to the browser. This is a slow algorithm, that might cause freezes for large files, but it works correctly in all cases.", - ), - ], - type: "string", - enum: ["simple", "advanced"], - default: "simple", - description: nls.localize( - "wrappingStrategy", - "Controls the algorithm that computes wrapping points. Note that when in accessibility mode, advanced will be used for the best experience.", - ), - }, - }); + super(EditorOption.wrappingStrategy, 'wrappingStrategy', 'simple', + { + 'editor.wrappingStrategy': { + enumDescriptions: [ + nls.localize('wrappingStrategy.simple', "Assumes that all characters are of the same width. This is a fast algorithm that works correctly for monospace fonts and certain scripts (like Latin characters) where glyphs are of equal width."), + nls.localize('wrappingStrategy.advanced', "Delegates wrapping points computation to the browser. This is a slow algorithm, that might cause freezes for large files, but it works correctly in all cases.") + ], + type: 'string', + enum: ['simple', 'advanced'], + default: 'simple', + description: nls.localize('wrappingStrategy', "Controls the algorithm that computes wrapping points. Note that when in accessibility mode, advanced will be used for the best experience.") + } + } + ); } - public validate(input: any): "simple" | "advanced" { - return stringSet<"simple" | "advanced">(input, "simple", [ - "simple", - "advanced", - ]); + public validate(input: any): 'simple' | 'advanced' { + return stringSet<'simple' | 'advanced'>(input, 'simple', ['simple', 'advanced']); } - public override compute( - env: IEnvironmentalOptions, - options: IComputedEditorOptions, - value: "simple" | "advanced", - ): "simple" | "advanced" { - const accessibilitySupport = options.get( - EditorOption.accessibilitySupport, - ); + public override compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: 'simple' | 'advanced'): 'simple' | 'advanced' { + const accessibilitySupport = options.get(EditorOption.accessibilitySupport); if (accessibilitySupport === AccessibilitySupport.Enabled) { // if we know for a fact that a screen reader is attached, we switch our strategy to advanced to // help that the editor's wrapping points match the textarea's wrapping points - return "advanced"; + return 'advanced'; } return value; } @@ -3497,9 +2810,9 @@ class WrappingStrategy extends BaseEditorOption< //#region lightbulb export enum ShowLightbulbIconMode { - Off = "off", - OnCode = "onCode", - On = "on", + Off = 'off', + OnCode = 'onCode', + On = 'on' } /** @@ -3519,61 +2832,37 @@ export interface IEditorLightbulbOptions { /** * @internal */ -export type EditorLightbulbOptions = Readonly< - Required ->; - -class EditorLightbulb extends BaseEditorOption< - EditorOption.lightbulb, - IEditorLightbulbOptions, - EditorLightbulbOptions -> { +export type EditorLightbulbOptions = Readonly>; + +class EditorLightbulb extends BaseEditorOption { + constructor() { - const defaults: EditorLightbulbOptions = { - enabled: ShowLightbulbIconMode.OnCode, - }; - super(EditorOption.lightbulb, "lightbulb", defaults, { - "editor.lightbulb.enabled": { - type: "string", - enum: [ - ShowLightbulbIconMode.Off, - ShowLightbulbIconMode.OnCode, - ShowLightbulbIconMode.On, - ], - default: defaults.enabled, - enumDescriptions: [ - nls.localize( - "editor.lightbulb.enabled.off", - "Disable the code action menu.", - ), - nls.localize( - "editor.lightbulb.enabled.onCode", - "Show the code action menu when the cursor is on lines with code.", - ), - nls.localize( - "editor.lightbulb.enabled.on", - "Show the code action menu when the cursor is on lines with code or on empty lines.", - ), - ], - description: nls.localize( - "enabled", - "Enables the Code Action lightbulb in the editor.", - ), - }, - }); + const defaults: EditorLightbulbOptions = { enabled: ShowLightbulbIconMode.OnCode }; + super( + EditorOption.lightbulb, 'lightbulb', defaults, + { + 'editor.lightbulb.enabled': { + type: 'string', + enum: [ShowLightbulbIconMode.Off, ShowLightbulbIconMode.OnCode, ShowLightbulbIconMode.On], + default: defaults.enabled, + enumDescriptions: [ + nls.localize('editor.lightbulb.enabled.off', 'Disable the code action menu.'), + nls.localize('editor.lightbulb.enabled.onCode', 'Show the code action menu when the cursor is on lines with code.'), + nls.localize('editor.lightbulb.enabled.on', 'Show the code action menu when the cursor is on lines with code or on empty lines.'), + ], + description: nls.localize('enabled', "Enables the Code Action lightbulb in the editor.") + } + } + ); } public validate(_input: any): EditorLightbulbOptions { - if (!_input || typeof _input !== "object") { + if (!_input || typeof _input !== 'object') { return this.defaultValue; } const input = _input as IEditorLightbulbOptions; return { - enabled: stringSet(input.enabled, this.defaultValue.enabled, [ - ShowLightbulbIconMode.Off, - ShowLightbulbIconMode.OnCode, - ShowLightbulbIconMode.On, - ]), + enabled: stringSet(input.enabled, this.defaultValue.enabled, [ShowLightbulbIconMode.Off, ShowLightbulbIconMode.OnCode, ShowLightbulbIconMode.On]) }; } } @@ -3594,7 +2883,7 @@ export interface IEditorStickyScrollOptions { /** * Model to choose for sticky scroll by default */ - defaultModel?: "outlineModel" | "foldingProviderModel" | "indentationModel"; + defaultModel?: 'outlineModel' | 'foldingProviderModel' | 'indentationModel'; /** * Define whether to scroll sticky scroll with editor horizontal scrollbae */ @@ -3604,89 +2893,52 @@ export interface IEditorStickyScrollOptions { /** * @internal */ -export type EditorStickyScrollOptions = Readonly< - Required ->; - -class EditorStickyScroll extends BaseEditorOption< - EditorOption.stickyScroll, - IEditorStickyScrollOptions, - EditorStickyScrollOptions -> { +export type EditorStickyScrollOptions = Readonly>; + +class EditorStickyScroll extends BaseEditorOption { + constructor() { - const defaults: EditorStickyScrollOptions = { - enabled: true, - maxLineCount: 5, - defaultModel: "outlineModel", - scrollWithEditor: true, - }; - super(EditorOption.stickyScroll, "stickyScroll", defaults, { - "editor.stickyScroll.enabled": { - type: "boolean", - default: defaults.enabled, - description: nls.localize( - "editor.stickyScroll.enabled", - "Shows the nested current scopes during the scroll at the top of the editor.", - ), - }, - "editor.stickyScroll.maxLineCount": { - type: "number", - default: defaults.maxLineCount, - minimum: 1, - maximum: 20, - description: nls.localize( - "editor.stickyScroll.maxLineCount", - "Defines the maximum number of sticky lines to show.", - ), - }, - "editor.stickyScroll.defaultModel": { - type: "string", - enum: [ - "outlineModel", - "foldingProviderModel", - "indentationModel", - ], - default: defaults.defaultModel, - description: nls.localize( - "editor.stickyScroll.defaultModel", - "Defines the model to use for determining which lines to stick. If the outline model does not exist, it will fall back on the folding provider model which falls back on the indentation model. This order is respected in all three cases.", - ), - }, - "editor.stickyScroll.scrollWithEditor": { - type: "boolean", - default: defaults.scrollWithEditor, - description: nls.localize( - "editor.stickyScroll.scrollWithEditor", - "Enable scrolling of Sticky Scroll with the editor's horizontal scrollbar.", - ), - }, - }); + const defaults: EditorStickyScrollOptions = { enabled: true, maxLineCount: 5, defaultModel: 'outlineModel', scrollWithEditor: true }; + super( + EditorOption.stickyScroll, 'stickyScroll', defaults, + { + 'editor.stickyScroll.enabled': { + type: 'boolean', + default: defaults.enabled, + description: nls.localize('editor.stickyScroll.enabled', "Shows the nested current scopes during the scroll at the top of the editor.") + }, + 'editor.stickyScroll.maxLineCount': { + type: 'number', + default: defaults.maxLineCount, + minimum: 1, + maximum: 20, + description: nls.localize('editor.stickyScroll.maxLineCount', "Defines the maximum number of sticky lines to show.") + }, + 'editor.stickyScroll.defaultModel': { + type: 'string', + enum: ['outlineModel', 'foldingProviderModel', 'indentationModel'], + default: defaults.defaultModel, + description: nls.localize('editor.stickyScroll.defaultModel', "Defines the model to use for determining which lines to stick. If the outline model does not exist, it will fall back on the folding provider model which falls back on the indentation model. This order is respected in all three cases.") + }, + 'editor.stickyScroll.scrollWithEditor': { + type: 'boolean', + default: defaults.scrollWithEditor, + description: nls.localize('editor.stickyScroll.scrollWithEditor', "Enable scrolling of Sticky Scroll with the editor's horizontal scrollbar.") + }, + } + ); } public validate(_input: any): EditorStickyScrollOptions { - if (!_input || typeof _input !== "object") { + if (!_input || typeof _input !== 'object') { return this.defaultValue; } const input = _input as IEditorStickyScrollOptions; return { enabled: boolean(input.enabled, this.defaultValue.enabled), - maxLineCount: EditorIntOption.clampedInt( - input.maxLineCount, - this.defaultValue.maxLineCount, - 1, - 20, - ), - defaultModel: stringSet< - "outlineModel" | "foldingProviderModel" | "indentationModel" - >(input.defaultModel, this.defaultValue.defaultModel, [ - "outlineModel", - "foldingProviderModel", - "indentationModel", - ]), - scrollWithEditor: boolean( - input.scrollWithEditor, - this.defaultValue.scrollWithEditor, - ), + maxLineCount: EditorIntOption.clampedInt(input.maxLineCount, this.defaultValue.maxLineCount, 1, 20), + defaultModel: stringSet<'outlineModel' | 'foldingProviderModel' | 'indentationModel'>(input.defaultModel, this.defaultValue.defaultModel, ['outlineModel', 'foldingProviderModel', 'indentationModel']), + scrollWithEditor: boolean(input.scrollWithEditor, this.defaultValue.scrollWithEditor) }; } } @@ -3703,7 +2955,7 @@ export interface IEditorInlayHintsOptions { * Enable the inline hints. * Defaults to true. */ - enabled?: "on" | "off" | "offUnlessPressed" | "onUnlessPressed"; + enabled?: 'on' | 'off' | 'offUnlessPressed' | 'onUnlessPressed'; /** * Font size of inline hints. @@ -3733,125 +2985,65 @@ export interface IEditorInlayHintsOptions { /** * @internal */ -export type EditorInlayHintsOptions = Readonly< - Required ->; - -class EditorInlayHints extends BaseEditorOption< - EditorOption.inlayHints, - IEditorInlayHintsOptions, - EditorInlayHintsOptions -> { +export type EditorInlayHintsOptions = Readonly>; + +class EditorInlayHints extends BaseEditorOption { + constructor() { - const defaults: EditorInlayHintsOptions = { - enabled: "on", - fontSize: 0, - fontFamily: "", - padding: false, - maximumLength: 43, - }; - super(EditorOption.inlayHints, "inlayHints", defaults, { - "editor.inlayHints.enabled": { - type: "string", - default: defaults.enabled, - description: nls.localize( - "inlayHints.enable", - "Enables the inlay hints in the editor.", - ), - enum: ["on", "onUnlessPressed", "offUnlessPressed", "off"], - markdownEnumDescriptions: [ - nls.localize( - "editor.inlayHints.on", - "Inlay hints are enabled", - ), - nls.localize( - "editor.inlayHints.onUnlessPressed", - "Inlay hints are showing by default and hide when holding {0}", - platform.isMacintosh ? `Ctrl+Option` : `Ctrl+Alt`, - ), - nls.localize( - "editor.inlayHints.offUnlessPressed", - "Inlay hints are hidden by default and show when holding {0}", - platform.isMacintosh ? `Ctrl+Option` : `Ctrl+Alt`, - ), - nls.localize( - "editor.inlayHints.off", - "Inlay hints are disabled", - ), - ], - }, - "editor.inlayHints.fontSize": { - type: "number", - default: defaults.fontSize, - markdownDescription: nls.localize( - "inlayHints.fontSize", - "Controls font size of inlay hints in the editor. As default the {0} is used when the configured value is less than {1} or greater than the editor font size.", - "`#editor.fontSize#`", - "`5`", - ), - }, - "editor.inlayHints.fontFamily": { - type: "string", - default: defaults.fontFamily, - markdownDescription: nls.localize( - "inlayHints.fontFamily", - "Controls font family of inlay hints in the editor. When set to empty, the {0} is used.", - "`#editor.fontFamily#`", - ), - }, - "editor.inlayHints.padding": { - type: "boolean", - default: defaults.padding, - description: nls.localize( - "inlayHints.padding", - "Enables the padding around the inlay hints in the editor.", - ), - }, - "editor.inlayHints.maximumLength": { - type: "number", - default: defaults.maximumLength, - markdownDescription: nls.localize( - "inlayHints.maximumLength", - "Maximum overall length of inlay hints, for a single line, before they get truncated by the editor. Set to `0` to never truncate", - ), - }, - }); + const defaults: EditorInlayHintsOptions = { enabled: 'on', fontSize: 0, fontFamily: '', padding: false, maximumLength: 43 }; + super( + EditorOption.inlayHints, 'inlayHints', defaults, + { + 'editor.inlayHints.enabled': { + type: 'string', + default: defaults.enabled, + description: nls.localize('inlayHints.enable', "Enables the inlay hints in the editor."), + enum: ['on', 'onUnlessPressed', 'offUnlessPressed', 'off'], + markdownEnumDescriptions: [ + nls.localize('editor.inlayHints.on', "Inlay hints are enabled"), + nls.localize('editor.inlayHints.onUnlessPressed', "Inlay hints are showing by default and hide when holding {0}", platform.isMacintosh ? `Ctrl+Option` : `Ctrl+Alt`), + nls.localize('editor.inlayHints.offUnlessPressed', "Inlay hints are hidden by default and show when holding {0}", platform.isMacintosh ? `Ctrl+Option` : `Ctrl+Alt`), + nls.localize('editor.inlayHints.off', "Inlay hints are disabled"), + ], + }, + 'editor.inlayHints.fontSize': { + type: 'number', + default: defaults.fontSize, + markdownDescription: nls.localize('inlayHints.fontSize', "Controls font size of inlay hints in the editor. As default the {0} is used when the configured value is less than {1} or greater than the editor font size.", '`#editor.fontSize#`', '`5`') + }, + 'editor.inlayHints.fontFamily': { + type: 'string', + default: defaults.fontFamily, + markdownDescription: nls.localize('inlayHints.fontFamily', "Controls font family of inlay hints in the editor. When set to empty, the {0} is used.", '`#editor.fontFamily#`') + }, + 'editor.inlayHints.padding': { + type: 'boolean', + default: defaults.padding, + description: nls.localize('inlayHints.padding', "Enables the padding around the inlay hints in the editor.") + }, + 'editor.inlayHints.maximumLength': { + type: 'number', + default: defaults.maximumLength, + markdownDescription: nls.localize('inlayHints.maximumLength', "Maximum overall length of inlay hints, for a single line, before they get truncated by the editor. Set to `0` to never truncate") + } + } + ); } public validate(_input: any): EditorInlayHintsOptions { - if (!_input || typeof _input !== "object") { + if (!_input || typeof _input !== 'object') { return this.defaultValue; } const input = _input as IEditorInlayHintsOptions; - if (typeof input.enabled === "boolean") { - input.enabled = input.enabled ? "on" : "off"; + if (typeof input.enabled === 'boolean') { + input.enabled = input.enabled ? 'on' : 'off'; } return { - enabled: stringSet< - "on" | "off" | "offUnlessPressed" | "onUnlessPressed" - >(input.enabled, this.defaultValue.enabled, [ - "on", - "off", - "offUnlessPressed", - "onUnlessPressed", - ]), - fontSize: EditorIntOption.clampedInt( - input.fontSize, - this.defaultValue.fontSize, - 0, - 100, - ), - fontFamily: EditorStringOption.string( - input.fontFamily, - this.defaultValue.fontFamily, - ), + enabled: stringSet<'on' | 'off' | 'offUnlessPressed' | 'onUnlessPressed'>(input.enabled, this.defaultValue.enabled, ['on', 'off', 'offUnlessPressed', 'onUnlessPressed']), + fontSize: EditorIntOption.clampedInt(input.fontSize, this.defaultValue.fontSize, 0, 100), + fontFamily: EditorStringOption.string(input.fontFamily, this.defaultValue.fontFamily), padding: boolean(input.padding, this.defaultValue.padding), - maximumLength: EditorIntOption.clampedInt( - input.maximumLength, - this.defaultValue.maximumLength, - 0, - Number.MAX_SAFE_INTEGER, - ), + maximumLength: EditorIntOption.clampedInt(input.maximumLength, this.defaultValue.maximumLength, 0, Number.MAX_SAFE_INTEGER), }; } } @@ -3860,42 +3052,25 @@ class EditorInlayHints extends BaseEditorOption< //#region lineDecorationsWidth -class EditorLineDecorationsWidth extends BaseEditorOption< - EditorOption.lineDecorationsWidth, - number | string, - number -> { +class EditorLineDecorationsWidth extends BaseEditorOption { + constructor() { - super(EditorOption.lineDecorationsWidth, "lineDecorationsWidth", 10); + super(EditorOption.lineDecorationsWidth, 'lineDecorationsWidth', 10); } public validate(input: any): number { - if (typeof input === "string" && /^\d+(\.\d+)?ch$/.test(input)) { + if (typeof input === 'string' && /^\d+(\.\d+)?ch$/.test(input)) { const multiple = parseFloat(input.substring(0, input.length - 2)); return -multiple; // negative numbers signal a multiple } else { - return EditorIntOption.clampedInt( - input, - this.defaultValue, - 0, - 1000, - ); + return EditorIntOption.clampedInt(input, this.defaultValue, 0, 1000); } } - public override compute( - env: IEnvironmentalOptions, - options: IComputedEditorOptions, - value: number, - ): number { + public override compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: number): number { if (value < 0) { // negative numbers signal a multiple - return EditorIntOption.clampedInt( - -value * env.fontInfo.typicalHalfwidthCharacterWidth, - this.defaultValue, - 0, - 1000, - ); + return EditorIntOption.clampedInt(-value * env.fontInfo.typicalHalfwidthCharacterWidth, this.defaultValue, 0, 1000); } else { return value; } @@ -3907,26 +3082,17 @@ class EditorLineDecorationsWidth extends BaseEditorOption< //#region lineHeight class EditorLineHeight extends EditorFloatOption { + constructor() { super( - EditorOption.lineHeight, - "lineHeight", + EditorOption.lineHeight, 'lineHeight', EDITOR_FONT_DEFAULTS.lineHeight, - (x) => EditorFloatOption.clamp(x, 0, 150), - { - markdownDescription: nls.localize( - "lineHeight", - "Controls the line height. \n - Use 0 to automatically compute the line height from the font size.\n - Values between 0 and 8 will be used as a multiplier with the font size.\n - Values greater than or equal to 8 will be used as effective values.", - ), - }, + x => EditorFloatOption.clamp(x, 0, 150), + { markdownDescription: nls.localize('lineHeight', "Controls the line height. \n - Use 0 to automatically compute the line height from the font size.\n - Values between 0 and 8 will be used as a multiplier with the font size.\n - Values greater than or equal to 8 will be used as effective values.") } ); } - public override compute( - env: IEnvironmentalOptions, - options: IComputedEditorOptions, - value: number, - ): number { + public override compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: number): number { // The lineHeight is computed from the fontSize if it is 0. // Moreover, the final lineHeight respects the editor zoom level. // So take the result from env.fontInfo @@ -3955,17 +3121,17 @@ export interface IEditorMinimapOptions { * Control the side of the minimap in editor. * Defaults to 'right'. */ - side?: "right" | "left"; + side?: 'right' | 'left'; /** * Control the minimap rendering mode. * Defaults to 'actual'. */ - size?: "proportional" | "fill" | "fit"; + size?: 'proportional' | 'fill' | 'fit'; /** * Control the rendering of the minimap slider. * Defaults to 'mouseover'. */ - showSlider?: "always" | "mouseover"; + showSlider?: 'always' | 'mouseover'; /** * Render the actual text on a line (as opposed to color blocks). * Defaults to true. @@ -4003,17 +3169,14 @@ export interface IEditorMinimapOptions { */ export type EditorMinimapOptions = Readonly>; -class EditorMinimap extends BaseEditorOption< - EditorOption.minimap, - IEditorMinimapOptions, - EditorMinimapOptions -> { +class EditorMinimap extends BaseEditorOption { + constructor() { const defaults: EditorMinimapOptions = { enabled: true, - size: "proportional", - side: "right", - showSlider: "mouseover", + size: 'proportional', + side: 'right', + showSlider: 'mouseover', autohide: false, renderCharacters: true, maxColumn: 120, @@ -4023,180 +3186,102 @@ class EditorMinimap extends BaseEditorOption< sectionHeaderFontSize: 9, sectionHeaderLetterSpacing: 1, }; - super(EditorOption.minimap, "minimap", defaults, { - "editor.minimap.enabled": { - type: "boolean", - default: defaults.enabled, - description: nls.localize( - "minimap.enabled", - "Controls whether the minimap is shown.", - ), - }, - "editor.minimap.autohide": { - type: "boolean", - default: defaults.autohide, - description: nls.localize( - "minimap.autohide", - "Controls whether the minimap is hidden automatically.", - ), - }, - "editor.minimap.size": { - type: "string", - enum: ["proportional", "fill", "fit"], - enumDescriptions: [ - nls.localize( - "minimap.size.proportional", - "The minimap has the same size as the editor contents (and might scroll).", - ), - nls.localize( - "minimap.size.fill", - "The minimap will stretch or shrink as necessary to fill the height of the editor (no scrolling).", - ), - nls.localize( - "minimap.size.fit", - "The minimap will shrink as necessary to never be larger than the editor (no scrolling).", - ), - ], - default: defaults.size, - description: nls.localize( - "minimap.size", - "Controls the size of the minimap.", - ), - }, - "editor.minimap.side": { - type: "string", - enum: ["left", "right"], - default: defaults.side, - description: nls.localize( - "minimap.side", - "Controls the side where to render the minimap.", - ), - }, - "editor.minimap.showSlider": { - type: "string", - enum: ["always", "mouseover"], - default: defaults.showSlider, - description: nls.localize( - "minimap.showSlider", - "Controls when the minimap slider is shown.", - ), - }, - "editor.minimap.scale": { - type: "number", - default: defaults.scale, - minimum: 1, - maximum: 3, - enum: [1, 2, 3], - description: nls.localize( - "minimap.scale", - "Scale of content drawn in the minimap: 1, 2 or 3.", - ), - }, - "editor.minimap.renderCharacters": { - type: "boolean", - default: defaults.renderCharacters, - description: nls.localize( - "minimap.renderCharacters", - "Render the actual characters on a line as opposed to color blocks.", - ), - }, - "editor.minimap.maxColumn": { - type: "number", - default: defaults.maxColumn, - description: nls.localize( - "minimap.maxColumn", - "Limit the width of the minimap to render at most a certain number of columns.", - ), - }, - "editor.minimap.showRegionSectionHeaders": { - type: "boolean", - default: defaults.showRegionSectionHeaders, - description: nls.localize( - "minimap.showRegionSectionHeaders", - "Controls whether named regions are shown as section headers in the minimap.", - ), - }, - "editor.minimap.showMarkSectionHeaders": { - type: "boolean", - default: defaults.showMarkSectionHeaders, - description: nls.localize( - "minimap.showMarkSectionHeaders", - "Controls whether MARK: comments are shown as section headers in the minimap.", - ), - }, - "editor.minimap.sectionHeaderFontSize": { - type: "number", - default: defaults.sectionHeaderFontSize, - description: nls.localize( - "minimap.sectionHeaderFontSize", - "Controls the font size of section headers in the minimap.", - ), - }, - "editor.minimap.sectionHeaderLetterSpacing": { - type: "number", - default: defaults.sectionHeaderLetterSpacing, - description: nls.localize( - "minimap.sectionHeaderLetterSpacing", - "Controls the amount of space (in pixels) between characters of section header. This helps the readability of the header in small font sizes.", - ), - }, - }); + super( + EditorOption.minimap, 'minimap', defaults, + { + 'editor.minimap.enabled': { + type: 'boolean', + default: defaults.enabled, + description: nls.localize('minimap.enabled', "Controls whether the minimap is shown.") + }, + 'editor.minimap.autohide': { + type: 'boolean', + default: defaults.autohide, + description: nls.localize('minimap.autohide', "Controls whether the minimap is hidden automatically.") + }, + 'editor.minimap.size': { + type: 'string', + enum: ['proportional', 'fill', 'fit'], + enumDescriptions: [ + nls.localize('minimap.size.proportional', "The minimap has the same size as the editor contents (and might scroll)."), + nls.localize('minimap.size.fill', "The minimap will stretch or shrink as necessary to fill the height of the editor (no scrolling)."), + nls.localize('minimap.size.fit', "The minimap will shrink as necessary to never be larger than the editor (no scrolling)."), + ], + default: defaults.size, + description: nls.localize('minimap.size', "Controls the size of the minimap.") + }, + 'editor.minimap.side': { + type: 'string', + enum: ['left', 'right'], + default: defaults.side, + description: nls.localize('minimap.side', "Controls the side where to render the minimap.") + }, + 'editor.minimap.showSlider': { + type: 'string', + enum: ['always', 'mouseover'], + default: defaults.showSlider, + description: nls.localize('minimap.showSlider', "Controls when the minimap slider is shown.") + }, + 'editor.minimap.scale': { + type: 'number', + default: defaults.scale, + minimum: 1, + maximum: 3, + enum: [1, 2, 3], + description: nls.localize('minimap.scale', "Scale of content drawn in the minimap: 1, 2 or 3.") + }, + 'editor.minimap.renderCharacters': { + type: 'boolean', + default: defaults.renderCharacters, + description: nls.localize('minimap.renderCharacters', "Render the actual characters on a line as opposed to color blocks.") + }, + 'editor.minimap.maxColumn': { + type: 'number', + default: defaults.maxColumn, + description: nls.localize('minimap.maxColumn', "Limit the width of the minimap to render at most a certain number of columns.") + }, + 'editor.minimap.showRegionSectionHeaders': { + type: 'boolean', + default: defaults.showRegionSectionHeaders, + description: nls.localize('minimap.showRegionSectionHeaders', "Controls whether named regions are shown as section headers in the minimap.") + }, + 'editor.minimap.showMarkSectionHeaders': { + type: 'boolean', + default: defaults.showMarkSectionHeaders, + description: nls.localize('minimap.showMarkSectionHeaders', "Controls whether MARK: comments are shown as section headers in the minimap.") + }, + 'editor.minimap.sectionHeaderFontSize': { + type: 'number', + default: defaults.sectionHeaderFontSize, + description: nls.localize('minimap.sectionHeaderFontSize', "Controls the font size of section headers in the minimap.") + }, + 'editor.minimap.sectionHeaderLetterSpacing': { + type: 'number', + default: defaults.sectionHeaderLetterSpacing, + description: nls.localize('minimap.sectionHeaderLetterSpacing', "Controls the amount of space (in pixels) between characters of section header. This helps the readability of the header in small font sizes.") + } + } + ); } public validate(_input: any): EditorMinimapOptions { - if (!_input || typeof _input !== "object") { + if (!_input || typeof _input !== 'object') { return this.defaultValue; } const input = _input as IEditorMinimapOptions; return { enabled: boolean(input.enabled, this.defaultValue.enabled), autohide: boolean(input.autohide, this.defaultValue.autohide), - size: stringSet<"proportional" | "fill" | "fit">( - input.size, - this.defaultValue.size, - ["proportional", "fill", "fit"], - ), - side: stringSet<"right" | "left">( - input.side, - this.defaultValue.side, - ["right", "left"], - ), - showSlider: stringSet<"always" | "mouseover">( - input.showSlider, - this.defaultValue.showSlider, - ["always", "mouseover"], - ), - renderCharacters: boolean( - input.renderCharacters, - this.defaultValue.renderCharacters, - ), + size: stringSet<'proportional' | 'fill' | 'fit'>(input.size, this.defaultValue.size, ['proportional', 'fill', 'fit']), + side: stringSet<'right' | 'left'>(input.side, this.defaultValue.side, ['right', 'left']), + showSlider: stringSet<'always' | 'mouseover'>(input.showSlider, this.defaultValue.showSlider, ['always', 'mouseover']), + renderCharacters: boolean(input.renderCharacters, this.defaultValue.renderCharacters), scale: EditorIntOption.clampedInt(input.scale, 1, 1, 3), - maxColumn: EditorIntOption.clampedInt( - input.maxColumn, - this.defaultValue.maxColumn, - 1, - 10000, - ), - showRegionSectionHeaders: boolean( - input.showRegionSectionHeaders, - this.defaultValue.showRegionSectionHeaders, - ), - showMarkSectionHeaders: boolean( - input.showMarkSectionHeaders, - this.defaultValue.showMarkSectionHeaders, - ), - sectionHeaderFontSize: EditorFloatOption.clamp( - input.sectionHeaderFontSize ?? - this.defaultValue.sectionHeaderFontSize, - 4, - 32, - ), - sectionHeaderLetterSpacing: EditorFloatOption.clamp( - input.sectionHeaderLetterSpacing ?? - this.defaultValue.sectionHeaderLetterSpacing, - 0, - 5, - ), + maxColumn: EditorIntOption.clampedInt(input.maxColumn, this.defaultValue.maxColumn, 1, 10000), + showRegionSectionHeaders: boolean(input.showRegionSectionHeaders, this.defaultValue.showRegionSectionHeaders), + showMarkSectionHeaders: boolean(input.showMarkSectionHeaders, this.defaultValue.showMarkSectionHeaders), + sectionHeaderFontSize: EditorFloatOption.clamp(input.sectionHeaderFontSize ?? this.defaultValue.sectionHeaderFontSize, 4, 32), + sectionHeaderLetterSpacing: EditorFloatOption.clamp(input.sectionHeaderLetterSpacing ?? this.defaultValue.sectionHeaderLetterSpacing, 0, 5), }; } } @@ -4205,13 +3290,11 @@ class EditorMinimap extends BaseEditorOption< //#region multiCursorModifier -function _multiCursorModifierFromString( - multiCursorModifier: "ctrlCmd" | "alt", -): "altKey" | "metaKey" | "ctrlKey" { - if (multiCursorModifier === "ctrlCmd") { - return platform.isMacintosh ? "metaKey" : "ctrlKey"; +function _multiCursorModifierFromString(multiCursorModifier: 'ctrlCmd' | 'alt'): 'altKey' | 'metaKey' | 'ctrlKey' { + if (multiCursorModifier === 'ctrlCmd') { + return (platform.isMacintosh ? 'metaKey' : 'ctrlKey'); } - return "altKey"; + return 'altKey'; } //#endregion @@ -4235,54 +3318,41 @@ export interface IEditorPaddingOptions { /** * @internal */ -export type InternalEditorPaddingOptions = Readonly< - Required ->; - -class EditorPadding extends BaseEditorOption< - EditorOption.padding, - IEditorPaddingOptions, - InternalEditorPaddingOptions -> { +export type InternalEditorPaddingOptions = Readonly>; + +class EditorPadding extends BaseEditorOption { + constructor() { super( - EditorOption.padding, - "padding", - { top: 0, bottom: 0 }, + EditorOption.padding, 'padding', { top: 0, bottom: 0 }, { - "editor.padding.top": { - type: "number", + 'editor.padding.top': { + type: 'number', default: 0, minimum: 0, maximum: 1000, - description: nls.localize( - "padding.top", - "Controls the amount of space between the top edge of the editor and the first line.", - ), + description: nls.localize('padding.top', "Controls the amount of space between the top edge of the editor and the first line.") }, - "editor.padding.bottom": { - type: "number", + 'editor.padding.bottom': { + type: 'number', default: 0, minimum: 0, maximum: 1000, - description: nls.localize( - "padding.bottom", - "Controls the amount of space between the bottom edge of the editor and the last line.", - ), - }, - }, + description: nls.localize('padding.bottom', "Controls the amount of space between the bottom edge of the editor and the last line.") + } + } ); } public validate(_input: any): InternalEditorPaddingOptions { - if (!_input || typeof _input !== "object") { + if (!_input || typeof _input !== 'object') { return this.defaultValue; } const input = _input as IEditorPaddingOptions; return { top: EditorIntOption.clampedInt(input.top, 0, 0, 1000), - bottom: EditorIntOption.clampedInt(input.bottom, 0, 0, 1000), + bottom: EditorIntOption.clampedInt(input.bottom, 0, 0, 1000) }; } } @@ -4309,48 +3379,40 @@ export interface IEditorParameterHintOptions { /** * @internal */ -export type InternalParameterHintOptions = Readonly< - Required ->; - -class EditorParameterHints extends BaseEditorOption< - EditorOption.parameterHints, - IEditorParameterHintOptions, - InternalParameterHintOptions -> { +export type InternalParameterHintOptions = Readonly>; + +class EditorParameterHints extends BaseEditorOption { + constructor() { const defaults: InternalParameterHintOptions = { enabled: true, - cycle: true, + cycle: true }; - super(EditorOption.parameterHints, "parameterHints", defaults, { - "editor.parameterHints.enabled": { - type: "boolean", - default: defaults.enabled, - description: nls.localize( - "parameterHints.enabled", - "Enables a pop-up that shows parameter documentation and type information as you type.", - ), - }, - "editor.parameterHints.cycle": { - type: "boolean", - default: defaults.cycle, - description: nls.localize( - "parameterHints.cycle", - "Controls whether the parameter hints menu cycles or closes when reaching the end of the list.", - ), - }, - }); + super( + EditorOption.parameterHints, 'parameterHints', defaults, + { + 'editor.parameterHints.enabled': { + type: 'boolean', + default: defaults.enabled, + description: nls.localize('parameterHints.enabled', "Enables a pop-up that shows parameter documentation and type information as you type.") + }, + 'editor.parameterHints.cycle': { + type: 'boolean', + default: defaults.cycle, + description: nls.localize('parameterHints.cycle', "Controls whether the parameter hints menu cycles or closes when reaching the end of the list.") + }, + } + ); } public validate(_input: any): InternalParameterHintOptions { - if (!_input || typeof _input !== "object") { + if (!_input || typeof _input !== 'object') { return this.defaultValue; } const input = _input as IEditorParameterHintOptions; return { enabled: boolean(input.enabled, this.defaultValue.enabled), - cycle: boolean(input.cycle, this.defaultValue.cycle), + cycle: boolean(input.cycle, this.defaultValue.cycle) }; } } @@ -4359,19 +3421,13 @@ class EditorParameterHints extends BaseEditorOption< //#region pixelRatio -class EditorPixelRatio extends ComputedEditorOption< - EditorOption.pixelRatio, - number -> { +class EditorPixelRatio extends ComputedEditorOption { + constructor() { super(EditorOption.pixelRatio); } - public compute( - env: IEnvironmentalOptions, - options: IComputedEditorOptions, - _: number, - ): number { + public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, _: number): number { return env.pixelRatio; } } @@ -4380,20 +3436,16 @@ class EditorPixelRatio extends ComputedEditorOption< //#region -class PlaceholderOption extends BaseEditorOption< - EditorOption.placeholder, - string | undefined, - string | undefined -> { +class PlaceholderOption extends BaseEditorOption { constructor() { - super(EditorOption.placeholder, "placeholder", undefined); + super(EditorOption.placeholder, 'placeholder', undefined); } public validate(input: any): string | undefined { - if (typeof input === "undefined") { + if (typeof input === 'undefined') { return this.defaultValue; } - if (typeof input === "string") { + if (typeof input === 'string') { return input; } return this.defaultValue; @@ -4403,7 +3455,7 @@ class PlaceholderOption extends BaseEditorOption< //#region quickSuggestions -export type QuickSuggestionsValue = "on" | "inline" | "off"; +export type QuickSuggestionsValue = 'on' | 'inline' | 'off'; /** * Configuration options for quick suggestions @@ -4420,124 +3472,86 @@ export interface InternalQuickSuggestionsOptions { readonly strings: QuickSuggestionsValue; } -class EditorQuickSuggestions extends BaseEditorOption< - EditorOption.quickSuggestions, - boolean | IQuickSuggestionsOptions, - InternalQuickSuggestionsOptions -> { +class EditorQuickSuggestions extends BaseEditorOption { + public override readonly defaultValue: InternalQuickSuggestionsOptions; constructor() { const defaults: InternalQuickSuggestionsOptions = { - other: "on", - comments: "off", - strings: "off", + other: 'on', + comments: 'off', + strings: 'off' }; const types: IJSONSchema[] = [ - { type: "boolean" }, + { type: 'boolean' }, { - type: "string", - enum: ["on", "inline", "off"], - enumDescriptions: [ - nls.localize( - "on", - "Quick suggestions show inside the suggest widget", - ), - nls.localize( - "inline", - "Quick suggestions show as ghost text", - ), - nls.localize("off", "Quick suggestions are disabled"), - ], - }, + type: 'string', + enum: ['on', 'inline', 'off'], + enumDescriptions: [nls.localize('on', "Quick suggestions show inside the suggest widget"), nls.localize('inline', "Quick suggestions show as ghost text"), nls.localize('off', "Quick suggestions are disabled")] + } ]; - super(EditorOption.quickSuggestions, "quickSuggestions", defaults, { - type: "object", + super(EditorOption.quickSuggestions, 'quickSuggestions', defaults, { + type: 'object', additionalProperties: false, properties: { strings: { anyOf: types, default: defaults.strings, - description: nls.localize( - "quickSuggestions.strings", - "Enable quick suggestions inside strings.", - ), + description: nls.localize('quickSuggestions.strings', "Enable quick suggestions inside strings.") }, comments: { anyOf: types, default: defaults.comments, - description: nls.localize( - "quickSuggestions.comments", - "Enable quick suggestions inside comments.", - ), + description: nls.localize('quickSuggestions.comments', "Enable quick suggestions inside comments.") }, other: { anyOf: types, default: defaults.other, - description: nls.localize( - "quickSuggestions.other", - "Enable quick suggestions outside of strings and comments.", - ), + description: nls.localize('quickSuggestions.other', "Enable quick suggestions outside of strings and comments.") }, }, default: defaults, - markdownDescription: nls.localize( - "quickSuggestions", - "Controls whether suggestions should automatically show up while typing. This can be controlled for typing in comments, strings, and other code. Quick suggestion can be configured to show as ghost text or with the suggest widget. Also be aware of the {0}-setting which controls if suggestions are triggered by special characters.", - "`#editor.suggestOnTriggerCharacters#`", - ), + markdownDescription: nls.localize('quickSuggestions', "Controls whether suggestions should automatically show up while typing. This can be controlled for typing in comments, strings, and other code. Quick suggestion can be configured to show as ghost text or with the suggest widget. Also be aware of the {0}-setting which controls if suggestions are triggered by special characters.", '`#editor.suggestOnTriggerCharacters#`') }); this.defaultValue = defaults; } public validate(input: any): InternalQuickSuggestionsOptions { - if (typeof input === "boolean") { + if (typeof input === 'boolean') { // boolean -> all on/off - const value = input ? "on" : "off"; + const value = input ? 'on' : 'off'; return { comments: value, strings: value, other: value }; } - if (!input || typeof input !== "object") { + if (!input || typeof input !== 'object') { // invalid object return this.defaultValue; } - const { other, comments, strings } = input; - const allowedValues: QuickSuggestionsValue[] = ["on", "inline", "off"]; + const { other, comments, strings } = (input); + const allowedValues: QuickSuggestionsValue[] = ['on', 'inline', 'off']; let validatedOther: QuickSuggestionsValue; let validatedComments: QuickSuggestionsValue; let validatedStrings: QuickSuggestionsValue; - if (typeof other === "boolean") { - validatedOther = other ? "on" : "off"; + if (typeof other === 'boolean') { + validatedOther = other ? 'on' : 'off'; } else { - validatedOther = stringSet( - other, - this.defaultValue.other, - allowedValues, - ); + validatedOther = stringSet(other, this.defaultValue.other, allowedValues); } - if (typeof comments === "boolean") { - validatedComments = comments ? "on" : "off"; + if (typeof comments === 'boolean') { + validatedComments = comments ? 'on' : 'off'; } else { - validatedComments = stringSet( - comments, - this.defaultValue.comments, - allowedValues, - ); + validatedComments = stringSet(comments, this.defaultValue.comments, allowedValues); } - if (typeof strings === "boolean") { - validatedStrings = strings ? "on" : "off"; + if (typeof strings === 'boolean') { + validatedStrings = strings ? 'on' : 'off'; } else { - validatedStrings = stringSet( - strings, - this.defaultValue.strings, - allowedValues, - ); + validatedStrings = stringSet(strings, this.defaultValue.strings, allowedValues); } return { other: validatedOther, comments: validatedComments, - strings: validatedStrings, + strings: validatedStrings }; } } @@ -4546,19 +3560,14 @@ class EditorQuickSuggestions extends BaseEditorOption< //#region renderLineNumbers -export type LineNumbersType = - | "on" - | "off" - | "relative" - | "interval" - | ((lineNumber: number) => string); +export type LineNumbersType = 'on' | 'off' | 'relative' | 'interval' | ((lineNumber: number) => string); export const enum RenderLineNumbersType { Off = 0, On = 1, Relative = 2, Interval = 3, - Custom = 4, + Custom = 4 } export interface InternalEditorRenderLineNumbersOptions { @@ -4566,60 +3575,39 @@ export interface InternalEditorRenderLineNumbersOptions { readonly renderFn: ((lineNumber: number) => string) | null; } -class EditorRenderLineNumbersOption extends BaseEditorOption< - EditorOption.lineNumbers, - LineNumbersType, - InternalEditorRenderLineNumbersOptions -> { +class EditorRenderLineNumbersOption extends BaseEditorOption { + constructor() { super( - EditorOption.lineNumbers, - "lineNumbers", - { renderType: RenderLineNumbersType.On, renderFn: null }, + EditorOption.lineNumbers, 'lineNumbers', { renderType: RenderLineNumbersType.On, renderFn: null }, { - type: "string", - enum: ["off", "on", "relative", "interval"], + type: 'string', + enum: ['off', 'on', 'relative', 'interval'], enumDescriptions: [ - nls.localize( - "lineNumbers.off", - "Line numbers are not rendered.", - ), - nls.localize( - "lineNumbers.on", - "Line numbers are rendered as absolute number.", - ), - nls.localize( - "lineNumbers.relative", - "Line numbers are rendered as distance in lines to cursor position.", - ), - nls.localize( - "lineNumbers.interval", - "Line numbers are rendered every 10 lines.", - ), + nls.localize('lineNumbers.off', "Line numbers are not rendered."), + nls.localize('lineNumbers.on', "Line numbers are rendered as absolute number."), + nls.localize('lineNumbers.relative', "Line numbers are rendered as distance in lines to cursor position."), + nls.localize('lineNumbers.interval', "Line numbers are rendered every 10 lines.") ], - default: "on", - description: nls.localize( - "lineNumbers", - "Controls the display of line numbers.", - ), - }, + default: 'on', + description: nls.localize('lineNumbers', "Controls the display of line numbers.") + } ); } public validate(lineNumbers: any): InternalEditorRenderLineNumbersOptions { let renderType: RenderLineNumbersType = this.defaultValue.renderType; - let renderFn: ((lineNumber: number) => string) | null = - this.defaultValue.renderFn; + let renderFn: ((lineNumber: number) => string) | null = this.defaultValue.renderFn; - if (typeof lineNumbers !== "undefined") { - if (typeof lineNumbers === "function") { + if (typeof lineNumbers !== 'undefined') { + if (typeof lineNumbers === 'function') { renderType = RenderLineNumbersType.Custom; renderFn = lineNumbers; - } else if (lineNumbers === "interval") { + } else if (lineNumbers === 'interval') { renderType = RenderLineNumbersType.Interval; - } else if (lineNumbers === "relative") { + } else if (lineNumbers === 'relative') { renderType = RenderLineNumbersType.Relative; - } else if (lineNumbers === "on") { + } else if (lineNumbers === 'on') { renderType = RenderLineNumbersType.On; } else { renderType = RenderLineNumbersType.Off; @@ -4628,7 +3616,7 @@ class EditorRenderLineNumbersOption extends BaseEditorOption< return { renderType, - renderFn, + renderFn }; } } @@ -4640,16 +3628,12 @@ class EditorRenderLineNumbersOption extends BaseEditorOption< /** * @internal */ -export function filterValidationDecorations( - options: IComputedEditorOptions, -): boolean { - const renderValidationDecorations = options.get( - EditorOption.renderValidationDecorations, - ); - if (renderValidationDecorations === "editable") { +export function filterValidationDecorations(options: IComputedEditorOptions): boolean { + const renderValidationDecorations = options.get(EditorOption.renderValidationDecorations); + if (renderValidationDecorations === 'editable') { return options.get(EditorOption.readOnly); } - return renderValidationDecorations === "on" ? false : true; + return renderValidationDecorations === 'on' ? false : true; } //#endregion @@ -4661,73 +3645,53 @@ export interface IRulerOption { readonly color: string | null; } -class EditorRulers extends BaseEditorOption< - EditorOption.rulers, - (number | IRulerOption)[], - IRulerOption[] -> { +class EditorRulers extends BaseEditorOption { + constructor() { const defaults: IRulerOption[] = []; - const columnSchema: IJSONSchema = { - type: "number", - description: nls.localize( - "rulers.size", - "Number of monospace characters at which this editor ruler will render.", - ), - }; - super(EditorOption.rulers, "rulers", defaults, { - type: "array", - items: { - anyOf: [ - columnSchema, - { - type: ["object"], - properties: { - column: columnSchema, - color: { - type: "string", - description: nls.localize( - "rulers.color", - "Color of this editor ruler.", - ), - format: "color-hex", - }, - }, - }, - ], - }, - default: defaults, - description: nls.localize( - "rulers", - "Render vertical rulers after a certain number of monospace characters. Use multiple values for multiple rulers. No rulers are drawn if array is empty.", - ), - }); + const columnSchema: IJSONSchema = { type: 'number', description: nls.localize('rulers.size', "Number of monospace characters at which this editor ruler will render.") }; + super( + EditorOption.rulers, 'rulers', defaults, + { + type: 'array', + items: { + anyOf: [ + columnSchema, + { + type: [ + 'object' + ], + properties: { + column: columnSchema, + color: { + type: 'string', + description: nls.localize('rulers.color', "Color of this editor ruler."), + format: 'color-hex' + } + } + } + ] + }, + default: defaults, + description: nls.localize('rulers', "Render vertical rulers after a certain number of monospace characters. Use multiple values for multiple rulers. No rulers are drawn if array is empty.") + } + ); } public validate(input: any): IRulerOption[] { if (Array.isArray(input)) { const rulers: IRulerOption[] = []; for (const _element of input) { - if (typeof _element === "number") { + if (typeof _element === 'number') { rulers.push({ - column: EditorIntOption.clampedInt( - _element, - 0, - 0, - 10000, - ), - color: null, + column: EditorIntOption.clampedInt(_element, 0, 0, 10000), + color: null }); - } else if (_element && typeof _element === "object") { + } else if (_element && typeof _element === 'object') { const element = _element as IRulerOption; rulers.push({ - column: EditorIntOption.clampedInt( - element.column, - 0, - 0, - 10000, - ), - color: element.color, + column: EditorIntOption.clampedInt(element.column, 0, 0, 10000), + color: element.color }); } } @@ -4745,19 +3709,17 @@ class EditorRulers extends BaseEditorOption< /** * Configuration options for readonly message */ -class ReadonlyMessage extends BaseEditorOption< - EditorOption.readOnlyMessage, - IMarkdownString | undefined, - IMarkdownString | undefined -> { +class ReadonlyMessage extends BaseEditorOption { constructor() { const defaults = undefined; - super(EditorOption.readOnlyMessage, "readOnlyMessage", defaults); + super( + EditorOption.readOnlyMessage, 'readOnlyMessage', defaults + ); } public validate(_input: any): IMarkdownString | undefined { - if (!_input || typeof _input !== "object") { + if (!_input || typeof _input !== 'object') { return this.defaultValue; } return _input as IMarkdownString; @@ -4782,12 +3744,12 @@ export interface IEditorScrollbarOptions { * Render vertical scrollbar. * Defaults to 'auto'. */ - vertical?: "auto" | "visible" | "hidden"; + vertical?: 'auto' | 'visible' | 'hidden'; /** * Render horizontal scrollbar. * Defaults to 'auto'. */ - horizontal?: "auto" | "visible" | "hidden"; + horizontal?: 'auto' | 'visible' | 'hidden'; /** * Cast horizontal and vertical shadows when the content is scrolled. * Defaults to true. @@ -4869,28 +3831,19 @@ export interface InternalEditorScrollbarOptions { readonly ignoreHorizontalScrollbarInContentHeight: boolean; } -function _scrollbarVisibilityFromString( - visibility: string | undefined, - defaultValue: ScrollbarVisibility, -): ScrollbarVisibility { - if (typeof visibility !== "string") { +function _scrollbarVisibilityFromString(visibility: string | undefined, defaultValue: ScrollbarVisibility): ScrollbarVisibility { + if (typeof visibility !== 'string') { return defaultValue; } switch (visibility) { - case "hidden": - return ScrollbarVisibility.Hidden; - case "visible": - return ScrollbarVisibility.Visible; - default: - return ScrollbarVisibility.Auto; + case 'hidden': return ScrollbarVisibility.Hidden; + case 'visible': return ScrollbarVisibility.Visible; + default: return ScrollbarVisibility.Auto; } } -class EditorScrollbar extends BaseEditorOption< - EditorOption.scrollbar, - IEditorScrollbarOptions, - InternalEditorScrollbarOptions -> { +class EditorScrollbar extends BaseEditorOption { + constructor() { const defaults: InternalEditorScrollbarOptions = { vertical: ScrollbarVisibility.Auto, @@ -4908,159 +3861,77 @@ class EditorScrollbar extends BaseEditorOption< scrollByPage: false, ignoreHorizontalScrollbarInContentHeight: false, }; - super(EditorOption.scrollbar, "scrollbar", defaults, { - "editor.scrollbar.vertical": { - type: "string", - enum: ["auto", "visible", "hidden"], - enumDescriptions: [ - nls.localize( - "scrollbar.vertical.auto", - "The vertical scrollbar will be visible only when necessary.", - ), - nls.localize( - "scrollbar.vertical.visible", - "The vertical scrollbar will always be visible.", - ), - nls.localize( - "scrollbar.vertical.fit", - "The vertical scrollbar will always be hidden.", - ), - ], - default: "auto", - description: nls.localize( - "scrollbar.vertical", - "Controls the visibility of the vertical scrollbar.", - ), - }, - "editor.scrollbar.horizontal": { - type: "string", - enum: ["auto", "visible", "hidden"], - enumDescriptions: [ - nls.localize( - "scrollbar.horizontal.auto", - "The horizontal scrollbar will be visible only when necessary.", - ), - nls.localize( - "scrollbar.horizontal.visible", - "The horizontal scrollbar will always be visible.", - ), - nls.localize( - "scrollbar.horizontal.fit", - "The horizontal scrollbar will always be hidden.", - ), - ], - default: "auto", - description: nls.localize( - "scrollbar.horizontal", - "Controls the visibility of the horizontal scrollbar.", - ), - }, - "editor.scrollbar.verticalScrollbarSize": { - type: "number", - default: defaults.verticalScrollbarSize, - description: nls.localize( - "scrollbar.verticalScrollbarSize", - "The width of the vertical scrollbar.", - ), - }, - "editor.scrollbar.horizontalScrollbarSize": { - type: "number", - default: defaults.horizontalScrollbarSize, - description: nls.localize( - "scrollbar.horizontalScrollbarSize", - "The height of the horizontal scrollbar.", - ), - }, - "editor.scrollbar.scrollByPage": { - type: "boolean", - default: defaults.scrollByPage, - description: nls.localize( - "scrollbar.scrollByPage", - "Controls whether clicks scroll by page or jump to click position.", - ), - }, - "editor.scrollbar.ignoreHorizontalScrollbarInContentHeight": { - type: "boolean", - default: defaults.ignoreHorizontalScrollbarInContentHeight, - description: nls.localize( - "scrollbar.ignoreHorizontalScrollbarInContentHeight", - "When set, the horizontal scrollbar will not increase the size of the editor's content.", - ), - }, - }); + super( + EditorOption.scrollbar, 'scrollbar', defaults, + { + 'editor.scrollbar.vertical': { + type: 'string', + enum: ['auto', 'visible', 'hidden'], + enumDescriptions: [ + nls.localize('scrollbar.vertical.auto', "The vertical scrollbar will be visible only when necessary."), + nls.localize('scrollbar.vertical.visible', "The vertical scrollbar will always be visible."), + nls.localize('scrollbar.vertical.fit', "The vertical scrollbar will always be hidden."), + ], + default: 'auto', + description: nls.localize('scrollbar.vertical', "Controls the visibility of the vertical scrollbar.") + }, + 'editor.scrollbar.horizontal': { + type: 'string', + enum: ['auto', 'visible', 'hidden'], + enumDescriptions: [ + nls.localize('scrollbar.horizontal.auto', "The horizontal scrollbar will be visible only when necessary."), + nls.localize('scrollbar.horizontal.visible', "The horizontal scrollbar will always be visible."), + nls.localize('scrollbar.horizontal.fit', "The horizontal scrollbar will always be hidden."), + ], + default: 'auto', + description: nls.localize('scrollbar.horizontal', "Controls the visibility of the horizontal scrollbar.") + }, + 'editor.scrollbar.verticalScrollbarSize': { + type: 'number', + default: defaults.verticalScrollbarSize, + description: nls.localize('scrollbar.verticalScrollbarSize', "The width of the vertical scrollbar.") + }, + 'editor.scrollbar.horizontalScrollbarSize': { + type: 'number', + default: defaults.horizontalScrollbarSize, + description: nls.localize('scrollbar.horizontalScrollbarSize', "The height of the horizontal scrollbar.") + }, + 'editor.scrollbar.scrollByPage': { + type: 'boolean', + default: defaults.scrollByPage, + description: nls.localize('scrollbar.scrollByPage', "Controls whether clicks scroll by page or jump to click position.") + }, + 'editor.scrollbar.ignoreHorizontalScrollbarInContentHeight': { + type: 'boolean', + default: defaults.ignoreHorizontalScrollbarInContentHeight, + description: nls.localize('scrollbar.ignoreHorizontalScrollbarInContentHeight', "When set, the horizontal scrollbar will not increase the size of the editor's content.") + } + } + ); } public validate(_input: any): InternalEditorScrollbarOptions { - if (!_input || typeof _input !== "object") { + if (!_input || typeof _input !== 'object') { return this.defaultValue; } const input = _input as IEditorScrollbarOptions; - const horizontalScrollbarSize = EditorIntOption.clampedInt( - input.horizontalScrollbarSize, - this.defaultValue.horizontalScrollbarSize, - 0, - 1000, - ); - const verticalScrollbarSize = EditorIntOption.clampedInt( - input.verticalScrollbarSize, - this.defaultValue.verticalScrollbarSize, - 0, - 1000, - ); + const horizontalScrollbarSize = EditorIntOption.clampedInt(input.horizontalScrollbarSize, this.defaultValue.horizontalScrollbarSize, 0, 1000); + const verticalScrollbarSize = EditorIntOption.clampedInt(input.verticalScrollbarSize, this.defaultValue.verticalScrollbarSize, 0, 1000); return { - arrowSize: EditorIntOption.clampedInt( - input.arrowSize, - this.defaultValue.arrowSize, - 0, - 1000, - ), - vertical: _scrollbarVisibilityFromString( - input.vertical, - this.defaultValue.vertical, - ), - horizontal: _scrollbarVisibilityFromString( - input.horizontal, - this.defaultValue.horizontal, - ), + arrowSize: EditorIntOption.clampedInt(input.arrowSize, this.defaultValue.arrowSize, 0, 1000), + vertical: _scrollbarVisibilityFromString(input.vertical, this.defaultValue.vertical), + horizontal: _scrollbarVisibilityFromString(input.horizontal, this.defaultValue.horizontal), useShadows: boolean(input.useShadows, this.defaultValue.useShadows), - verticalHasArrows: boolean( - input.verticalHasArrows, - this.defaultValue.verticalHasArrows, - ), - horizontalHasArrows: boolean( - input.horizontalHasArrows, - this.defaultValue.horizontalHasArrows, - ), - handleMouseWheel: boolean( - input.handleMouseWheel, - this.defaultValue.handleMouseWheel, - ), - alwaysConsumeMouseWheel: boolean( - input.alwaysConsumeMouseWheel, - this.defaultValue.alwaysConsumeMouseWheel, - ), + verticalHasArrows: boolean(input.verticalHasArrows, this.defaultValue.verticalHasArrows), + horizontalHasArrows: boolean(input.horizontalHasArrows, this.defaultValue.horizontalHasArrows), + handleMouseWheel: boolean(input.handleMouseWheel, this.defaultValue.handleMouseWheel), + alwaysConsumeMouseWheel: boolean(input.alwaysConsumeMouseWheel, this.defaultValue.alwaysConsumeMouseWheel), horizontalScrollbarSize: horizontalScrollbarSize, - horizontalSliderSize: EditorIntOption.clampedInt( - input.horizontalSliderSize, - horizontalScrollbarSize, - 0, - 1000, - ), + horizontalSliderSize: EditorIntOption.clampedInt(input.horizontalSliderSize, horizontalScrollbarSize, 0, 1000), verticalScrollbarSize: verticalScrollbarSize, - verticalSliderSize: EditorIntOption.clampedInt( - input.verticalSliderSize, - verticalScrollbarSize, - 0, - 1000, - ), - scrollByPage: boolean( - input.scrollByPage, - this.defaultValue.scrollByPage, - ), - ignoreHorizontalScrollbarInContentHeight: boolean( - input.ignoreHorizontalScrollbarInContentHeight, - this.defaultValue.ignoreHorizontalScrollbarInContentHeight, - ), + verticalSliderSize: EditorIntOption.clampedInt(input.verticalSliderSize, verticalScrollbarSize, 0, 1000), + scrollByPage: boolean(input.scrollByPage, this.defaultValue.scrollByPage), + ignoreHorizontalScrollbarInContentHeight: boolean(input.ignoreHorizontalScrollbarInContentHeight, this.defaultValue.ignoreHorizontalScrollbarInContentHeight), }; } } @@ -5069,18 +3940,18 @@ class EditorScrollbar extends BaseEditorOption< //#region UnicodeHighlight -export type InUntrustedWorkspace = "inUntrustedWorkspace"; +export type InUntrustedWorkspace = 'inUntrustedWorkspace'; /** * @internal - */ -export const inUntrustedWorkspace: InUntrustedWorkspace = - "inUntrustedWorkspace"; +*/ +export const inUntrustedWorkspace: InUntrustedWorkspace = 'inUntrustedWorkspace'; /** * Configuration options for unicode highlighting. */ export interface IUnicodeHighlightOptions { + /** * Controls whether all non-basic ASCII characters are highlighted. Only characters between U+0020 and U+007E, tab, line-feed and carriage-return are considered basic ASCII. */ @@ -5114,34 +3985,28 @@ export interface IUnicodeHighlightOptions { /** * Unicode characters that are common in allowed locales are not being highlighted. */ - allowedLocales?: Record; + allowedLocales?: Record; } /** * @internal */ -export type InternalUnicodeHighlightOptions = Required< - Readonly ->; +export type InternalUnicodeHighlightOptions = Required>; /** * @internal */ export const unicodeHighlightConfigKeys = { - allowedCharacters: "editor.unicodeHighlight.allowedCharacters", - invisibleCharacters: "editor.unicodeHighlight.invisibleCharacters", - nonBasicASCII: "editor.unicodeHighlight.nonBasicASCII", - ambiguousCharacters: "editor.unicodeHighlight.ambiguousCharacters", - includeComments: "editor.unicodeHighlight.includeComments", - includeStrings: "editor.unicodeHighlight.includeStrings", - allowedLocales: "editor.unicodeHighlight.allowedLocales", + allowedCharacters: 'editor.unicodeHighlight.allowedCharacters', + invisibleCharacters: 'editor.unicodeHighlight.invisibleCharacters', + nonBasicASCII: 'editor.unicodeHighlight.nonBasicASCII', + ambiguousCharacters: 'editor.unicodeHighlight.ambiguousCharacters', + includeComments: 'editor.unicodeHighlight.includeComments', + includeStrings: 'editor.unicodeHighlight.includeStrings', + allowedLocales: 'editor.unicodeHighlight.allowedLocales', }; -class UnicodeHighlight extends BaseEditorOption< - EditorOption.unicodeHighlighting, - IUnicodeHighlightOptions, - InternalUnicodeHighlightOptions -> { +class UnicodeHighlight extends BaseEditorOption { constructor() { const defaults: InternalUnicodeHighlightOptions = { nonBasicASCII: inUntrustedWorkspace, @@ -5153,99 +4018,70 @@ class UnicodeHighlight extends BaseEditorOption< allowedLocales: { _os: true, _vscode: true }, }; - super(EditorOption.unicodeHighlighting, "unicodeHighlight", defaults, { - [unicodeHighlightConfigKeys.nonBasicASCII]: { - restricted: true, - type: ["boolean", "string"], - enum: [true, false, inUntrustedWorkspace], - default: defaults.nonBasicASCII, - description: nls.localize( - "unicodeHighlight.nonBasicASCII", - "Controls whether all non-basic ASCII characters are highlighted. Only characters between U+0020 and U+007E, tab, line-feed and carriage-return are considered basic ASCII.", - ), - }, - [unicodeHighlightConfigKeys.invisibleCharacters]: { - restricted: true, - type: "boolean", - default: defaults.invisibleCharacters, - description: nls.localize( - "unicodeHighlight.invisibleCharacters", - "Controls whether characters that just reserve space or have no width at all are highlighted.", - ), - }, - [unicodeHighlightConfigKeys.ambiguousCharacters]: { - restricted: true, - type: "boolean", - default: defaults.ambiguousCharacters, - description: nls.localize( - "unicodeHighlight.ambiguousCharacters", - "Controls whether characters are highlighted that can be confused with basic ASCII characters, except those that are common in the current user locale.", - ), - }, - [unicodeHighlightConfigKeys.includeComments]: { - restricted: true, - type: ["boolean", "string"], - enum: [true, false, inUntrustedWorkspace], - default: defaults.includeComments, - description: nls.localize( - "unicodeHighlight.includeComments", - "Controls whether characters in comments should also be subject to Unicode highlighting.", - ), - }, - [unicodeHighlightConfigKeys.includeStrings]: { - restricted: true, - type: ["boolean", "string"], - enum: [true, false, inUntrustedWorkspace], - default: defaults.includeStrings, - description: nls.localize( - "unicodeHighlight.includeStrings", - "Controls whether characters in strings should also be subject to Unicode highlighting.", - ), - }, - [unicodeHighlightConfigKeys.allowedCharacters]: { - restricted: true, - type: "object", - default: defaults.allowedCharacters, - description: nls.localize( - "unicodeHighlight.allowedCharacters", - "Defines allowed characters that are not being highlighted.", - ), - additionalProperties: { - type: "boolean", + super( + EditorOption.unicodeHighlighting, 'unicodeHighlight', defaults, + { + [unicodeHighlightConfigKeys.nonBasicASCII]: { + restricted: true, + type: ['boolean', 'string'], + enum: [true, false, inUntrustedWorkspace], + default: defaults.nonBasicASCII, + description: nls.localize('unicodeHighlight.nonBasicASCII', "Controls whether all non-basic ASCII characters are highlighted. Only characters between U+0020 and U+007E, tab, line-feed and carriage-return are considered basic ASCII.") }, - }, - [unicodeHighlightConfigKeys.allowedLocales]: { - restricted: true, - type: "object", - additionalProperties: { - type: "boolean", + [unicodeHighlightConfigKeys.invisibleCharacters]: { + restricted: true, + type: 'boolean', + default: defaults.invisibleCharacters, + description: nls.localize('unicodeHighlight.invisibleCharacters', "Controls whether characters that just reserve space or have no width at all are highlighted.") }, - default: defaults.allowedLocales, - description: nls.localize( - "unicodeHighlight.allowedLocales", - "Unicode characters that are common in allowed locales are not being highlighted.", - ), - }, - }); + [unicodeHighlightConfigKeys.ambiguousCharacters]: { + restricted: true, + type: 'boolean', + default: defaults.ambiguousCharacters, + description: nls.localize('unicodeHighlight.ambiguousCharacters', "Controls whether characters are highlighted that can be confused with basic ASCII characters, except those that are common in the current user locale.") + }, + [unicodeHighlightConfigKeys.includeComments]: { + restricted: true, + type: ['boolean', 'string'], + enum: [true, false, inUntrustedWorkspace], + default: defaults.includeComments, + description: nls.localize('unicodeHighlight.includeComments', "Controls whether characters in comments should also be subject to Unicode highlighting.") + }, + [unicodeHighlightConfigKeys.includeStrings]: { + restricted: true, + type: ['boolean', 'string'], + enum: [true, false, inUntrustedWorkspace], + default: defaults.includeStrings, + description: nls.localize('unicodeHighlight.includeStrings', "Controls whether characters in strings should also be subject to Unicode highlighting.") + }, + [unicodeHighlightConfigKeys.allowedCharacters]: { + restricted: true, + type: 'object', + default: defaults.allowedCharacters, + description: nls.localize('unicodeHighlight.allowedCharacters', "Defines allowed characters that are not being highlighted."), + additionalProperties: { + type: 'boolean' + } + }, + [unicodeHighlightConfigKeys.allowedLocales]: { + restricted: true, + type: 'object', + additionalProperties: { + type: 'boolean' + }, + default: defaults.allowedLocales, + description: nls.localize('unicodeHighlight.allowedLocales', "Unicode characters that are common in allowed locales are not being highlighted.") + }, + } + ); } - public override applyUpdate( - value: Required> | undefined, - update: Required>, - ): ApplyUpdateResult>> { + public override applyUpdate(value: Required> | undefined, update: Required>): ApplyUpdateResult>> { let didChange = false; if (update.allowedCharacters && value) { // Treat allowedCharacters atomically - if ( - !objects.equals( - value.allowedCharacters, - update.allowedCharacters, - ) - ) { - value = { - ...value, - allowedCharacters: update.allowedCharacters, - }; + if (!objects.equals(value.allowedCharacters, update.allowedCharacters)) { + value = { ...value, allowedCharacters: update.allowedCharacters }; didChange = true; } } @@ -5265,50 +4101,23 @@ class UnicodeHighlight extends BaseEditorOption< } public validate(_input: any): InternalUnicodeHighlightOptions { - if (!_input || typeof _input !== "object") { + if (!_input || typeof _input !== 'object') { return this.defaultValue; } const input = _input as IUnicodeHighlightOptions; return { - nonBasicASCII: primitiveSet( - input.nonBasicASCII, - inUntrustedWorkspace, - [true, false, inUntrustedWorkspace], - ), - invisibleCharacters: boolean( - input.invisibleCharacters, - this.defaultValue.invisibleCharacters, - ), - ambiguousCharacters: boolean( - input.ambiguousCharacters, - this.defaultValue.ambiguousCharacters, - ), - includeComments: primitiveSet( - input.includeComments, - inUntrustedWorkspace, - [true, false, inUntrustedWorkspace], - ), - includeStrings: primitiveSet( - input.includeStrings, - inUntrustedWorkspace, - [true, false, inUntrustedWorkspace], - ), - allowedCharacters: this.validateBooleanMap( - _input.allowedCharacters, - this.defaultValue.allowedCharacters, - ), - allowedLocales: this.validateBooleanMap( - _input.allowedLocales, - this.defaultValue.allowedLocales, - ), + nonBasicASCII: primitiveSet(input.nonBasicASCII, inUntrustedWorkspace, [true, false, inUntrustedWorkspace]), + invisibleCharacters: boolean(input.invisibleCharacters, this.defaultValue.invisibleCharacters), + ambiguousCharacters: boolean(input.ambiguousCharacters, this.defaultValue.ambiguousCharacters), + includeComments: primitiveSet(input.includeComments, inUntrustedWorkspace, [true, false, inUntrustedWorkspace]), + includeStrings: primitiveSet(input.includeStrings, inUntrustedWorkspace, [true, false, inUntrustedWorkspace]), + allowedCharacters: this.validateBooleanMap(_input.allowedCharacters, this.defaultValue.allowedCharacters), + allowedLocales: this.validateBooleanMap(_input.allowedLocales, this.defaultValue.allowedLocales), }; } - private validateBooleanMap( - map: unknown, - defaultValue: Record, - ): Record { - if (typeof map !== "object" || !map) { + private validateBooleanMap(map: unknown, defaultValue: Record): Record { + if ((typeof map !== 'object') || !map) { return defaultValue; } const result: Record = {}; @@ -5328,7 +4137,7 @@ class UnicodeHighlight extends BaseEditorOption< export interface IInlineSuggestOptions { /** * Enable or disable the rendering of automatic inline completions. - */ + */ enabled?: boolean; /** @@ -5337,10 +4146,10 @@ export interface IInlineSuggestOptions { * Use `subword` to only show ghost text if the replace text is a subword of the suggestion text. * Use `subwordSmart` to only show ghost text if the replace text is a subword of the suggestion text, but the subword must start after the cursor position. * Defaults to `prefix`. - */ - mode?: "prefix" | "subword" | "subwordSmart"; + */ + mode?: 'prefix' | 'subword' | 'subwordSmart'; - showToolbar?: "always" | "onHover" | "never"; + showToolbar?: 'always' | 'onHover' | 'never'; syntaxHighlightingEnabled?: boolean; @@ -5354,191 +4163,121 @@ export interface IInlineSuggestOptions { /** * Font family for inline suggestions. */ - fontFamily?: string | "default"; + fontFamily?: string | 'default'; edits?: { experimental?: { enabled?: boolean; - useMixedLinesDiff?: - | "never" - | "whenPossible" - | "afterJumpWhenPossible"; - useInterleavedLinesDiff?: "never" | "always" | "afterJump"; + useMixedLinesDiff?: 'never' | 'whenPossible' | 'afterJumpWhenPossible'; + useInterleavedLinesDiff?: 'never' | 'always' | 'afterJump'; }; }; } type RequiredRecursive = { - [P in keyof T]-?: T[P] extends object | undefined - ? RequiredRecursive - : T[P]; + [P in keyof T]-?: T[P] extends object | undefined ? RequiredRecursive : T[P]; }; /** * @internal */ -export type InternalInlineSuggestOptions = Readonly< - RequiredRecursive ->; +export type InternalInlineSuggestOptions = Readonly>; /** * Configuration options for inline suggestions */ -class InlineEditorSuggest extends BaseEditorOption< - EditorOption.inlineSuggest, - IInlineSuggestOptions, - InternalInlineSuggestOptions -> { +class InlineEditorSuggest extends BaseEditorOption { constructor() { const defaults: InternalInlineSuggestOptions = { enabled: true, - mode: "subwordSmart", - showToolbar: "onHover", + mode: 'subwordSmart', + showToolbar: 'onHover', suppressSuggestions: false, keepOnBlur: false, - fontFamily: "default", + fontFamily: 'default', syntaxHighlightingEnabled: false, edits: { experimental: { enabled: true, - useMixedLinesDiff: "never", - useInterleavedLinesDiff: "never", + useMixedLinesDiff: 'never', + useInterleavedLinesDiff: 'never', }, }, }; - super(EditorOption.inlineSuggest, "inlineSuggest", defaults, { - "editor.inlineSuggest.enabled": { - type: "boolean", - default: defaults.enabled, - description: nls.localize( - "inlineSuggest.enabled", - "Controls whether to automatically show inline suggestions in the editor.", - ), - }, - "editor.inlineSuggest.showToolbar": { - type: "string", - default: defaults.showToolbar, - enum: ["always", "onHover", "never"], - enumDescriptions: [ - nls.localize( - "inlineSuggest.showToolbar.always", - "Show the inline suggestion toolbar whenever an inline suggestion is shown.", - ), - nls.localize( - "inlineSuggest.showToolbar.onHover", - "Show the inline suggestion toolbar when hovering over an inline suggestion.", - ), - nls.localize( - "inlineSuggest.showToolbar.never", - "Never show the inline suggestion toolbar.", - ), - ], - description: nls.localize( - "inlineSuggest.showToolbar", - "Controls when to show the inline suggestion toolbar.", - ), - }, - "editor.inlineSuggest.syntaxHighlightingEnabled": { - type: "boolean", - default: defaults.syntaxHighlightingEnabled, - description: nls.localize( - "inlineSuggest.syntaxHighlightingEnabled", - "Controls whether to show syntax highlighting for inline suggestions in the editor.", - ), - }, - "editor.inlineSuggest.suppressSuggestions": { - type: "boolean", - default: defaults.suppressSuggestions, - description: nls.localize( - "inlineSuggest.suppressSuggestions", - "Controls how inline suggestions interact with the suggest widget. If enabled, the suggest widget is not shown automatically when inline suggestions are available.", - ), - }, - "editor.inlineSuggest.fontFamily": { - type: "string", - default: defaults.fontFamily, - description: nls.localize( - "inlineSuggest.fontFamily", - "Controls the font family of the inline suggestions.", - ), - }, - "editor.inlineSuggest.edits.experimental.enabled": { - type: "boolean", - default: defaults.edits.experimental.enabled, - description: nls.localize( - "inlineSuggest.edits.experimental.enabled", - "Controls whether to enable experimental edits in inline suggestions.", - ), - }, - "editor.inlineSuggest.edits.experimental.useMixedLinesDiff": { - type: "string", - default: defaults.edits.experimental.useMixedLinesDiff, - description: nls.localize( - "inlineSuggest.edits.experimental.useMixedLinesDiff", - "Controls whether to enable experimental edits in inline suggestions.", - ), - enum: ["never", "whenPossible"], - }, - "editor.inlineSuggest.edits.experimental.useInterleavedLinesDiff": { - type: "string", - default: defaults.edits.experimental.useInterleavedLinesDiff, - description: nls.localize( - "inlineSuggest.edits.experimental.useInterleavedLinesDiff", - "Controls whether to enable experimental interleaved lines diff in inline suggestions.", - ), - enum: ["never", "always", "afterJump"], - }, - }); + super( + EditorOption.inlineSuggest, 'inlineSuggest', defaults, + { + 'editor.inlineSuggest.enabled': { + type: 'boolean', + default: defaults.enabled, + description: nls.localize('inlineSuggest.enabled', "Controls whether to automatically show inline suggestions in the editor.") + }, + 'editor.inlineSuggest.showToolbar': { + type: 'string', + default: defaults.showToolbar, + enum: ['always', 'onHover', 'never'], + enumDescriptions: [ + nls.localize('inlineSuggest.showToolbar.always', "Show the inline suggestion toolbar whenever an inline suggestion is shown."), + nls.localize('inlineSuggest.showToolbar.onHover', "Show the inline suggestion toolbar when hovering over an inline suggestion."), + nls.localize('inlineSuggest.showToolbar.never', "Never show the inline suggestion toolbar."), + ], + description: nls.localize('inlineSuggest.showToolbar', "Controls when to show the inline suggestion toolbar."), + }, + 'editor.inlineSuggest.syntaxHighlightingEnabled': { + type: 'boolean', + default: defaults.syntaxHighlightingEnabled, + description: nls.localize('inlineSuggest.syntaxHighlightingEnabled', "Controls whether to show syntax highlighting for inline suggestions in the editor."), + }, + 'editor.inlineSuggest.suppressSuggestions': { + type: 'boolean', + default: defaults.suppressSuggestions, + description: nls.localize('inlineSuggest.suppressSuggestions', "Controls how inline suggestions interact with the suggest widget. If enabled, the suggest widget is not shown automatically when inline suggestions are available.") + }, + 'editor.inlineSuggest.fontFamily': { + type: 'string', + default: defaults.fontFamily, + description: nls.localize('inlineSuggest.fontFamily', "Controls the font family of the inline suggestions.") + }, + 'editor.inlineSuggest.edits.experimental.enabled': { + type: 'boolean', + default: defaults.edits.experimental.enabled, + description: nls.localize('inlineSuggest.edits.experimental.enabled', "Controls whether to enable experimental edits in inline suggestions.") + }, + 'editor.inlineSuggest.edits.experimental.useMixedLinesDiff': { + type: 'string', + default: defaults.edits.experimental.useMixedLinesDiff, + description: nls.localize('inlineSuggest.edits.experimental.useMixedLinesDiff', "Controls whether to enable experimental edits in inline suggestions."), + enum: ['never', 'whenPossible'], + }, + 'editor.inlineSuggest.edits.experimental.useInterleavedLinesDiff': { + type: 'string', + default: defaults.edits.experimental.useInterleavedLinesDiff, + description: nls.localize('inlineSuggest.edits.experimental.useInterleavedLinesDiff', "Controls whether to enable experimental interleaved lines diff in inline suggestions."), + enum: ['never', 'always', 'afterJump'], + }, + } + ); } public validate(_input: any): InternalInlineSuggestOptions { - if (!_input || typeof _input !== "object") { + if (!_input || typeof _input !== 'object') { return this.defaultValue; } const input = _input as IInlineSuggestOptions; return { enabled: boolean(input.enabled, this.defaultValue.enabled), - mode: stringSet(input.mode, this.defaultValue.mode, [ - "prefix", - "subword", - "subwordSmart", - ]), - showToolbar: stringSet( - input.showToolbar, - this.defaultValue.showToolbar, - ["always", "onHover", "never"], - ), - suppressSuggestions: boolean( - input.suppressSuggestions, - this.defaultValue.suppressSuggestions, - ), + mode: stringSet(input.mode, this.defaultValue.mode, ['prefix', 'subword', 'subwordSmart']), + showToolbar: stringSet(input.showToolbar, this.defaultValue.showToolbar, ['always', 'onHover', 'never']), + suppressSuggestions: boolean(input.suppressSuggestions, this.defaultValue.suppressSuggestions), keepOnBlur: boolean(input.keepOnBlur, this.defaultValue.keepOnBlur), - fontFamily: EditorStringOption.string( - input.fontFamily, - this.defaultValue.fontFamily, - ), - syntaxHighlightingEnabled: boolean( - input.syntaxHighlightingEnabled, - this.defaultValue.syntaxHighlightingEnabled, - ), + fontFamily: EditorStringOption.string(input.fontFamily, this.defaultValue.fontFamily), + syntaxHighlightingEnabled: boolean(input.syntaxHighlightingEnabled, this.defaultValue.syntaxHighlightingEnabled), edits: { experimental: { - enabled: boolean( - input.edits?.experimental?.enabled, - this.defaultValue.edits.experimental.enabled, - ), - useMixedLinesDiff: stringSet( - input.edits?.experimental?.useMixedLinesDiff, - this.defaultValue.edits.experimental.useMixedLinesDiff, - ["never", "whenPossible", "afterJumpWhenPossible"], - ), - useInterleavedLinesDiff: stringSet( - input.edits?.experimental?.useInterleavedLinesDiff, - this.defaultValue.edits.experimental - .useInterleavedLinesDiff, - ["never", "always", "afterJump"], - ), + enabled: boolean(input.edits?.experimental?.enabled, this.defaultValue.edits.experimental.enabled), + useMixedLinesDiff: stringSet(input.edits?.experimental?.useMixedLinesDiff, this.defaultValue.edits.experimental.useMixedLinesDiff, ['never', 'whenPossible', 'afterJumpWhenPossible']), + useInterleavedLinesDiff: stringSet(input.edits?.experimental?.useInterleavedLinesDiff, this.defaultValue.edits.experimental.useInterleavedLinesDiff, ['never', 'always', 'afterJump']), }, }, }; @@ -5552,77 +4291,55 @@ class InlineEditorSuggest extends BaseEditorOption< export interface IBracketPairColorizationOptions { /** * Enable or disable bracket pair colorization. - */ + */ enabled?: boolean; /** * Use independent color pool per bracket type. - */ + */ independentColorPoolPerBracketType?: boolean; } /** * @internal */ -export type InternalBracketPairColorizationOptions = Readonly< - Required ->; +export type InternalBracketPairColorizationOptions = Readonly>; /** * Configuration options for inline suggestions */ -class BracketPairColorization extends BaseEditorOption< - EditorOption.bracketPairColorization, - IBracketPairColorizationOptions, - InternalBracketPairColorizationOptions -> { +class BracketPairColorization extends BaseEditorOption { constructor() { const defaults: InternalBracketPairColorizationOptions = { - enabled: - EDITOR_MODEL_DEFAULTS.bracketPairColorizationOptions.enabled, - independentColorPoolPerBracketType: - EDITOR_MODEL_DEFAULTS.bracketPairColorizationOptions - .independentColorPoolPerBracketType, + enabled: EDITOR_MODEL_DEFAULTS.bracketPairColorizationOptions.enabled, + independentColorPoolPerBracketType: EDITOR_MODEL_DEFAULTS.bracketPairColorizationOptions.independentColorPoolPerBracketType, }; super( - EditorOption.bracketPairColorization, - "bracketPairColorization", - defaults, + EditorOption.bracketPairColorization, 'bracketPairColorization', defaults, { - "editor.bracketPairColorization.enabled": { - type: "boolean", + 'editor.bracketPairColorization.enabled': { + type: 'boolean', default: defaults.enabled, - markdownDescription: nls.localize( - "bracketPairColorization.enabled", - "Controls whether bracket pair colorization is enabled or not. Use {0} to override the bracket highlight colors.", - "`#workbench.colorCustomizations#`", - ), + markdownDescription: nls.localize('bracketPairColorization.enabled', "Controls whether bracket pair colorization is enabled or not. Use {0} to override the bracket highlight colors.", '`#workbench.colorCustomizations#`') }, - "editor.bracketPairColorization.independentColorPoolPerBracketType": - { - type: "boolean", - default: defaults.independentColorPoolPerBracketType, - description: nls.localize( - "bracketPairColorization.independentColorPoolPerBracketType", - "Controls whether each bracket type has its own independent color pool.", - ), - }, - }, + 'editor.bracketPairColorization.independentColorPoolPerBracketType': { + type: 'boolean', + default: defaults.independentColorPoolPerBracketType, + description: nls.localize('bracketPairColorization.independentColorPoolPerBracketType', "Controls whether each bracket type has its own independent color pool.") + }, + } ); } public validate(_input: any): InternalBracketPairColorizationOptions { - if (!_input || typeof _input !== "object") { + if (!_input || typeof _input !== 'object') { return this.defaultValue; } const input = _input as IBracketPairColorizationOptions; return { enabled: boolean(input.enabled, this.defaultValue.enabled), - independentColorPoolPerBracketType: boolean( - input.independentColorPoolPerBracketType, - this.defaultValue.independentColorPoolPerBracketType, - ), + independentColorPoolPerBracketType: boolean(input.independentColorPoolPerBracketType, this.defaultValue.independentColorPoolPerBracketType), }; } } @@ -5635,19 +4352,19 @@ export interface IGuidesOptions { /** * Enable rendering of bracket pair guides. * Defaults to false. - */ - bracketPairs?: boolean | "active"; + */ + bracketPairs?: boolean | 'active'; /** * Enable rendering of vertical bracket pair guides. * Defaults to 'active'. */ - bracketPairsHorizontal?: boolean | "active"; + bracketPairsHorizontal?: boolean | 'active'; /** * Enable highlighting of the active bracket pair. * Defaults to true. - */ + */ highlightActiveBracketPair?: boolean; /** @@ -5660,7 +4377,7 @@ export interface IGuidesOptions { * Enable highlighting of the active indent guide. * Defaults to true. */ - highlightActiveIndentation?: boolean | "always"; + highlightActiveIndentation?: boolean | 'always'; } /** @@ -5671,150 +4388,85 @@ export type InternalGuidesOptions = Readonly>; /** * Configuration options for inline suggestions */ -class GuideOptions extends BaseEditorOption< - EditorOption.guides, - IGuidesOptions, - InternalGuidesOptions -> { +class GuideOptions extends BaseEditorOption { constructor() { const defaults: InternalGuidesOptions = { bracketPairs: false, - bracketPairsHorizontal: "active", + bracketPairsHorizontal: 'active', highlightActiveBracketPair: true, indentation: true, - highlightActiveIndentation: true, + highlightActiveIndentation: true }; - super(EditorOption.guides, "guides", defaults, { - "editor.guides.bracketPairs": { - type: ["boolean", "string"], - enum: [true, "active", false], - enumDescriptions: [ - nls.localize( - "editor.guides.bracketPairs.true", - "Enables bracket pair guides.", - ), - nls.localize( - "editor.guides.bracketPairs.active", - "Enables bracket pair guides only for the active bracket pair.", - ), - nls.localize( - "editor.guides.bracketPairs.false", - "Disables bracket pair guides.", - ), - ], - default: defaults.bracketPairs, - description: nls.localize( - "editor.guides.bracketPairs", - "Controls whether bracket pair guides are enabled or not.", - ), - }, - "editor.guides.bracketPairsHorizontal": { - type: ["boolean", "string"], - enum: [true, "active", false], - enumDescriptions: [ - nls.localize( - "editor.guides.bracketPairsHorizontal.true", - "Enables horizontal guides as addition to vertical bracket pair guides.", - ), - nls.localize( - "editor.guides.bracketPairsHorizontal.active", - "Enables horizontal guides only for the active bracket pair.", - ), - nls.localize( - "editor.guides.bracketPairsHorizontal.false", - "Disables horizontal bracket pair guides.", - ), - ], - default: defaults.bracketPairsHorizontal, - description: nls.localize( - "editor.guides.bracketPairsHorizontal", - "Controls whether horizontal bracket pair guides are enabled or not.", - ), - }, - "editor.guides.highlightActiveBracketPair": { - type: "boolean", - default: defaults.highlightActiveBracketPair, - description: nls.localize( - "editor.guides.highlightActiveBracketPair", - "Controls whether the editor should highlight the active bracket pair.", - ), - }, - "editor.guides.indentation": { - type: "boolean", - default: defaults.indentation, - description: nls.localize( - "editor.guides.indentation", - "Controls whether the editor should render indent guides.", - ), - }, - "editor.guides.highlightActiveIndentation": { - type: ["boolean", "string"], - enum: [true, "always", false], - enumDescriptions: [ - nls.localize( - "editor.guides.highlightActiveIndentation.true", - "Highlights the active indent guide.", - ), - nls.localize( - "editor.guides.highlightActiveIndentation.always", - "Highlights the active indent guide even if bracket guides are highlighted.", - ), - nls.localize( - "editor.guides.highlightActiveIndentation.false", - "Do not highlight the active indent guide.", - ), - ], - default: defaults.highlightActiveIndentation, + super( + EditorOption.guides, 'guides', defaults, + { + 'editor.guides.bracketPairs': { + type: ['boolean', 'string'], + enum: [true, 'active', false], + enumDescriptions: [ + nls.localize('editor.guides.bracketPairs.true', "Enables bracket pair guides."), + nls.localize('editor.guides.bracketPairs.active', "Enables bracket pair guides only for the active bracket pair."), + nls.localize('editor.guides.bracketPairs.false', "Disables bracket pair guides."), + ], + default: defaults.bracketPairs, + description: nls.localize('editor.guides.bracketPairs', "Controls whether bracket pair guides are enabled or not.") + }, + 'editor.guides.bracketPairsHorizontal': { + type: ['boolean', 'string'], + enum: [true, 'active', false], + enumDescriptions: [ + nls.localize('editor.guides.bracketPairsHorizontal.true', "Enables horizontal guides as addition to vertical bracket pair guides."), + nls.localize('editor.guides.bracketPairsHorizontal.active', "Enables horizontal guides only for the active bracket pair."), + nls.localize('editor.guides.bracketPairsHorizontal.false', "Disables horizontal bracket pair guides."), + ], + default: defaults.bracketPairsHorizontal, + description: nls.localize('editor.guides.bracketPairsHorizontal', "Controls whether horizontal bracket pair guides are enabled or not.") + }, + 'editor.guides.highlightActiveBracketPair': { + type: 'boolean', + default: defaults.highlightActiveBracketPair, + description: nls.localize('editor.guides.highlightActiveBracketPair', "Controls whether the editor should highlight the active bracket pair.") + }, + 'editor.guides.indentation': { + type: 'boolean', + default: defaults.indentation, + description: nls.localize('editor.guides.indentation', "Controls whether the editor should render indent guides.") + }, + 'editor.guides.highlightActiveIndentation': { + type: ['boolean', 'string'], + enum: [true, 'always', false], + enumDescriptions: [ + nls.localize('editor.guides.highlightActiveIndentation.true', "Highlights the active indent guide."), + nls.localize('editor.guides.highlightActiveIndentation.always', "Highlights the active indent guide even if bracket guides are highlighted."), + nls.localize('editor.guides.highlightActiveIndentation.false', "Do not highlight the active indent guide."), + ], + default: defaults.highlightActiveIndentation, - description: nls.localize( - "editor.guides.highlightActiveIndentation", - "Controls whether the editor should highlight the active indent guide.", - ), - }, - }); + description: nls.localize('editor.guides.highlightActiveIndentation', "Controls whether the editor should highlight the active indent guide.") + } + } + ); } public validate(_input: any): InternalGuidesOptions { - if (!_input || typeof _input !== "object") { + if (!_input || typeof _input !== 'object') { return this.defaultValue; } const input = _input as IGuidesOptions; return { - bracketPairs: primitiveSet( - input.bracketPairs, - this.defaultValue.bracketPairs, - [true, false, "active"], - ), - bracketPairsHorizontal: primitiveSet( - input.bracketPairsHorizontal, - this.defaultValue.bracketPairsHorizontal, - [true, false, "active"], - ), - highlightActiveBracketPair: boolean( - input.highlightActiveBracketPair, - this.defaultValue.highlightActiveBracketPair, - ), - - indentation: boolean( - input.indentation, - this.defaultValue.indentation, - ), - highlightActiveIndentation: primitiveSet( - input.highlightActiveIndentation, - this.defaultValue.highlightActiveIndentation, - [true, false, "always"], - ), + bracketPairs: primitiveSet(input.bracketPairs, this.defaultValue.bracketPairs, [true, false, 'active']), + bracketPairsHorizontal: primitiveSet(input.bracketPairsHorizontal, this.defaultValue.bracketPairsHorizontal, [true, false, 'active']), + highlightActiveBracketPair: boolean(input.highlightActiveBracketPair, this.defaultValue.highlightActiveBracketPair), + + indentation: boolean(input.indentation, this.defaultValue.indentation), + highlightActiveIndentation: primitiveSet(input.highlightActiveIndentation, this.defaultValue.highlightActiveIndentation, [true, false, 'always']), }; } } -function primitiveSet( - value: unknown, - defaultValue: T, - allowedValues: T[], -): T { +function primitiveSet(value: unknown, defaultValue: T, allowedValues: T[]): T { const idx = allowedValues.indexOf(value as any); if (idx === -1) { return defaultValue; @@ -5833,7 +4485,7 @@ export interface ISuggestOptions { /** * Overwrite word ends on accept. Default to false. */ - insertMode?: "insert" | "replace"; + insertMode?: 'insert' | 'replace'; /** * Enable graceful matching. Defaults to true. */ @@ -5853,11 +4505,7 @@ export interface ISuggestOptions { /** * Select suggestions when triggered via quick suggest or trigger characters */ - selectionMode?: - | "always" - | "never" - | "whenTriggerCharacter" - | "whenQuickSuggestion"; + selectionMode?: 'always' | 'never' | 'whenTriggerCharacter' | 'whenQuickSuggestion'; /** * Enable or disable icons in suggestions. Defaults to true. */ @@ -5872,8 +4520,8 @@ export interface ISuggestOptions { preview?: boolean; /** * Configures the mode of the preview. - */ - previewMode?: "prefix" | "subword" | "subwordSmart"; + */ + previewMode?: 'prefix' | 'subword' | 'subwordSmart'; /** * Show details inline with the label. Defaults to true. */ @@ -6001,23 +4649,20 @@ export interface ISuggestOptions { */ export type InternalSuggestOptions = Readonly>; -class EditorSuggest extends BaseEditorOption< - EditorOption.suggest, - ISuggestOptions, - InternalSuggestOptions -> { +class EditorSuggest extends BaseEditorOption { + constructor() { const defaults: InternalSuggestOptions = { - insertMode: "insert", + insertMode: 'insert', filterGraceful: true, snippetsPreventQuickSuggestions: false, localityBonus: false, shareSuggestSelections: false, - selectionMode: "always", + selectionMode: 'always', showIcons: true, showStatusBar: false, preview: false, - previewMode: "subwordSmart", + previewMode: 'subwordSmart', showInlineDetails: true, showMethods: true, showFunctions: true, @@ -6049,520 +4694,280 @@ class EditorSuggest extends BaseEditorOption< showUsers: true, showIssues: true, }; - super(EditorOption.suggest, "suggest", defaults, { - "editor.suggest.insertMode": { - type: "string", - enum: ["insert", "replace"], - enumDescriptions: [ - nls.localize( - "suggest.insertMode.insert", - "Insert suggestion without overwriting text right of the cursor.", - ), - nls.localize( - "suggest.insertMode.replace", - "Insert suggestion and overwrite text right of the cursor.", - ), - ], - default: defaults.insertMode, - description: nls.localize( - "suggest.insertMode", - "Controls whether words are overwritten when accepting completions. Note that this depends on extensions opting into this feature.", - ), - }, - "editor.suggest.filterGraceful": { - type: "boolean", - default: defaults.filterGraceful, - description: nls.localize( - "suggest.filterGraceful", - "Controls whether filtering and sorting suggestions accounts for small typos.", - ), - }, - "editor.suggest.localityBonus": { - type: "boolean", - default: defaults.localityBonus, - description: nls.localize( - "suggest.localityBonus", - "Controls whether sorting favors words that appear close to the cursor.", - ), - }, - "editor.suggest.shareSuggestSelections": { - type: "boolean", - default: defaults.shareSuggestSelections, - markdownDescription: nls.localize( - "suggest.shareSuggestSelections", - "Controls whether remembered suggestion selections are shared between multiple workspaces and windows (needs `#editor.suggestSelection#`).", - ), - }, - "editor.suggest.selectionMode": { - type: "string", - enum: [ - "always", - "never", - "whenTriggerCharacter", - "whenQuickSuggestion", - ], - enumDescriptions: [ - nls.localize( - "suggest.insertMode.always", - "Always select a suggestion when automatically triggering IntelliSense.", - ), - nls.localize( - "suggest.insertMode.never", - "Never select a suggestion when automatically triggering IntelliSense.", - ), - nls.localize( - "suggest.insertMode.whenTriggerCharacter", - "Select a suggestion only when triggering IntelliSense from a trigger character.", - ), - nls.localize( - "suggest.insertMode.whenQuickSuggestion", - "Select a suggestion only when triggering IntelliSense as you type.", - ), - ], - default: defaults.selectionMode, - markdownDescription: nls.localize( - "suggest.selectionMode", - "Controls whether a suggestion is selected when the widget shows. Note that this only applies to automatically triggered suggestions ({0} and {1}) and that a suggestion is always selected when explicitly invoked, e.g via `Ctrl+Space`.", - "`#editor.quickSuggestions#`", - "`#editor.suggestOnTriggerCharacters#`", - ), - }, - "editor.suggest.snippetsPreventQuickSuggestions": { - type: "boolean", - default: defaults.snippetsPreventQuickSuggestions, - description: nls.localize( - "suggest.snippetsPreventQuickSuggestions", - "Controls whether an active snippet prevents quick suggestions.", - ), - }, - "editor.suggest.showIcons": { - type: "boolean", - default: defaults.showIcons, - description: nls.localize( - "suggest.showIcons", - "Controls whether to show or hide icons in suggestions.", - ), - }, - "editor.suggest.showStatusBar": { - type: "boolean", - default: defaults.showStatusBar, - description: nls.localize( - "suggest.showStatusBar", - "Controls the visibility of the status bar at the bottom of the suggest widget.", - ), - }, - "editor.suggest.preview": { - type: "boolean", - default: defaults.preview, - description: nls.localize( - "suggest.preview", - "Controls whether to preview the suggestion outcome in the editor.", - ), - }, - "editor.suggest.showInlineDetails": { - type: "boolean", - default: defaults.showInlineDetails, - description: nls.localize( - "suggest.showInlineDetails", - "Controls whether suggest details show inline with the label or only in the details widget.", - ), - }, - "editor.suggest.maxVisibleSuggestions": { - type: "number", - deprecationMessage: nls.localize( - "suggest.maxVisibleSuggestions.dep", - "This setting is deprecated. The suggest widget can now be resized.", - ), - }, - "editor.suggest.filteredTypes": { - type: "object", - deprecationMessage: nls.localize( - "deprecated", - "This setting is deprecated, please use separate settings like 'editor.suggest.showKeywords' or 'editor.suggest.showSnippets' instead.", - ), - }, - "editor.suggest.showMethods": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showMethods", - "When enabled IntelliSense shows `method`-suggestions.", - ), - }, - "editor.suggest.showFunctions": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showFunctions", - "When enabled IntelliSense shows `function`-suggestions.", - ), - }, - "editor.suggest.showConstructors": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showConstructors", - "When enabled IntelliSense shows `constructor`-suggestions.", - ), - }, - "editor.suggest.showDeprecated": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showDeprecated", - "When enabled IntelliSense shows `deprecated`-suggestions.", - ), - }, - "editor.suggest.matchOnWordStartOnly": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.matchOnWordStartOnly", - "When enabled IntelliSense filtering requires that the first character matches on a word start. For example, `c` on `Console` or `WebContext` but _not_ on `description`. When disabled IntelliSense will show more results but still sorts them by match quality.", - ), - }, - "editor.suggest.showFields": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showFields", - "When enabled IntelliSense shows `field`-suggestions.", - ), - }, - "editor.suggest.showVariables": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showVariables", - "When enabled IntelliSense shows `variable`-suggestions.", - ), - }, - "editor.suggest.showClasses": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showClasss", - "When enabled IntelliSense shows `class`-suggestions.", - ), - }, - "editor.suggest.showStructs": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showStructs", - "When enabled IntelliSense shows `struct`-suggestions.", - ), - }, - "editor.suggest.showInterfaces": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showInterfaces", - "When enabled IntelliSense shows `interface`-suggestions.", - ), - }, - "editor.suggest.showModules": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showModules", - "When enabled IntelliSense shows `module`-suggestions.", - ), - }, - "editor.suggest.showProperties": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showPropertys", - "When enabled IntelliSense shows `property`-suggestions.", - ), - }, - "editor.suggest.showEvents": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showEvents", - "When enabled IntelliSense shows `event`-suggestions.", - ), - }, - "editor.suggest.showOperators": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showOperators", - "When enabled IntelliSense shows `operator`-suggestions.", - ), - }, - "editor.suggest.showUnits": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showUnits", - "When enabled IntelliSense shows `unit`-suggestions.", - ), - }, - "editor.suggest.showValues": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showValues", - "When enabled IntelliSense shows `value`-suggestions.", - ), - }, - "editor.suggest.showConstants": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showConstants", - "When enabled IntelliSense shows `constant`-suggestions.", - ), - }, - "editor.suggest.showEnums": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showEnums", - "When enabled IntelliSense shows `enum`-suggestions.", - ), - }, - "editor.suggest.showEnumMembers": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showEnumMembers", - "When enabled IntelliSense shows `enumMember`-suggestions.", - ), - }, - "editor.suggest.showKeywords": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showKeywords", - "When enabled IntelliSense shows `keyword`-suggestions.", - ), - }, - "editor.suggest.showWords": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showTexts", - "When enabled IntelliSense shows `text`-suggestions.", - ), - }, - "editor.suggest.showColors": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showColors", - "When enabled IntelliSense shows `color`-suggestions.", - ), - }, - "editor.suggest.showFiles": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showFiles", - "When enabled IntelliSense shows `file`-suggestions.", - ), - }, - "editor.suggest.showReferences": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showReferences", - "When enabled IntelliSense shows `reference`-suggestions.", - ), - }, - "editor.suggest.showCustomcolors": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showCustomcolors", - "When enabled IntelliSense shows `customcolor`-suggestions.", - ), - }, - "editor.suggest.showFolders": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showFolders", - "When enabled IntelliSense shows `folder`-suggestions.", - ), - }, - "editor.suggest.showTypeParameters": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showTypeParameters", - "When enabled IntelliSense shows `typeParameter`-suggestions.", - ), - }, - "editor.suggest.showSnippets": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showSnippets", - "When enabled IntelliSense shows `snippet`-suggestions.", - ), - }, - "editor.suggest.showUsers": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showUsers", - "When enabled IntelliSense shows `user`-suggestions.", - ), - }, - "editor.suggest.showIssues": { - type: "boolean", - default: true, - markdownDescription: nls.localize( - "editor.suggest.showIssues", - "When enabled IntelliSense shows `issues`-suggestions.", - ), - }, - }); - } - - public validate(_input: any): InternalSuggestOptions { - if (!_input || typeof _input !== "object") { - return this.defaultValue; - } - const input = _input as ISuggestOptions; - return { - insertMode: stringSet( - input.insertMode, - this.defaultValue.insertMode, - ["insert", "replace"], - ), - filterGraceful: boolean( - input.filterGraceful, - this.defaultValue.filterGraceful, - ), - snippetsPreventQuickSuggestions: boolean( - input.snippetsPreventQuickSuggestions, - this.defaultValue.filterGraceful, - ), - localityBonus: boolean( - input.localityBonus, - this.defaultValue.localityBonus, - ), - shareSuggestSelections: boolean( - input.shareSuggestSelections, - this.defaultValue.shareSuggestSelections, - ), - selectionMode: stringSet( - input.selectionMode, - this.defaultValue.selectionMode, - [ - "always", - "never", - "whenQuickSuggestion", - "whenTriggerCharacter", - ], - ), - showIcons: boolean(input.showIcons, this.defaultValue.showIcons), - showStatusBar: boolean( - input.showStatusBar, - this.defaultValue.showStatusBar, - ), - preview: boolean(input.preview, this.defaultValue.preview), - previewMode: stringSet( - input.previewMode, - this.defaultValue.previewMode, - ["prefix", "subword", "subwordSmart"], - ), - showInlineDetails: boolean( - input.showInlineDetails, - this.defaultValue.showInlineDetails, - ), - showMethods: boolean( - input.showMethods, - this.defaultValue.showMethods, - ), - showFunctions: boolean( - input.showFunctions, - this.defaultValue.showFunctions, - ), - showConstructors: boolean( - input.showConstructors, - this.defaultValue.showConstructors, - ), - showDeprecated: boolean( - input.showDeprecated, - this.defaultValue.showDeprecated, - ), - matchOnWordStartOnly: boolean( - input.matchOnWordStartOnly, - this.defaultValue.matchOnWordStartOnly, - ), - showFields: boolean(input.showFields, this.defaultValue.showFields), - showVariables: boolean( - input.showVariables, - this.defaultValue.showVariables, - ), - showClasses: boolean( - input.showClasses, - this.defaultValue.showClasses, - ), - showStructs: boolean( - input.showStructs, - this.defaultValue.showStructs, - ), - showInterfaces: boolean( - input.showInterfaces, - this.defaultValue.showInterfaces, - ), - showModules: boolean( - input.showModules, - this.defaultValue.showModules, - ), - showProperties: boolean( - input.showProperties, - this.defaultValue.showProperties, - ), - showEvents: boolean(input.showEvents, this.defaultValue.showEvents), - showOperators: boolean( - input.showOperators, - this.defaultValue.showOperators, - ), - showUnits: boolean(input.showUnits, this.defaultValue.showUnits), - showValues: boolean(input.showValues, this.defaultValue.showValues), - showConstants: boolean( - input.showConstants, - this.defaultValue.showConstants, - ), - showEnums: boolean(input.showEnums, this.defaultValue.showEnums), - showEnumMembers: boolean( - input.showEnumMembers, - this.defaultValue.showEnumMembers, - ), - showKeywords: boolean( - input.showKeywords, - this.defaultValue.showKeywords, - ), - showWords: boolean(input.showWords, this.defaultValue.showWords), - showColors: boolean(input.showColors, this.defaultValue.showColors), - showFiles: boolean(input.showFiles, this.defaultValue.showFiles), - showReferences: boolean( - input.showReferences, - this.defaultValue.showReferences, - ), - showFolders: boolean( - input.showFolders, - this.defaultValue.showFolders, - ), - showTypeParameters: boolean( - input.showTypeParameters, - this.defaultValue.showTypeParameters, - ), - showSnippets: boolean( - input.showSnippets, - this.defaultValue.showSnippets, - ), - showUsers: boolean(input.showUsers, this.defaultValue.showUsers), - showIssues: boolean(input.showIssues, this.defaultValue.showIssues), - }; + super( + EditorOption.suggest, 'suggest', defaults, + { + 'editor.suggest.insertMode': { + type: 'string', + enum: ['insert', 'replace'], + enumDescriptions: [ + nls.localize('suggest.insertMode.insert', "Insert suggestion without overwriting text right of the cursor."), + nls.localize('suggest.insertMode.replace', "Insert suggestion and overwrite text right of the cursor."), + ], + default: defaults.insertMode, + description: nls.localize('suggest.insertMode', "Controls whether words are overwritten when accepting completions. Note that this depends on extensions opting into this feature.") + }, + 'editor.suggest.filterGraceful': { + type: 'boolean', + default: defaults.filterGraceful, + description: nls.localize('suggest.filterGraceful', "Controls whether filtering and sorting suggestions accounts for small typos.") + }, + 'editor.suggest.localityBonus': { + type: 'boolean', + default: defaults.localityBonus, + description: nls.localize('suggest.localityBonus', "Controls whether sorting favors words that appear close to the cursor.") + }, + 'editor.suggest.shareSuggestSelections': { + type: 'boolean', + default: defaults.shareSuggestSelections, + markdownDescription: nls.localize('suggest.shareSuggestSelections', "Controls whether remembered suggestion selections are shared between multiple workspaces and windows (needs `#editor.suggestSelection#`).") + }, + 'editor.suggest.selectionMode': { + type: 'string', + enum: ['always', 'never', 'whenTriggerCharacter', 'whenQuickSuggestion'], + enumDescriptions: [ + nls.localize('suggest.insertMode.always', "Always select a suggestion when automatically triggering IntelliSense."), + nls.localize('suggest.insertMode.never', "Never select a suggestion when automatically triggering IntelliSense."), + nls.localize('suggest.insertMode.whenTriggerCharacter', "Select a suggestion only when triggering IntelliSense from a trigger character."), + nls.localize('suggest.insertMode.whenQuickSuggestion', "Select a suggestion only when triggering IntelliSense as you type."), + ], + default: defaults.selectionMode, + markdownDescription: nls.localize('suggest.selectionMode', "Controls whether a suggestion is selected when the widget shows. Note that this only applies to automatically triggered suggestions ({0} and {1}) and that a suggestion is always selected when explicitly invoked, e.g via `Ctrl+Space`.", '`#editor.quickSuggestions#`', '`#editor.suggestOnTriggerCharacters#`') + }, + 'editor.suggest.snippetsPreventQuickSuggestions': { + type: 'boolean', + default: defaults.snippetsPreventQuickSuggestions, + description: nls.localize('suggest.snippetsPreventQuickSuggestions', "Controls whether an active snippet prevents quick suggestions.") + }, + 'editor.suggest.showIcons': { + type: 'boolean', + default: defaults.showIcons, + description: nls.localize('suggest.showIcons', "Controls whether to show or hide icons in suggestions.") + }, + 'editor.suggest.showStatusBar': { + type: 'boolean', + default: defaults.showStatusBar, + description: nls.localize('suggest.showStatusBar', "Controls the visibility of the status bar at the bottom of the suggest widget.") + }, + 'editor.suggest.preview': { + type: 'boolean', + default: defaults.preview, + description: nls.localize('suggest.preview', "Controls whether to preview the suggestion outcome in the editor.") + }, + 'editor.suggest.showInlineDetails': { + type: 'boolean', + default: defaults.showInlineDetails, + description: nls.localize('suggest.showInlineDetails', "Controls whether suggest details show inline with the label or only in the details widget.") + }, + 'editor.suggest.maxVisibleSuggestions': { + type: 'number', + deprecationMessage: nls.localize('suggest.maxVisibleSuggestions.dep', "This setting is deprecated. The suggest widget can now be resized."), + }, + 'editor.suggest.filteredTypes': { + type: 'object', + deprecationMessage: nls.localize('deprecated', "This setting is deprecated, please use separate settings like 'editor.suggest.showKeywords' or 'editor.suggest.showSnippets' instead.") + }, + 'editor.suggest.showMethods': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showMethods', "When enabled IntelliSense shows `method`-suggestions.") + }, + 'editor.suggest.showFunctions': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showFunctions', "When enabled IntelliSense shows `function`-suggestions.") + }, + 'editor.suggest.showConstructors': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showConstructors', "When enabled IntelliSense shows `constructor`-suggestions.") + }, + 'editor.suggest.showDeprecated': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showDeprecated', "When enabled IntelliSense shows `deprecated`-suggestions.") + }, + 'editor.suggest.matchOnWordStartOnly': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.matchOnWordStartOnly', "When enabled IntelliSense filtering requires that the first character matches on a word start. For example, `c` on `Console` or `WebContext` but _not_ on `description`. When disabled IntelliSense will show more results but still sorts them by match quality.") + }, + 'editor.suggest.showFields': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showFields', "When enabled IntelliSense shows `field`-suggestions.") + }, + 'editor.suggest.showVariables': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showVariables', "When enabled IntelliSense shows `variable`-suggestions.") + }, + 'editor.suggest.showClasses': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showClasss', "When enabled IntelliSense shows `class`-suggestions.") + }, + 'editor.suggest.showStructs': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showStructs', "When enabled IntelliSense shows `struct`-suggestions.") + }, + 'editor.suggest.showInterfaces': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showInterfaces', "When enabled IntelliSense shows `interface`-suggestions.") + }, + 'editor.suggest.showModules': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showModules', "When enabled IntelliSense shows `module`-suggestions.") + }, + 'editor.suggest.showProperties': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showPropertys', "When enabled IntelliSense shows `property`-suggestions.") + }, + 'editor.suggest.showEvents': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showEvents', "When enabled IntelliSense shows `event`-suggestions.") + }, + 'editor.suggest.showOperators': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showOperators', "When enabled IntelliSense shows `operator`-suggestions.") + }, + 'editor.suggest.showUnits': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showUnits', "When enabled IntelliSense shows `unit`-suggestions.") + }, + 'editor.suggest.showValues': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showValues', "When enabled IntelliSense shows `value`-suggestions.") + }, + 'editor.suggest.showConstants': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showConstants', "When enabled IntelliSense shows `constant`-suggestions.") + }, + 'editor.suggest.showEnums': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showEnums', "When enabled IntelliSense shows `enum`-suggestions.") + }, + 'editor.suggest.showEnumMembers': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showEnumMembers', "When enabled IntelliSense shows `enumMember`-suggestions.") + }, + 'editor.suggest.showKeywords': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showKeywords', "When enabled IntelliSense shows `keyword`-suggestions.") + }, + 'editor.suggest.showWords': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showTexts', "When enabled IntelliSense shows `text`-suggestions.") + }, + 'editor.suggest.showColors': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showColors', "When enabled IntelliSense shows `color`-suggestions.") + }, + 'editor.suggest.showFiles': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showFiles', "When enabled IntelliSense shows `file`-suggestions.") + }, + 'editor.suggest.showReferences': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showReferences', "When enabled IntelliSense shows `reference`-suggestions.") + }, + 'editor.suggest.showCustomcolors': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showCustomcolors', "When enabled IntelliSense shows `customcolor`-suggestions.") + }, + 'editor.suggest.showFolders': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showFolders', "When enabled IntelliSense shows `folder`-suggestions.") + }, + 'editor.suggest.showTypeParameters': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showTypeParameters', "When enabled IntelliSense shows `typeParameter`-suggestions.") + }, + 'editor.suggest.showSnippets': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showSnippets', "When enabled IntelliSense shows `snippet`-suggestions.") + }, + 'editor.suggest.showUsers': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showUsers', "When enabled IntelliSense shows `user`-suggestions.") + }, + 'editor.suggest.showIssues': { + type: 'boolean', + default: true, + markdownDescription: nls.localize('editor.suggest.showIssues', "When enabled IntelliSense shows `issues`-suggestions.") + } + } + ); + } + + public validate(_input: any): InternalSuggestOptions { + if (!_input || typeof _input !== 'object') { + return this.defaultValue; + } + const input = _input as ISuggestOptions; + return { + insertMode: stringSet(input.insertMode, this.defaultValue.insertMode, ['insert', 'replace']), + filterGraceful: boolean(input.filterGraceful, this.defaultValue.filterGraceful), + snippetsPreventQuickSuggestions: boolean(input.snippetsPreventQuickSuggestions, this.defaultValue.filterGraceful), + localityBonus: boolean(input.localityBonus, this.defaultValue.localityBonus), + shareSuggestSelections: boolean(input.shareSuggestSelections, this.defaultValue.shareSuggestSelections), + selectionMode: stringSet(input.selectionMode, this.defaultValue.selectionMode, ['always', 'never', 'whenQuickSuggestion', 'whenTriggerCharacter']), + showIcons: boolean(input.showIcons, this.defaultValue.showIcons), + showStatusBar: boolean(input.showStatusBar, this.defaultValue.showStatusBar), + preview: boolean(input.preview, this.defaultValue.preview), + previewMode: stringSet(input.previewMode, this.defaultValue.previewMode, ['prefix', 'subword', 'subwordSmart']), + showInlineDetails: boolean(input.showInlineDetails, this.defaultValue.showInlineDetails), + showMethods: boolean(input.showMethods, this.defaultValue.showMethods), + showFunctions: boolean(input.showFunctions, this.defaultValue.showFunctions), + showConstructors: boolean(input.showConstructors, this.defaultValue.showConstructors), + showDeprecated: boolean(input.showDeprecated, this.defaultValue.showDeprecated), + matchOnWordStartOnly: boolean(input.matchOnWordStartOnly, this.defaultValue.matchOnWordStartOnly), + showFields: boolean(input.showFields, this.defaultValue.showFields), + showVariables: boolean(input.showVariables, this.defaultValue.showVariables), + showClasses: boolean(input.showClasses, this.defaultValue.showClasses), + showStructs: boolean(input.showStructs, this.defaultValue.showStructs), + showInterfaces: boolean(input.showInterfaces, this.defaultValue.showInterfaces), + showModules: boolean(input.showModules, this.defaultValue.showModules), + showProperties: boolean(input.showProperties, this.defaultValue.showProperties), + showEvents: boolean(input.showEvents, this.defaultValue.showEvents), + showOperators: boolean(input.showOperators, this.defaultValue.showOperators), + showUnits: boolean(input.showUnits, this.defaultValue.showUnits), + showValues: boolean(input.showValues, this.defaultValue.showValues), + showConstants: boolean(input.showConstants, this.defaultValue.showConstants), + showEnums: boolean(input.showEnums, this.defaultValue.showEnums), + showEnumMembers: boolean(input.showEnumMembers, this.defaultValue.showEnumMembers), + showKeywords: boolean(input.showKeywords, this.defaultValue.showKeywords), + showWords: boolean(input.showWords, this.defaultValue.showWords), + showColors: boolean(input.showColors, this.defaultValue.showColors), + showFiles: boolean(input.showFiles, this.defaultValue.showFiles), + showReferences: boolean(input.showReferences, this.defaultValue.showReferences), + showFolders: boolean(input.showFolders, this.defaultValue.showFolders), + showTypeParameters: boolean(input.showTypeParameters, this.defaultValue.showTypeParameters), + showSnippets: boolean(input.showSnippets, this.defaultValue.showSnippets), + showUsers: boolean(input.showUsers, this.defaultValue.showUsers), + showIssues: boolean(input.showIssues, this.defaultValue.showIssues), + }; } } @@ -6580,54 +4985,37 @@ export interface ISmartSelectOptions { */ export type SmartSelectOptions = Readonly>; -class SmartSelect extends BaseEditorOption< - EditorOption.smartSelect, - ISmartSelectOptions, - SmartSelectOptions -> { +class SmartSelect extends BaseEditorOption { + constructor() { super( - EditorOption.smartSelect, - "smartSelect", + EditorOption.smartSelect, 'smartSelect', { selectLeadingAndTrailingWhitespace: true, selectSubwords: true, }, { - "editor.smartSelect.selectLeadingAndTrailingWhitespace": { - description: nls.localize( - "selectLeadingAndTrailingWhitespace", - "Whether leading and trailing whitespace should always be selected.", - ), + 'editor.smartSelect.selectLeadingAndTrailingWhitespace': { + description: nls.localize('selectLeadingAndTrailingWhitespace', "Whether leading and trailing whitespace should always be selected."), default: true, - type: "boolean", + type: 'boolean' }, - "editor.smartSelect.selectSubwords": { - description: nls.localize( - "selectSubwords", - "Whether subwords (like 'foo' in 'fooBar' or 'foo_bar') should be selected.", - ), + 'editor.smartSelect.selectSubwords': { + description: nls.localize('selectSubwords', "Whether subwords (like 'foo' in 'fooBar' or 'foo_bar') should be selected."), default: true, - type: "boolean", - }, - }, + type: 'boolean' + } + } ); } public validate(input: any): Readonly> { - if (!input || typeof input !== "object") { + if (!input || typeof input !== 'object') { return this.defaultValue; } return { - selectLeadingAndTrailingWhitespace: boolean( - (input as ISmartSelectOptions) - .selectLeadingAndTrailingWhitespace, - this.defaultValue.selectLeadingAndTrailingWhitespace, - ), - selectSubwords: boolean( - (input as ISmartSelectOptions).selectSubwords, - this.defaultValue.selectSubwords, - ), + selectLeadingAndTrailingWhitespace: boolean((input as ISmartSelectOptions).selectLeadingAndTrailingWhitespace, this.defaultValue.selectLeadingAndTrailingWhitespace), + selectSubwords: boolean((input as ISmartSelectOptions).selectSubwords, this.defaultValue.selectSubwords), }; } } @@ -6641,54 +5029,39 @@ class SmartSelect extends BaseEditorOption< * * Specify the BCP 47 language tag of the word you wish to recognize (e.g., ja, zh-CN, zh-Hant-TW, etc.). */ -class WordSegmenterLocales extends BaseEditorOption< - EditorOption.wordSegmenterLocales, - string | string[], - string[] -> { +class WordSegmenterLocales extends BaseEditorOption { constructor() { const defaults: string[] = []; super( - EditorOption.wordSegmenterLocales, - "wordSegmenterLocales", - defaults, + EditorOption.wordSegmenterLocales, 'wordSegmenterLocales', defaults, { anyOf: [ { - description: nls.localize( - "wordSegmenterLocales", - "Locales to be used for word segmentation when doing word related navigations or operations. Specify the BCP 47 language tag of the word you wish to recognize (e.g., ja, zh-CN, zh-Hant-TW, etc.).", - ), - type: "string", - }, - { - description: nls.localize( - "wordSegmenterLocales", - "Locales to be used for word segmentation when doing word related navigations or operations. Specify the BCP 47 language tag of the word you wish to recognize (e.g., ja, zh-CN, zh-Hant-TW, etc.).", - ), - type: "array", + description: nls.localize('wordSegmenterLocales', "Locales to be used for word segmentation when doing word related navigations or operations. Specify the BCP 47 language tag of the word you wish to recognize (e.g., ja, zh-CN, zh-Hant-TW, etc.)."), + type: 'string', + }, { + description: nls.localize('wordSegmenterLocales', "Locales to be used for word segmentation when doing word related navigations or operations. Specify the BCP 47 language tag of the word you wish to recognize (e.g., ja, zh-CN, zh-Hant-TW, etc.)."), + type: 'array', items: { - type: "string", - }, - }, - ], - }, + type: 'string' + } + } + ] + } ); } public validate(input: any): string[] { - if (typeof input === "string") { + if (typeof input === 'string') { input = [input]; } if (Array.isArray(input)) { const validLocales: string[] = []; for (const locale of input) { - if (typeof locale === "string") { + if (typeof locale === 'string') { try { - if ( - Intl.Segmenter.supportedLocalesOf(locale).length > 0 - ) { + if (Intl.Segmenter.supportedLocalesOf(locale).length > 0) { validLocales.push(locale); } } catch { @@ -6703,6 +5076,7 @@ class WordSegmenterLocales extends BaseEditorOption< } } + //#endregion //#region wrappingIndent @@ -6726,73 +5100,42 @@ export const enum WrappingIndent { /** * DeepIndent => wrapped lines get +2 indentation toward the parent. */ - DeepIndent = 3, + DeepIndent = 3 } -class WrappingIndentOption extends BaseEditorOption< - EditorOption.wrappingIndent, - "none" | "same" | "indent" | "deepIndent", - WrappingIndent -> { +class WrappingIndentOption extends BaseEditorOption { + constructor() { - super( - EditorOption.wrappingIndent, - "wrappingIndent", - WrappingIndent.Same, + super(EditorOption.wrappingIndent, 'wrappingIndent', WrappingIndent.Same, { - "editor.wrappingIndent": { - type: "string", - enum: ["none", "same", "indent", "deepIndent"], + 'editor.wrappingIndent': { + type: 'string', + enum: ['none', 'same', 'indent', 'deepIndent'], enumDescriptions: [ - nls.localize( - "wrappingIndent.none", - "No indentation. Wrapped lines begin at column 1.", - ), - nls.localize( - "wrappingIndent.same", - "Wrapped lines get the same indentation as the parent.", - ), - nls.localize( - "wrappingIndent.indent", - "Wrapped lines get +1 indentation toward the parent.", - ), - nls.localize( - "wrappingIndent.deepIndent", - "Wrapped lines get +2 indentation toward the parent.", - ), + nls.localize('wrappingIndent.none', "No indentation. Wrapped lines begin at column 1."), + nls.localize('wrappingIndent.same', "Wrapped lines get the same indentation as the parent."), + nls.localize('wrappingIndent.indent', "Wrapped lines get +1 indentation toward the parent."), + nls.localize('wrappingIndent.deepIndent', "Wrapped lines get +2 indentation toward the parent."), ], - description: nls.localize( - "wrappingIndent", - "Controls the indentation of wrapped lines.", - ), - default: "same", - }, - }, + description: nls.localize('wrappingIndent', "Controls the indentation of wrapped lines."), + default: 'same' + } + } ); } public validate(input: any): WrappingIndent { switch (input) { - case "none": - return WrappingIndent.None; - case "same": - return WrappingIndent.Same; - case "indent": - return WrappingIndent.Indent; - case "deepIndent": - return WrappingIndent.DeepIndent; + case 'none': return WrappingIndent.None; + case 'same': return WrappingIndent.Same; + case 'indent': return WrappingIndent.Indent; + case 'deepIndent': return WrappingIndent.DeepIndent; } return WrappingIndent.Same; } - public override compute( - env: IEnvironmentalOptions, - options: IComputedEditorOptions, - value: WrappingIndent, - ): WrappingIndent { - const accessibilitySupport = options.get( - EditorOption.accessibilitySupport, - ); + public override compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: WrappingIndent): WrappingIndent { + const accessibilitySupport = options.get(EditorOption.accessibilitySupport); if (accessibilitySupport === AccessibilitySupport.Enabled) { // if we know for a fact that a screen reader is attached, we use no indent wrapping to // help that the editor's wrapping points match the textarea's wrapping points @@ -6813,19 +5156,13 @@ export interface EditorWrappingInfo { readonly wrappingColumn: number; } -class EditorWrappingInfoComputer extends ComputedEditorOption< - EditorOption.wrappingInfo, - EditorWrappingInfo -> { +class EditorWrappingInfoComputer extends ComputedEditorOption { + constructor() { super(EditorOption.wrappingInfo); } - public compute( - env: IEnvironmentalOptions, - options: IComputedEditorOptions, - _: EditorWrappingInfo, - ): EditorWrappingInfo { + public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, _: EditorWrappingInfo): EditorWrappingInfo { const layoutInfo = options.get(EditorOption.layoutInfo); return { @@ -6855,69 +5192,51 @@ export interface IDropIntoEditorOptions { * Controls if a widget is shown after a drop. * Defaults to 'afterDrop'. */ - showDropSelector?: "afterDrop" | "never"; + showDropSelector?: 'afterDrop' | 'never'; } /** * @internal */ -export type EditorDropIntoEditorOptions = Readonly< - Required ->; - -class EditorDropIntoEditor extends BaseEditorOption< - EditorOption.dropIntoEditor, - IDropIntoEditorOptions, - EditorDropIntoEditorOptions -> { +export type EditorDropIntoEditorOptions = Readonly>; + +class EditorDropIntoEditor extends BaseEditorOption { + constructor() { - const defaults: EditorDropIntoEditorOptions = { - enabled: true, - showDropSelector: "afterDrop", - }; - super(EditorOption.dropIntoEditor, "dropIntoEditor", defaults, { - "editor.dropIntoEditor.enabled": { - type: "boolean", - default: defaults.enabled, - markdownDescription: nls.localize( - "dropIntoEditor.enabled", - "Controls whether you can drag and drop a file into a text editor by holding down the `Shift` key (instead of opening the file in an editor).", - ), - }, - "editor.dropIntoEditor.showDropSelector": { - type: "string", - markdownDescription: nls.localize( - "dropIntoEditor.showDropSelector", - "Controls if a widget is shown when dropping files into the editor. This widget lets you control how the file is dropped.", - ), - enum: ["afterDrop", "never"], - enumDescriptions: [ - nls.localize( - "dropIntoEditor.showDropSelector.afterDrop", - "Show the drop selector widget after a file is dropped into the editor.", - ), - nls.localize( - "dropIntoEditor.showDropSelector.never", - "Never show the drop selector widget. Instead the default drop provider is always used.", - ), - ], - default: "afterDrop", - }, - }); + const defaults: EditorDropIntoEditorOptions = { enabled: true, showDropSelector: 'afterDrop' }; + super( + EditorOption.dropIntoEditor, 'dropIntoEditor', defaults, + { + 'editor.dropIntoEditor.enabled': { + type: 'boolean', + default: defaults.enabled, + markdownDescription: nls.localize('dropIntoEditor.enabled', "Controls whether you can drag and drop a file into a text editor by holding down the `Shift` key (instead of opening the file in an editor)."), + }, + 'editor.dropIntoEditor.showDropSelector': { + type: 'string', + markdownDescription: nls.localize('dropIntoEditor.showDropSelector', "Controls if a widget is shown when dropping files into the editor. This widget lets you control how the file is dropped."), + enum: [ + 'afterDrop', + 'never' + ], + enumDescriptions: [ + nls.localize('dropIntoEditor.showDropSelector.afterDrop', "Show the drop selector widget after a file is dropped into the editor."), + nls.localize('dropIntoEditor.showDropSelector.never', "Never show the drop selector widget. Instead the default drop provider is always used."), + ], + default: 'afterDrop', + }, + } + ); } public validate(_input: any): EditorDropIntoEditorOptions { - if (!_input || typeof _input !== "object") { + if (!_input || typeof _input !== 'object') { return this.defaultValue; } const input = _input as IDropIntoEditorOptions; return { enabled: boolean(input.enabled, this.defaultValue.enabled), - showDropSelector: stringSet( - input.showDropSelector, - this.defaultValue.showDropSelector, - ["afterDrop", "never"], - ), + showDropSelector: stringSet(input.showDropSelector, this.defaultValue.showDropSelector, ['afterDrop', 'never']), }; } } @@ -6940,7 +5259,7 @@ export interface IPasteAsOptions { * Controls if a widget is shown after a drop. * Defaults to 'afterPaste'. */ - showPasteSelector?: "afterPaste" | "never"; + showPasteSelector?: 'afterPaste' | 'never'; } /** @@ -6948,80 +5267,64 @@ export interface IPasteAsOptions { */ export type EditorPasteAsOptions = Readonly>; -class EditorPasteAs extends BaseEditorOption< - EditorOption.pasteAs, - IPasteAsOptions, - EditorPasteAsOptions -> { +class EditorPasteAs extends BaseEditorOption { + constructor() { - const defaults: EditorPasteAsOptions = { - enabled: true, - showPasteSelector: "afterPaste", - }; - super(EditorOption.pasteAs, "pasteAs", defaults, { - "editor.pasteAs.enabled": { - type: "boolean", - default: defaults.enabled, - markdownDescription: nls.localize( - "pasteAs.enabled", - "Controls whether you can paste content in different ways.", - ), - }, - "editor.pasteAs.showPasteSelector": { - type: "string", - markdownDescription: nls.localize( - "pasteAs.showPasteSelector", - "Controls if a widget is shown when pasting content in to the editor. This widget lets you control how the file is pasted.", - ), - enum: ["afterPaste", "never"], - enumDescriptions: [ - nls.localize( - "pasteAs.showPasteSelector.afterPaste", - "Show the paste selector widget after content is pasted into the editor.", - ), - nls.localize( - "pasteAs.showPasteSelector.never", - "Never show the paste selector widget. Instead the default pasting behavior is always used.", - ), - ], - default: "afterPaste", - }, - }); + const defaults: EditorPasteAsOptions = { enabled: true, showPasteSelector: 'afterPaste' }; + super( + EditorOption.pasteAs, 'pasteAs', defaults, + { + 'editor.pasteAs.enabled': { + type: 'boolean', + default: defaults.enabled, + markdownDescription: nls.localize('pasteAs.enabled', "Controls whether you can paste content in different ways."), + }, + 'editor.pasteAs.showPasteSelector': { + type: 'string', + markdownDescription: nls.localize('pasteAs.showPasteSelector', "Controls if a widget is shown when pasting content in to the editor. This widget lets you control how the file is pasted."), + enum: [ + 'afterPaste', + 'never' + ], + enumDescriptions: [ + nls.localize('pasteAs.showPasteSelector.afterPaste', "Show the paste selector widget after content is pasted into the editor."), + nls.localize('pasteAs.showPasteSelector.never', "Never show the paste selector widget. Instead the default pasting behavior is always used."), + ], + default: 'afterPaste', + }, + } + ); } public validate(_input: any): EditorPasteAsOptions { - if (!_input || typeof _input !== "object") { + if (!_input || typeof _input !== 'object') { return this.defaultValue; } const input = _input as IPasteAsOptions; return { enabled: boolean(input.enabled, this.defaultValue.enabled), - showPasteSelector: stringSet( - input.showPasteSelector, - this.defaultValue.showPasteSelector, - ["afterPaste", "never"], - ), + showPasteSelector: stringSet(input.showPasteSelector, this.defaultValue.showPasteSelector, ['afterPaste', 'never']), }; } } //#endregion -const DEFAULT_WINDOWS_FONT_FAMILY = "Consolas, 'Courier New', monospace"; -const DEFAULT_MAC_FONT_FAMILY = "Menlo, Monaco, 'Courier New', monospace"; -const DEFAULT_LINUX_FONT_FAMILY = "'Droid Sans Mono', 'monospace', monospace"; +const DEFAULT_WINDOWS_FONT_FAMILY = 'Consolas, \'Courier New\', monospace'; +const DEFAULT_MAC_FONT_FAMILY = 'Menlo, Monaco, \'Courier New\', monospace'; +const DEFAULT_LINUX_FONT_FAMILY = '\'Droid Sans Mono\', \'monospace\', monospace'; /** * @internal */ export const EDITOR_FONT_DEFAULTS = { - fontFamily: platform.isMacintosh - ? DEFAULT_MAC_FONT_FAMILY - : platform.isLinux - ? DEFAULT_LINUX_FONT_FAMILY - : DEFAULT_WINDOWS_FONT_FAMILY, - fontWeight: "normal", - fontSize: platform.isMacintosh ? 12 : 14, + fontFamily: ( + platform.isMacintosh ? DEFAULT_MAC_FONT_FAMILY : (platform.isLinux ? DEFAULT_LINUX_FONT_FAMILY : DEFAULT_WINDOWS_FONT_FAMILY) + ), + fontWeight: 'normal', + fontSize: ( + platform.isMacintosh ? 12 : 14 + ), lineHeight: 0, letterSpacing: 0, }; @@ -7031,9 +5334,7 @@ export const EDITOR_FONT_DEFAULTS = { */ export const editorOptionsRegistry: IEditorOption[] = []; -function register( - option: IEditorOption, -): IEditorOption { +function register(option: IEditorOption): IEditorOption { editorOptionsRegistry[option.id] = option; return option; } @@ -7192,1839 +5493,820 @@ export const enum EditorOption { wrappingInfo, defaultColorDecorators, colorDecoratorsActivatedOn, - inlineCompletionsAccessibilityVerbose, + inlineCompletionsAccessibilityVerbose } export const EditorOptions = { - acceptSuggestionOnCommitCharacter: register( - new EditorBooleanOption( - EditorOption.acceptSuggestionOnCommitCharacter, - "acceptSuggestionOnCommitCharacter", - true, - { - markdownDescription: nls.localize( - "acceptSuggestionOnCommitCharacter", - "Controls whether suggestions should be accepted on commit characters. For example, in JavaScript, the semi-colon (`;`) can be a commit character that accepts a suggestion and types that character.", - ), - }, - ), - ), - acceptSuggestionOnEnter: register( - new EditorStringEnumOption( - EditorOption.acceptSuggestionOnEnter, - "acceptSuggestionOnEnter", - "on" as "on" | "smart" | "off", - ["on", "smart", "off"] as const, - { - markdownEnumDescriptions: [ - "", - nls.localize( - "acceptSuggestionOnEnterSmart", - "Only accept a suggestion with `Enter` when it makes a textual change.", - ), - "", - ], - markdownDescription: nls.localize( - "acceptSuggestionOnEnter", - "Controls whether suggestions should be accepted on `Enter`, in addition to `Tab`. Helps to avoid ambiguity between inserting new lines or accepting suggestions.", - ), - }, - ), - ), + acceptSuggestionOnCommitCharacter: register(new EditorBooleanOption( + EditorOption.acceptSuggestionOnCommitCharacter, 'acceptSuggestionOnCommitCharacter', true, + { markdownDescription: nls.localize('acceptSuggestionOnCommitCharacter', "Controls whether suggestions should be accepted on commit characters. For example, in JavaScript, the semi-colon (`;`) can be a commit character that accepts a suggestion and types that character.") } + )), + acceptSuggestionOnEnter: register(new EditorStringEnumOption( + EditorOption.acceptSuggestionOnEnter, 'acceptSuggestionOnEnter', + 'on' as 'on' | 'smart' | 'off', + ['on', 'smart', 'off'] as const, + { + markdownEnumDescriptions: [ + '', + nls.localize('acceptSuggestionOnEnterSmart', "Only accept a suggestion with `Enter` when it makes a textual change."), + '' + ], + markdownDescription: nls.localize('acceptSuggestionOnEnter', "Controls whether suggestions should be accepted on `Enter`, in addition to `Tab`. Helps to avoid ambiguity between inserting new lines or accepting suggestions.") + } + )), accessibilitySupport: register(new EditorAccessibilitySupport()), - accessibilityPageSize: register( - new EditorIntOption( - EditorOption.accessibilityPageSize, - "accessibilityPageSize", - 10, - 1, - Constants.MAX_SAFE_SMALL_INTEGER, - { - description: nls.localize( - "accessibilityPageSize", - "Controls the number of lines in the editor that can be read out by a screen reader at once. When we detect a screen reader we automatically set the default to be 500. Warning: this has a performance implication for numbers larger than the default.", - ), - tags: ["accessibility"], - }, - ), - ), - ariaLabel: register( - new EditorStringOption( - EditorOption.ariaLabel, - "ariaLabel", - nls.localize("editorViewAccessibleLabel", "Editor content"), - ), - ), - ariaRequired: register( - new EditorBooleanOption( - EditorOption.ariaRequired, - "ariaRequired", - false, - undefined, - ), - ), - screenReaderAnnounceInlineSuggestion: register( - new EditorBooleanOption( - EditorOption.screenReaderAnnounceInlineSuggestion, - "screenReaderAnnounceInlineSuggestion", - true, - { - description: nls.localize( - "screenReaderAnnounceInlineSuggestion", - "Control whether inline suggestions are announced by a screen reader.", - ), - tags: ["accessibility"], - }, - ), - ), - autoClosingBrackets: register( - new EditorStringEnumOption( - EditorOption.autoClosingBrackets, - "autoClosingBrackets", - "languageDefined" as - | "always" - | "languageDefined" - | "beforeWhitespace" - | "never", - ["always", "languageDefined", "beforeWhitespace", "never"] as const, - { - enumDescriptions: [ - "", - nls.localize( - "editor.autoClosingBrackets.languageDefined", - "Use language configurations to determine when to autoclose brackets.", - ), - nls.localize( - "editor.autoClosingBrackets.beforeWhitespace", - "Autoclose brackets only when the cursor is to the left of whitespace.", - ), - "", - ], - description: nls.localize( - "autoClosingBrackets", - "Controls whether the editor should automatically close brackets after the user adds an opening bracket.", - ), - }, - ), - ), - autoClosingComments: register( - new EditorStringEnumOption( - EditorOption.autoClosingComments, - "autoClosingComments", - "languageDefined" as - | "always" - | "languageDefined" - | "beforeWhitespace" - | "never", - ["always", "languageDefined", "beforeWhitespace", "never"] as const, - { - enumDescriptions: [ - "", - nls.localize( - "editor.autoClosingComments.languageDefined", - "Use language configurations to determine when to autoclose comments.", - ), - nls.localize( - "editor.autoClosingComments.beforeWhitespace", - "Autoclose comments only when the cursor is to the left of whitespace.", - ), - "", - ], - description: nls.localize( - "autoClosingComments", - "Controls whether the editor should automatically close comments after the user adds an opening comment.", - ), - }, - ), - ), - autoClosingDelete: register( - new EditorStringEnumOption( - EditorOption.autoClosingDelete, - "autoClosingDelete", - "auto" as "always" | "auto" | "never", - ["always", "auto", "never"] as const, - { - enumDescriptions: [ - "", - nls.localize( - "editor.autoClosingDelete.auto", - "Remove adjacent closing quotes or brackets only if they were automatically inserted.", - ), - "", - ], - description: nls.localize( - "autoClosingDelete", - "Controls whether the editor should remove adjacent closing quotes or brackets when deleting.", - ), - }, - ), - ), - autoClosingOvertype: register( - new EditorStringEnumOption( - EditorOption.autoClosingOvertype, - "autoClosingOvertype", - "auto" as "always" | "auto" | "never", - ["always", "auto", "never"] as const, - { - enumDescriptions: [ - "", - nls.localize( - "editor.autoClosingOvertype.auto", - "Type over closing quotes or brackets only if they were automatically inserted.", - ), - "", - ], - description: nls.localize( - "autoClosingOvertype", - "Controls whether the editor should type over closing quotes or brackets.", - ), - }, - ), - ), - autoClosingQuotes: register( - new EditorStringEnumOption( - EditorOption.autoClosingQuotes, - "autoClosingQuotes", - "languageDefined" as - | "always" - | "languageDefined" - | "beforeWhitespace" - | "never", - ["always", "languageDefined", "beforeWhitespace", "never"] as const, - { - enumDescriptions: [ - "", - nls.localize( - "editor.autoClosingQuotes.languageDefined", - "Use language configurations to determine when to autoclose quotes.", - ), - nls.localize( - "editor.autoClosingQuotes.beforeWhitespace", - "Autoclose quotes only when the cursor is to the left of whitespace.", - ), - "", - ], - description: nls.localize( - "autoClosingQuotes", - "Controls whether the editor should automatically close quotes after the user adds an opening quote.", - ), - }, - ), - ), - autoIndent: register( - new EditorEnumOption( - EditorOption.autoIndent, - "autoIndent", - EditorAutoIndentStrategy.Full, - "full", - ["none", "keep", "brackets", "advanced", "full"], - _autoIndentFromString, - { - enumDescriptions: [ - nls.localize( - "editor.autoIndent.none", - "The editor will not insert indentation automatically.", - ), - nls.localize( - "editor.autoIndent.keep", - "The editor will keep the current line's indentation.", - ), - nls.localize( - "editor.autoIndent.brackets", - "The editor will keep the current line's indentation and honor language defined brackets.", - ), - nls.localize( - "editor.autoIndent.advanced", - "The editor will keep the current line's indentation, honor language defined brackets and invoke special onEnterRules defined by languages.", - ), - nls.localize( - "editor.autoIndent.full", - "The editor will keep the current line's indentation, honor language defined brackets, invoke special onEnterRules defined by languages, and honor indentationRules defined by languages.", - ), - ], - description: nls.localize( - "autoIndent", - "Controls whether the editor should automatically adjust the indentation when users type, paste, move or indent lines.", - ), - }, - ), - ), - automaticLayout: register( - new EditorBooleanOption( - EditorOption.automaticLayout, - "automaticLayout", - false, - ), - ), - autoSurround: register( - new EditorStringEnumOption( - EditorOption.autoSurround, - "autoSurround", - "languageDefined" as - | "languageDefined" - | "quotes" - | "brackets" - | "never", - ["languageDefined", "quotes", "brackets", "never"] as const, - { - enumDescriptions: [ - nls.localize( - "editor.autoSurround.languageDefined", - "Use language configurations to determine when to automatically surround selections.", - ), - nls.localize( - "editor.autoSurround.quotes", - "Surround with quotes but not brackets.", - ), - nls.localize( - "editor.autoSurround.brackets", - "Surround with brackets but not quotes.", - ), - "", - ], - description: nls.localize( - "autoSurround", - "Controls whether the editor should automatically surround selections when typing quotes or brackets.", - ), - }, - ), - ), + accessibilityPageSize: register(new EditorIntOption(EditorOption.accessibilityPageSize, 'accessibilityPageSize', 10, 1, Constants.MAX_SAFE_SMALL_INTEGER, + { + description: nls.localize('accessibilityPageSize', "Controls the number of lines in the editor that can be read out by a screen reader at once. When we detect a screen reader we automatically set the default to be 500. Warning: this has a performance implication for numbers larger than the default."), + tags: ['accessibility'] + })), + ariaLabel: register(new EditorStringOption( + EditorOption.ariaLabel, 'ariaLabel', nls.localize('editorViewAccessibleLabel', "Editor content") + )), + ariaRequired: register(new EditorBooleanOption( + EditorOption.ariaRequired, 'ariaRequired', false, undefined + )), + screenReaderAnnounceInlineSuggestion: register(new EditorBooleanOption( + EditorOption.screenReaderAnnounceInlineSuggestion, 'screenReaderAnnounceInlineSuggestion', true, + { + description: nls.localize('screenReaderAnnounceInlineSuggestion', "Control whether inline suggestions are announced by a screen reader."), + tags: ['accessibility'] + } + )), + autoClosingBrackets: register(new EditorStringEnumOption( + EditorOption.autoClosingBrackets, 'autoClosingBrackets', + 'languageDefined' as 'always' | 'languageDefined' | 'beforeWhitespace' | 'never', + ['always', 'languageDefined', 'beforeWhitespace', 'never'] as const, + { + enumDescriptions: [ + '', + nls.localize('editor.autoClosingBrackets.languageDefined', "Use language configurations to determine when to autoclose brackets."), + nls.localize('editor.autoClosingBrackets.beforeWhitespace', "Autoclose brackets only when the cursor is to the left of whitespace."), + '', + ], + description: nls.localize('autoClosingBrackets', "Controls whether the editor should automatically close brackets after the user adds an opening bracket.") + } + )), + autoClosingComments: register(new EditorStringEnumOption( + EditorOption.autoClosingComments, 'autoClosingComments', + 'languageDefined' as 'always' | 'languageDefined' | 'beforeWhitespace' | 'never', + ['always', 'languageDefined', 'beforeWhitespace', 'never'] as const, + { + enumDescriptions: [ + '', + nls.localize('editor.autoClosingComments.languageDefined', "Use language configurations to determine when to autoclose comments."), + nls.localize('editor.autoClosingComments.beforeWhitespace', "Autoclose comments only when the cursor is to the left of whitespace."), + '', + ], + description: nls.localize('autoClosingComments', "Controls whether the editor should automatically close comments after the user adds an opening comment.") + } + )), + autoClosingDelete: register(new EditorStringEnumOption( + EditorOption.autoClosingDelete, 'autoClosingDelete', + 'auto' as 'always' | 'auto' | 'never', + ['always', 'auto', 'never'] as const, + { + enumDescriptions: [ + '', + nls.localize('editor.autoClosingDelete.auto', "Remove adjacent closing quotes or brackets only if they were automatically inserted."), + '', + ], + description: nls.localize('autoClosingDelete', "Controls whether the editor should remove adjacent closing quotes or brackets when deleting.") + } + )), + autoClosingOvertype: register(new EditorStringEnumOption( + EditorOption.autoClosingOvertype, 'autoClosingOvertype', + 'auto' as 'always' | 'auto' | 'never', + ['always', 'auto', 'never'] as const, + { + enumDescriptions: [ + '', + nls.localize('editor.autoClosingOvertype.auto', "Type over closing quotes or brackets only if they were automatically inserted."), + '', + ], + description: nls.localize('autoClosingOvertype', "Controls whether the editor should type over closing quotes or brackets.") + } + )), + autoClosingQuotes: register(new EditorStringEnumOption( + EditorOption.autoClosingQuotes, 'autoClosingQuotes', + 'languageDefined' as 'always' | 'languageDefined' | 'beforeWhitespace' | 'never', + ['always', 'languageDefined', 'beforeWhitespace', 'never'] as const, + { + enumDescriptions: [ + '', + nls.localize('editor.autoClosingQuotes.languageDefined', "Use language configurations to determine when to autoclose quotes."), + nls.localize('editor.autoClosingQuotes.beforeWhitespace', "Autoclose quotes only when the cursor is to the left of whitespace."), + '', + ], + description: nls.localize('autoClosingQuotes', "Controls whether the editor should automatically close quotes after the user adds an opening quote.") + } + )), + autoIndent: register(new EditorEnumOption( + EditorOption.autoIndent, 'autoIndent', + EditorAutoIndentStrategy.Full, 'full', + ['none', 'keep', 'brackets', 'advanced', 'full'], + _autoIndentFromString, + { + enumDescriptions: [ + nls.localize('editor.autoIndent.none', "The editor will not insert indentation automatically."), + nls.localize('editor.autoIndent.keep', "The editor will keep the current line's indentation."), + nls.localize('editor.autoIndent.brackets', "The editor will keep the current line's indentation and honor language defined brackets."), + nls.localize('editor.autoIndent.advanced', "The editor will keep the current line's indentation, honor language defined brackets and invoke special onEnterRules defined by languages."), + nls.localize('editor.autoIndent.full', "The editor will keep the current line's indentation, honor language defined brackets, invoke special onEnterRules defined by languages, and honor indentationRules defined by languages."), + ], + description: nls.localize('autoIndent', "Controls whether the editor should automatically adjust the indentation when users type, paste, move or indent lines.") + } + )), + automaticLayout: register(new EditorBooleanOption( + EditorOption.automaticLayout, 'automaticLayout', false, + )), + autoSurround: register(new EditorStringEnumOption( + EditorOption.autoSurround, 'autoSurround', + 'languageDefined' as 'languageDefined' | 'quotes' | 'brackets' | 'never', + ['languageDefined', 'quotes', 'brackets', 'never'] as const, + { + enumDescriptions: [ + nls.localize('editor.autoSurround.languageDefined', "Use language configurations to determine when to automatically surround selections."), + nls.localize('editor.autoSurround.quotes', "Surround with quotes but not brackets."), + nls.localize('editor.autoSurround.brackets', "Surround with brackets but not quotes."), + '' + ], + description: nls.localize('autoSurround', "Controls whether the editor should automatically surround selections when typing quotes or brackets.") + } + )), bracketPairColorization: register(new BracketPairColorization()), bracketPairGuides: register(new GuideOptions()), - stickyTabStops: register( - new EditorBooleanOption( - EditorOption.stickyTabStops, - "stickyTabStops", - false, - { - description: nls.localize( - "stickyTabStops", - "Emulate selection behavior of tab characters when using spaces for indentation. Selection will stick to tab stops.", - ), - }, - ), - ), - codeLens: register( - new EditorBooleanOption(EditorOption.codeLens, "codeLens", true, { - description: nls.localize( - "codeLens", - "Controls whether the editor shows CodeLens.", - ), - }), - ), - codeLensFontFamily: register( - new EditorStringOption( - EditorOption.codeLensFontFamily, - "codeLensFontFamily", - "", - { - description: nls.localize( - "codeLensFontFamily", - "Controls the font family for CodeLens.", - ), - }, - ), - ), - codeLensFontSize: register( - new EditorIntOption( - EditorOption.codeLensFontSize, - "codeLensFontSize", - 0, - 0, - 100, - { - type: "number", - default: 0, - minimum: 0, - maximum: 100, - markdownDescription: nls.localize( - "codeLensFontSize", - "Controls the font size in pixels for CodeLens. When set to 0, 90% of `#editor.fontSize#` is used.", - ), - }, - ), - ), - colorDecorators: register( - new EditorBooleanOption( - EditorOption.colorDecorators, - "colorDecorators", - true, - { - description: nls.localize( - "colorDecorators", - "Controls whether the editor should render the inline color decorators and color picker.", - ), - }, - ), - ), - colorDecoratorActivatedOn: register( - new EditorStringEnumOption( - EditorOption.colorDecoratorsActivatedOn, - "colorDecoratorsActivatedOn", - "clickAndHover" as "clickAndHover" | "hover" | "click", - ["clickAndHover", "hover", "click"] as const, - { - enumDescriptions: [ - nls.localize( - "editor.colorDecoratorActivatedOn.clickAndHover", - "Make the color picker appear both on click and hover of the color decorator", - ), - nls.localize( - "editor.colorDecoratorActivatedOn.hover", - "Make the color picker appear on hover of the color decorator", - ), - nls.localize( - "editor.colorDecoratorActivatedOn.click", - "Make the color picker appear on click of the color decorator", - ), - ], - description: nls.localize( - "colorDecoratorActivatedOn", - "Controls the condition to make a color picker appear from a color decorator", - ), - }, - ), - ), - colorDecoratorsLimit: register( - new EditorIntOption( - EditorOption.colorDecoratorsLimit, - "colorDecoratorsLimit", - 500, - 1, - 1000000, - { - markdownDescription: nls.localize( - "colorDecoratorsLimit", - "Controls the max number of color decorators that can be rendered in an editor at once.", - ), - }, - ), - ), - columnSelection: register( - new EditorBooleanOption( - EditorOption.columnSelection, - "columnSelection", - false, - { - description: nls.localize( - "columnSelection", - "Enable that the selection with the mouse and keys is doing column selection.", - ), - }, - ), - ), + stickyTabStops: register(new EditorBooleanOption( + EditorOption.stickyTabStops, 'stickyTabStops', false, + { description: nls.localize('stickyTabStops', "Emulate selection behavior of tab characters when using spaces for indentation. Selection will stick to tab stops.") } + )), + codeLens: register(new EditorBooleanOption( + EditorOption.codeLens, 'codeLens', true, + { description: nls.localize('codeLens', "Controls whether the editor shows CodeLens.") } + )), + codeLensFontFamily: register(new EditorStringOption( + EditorOption.codeLensFontFamily, 'codeLensFontFamily', '', + { description: nls.localize('codeLensFontFamily', "Controls the font family for CodeLens.") } + )), + codeLensFontSize: register(new EditorIntOption(EditorOption.codeLensFontSize, 'codeLensFontSize', 0, 0, 100, { + type: 'number', + default: 0, + minimum: 0, + maximum: 100, + markdownDescription: nls.localize('codeLensFontSize', "Controls the font size in pixels for CodeLens. When set to 0, 90% of `#editor.fontSize#` is used.") + })), + colorDecorators: register(new EditorBooleanOption( + EditorOption.colorDecorators, 'colorDecorators', true, + { description: nls.localize('colorDecorators', "Controls whether the editor should render the inline color decorators and color picker.") } + )), + colorDecoratorActivatedOn: register(new EditorStringEnumOption(EditorOption.colorDecoratorsActivatedOn, 'colorDecoratorsActivatedOn', 'clickAndHover' as 'clickAndHover' | 'hover' | 'click', ['clickAndHover', 'hover', 'click'] as const, { + enumDescriptions: [ + nls.localize('editor.colorDecoratorActivatedOn.clickAndHover', "Make the color picker appear both on click and hover of the color decorator"), + nls.localize('editor.colorDecoratorActivatedOn.hover', "Make the color picker appear on hover of the color decorator"), + nls.localize('editor.colorDecoratorActivatedOn.click', "Make the color picker appear on click of the color decorator") + ], + description: nls.localize('colorDecoratorActivatedOn', "Controls the condition to make a color picker appear from a color decorator") + })), + colorDecoratorsLimit: register(new EditorIntOption( + EditorOption.colorDecoratorsLimit, 'colorDecoratorsLimit', 500, 1, 1000000, + { + markdownDescription: nls.localize('colorDecoratorsLimit', "Controls the max number of color decorators that can be rendered in an editor at once.") + } + )), + columnSelection: register(new EditorBooleanOption( + EditorOption.columnSelection, 'columnSelection', false, + { description: nls.localize('columnSelection', "Enable that the selection with the mouse and keys is doing column selection.") } + )), comments: register(new EditorComments()), - contextmenu: register( - new EditorBooleanOption(EditorOption.contextmenu, "contextmenu", true), - ), - copyWithSyntaxHighlighting: register( - new EditorBooleanOption( - EditorOption.copyWithSyntaxHighlighting, - "copyWithSyntaxHighlighting", - true, - { - description: nls.localize( - "copyWithSyntaxHighlighting", - "Controls whether syntax highlighting should be copied into the clipboard.", - ), - }, - ), - ), - cursorBlinking: register( - new EditorEnumOption( - EditorOption.cursorBlinking, - "cursorBlinking", - TextEditorCursorBlinkingStyle.Blink, - "blink", - ["blink", "smooth", "phase", "expand", "solid"], - cursorBlinkingStyleFromString, - { - description: nls.localize( - "cursorBlinking", - "Control the cursor animation style.", - ), - }, - ), - ), - cursorSmoothCaretAnimation: register( - new EditorStringEnumOption( - EditorOption.cursorSmoothCaretAnimation, - "cursorSmoothCaretAnimation", - "off" as "off" | "explicit" | "on", - ["off", "explicit", "on"] as const, - { - enumDescriptions: [ - nls.localize( - "cursorSmoothCaretAnimation.off", - "Smooth caret animation is disabled.", - ), - nls.localize( - "cursorSmoothCaretAnimation.explicit", - "Smooth caret animation is enabled only when the user moves the cursor with an explicit gesture.", - ), - nls.localize( - "cursorSmoothCaretAnimation.on", - "Smooth caret animation is always enabled.", - ), - ], - description: nls.localize( - "cursorSmoothCaretAnimation", - "Controls whether the smooth caret animation should be enabled.", - ), - }, - ), - ), - cursorStyle: register( - new EditorEnumOption( - EditorOption.cursorStyle, - "cursorStyle", - TextEditorCursorStyle.Line, - "line", - [ - "line", - "block", - "underline", - "line-thin", - "block-outline", - "underline-thin", + contextmenu: register(new EditorBooleanOption( + EditorOption.contextmenu, 'contextmenu', true, + )), + copyWithSyntaxHighlighting: register(new EditorBooleanOption( + EditorOption.copyWithSyntaxHighlighting, 'copyWithSyntaxHighlighting', true, + { description: nls.localize('copyWithSyntaxHighlighting', "Controls whether syntax highlighting should be copied into the clipboard.") } + )), + cursorBlinking: register(new EditorEnumOption( + EditorOption.cursorBlinking, 'cursorBlinking', + TextEditorCursorBlinkingStyle.Blink, 'blink', + ['blink', 'smooth', 'phase', 'expand', 'solid'], + cursorBlinkingStyleFromString, + { description: nls.localize('cursorBlinking', "Control the cursor animation style.") } + )), + cursorSmoothCaretAnimation: register(new EditorStringEnumOption( + EditorOption.cursorSmoothCaretAnimation, 'cursorSmoothCaretAnimation', + 'off' as 'off' | 'explicit' | 'on', + ['off', 'explicit', 'on'] as const, + { + enumDescriptions: [ + nls.localize('cursorSmoothCaretAnimation.off', "Smooth caret animation is disabled."), + nls.localize('cursorSmoothCaretAnimation.explicit', "Smooth caret animation is enabled only when the user moves the cursor with an explicit gesture."), + nls.localize('cursorSmoothCaretAnimation.on', "Smooth caret animation is always enabled.") ], - cursorStyleFromString, - { - description: nls.localize( - "cursorStyle", - "Controls the cursor style.", - ), - }, - ), - ), - cursorSurroundingLines: register( - new EditorIntOption( - EditorOption.cursorSurroundingLines, - "cursorSurroundingLines", - 0, - 0, - Constants.MAX_SAFE_SMALL_INTEGER, - { - description: nls.localize( - "cursorSurroundingLines", - "Controls the minimal number of visible leading lines (minimum 0) and trailing lines (minimum 1) surrounding the cursor. Known as 'scrollOff' or 'scrollOffset' in some other editors.", - ), - }, - ), - ), - cursorSurroundingLinesStyle: register( - new EditorStringEnumOption( - EditorOption.cursorSurroundingLinesStyle, - "cursorSurroundingLinesStyle", - "default" as "default" | "all", - ["default", "all"] as const, - { - enumDescriptions: [ - nls.localize( - "cursorSurroundingLinesStyle.default", - "`cursorSurroundingLines` is enforced only when triggered via the keyboard or API.", - ), - nls.localize( - "cursorSurroundingLinesStyle.all", - "`cursorSurroundingLines` is enforced always.", - ), - ], - markdownDescription: nls.localize( - "cursorSurroundingLinesStyle", - "Controls when `#editor.cursorSurroundingLines#` should be enforced.", - ), - }, - ), - ), - cursorWidth: register( - new EditorIntOption( - EditorOption.cursorWidth, - "cursorWidth", - 0, - 0, - Constants.MAX_SAFE_SMALL_INTEGER, - { - markdownDescription: nls.localize( - "cursorWidth", - "Controls the width of the cursor when `#editor.cursorStyle#` is set to `line`.", - ), - }, - ), - ), - disableLayerHinting: register( - new EditorBooleanOption( - EditorOption.disableLayerHinting, - "disableLayerHinting", - false, - ), - ), - disableMonospaceOptimizations: register( - new EditorBooleanOption( - EditorOption.disableMonospaceOptimizations, - "disableMonospaceOptimizations", - false, - ), - ), - domReadOnly: register( - new EditorBooleanOption(EditorOption.domReadOnly, "domReadOnly", false), - ), - dragAndDrop: register( - new EditorBooleanOption(EditorOption.dragAndDrop, "dragAndDrop", true, { - description: nls.localize( - "dragAndDrop", - "Controls whether the editor should allow moving selections via drag and drop.", - ), - }), - ), + description: nls.localize('cursorSmoothCaretAnimation', "Controls whether the smooth caret animation should be enabled.") + } + )), + cursorStyle: register(new EditorEnumOption( + EditorOption.cursorStyle, 'cursorStyle', + TextEditorCursorStyle.Line, 'line', + ['line', 'block', 'underline', 'line-thin', 'block-outline', 'underline-thin'], + cursorStyleFromString, + { description: nls.localize('cursorStyle', "Controls the cursor style.") } + )), + cursorSurroundingLines: register(new EditorIntOption( + EditorOption.cursorSurroundingLines, 'cursorSurroundingLines', + 0, 0, Constants.MAX_SAFE_SMALL_INTEGER, + { description: nls.localize('cursorSurroundingLines', "Controls the minimal number of visible leading lines (minimum 0) and trailing lines (minimum 1) surrounding the cursor. Known as 'scrollOff' or 'scrollOffset' in some other editors.") } + )), + cursorSurroundingLinesStyle: register(new EditorStringEnumOption( + EditorOption.cursorSurroundingLinesStyle, 'cursorSurroundingLinesStyle', + 'default' as 'default' | 'all', + ['default', 'all'] as const, + { + enumDescriptions: [ + nls.localize('cursorSurroundingLinesStyle.default', "`cursorSurroundingLines` is enforced only when triggered via the keyboard or API."), + nls.localize('cursorSurroundingLinesStyle.all', "`cursorSurroundingLines` is enforced always.") + ], + markdownDescription: nls.localize('cursorSurroundingLinesStyle', "Controls when `#editor.cursorSurroundingLines#` should be enforced.") + } + )), + cursorWidth: register(new EditorIntOption( + EditorOption.cursorWidth, 'cursorWidth', + 0, 0, Constants.MAX_SAFE_SMALL_INTEGER, + { markdownDescription: nls.localize('cursorWidth', "Controls the width of the cursor when `#editor.cursorStyle#` is set to `line`.") } + )), + disableLayerHinting: register(new EditorBooleanOption( + EditorOption.disableLayerHinting, 'disableLayerHinting', false, + )), + disableMonospaceOptimizations: register(new EditorBooleanOption( + EditorOption.disableMonospaceOptimizations, 'disableMonospaceOptimizations', false + )), + domReadOnly: register(new EditorBooleanOption( + EditorOption.domReadOnly, 'domReadOnly', false, + )), + dragAndDrop: register(new EditorBooleanOption( + EditorOption.dragAndDrop, 'dragAndDrop', true, + { description: nls.localize('dragAndDrop', "Controls whether the editor should allow moving selections via drag and drop.") } + )), emptySelectionClipboard: register(new EditorEmptySelectionClipboard()), dropIntoEditor: register(new EditorDropIntoEditor()), - experimentalEditContextEnabled: register( - new EditorBooleanOption( - EditorOption.experimentalEditContextEnabled, - "experimentalEditContextEnabled", - product.quality !== "stable", - { - description: nls.localize( - "experimentalEditContextEnabled", - "Sets whether the new experimental edit context should be used instead of the text area.", - ), - included: - platform.isChrome || platform.isEdge || platform.isNative, - }, - ), - ), + experimentalEditContextEnabled: register(new EditorBooleanOption( + EditorOption.experimentalEditContextEnabled, 'experimentalEditContextEnabled', false, + { + description: nls.localize('experimentalEditContextEnabled', "Sets whether the new experimental edit context should be used instead of the text area."), + included: platform.isChrome || platform.isEdge || platform.isNative + } + )), stickyScroll: register(new EditorStickyScroll()), - experimentalGpuAcceleration: register( - new EditorStringEnumOption( - EditorOption.experimentalGpuAcceleration, - "experimentalGpuAcceleration", - "off" as "off" | "on", - ["off", "on"] as const, - undefined, - // TODO: Uncomment when we want to expose the setting to VS Code users - // { - // enumDescriptions: [ - // nls.localize('experimentalGpuAcceleration.off', "Use regular DOM-based rendering."), - // nls.localize('experimentalGpuAcceleration.on', "Use GPU acceleration."), - // ], - // description: nls.localize('experimentalGpuAcceleration', "Controls whether to use the (very) experimental GPU acceleration to render the editor.") - // } - ), - ), - experimentalWhitespaceRendering: register( - new EditorStringEnumOption( - EditorOption.experimentalWhitespaceRendering, - "experimentalWhitespaceRendering", - "svg" as "svg" | "font" | "off", - ["svg", "font", "off"] as const, - { - enumDescriptions: [ - nls.localize( - "experimentalWhitespaceRendering.svg", - "Use a new rendering method with svgs.", - ), - nls.localize( - "experimentalWhitespaceRendering.font", - "Use a new rendering method with font characters.", - ), - nls.localize( - "experimentalWhitespaceRendering.off", - "Use the stable rendering method.", - ), - ], - description: nls.localize( - "experimentalWhitespaceRendering", - "Controls whether whitespace is rendered with a new, experimental method.", - ), - }, - ), - ), - extraEditorClassName: register( - new EditorStringOption( - EditorOption.extraEditorClassName, - "extraEditorClassName", - "", - ), - ), - fastScrollSensitivity: register( - new EditorFloatOption( - EditorOption.fastScrollSensitivity, - "fastScrollSensitivity", - 5, - (x) => (x <= 0 ? 5 : x), - { - markdownDescription: nls.localize( - "fastScrollSensitivity", - "Scrolling speed multiplier when pressing `Alt`.", - ), - }, - ), - ), + experimentalGpuAcceleration: register(new EditorStringEnumOption( + EditorOption.experimentalGpuAcceleration, 'experimentalGpuAcceleration', + 'off' as 'off' | 'on', + ['off', 'on'] as const, + undefined + // TODO: Uncomment when we want to expose the setting to VS Code users + // { + // enumDescriptions: [ + // nls.localize('experimentalGpuAcceleration.off', "Use regular DOM-based rendering."), + // nls.localize('experimentalGpuAcceleration.on', "Use GPU acceleration."), + // ], + // description: nls.localize('experimentalGpuAcceleration', "Controls whether to use the (very) experimental GPU acceleration to render the editor.") + // } + )), + experimentalWhitespaceRendering: register(new EditorStringEnumOption( + EditorOption.experimentalWhitespaceRendering, 'experimentalWhitespaceRendering', + 'svg' as 'svg' | 'font' | 'off', + ['svg', 'font', 'off'] as const, + { + enumDescriptions: [ + nls.localize('experimentalWhitespaceRendering.svg', "Use a new rendering method with svgs."), + nls.localize('experimentalWhitespaceRendering.font', "Use a new rendering method with font characters."), + nls.localize('experimentalWhitespaceRendering.off', "Use the stable rendering method."), + ], + description: nls.localize('experimentalWhitespaceRendering', "Controls whether whitespace is rendered with a new, experimental method.") + } + )), + extraEditorClassName: register(new EditorStringOption( + EditorOption.extraEditorClassName, 'extraEditorClassName', '', + )), + fastScrollSensitivity: register(new EditorFloatOption( + EditorOption.fastScrollSensitivity, 'fastScrollSensitivity', + 5, x => (x <= 0 ? 5 : x), + { markdownDescription: nls.localize('fastScrollSensitivity', "Scrolling speed multiplier when pressing `Alt`.") } + )), find: register(new EditorFind()), - fixedOverflowWidgets: register( - new EditorBooleanOption( - EditorOption.fixedOverflowWidgets, - "fixedOverflowWidgets", - false, - ), - ), - folding: register( - new EditorBooleanOption(EditorOption.folding, "folding", true, { - description: nls.localize( - "folding", - "Controls whether the editor has code folding enabled.", - ), - }), - ), - foldingStrategy: register( - new EditorStringEnumOption( - EditorOption.foldingStrategy, - "foldingStrategy", - "auto" as "auto" | "indentation", - ["auto", "indentation"] as const, - { - enumDescriptions: [ - nls.localize( - "foldingStrategy.auto", - "Use a language-specific folding strategy if available, else the indentation-based one.", - ), - nls.localize( - "foldingStrategy.indentation", - "Use the indentation-based folding strategy.", - ), - ], - description: nls.localize( - "foldingStrategy", - "Controls the strategy for computing folding ranges.", - ), - }, - ), - ), - foldingHighlight: register( - new EditorBooleanOption( - EditorOption.foldingHighlight, - "foldingHighlight", - true, - { - description: nls.localize( - "foldingHighlight", - "Controls whether the editor should highlight folded ranges.", - ), - }, - ), - ), - foldingImportsByDefault: register( - new EditorBooleanOption( - EditorOption.foldingImportsByDefault, - "foldingImportsByDefault", - false, - { - description: nls.localize( - "foldingImportsByDefault", - "Controls whether the editor automatically collapses import ranges.", - ), - }, - ), - ), - foldingMaximumRegions: register( - new EditorIntOption( - EditorOption.foldingMaximumRegions, - "foldingMaximumRegions", - 5000, - 10, - 65000, // limit must be less than foldingRanges MAX_FOLDING_REGIONS - { - description: nls.localize( - "foldingMaximumRegions", - "The maximum number of foldable regions. Increasing this value may result in the editor becoming less responsive when the current source has a large number of foldable regions.", - ), - }, - ), - ), - unfoldOnClickAfterEndOfLine: register( - new EditorBooleanOption( - EditorOption.unfoldOnClickAfterEndOfLine, - "unfoldOnClickAfterEndOfLine", - false, - { - description: nls.localize( - "unfoldOnClickAfterEndOfLine", - "Controls whether clicking on the empty content after a folded line will unfold the line.", - ), - }, - ), - ), - fontFamily: register( - new EditorStringOption( - EditorOption.fontFamily, - "fontFamily", - EDITOR_FONT_DEFAULTS.fontFamily, - { - description: nls.localize( - "fontFamily", - "Controls the font family.", - ), - }, - ), - ), + fixedOverflowWidgets: register(new EditorBooleanOption( + EditorOption.fixedOverflowWidgets, 'fixedOverflowWidgets', false, + )), + folding: register(new EditorBooleanOption( + EditorOption.folding, 'folding', true, + { description: nls.localize('folding', "Controls whether the editor has code folding enabled.") } + )), + foldingStrategy: register(new EditorStringEnumOption( + EditorOption.foldingStrategy, 'foldingStrategy', + 'auto' as 'auto' | 'indentation', + ['auto', 'indentation'] as const, + { + enumDescriptions: [ + nls.localize('foldingStrategy.auto', "Use a language-specific folding strategy if available, else the indentation-based one."), + nls.localize('foldingStrategy.indentation', "Use the indentation-based folding strategy."), + ], + description: nls.localize('foldingStrategy', "Controls the strategy for computing folding ranges.") + } + )), + foldingHighlight: register(new EditorBooleanOption( + EditorOption.foldingHighlight, 'foldingHighlight', true, + { description: nls.localize('foldingHighlight', "Controls whether the editor should highlight folded ranges.") } + )), + foldingImportsByDefault: register(new EditorBooleanOption( + EditorOption.foldingImportsByDefault, 'foldingImportsByDefault', false, + { description: nls.localize('foldingImportsByDefault', "Controls whether the editor automatically collapses import ranges.") } + )), + foldingMaximumRegions: register(new EditorIntOption( + EditorOption.foldingMaximumRegions, 'foldingMaximumRegions', + 5000, 10, 65000, // limit must be less than foldingRanges MAX_FOLDING_REGIONS + { description: nls.localize('foldingMaximumRegions', "The maximum number of foldable regions. Increasing this value may result in the editor becoming less responsive when the current source has a large number of foldable regions.") } + )), + unfoldOnClickAfterEndOfLine: register(new EditorBooleanOption( + EditorOption.unfoldOnClickAfterEndOfLine, 'unfoldOnClickAfterEndOfLine', false, + { description: nls.localize('unfoldOnClickAfterEndOfLine', "Controls whether clicking on the empty content after a folded line will unfold the line.") } + )), + fontFamily: register(new EditorStringOption( + EditorOption.fontFamily, 'fontFamily', EDITOR_FONT_DEFAULTS.fontFamily, + { description: nls.localize('fontFamily', "Controls the font family.") } + )), fontInfo: register(new EditorFontInfo()), fontLigatures2: register(new EditorFontLigatures()), fontSize: register(new EditorFontSize()), fontWeight: register(new EditorFontWeight()), fontVariations: register(new EditorFontVariations()), - formatOnPaste: register( - new EditorBooleanOption( - EditorOption.formatOnPaste, - "formatOnPaste", - false, - { - description: nls.localize( - "formatOnPaste", - "Controls whether the editor should automatically format the pasted content. A formatter must be available and the formatter should be able to format a range in a document.", - ), - }, - ), - ), - formatOnType: register( - new EditorBooleanOption( - EditorOption.formatOnType, - "formatOnType", - false, - { - description: nls.localize( - "formatOnType", - "Controls whether the editor should automatically format the line after typing.", - ), - }, - ), - ), - glyphMargin: register( - new EditorBooleanOption(EditorOption.glyphMargin, "glyphMargin", true, { - description: nls.localize( - "glyphMargin", - "Controls whether the editor should render the vertical glyph margin. Glyph margin is mostly used for debugging.", - ), - }), - ), + formatOnPaste: register(new EditorBooleanOption( + EditorOption.formatOnPaste, 'formatOnPaste', false, + { description: nls.localize('formatOnPaste', "Controls whether the editor should automatically format the pasted content. A formatter must be available and the formatter should be able to format a range in a document.") } + )), + formatOnType: register(new EditorBooleanOption( + EditorOption.formatOnType, 'formatOnType', false, + { description: nls.localize('formatOnType', "Controls whether the editor should automatically format the line after typing.") } + )), + glyphMargin: register(new EditorBooleanOption( + EditorOption.glyphMargin, 'glyphMargin', true, + { description: nls.localize('glyphMargin', "Controls whether the editor should render the vertical glyph margin. Glyph margin is mostly used for debugging.") } + )), gotoLocation: register(new EditorGoToLocation()), - hideCursorInOverviewRuler: register( - new EditorBooleanOption( - EditorOption.hideCursorInOverviewRuler, - "hideCursorInOverviewRuler", - false, - { - description: nls.localize( - "hideCursorInOverviewRuler", - "Controls whether the cursor should be hidden in the overview ruler.", - ), - }, - ), - ), + hideCursorInOverviewRuler: register(new EditorBooleanOption( + EditorOption.hideCursorInOverviewRuler, 'hideCursorInOverviewRuler', false, + { description: nls.localize('hideCursorInOverviewRuler', "Controls whether the cursor should be hidden in the overview ruler.") } + )), hover: register(new EditorHover()), - inDiffEditor: register( - new EditorBooleanOption( - EditorOption.inDiffEditor, - "inDiffEditor", - false, - ), - ), - letterSpacing: register( - new EditorFloatOption( - EditorOption.letterSpacing, - "letterSpacing", - EDITOR_FONT_DEFAULTS.letterSpacing, - (x) => EditorFloatOption.clamp(x, -5, 20), - { - description: nls.localize( - "letterSpacing", - "Controls the letter spacing in pixels.", - ), - }, - ), - ), + inDiffEditor: register(new EditorBooleanOption( + EditorOption.inDiffEditor, 'inDiffEditor', false + )), + letterSpacing: register(new EditorFloatOption( + EditorOption.letterSpacing, 'letterSpacing', + EDITOR_FONT_DEFAULTS.letterSpacing, x => EditorFloatOption.clamp(x, -5, 20), + { description: nls.localize('letterSpacing', "Controls the letter spacing in pixels.") } + )), lightbulb: register(new EditorLightbulb()), lineDecorationsWidth: register(new EditorLineDecorationsWidth()), lineHeight: register(new EditorLineHeight()), lineNumbers: register(new EditorRenderLineNumbersOption()), - lineNumbersMinChars: register( - new EditorIntOption( - EditorOption.lineNumbersMinChars, - "lineNumbersMinChars", - 5, - 1, - 300, - ), - ), - linkedEditing: register( - new EditorBooleanOption( - EditorOption.linkedEditing, - "linkedEditing", - false, - { - description: nls.localize( - "linkedEditing", - "Controls whether the editor has linked editing enabled. Depending on the language, related symbols such as HTML tags, are updated while editing.", - ), - }, - ), - ), - links: register( - new EditorBooleanOption(EditorOption.links, "links", true, { - description: nls.localize( - "links", - "Controls whether the editor should detect links and make them clickable.", - ), - }), - ), - matchBrackets: register( - new EditorStringEnumOption( - EditorOption.matchBrackets, - "matchBrackets", - "always" as "never" | "near" | "always", - ["always", "near", "never"] as const, - { - description: nls.localize( - "matchBrackets", - "Highlight matching brackets.", - ), - }, - ), - ), + lineNumbersMinChars: register(new EditorIntOption( + EditorOption.lineNumbersMinChars, 'lineNumbersMinChars', + 5, 1, 300 + )), + linkedEditing: register(new EditorBooleanOption( + EditorOption.linkedEditing, 'linkedEditing', false, + { description: nls.localize('linkedEditing', "Controls whether the editor has linked editing enabled. Depending on the language, related symbols such as HTML tags, are updated while editing.") } + )), + links: register(new EditorBooleanOption( + EditorOption.links, 'links', true, + { description: nls.localize('links', "Controls whether the editor should detect links and make them clickable.") } + )), + matchBrackets: register(new EditorStringEnumOption( + EditorOption.matchBrackets, 'matchBrackets', + 'always' as 'never' | 'near' | 'always', + ['always', 'near', 'never'] as const, + { description: nls.localize('matchBrackets', "Highlight matching brackets.") } + )), minimap: register(new EditorMinimap()), - mouseStyle: register( - new EditorStringEnumOption( - EditorOption.mouseStyle, - "mouseStyle", - "text" as "text" | "default" | "copy", - ["text", "default", "copy"] as const, - ), - ), - mouseWheelScrollSensitivity: register( - new EditorFloatOption( - EditorOption.mouseWheelScrollSensitivity, - "mouseWheelScrollSensitivity", - 1, - (x) => (x === 0 ? 1 : x), - { - markdownDescription: nls.localize( - "mouseWheelScrollSensitivity", - "A multiplier to be used on the `deltaX` and `deltaY` of mouse wheel scroll events.", - ), - }, - ), - ), - mouseWheelZoom: register( - new EditorBooleanOption( - EditorOption.mouseWheelZoom, - "mouseWheelZoom", - false, - { - markdownDescription: platform.isMacintosh - ? nls.localize( - "mouseWheelZoom.mac", - "Zoom the font of the editor when using mouse wheel and holding `Cmd`.", - ) - : nls.localize( - "mouseWheelZoom", - "Zoom the font of the editor when using mouse wheel and holding `Ctrl`.", - ), - }, - ), - ), - multiCursorMergeOverlapping: register( - new EditorBooleanOption( - EditorOption.multiCursorMergeOverlapping, - "multiCursorMergeOverlapping", - true, - { - description: nls.localize( - "multiCursorMergeOverlapping", - "Merge multiple cursors when they are overlapping.", - ), - }, - ), - ), - multiCursorModifier: register( - new EditorEnumOption( - EditorOption.multiCursorModifier, - "multiCursorModifier", - "altKey", - "alt", - ["ctrlCmd", "alt"], - _multiCursorModifierFromString, - { - markdownEnumDescriptions: [ - nls.localize( - "multiCursorModifier.ctrlCmd", - "Maps to `Control` on Windows and Linux and to `Command` on macOS.", - ), - nls.localize( - "multiCursorModifier.alt", - "Maps to `Alt` on Windows and Linux and to `Option` on macOS.", - ), - ], - markdownDescription: nls.localize( - { - key: "multiCursorModifier", - comment: [ - "- `ctrlCmd` refers to a value the setting can take and should not be localized.", - "- `Control` and `Command` refer to the modifier keys Ctrl or Cmd on the keyboard and can be localized.", - ], - }, - "The modifier to be used to add multiple cursors with the mouse. The Go to Definition and Open Link mouse gestures will adapt such that they do not conflict with the [multicursor modifier](https://code.visualstudio.com/docs/editor/codebasics#_multicursor-modifier).", - ), - }, - ), - ), - multiCursorPaste: register( - new EditorStringEnumOption( - EditorOption.multiCursorPaste, - "multiCursorPaste", - "spread" as "spread" | "full", - ["spread", "full"] as const, - { - markdownEnumDescriptions: [ - nls.localize( - "multiCursorPaste.spread", - "Each cursor pastes a single line of the text.", - ), - nls.localize( - "multiCursorPaste.full", - "Each cursor pastes the full text.", - ), - ], - markdownDescription: nls.localize( - "multiCursorPaste", - "Controls pasting when the line count of the pasted text matches the cursor count.", - ), - }, - ), - ), - multiCursorLimit: register( - new EditorIntOption( - EditorOption.multiCursorLimit, - "multiCursorLimit", - 10000, - 1, - 100000, - { - markdownDescription: nls.localize( - "multiCursorLimit", - "Controls the max number of cursors that can be in an active editor at once.", - ), - }, - ), - ), - occurrencesHighlight: register( - new EditorStringEnumOption( - EditorOption.occurrencesHighlight, - "occurrencesHighlight", - "singleFile" as "off" | "singleFile" | "multiFile", - ["off", "singleFile", "multiFile"] as const, - { - markdownEnumDescriptions: [ - nls.localize( - "occurrencesHighlight.off", - "Does not highlight occurrences.", - ), - nls.localize( - "occurrencesHighlight.singleFile", - "Highlights occurrences only in the current file.", - ), - nls.localize( - "occurrencesHighlight.multiFile", - "Experimental: Highlights occurrences across all valid open files.", - ), - ], - markdownDescription: nls.localize( - "occurrencesHighlight", - "Controls whether occurrences should be highlighted across open files.", - ), - }, - ), - ), - occurrencesHighlightDelay: register( - new EditorIntOption( - EditorOption.occurrencesHighlightDelay, - "occurrencesHighlightDelay", - 250, - 0, - 2000, - { - description: nls.localize( - "occurrencesHighlightDelay", - "Controls the delay in milliseconds after which occurrences are highlighted.", - ), - tags: ["preview"], - }, - ), - ), - overviewRulerBorder: register( - new EditorBooleanOption( - EditorOption.overviewRulerBorder, - "overviewRulerBorder", - true, - { - description: nls.localize( - "overviewRulerBorder", - "Controls whether a border should be drawn around the overview ruler.", - ), - }, - ), - ), - overviewRulerLanes: register( - new EditorIntOption( - EditorOption.overviewRulerLanes, - "overviewRulerLanes", - 3, - 0, - 3, - ), - ), + mouseStyle: register(new EditorStringEnumOption( + EditorOption.mouseStyle, 'mouseStyle', + 'text' as 'text' | 'default' | 'copy', + ['text', 'default', 'copy'] as const, + )), + mouseWheelScrollSensitivity: register(new EditorFloatOption( + EditorOption.mouseWheelScrollSensitivity, 'mouseWheelScrollSensitivity', + 1, x => (x === 0 ? 1 : x), + { markdownDescription: nls.localize('mouseWheelScrollSensitivity', "A multiplier to be used on the `deltaX` and `deltaY` of mouse wheel scroll events.") } + )), + mouseWheelZoom: register(new EditorBooleanOption( + EditorOption.mouseWheelZoom, 'mouseWheelZoom', false, + { + markdownDescription: platform.isMacintosh + ? nls.localize('mouseWheelZoom.mac', "Zoom the font of the editor when using mouse wheel and holding `Cmd`.") + : nls.localize('mouseWheelZoom', "Zoom the font of the editor when using mouse wheel and holding `Ctrl`.") + } + )), + multiCursorMergeOverlapping: register(new EditorBooleanOption( + EditorOption.multiCursorMergeOverlapping, 'multiCursorMergeOverlapping', true, + { description: nls.localize('multiCursorMergeOverlapping', "Merge multiple cursors when they are overlapping.") } + )), + multiCursorModifier: register(new EditorEnumOption( + EditorOption.multiCursorModifier, 'multiCursorModifier', + 'altKey', 'alt', + ['ctrlCmd', 'alt'], + _multiCursorModifierFromString, + { + markdownEnumDescriptions: [ + nls.localize('multiCursorModifier.ctrlCmd', "Maps to `Control` on Windows and Linux and to `Command` on macOS."), + nls.localize('multiCursorModifier.alt', "Maps to `Alt` on Windows and Linux and to `Option` on macOS.") + ], + markdownDescription: nls.localize({ + key: 'multiCursorModifier', + comment: [ + '- `ctrlCmd` refers to a value the setting can take and should not be localized.', + '- `Control` and `Command` refer to the modifier keys Ctrl or Cmd on the keyboard and can be localized.' + ] + }, "The modifier to be used to add multiple cursors with the mouse. The Go to Definition and Open Link mouse gestures will adapt such that they do not conflict with the [multicursor modifier](https://code.visualstudio.com/docs/editor/codebasics#_multicursor-modifier).") + } + )), + multiCursorPaste: register(new EditorStringEnumOption( + EditorOption.multiCursorPaste, 'multiCursorPaste', + 'spread' as 'spread' | 'full', + ['spread', 'full'] as const, + { + markdownEnumDescriptions: [ + nls.localize('multiCursorPaste.spread', "Each cursor pastes a single line of the text."), + nls.localize('multiCursorPaste.full', "Each cursor pastes the full text.") + ], + markdownDescription: nls.localize('multiCursorPaste', "Controls pasting when the line count of the pasted text matches the cursor count.") + } + )), + multiCursorLimit: register(new EditorIntOption( + EditorOption.multiCursorLimit, 'multiCursorLimit', 10000, 1, 100000, + { + markdownDescription: nls.localize('multiCursorLimit', "Controls the max number of cursors that can be in an active editor at once.") + } + )), + occurrencesHighlight: register(new EditorStringEnumOption( + EditorOption.occurrencesHighlight, 'occurrencesHighlight', + 'singleFile' as 'off' | 'singleFile' | 'multiFile', + ['off', 'singleFile', 'multiFile'] as const, + { + markdownEnumDescriptions: [ + nls.localize('occurrencesHighlight.off', "Does not highlight occurrences."), + nls.localize('occurrencesHighlight.singleFile', "Highlights occurrences only in the current file."), + nls.localize('occurrencesHighlight.multiFile', "Experimental: Highlights occurrences across all valid open files.") + ], + markdownDescription: nls.localize('occurrencesHighlight', "Controls whether occurrences should be highlighted across open files.") + } + )), + occurrencesHighlightDelay: register(new EditorIntOption( + EditorOption.occurrencesHighlightDelay, 'occurrencesHighlightDelay', + 250, 0, 2000, + { + description: nls.localize('occurrencesHighlightDelay', "Controls the delay in milliseconds after which occurrences are highlighted."), + tags: ['preview'] + } + )), + overviewRulerBorder: register(new EditorBooleanOption( + EditorOption.overviewRulerBorder, 'overviewRulerBorder', true, + { description: nls.localize('overviewRulerBorder', "Controls whether a border should be drawn around the overview ruler.") } + )), + overviewRulerLanes: register(new EditorIntOption( + EditorOption.overviewRulerLanes, 'overviewRulerLanes', + 3, 0, 3 + )), padding: register(new EditorPadding()), pasteAs: register(new EditorPasteAs()), parameterHints: register(new EditorParameterHints()), - peekWidgetDefaultFocus: register( - new EditorStringEnumOption( - EditorOption.peekWidgetDefaultFocus, - "peekWidgetDefaultFocus", - "tree" as "tree" | "editor", - ["tree", "editor"] as const, - { - enumDescriptions: [ - nls.localize( - "peekWidgetDefaultFocus.tree", - "Focus the tree when opening peek", - ), - nls.localize( - "peekWidgetDefaultFocus.editor", - "Focus the editor when opening peek", - ), - ], - description: nls.localize( - "peekWidgetDefaultFocus", - "Controls whether to focus the inline editor or the tree in the peek widget.", - ), - }, - ), - ), + peekWidgetDefaultFocus: register(new EditorStringEnumOption( + EditorOption.peekWidgetDefaultFocus, 'peekWidgetDefaultFocus', + 'tree' as 'tree' | 'editor', + ['tree', 'editor'] as const, + { + enumDescriptions: [ + nls.localize('peekWidgetDefaultFocus.tree', "Focus the tree when opening peek"), + nls.localize('peekWidgetDefaultFocus.editor', "Focus the editor when opening peek") + ], + description: nls.localize('peekWidgetDefaultFocus', "Controls whether to focus the inline editor or the tree in the peek widget.") + } + )), placeholder: register(new PlaceholderOption()), - definitionLinkOpensInPeek: register( - new EditorBooleanOption( - EditorOption.definitionLinkOpensInPeek, - "definitionLinkOpensInPeek", - false, - { - description: nls.localize( - "definitionLinkOpensInPeek", - "Controls whether the Go to Definition mouse gesture always opens the peek widget.", - ), - }, - ), - ), + definitionLinkOpensInPeek: register(new EditorBooleanOption( + EditorOption.definitionLinkOpensInPeek, 'definitionLinkOpensInPeek', false, + { description: nls.localize('definitionLinkOpensInPeek', "Controls whether the Go to Definition mouse gesture always opens the peek widget.") } + )), quickSuggestions: register(new EditorQuickSuggestions()), - quickSuggestionsDelay: register( - new EditorIntOption( - EditorOption.quickSuggestionsDelay, - "quickSuggestionsDelay", - 10, - 0, - Constants.MAX_SAFE_SMALL_INTEGER, - { - description: nls.localize( - "quickSuggestionsDelay", - "Controls the delay in milliseconds after which quick suggestions will show up.", - ), - }, - ), - ), - readOnly: register( - new EditorBooleanOption(EditorOption.readOnly, "readOnly", false), - ), + quickSuggestionsDelay: register(new EditorIntOption( + EditorOption.quickSuggestionsDelay, 'quickSuggestionsDelay', + 10, 0, Constants.MAX_SAFE_SMALL_INTEGER, + { description: nls.localize('quickSuggestionsDelay', "Controls the delay in milliseconds after which quick suggestions will show up.") } + )), + readOnly: register(new EditorBooleanOption( + EditorOption.readOnly, 'readOnly', false, + )), readOnlyMessage: register(new ReadonlyMessage()), - renameOnType: register( - new EditorBooleanOption( - EditorOption.renameOnType, - "renameOnType", - false, - { - description: nls.localize( - "renameOnType", - "Controls whether the editor auto renames on type.", - ), - markdownDeprecationMessage: nls.localize( - "renameOnTypeDeprecate", - "Deprecated, use `editor.linkedEditing` instead.", - ), - }, - ), - ), - renderControlCharacters: register( - new EditorBooleanOption( - EditorOption.renderControlCharacters, - "renderControlCharacters", - true, - { - description: nls.localize( - "renderControlCharacters", - "Controls whether the editor should render control characters.", - ), - restricted: true, - }, - ), - ), - renderFinalNewline: register( - new EditorStringEnumOption( - EditorOption.renderFinalNewline, - "renderFinalNewline", - (platform.isLinux ? "dimmed" : "on") as "off" | "on" | "dimmed", - ["off", "on", "dimmed"] as const, - { - description: nls.localize( - "renderFinalNewline", - "Render last line number when the file ends with a newline.", - ), - }, - ), - ), - renderLineHighlight: register( - new EditorStringEnumOption( - EditorOption.renderLineHighlight, - "renderLineHighlight", - "line" as "none" | "gutter" | "line" | "all", - ["none", "gutter", "line", "all"] as const, - { - enumDescriptions: [ - "", - "", - "", - nls.localize( - "renderLineHighlight.all", - "Highlights both the gutter and the current line.", - ), - ], - description: nls.localize( - "renderLineHighlight", - "Controls how the editor should render the current line highlight.", - ), - }, - ), - ), - renderLineHighlightOnlyWhenFocus: register( - new EditorBooleanOption( - EditorOption.renderLineHighlightOnlyWhenFocus, - "renderLineHighlightOnlyWhenFocus", - false, - { - description: nls.localize( - "renderLineHighlightOnlyWhenFocus", - "Controls if the editor should render the current line highlight only when the editor is focused.", - ), - }, - ), - ), - renderValidationDecorations: register( - new EditorStringEnumOption( - EditorOption.renderValidationDecorations, - "renderValidationDecorations", - "editable" as "editable" | "on" | "off", - ["editable", "on", "off"] as const, - ), - ), - renderWhitespace: register( - new EditorStringEnumOption( - EditorOption.renderWhitespace, - "renderWhitespace", - "selection" as - | "selection" - | "none" - | "boundary" - | "trailing" - | "all", - ["none", "boundary", "selection", "trailing", "all"] as const, - { - enumDescriptions: [ - "", - nls.localize( - "renderWhitespace.boundary", - "Render whitespace characters except for single spaces between words.", - ), - nls.localize( - "renderWhitespace.selection", - "Render whitespace characters only on selected text.", - ), - nls.localize( - "renderWhitespace.trailing", - "Render only trailing whitespace characters.", - ), - "", - ], - description: nls.localize( - "renderWhitespace", - "Controls how the editor should render whitespace characters.", - ), - }, - ), - ), - revealHorizontalRightPadding: register( - new EditorIntOption( - EditorOption.revealHorizontalRightPadding, - "revealHorizontalRightPadding", - 15, - 0, - 1000, - ), - ), - roundedSelection: register( - new EditorBooleanOption( - EditorOption.roundedSelection, - "roundedSelection", - true, - { - description: nls.localize( - "roundedSelection", - "Controls whether selections should have rounded corners.", - ), - }, - ), - ), + renameOnType: register(new EditorBooleanOption( + EditorOption.renameOnType, 'renameOnType', false, + { description: nls.localize('renameOnType', "Controls whether the editor auto renames on type."), markdownDeprecationMessage: nls.localize('renameOnTypeDeprecate', "Deprecated, use `editor.linkedEditing` instead.") } + )), + renderControlCharacters: register(new EditorBooleanOption( + EditorOption.renderControlCharacters, 'renderControlCharacters', true, + { description: nls.localize('renderControlCharacters', "Controls whether the editor should render control characters."), restricted: true } + )), + renderFinalNewline: register(new EditorStringEnumOption( + EditorOption.renderFinalNewline, 'renderFinalNewline', + (platform.isLinux ? 'dimmed' : 'on') as 'off' | 'on' | 'dimmed', + ['off', 'on', 'dimmed'] as const, + { description: nls.localize('renderFinalNewline', "Render last line number when the file ends with a newline.") } + )), + renderLineHighlight: register(new EditorStringEnumOption( + EditorOption.renderLineHighlight, 'renderLineHighlight', + 'line' as 'none' | 'gutter' | 'line' | 'all', + ['none', 'gutter', 'line', 'all'] as const, + { + enumDescriptions: [ + '', + '', + '', + nls.localize('renderLineHighlight.all', "Highlights both the gutter and the current line."), + ], + description: nls.localize('renderLineHighlight', "Controls how the editor should render the current line highlight.") + } + )), + renderLineHighlightOnlyWhenFocus: register(new EditorBooleanOption( + EditorOption.renderLineHighlightOnlyWhenFocus, 'renderLineHighlightOnlyWhenFocus', false, + { description: nls.localize('renderLineHighlightOnlyWhenFocus', "Controls if the editor should render the current line highlight only when the editor is focused.") } + )), + renderValidationDecorations: register(new EditorStringEnumOption( + EditorOption.renderValidationDecorations, 'renderValidationDecorations', + 'editable' as 'editable' | 'on' | 'off', + ['editable', 'on', 'off'] as const + )), + renderWhitespace: register(new EditorStringEnumOption( + EditorOption.renderWhitespace, 'renderWhitespace', + 'selection' as 'selection' | 'none' | 'boundary' | 'trailing' | 'all', + ['none', 'boundary', 'selection', 'trailing', 'all'] as const, + { + enumDescriptions: [ + '', + nls.localize('renderWhitespace.boundary', "Render whitespace characters except for single spaces between words."), + nls.localize('renderWhitespace.selection', "Render whitespace characters only on selected text."), + nls.localize('renderWhitespace.trailing', "Render only trailing whitespace characters."), + '' + ], + description: nls.localize('renderWhitespace', "Controls how the editor should render whitespace characters.") + } + )), + revealHorizontalRightPadding: register(new EditorIntOption( + EditorOption.revealHorizontalRightPadding, 'revealHorizontalRightPadding', + 15, 0, 1000, + )), + roundedSelection: register(new EditorBooleanOption( + EditorOption.roundedSelection, 'roundedSelection', true, + { description: nls.localize('roundedSelection', "Controls whether selections should have rounded corners.") } + )), rulers: register(new EditorRulers()), scrollbar: register(new EditorScrollbar()), - scrollBeyondLastColumn: register( - new EditorIntOption( - EditorOption.scrollBeyondLastColumn, - "scrollBeyondLastColumn", - 4, - 0, - Constants.MAX_SAFE_SMALL_INTEGER, - { - description: nls.localize( - "scrollBeyondLastColumn", - "Controls the number of extra characters beyond which the editor will scroll horizontally.", - ), - }, - ), - ), - scrollBeyondLastLine: register( - new EditorBooleanOption( - EditorOption.scrollBeyondLastLine, - "scrollBeyondLastLine", - true, - { - description: nls.localize( - "scrollBeyondLastLine", - "Controls whether the editor will scroll beyond the last line.", - ), - }, - ), - ), - scrollPredominantAxis: register( - new EditorBooleanOption( - EditorOption.scrollPredominantAxis, - "scrollPredominantAxis", - true, - { - description: nls.localize( - "scrollPredominantAxis", - "Scroll only along the predominant axis when scrolling both vertically and horizontally at the same time. Prevents horizontal drift when scrolling vertically on a trackpad.", - ), - }, - ), - ), - selectionClipboard: register( - new EditorBooleanOption( - EditorOption.selectionClipboard, - "selectionClipboard", - true, - { - description: nls.localize( - "selectionClipboard", - "Controls whether the Linux primary clipboard should be supported.", - ), - included: platform.isLinux, - }, - ), - ), - selectionHighlight: register( - new EditorBooleanOption( - EditorOption.selectionHighlight, - "selectionHighlight", - true, - { - description: nls.localize( - "selectionHighlight", - "Controls whether the editor should highlight matches similar to the selection.", - ), - }, - ), - ), - selectOnLineNumbers: register( - new EditorBooleanOption( - EditorOption.selectOnLineNumbers, - "selectOnLineNumbers", - true, - ), - ), - showFoldingControls: register( - new EditorStringEnumOption( - EditorOption.showFoldingControls, - "showFoldingControls", - "mouseover" as "always" | "never" | "mouseover", - ["always", "never", "mouseover"] as const, - { - enumDescriptions: [ - nls.localize( - "showFoldingControls.always", - "Always show the folding controls.", - ), - nls.localize( - "showFoldingControls.never", - "Never show the folding controls and reduce the gutter size.", - ), - nls.localize( - "showFoldingControls.mouseover", - "Only show the folding controls when the mouse is over the gutter.", - ), - ], - description: nls.localize( - "showFoldingControls", - "Controls when the folding controls on the gutter are shown.", - ), - }, - ), - ), - showUnused: register( - new EditorBooleanOption(EditorOption.showUnused, "showUnused", true, { - description: nls.localize( - "showUnused", - "Controls fading out of unused code.", - ), - }), - ), - showDeprecated: register( - new EditorBooleanOption( - EditorOption.showDeprecated, - "showDeprecated", - true, - { - description: nls.localize( - "showDeprecated", - "Controls strikethrough deprecated variables.", - ), - }, - ), - ), + scrollBeyondLastColumn: register(new EditorIntOption( + EditorOption.scrollBeyondLastColumn, 'scrollBeyondLastColumn', + 4, 0, Constants.MAX_SAFE_SMALL_INTEGER, + { description: nls.localize('scrollBeyondLastColumn', "Controls the number of extra characters beyond which the editor will scroll horizontally.") } + )), + scrollBeyondLastLine: register(new EditorBooleanOption( + EditorOption.scrollBeyondLastLine, 'scrollBeyondLastLine', true, + { description: nls.localize('scrollBeyondLastLine', "Controls whether the editor will scroll beyond the last line.") } + )), + scrollPredominantAxis: register(new EditorBooleanOption( + EditorOption.scrollPredominantAxis, 'scrollPredominantAxis', true, + { description: nls.localize('scrollPredominantAxis', "Scroll only along the predominant axis when scrolling both vertically and horizontally at the same time. Prevents horizontal drift when scrolling vertically on a trackpad.") } + )), + selectionClipboard: register(new EditorBooleanOption( + EditorOption.selectionClipboard, 'selectionClipboard', true, + { + description: nls.localize('selectionClipboard', "Controls whether the Linux primary clipboard should be supported."), + included: platform.isLinux + } + )), + selectionHighlight: register(new EditorBooleanOption( + EditorOption.selectionHighlight, 'selectionHighlight', true, + { description: nls.localize('selectionHighlight', "Controls whether the editor should highlight matches similar to the selection.") } + )), + selectOnLineNumbers: register(new EditorBooleanOption( + EditorOption.selectOnLineNumbers, 'selectOnLineNumbers', true, + )), + showFoldingControls: register(new EditorStringEnumOption( + EditorOption.showFoldingControls, 'showFoldingControls', + 'mouseover' as 'always' | 'never' | 'mouseover', + ['always', 'never', 'mouseover'] as const, + { + enumDescriptions: [ + nls.localize('showFoldingControls.always', "Always show the folding controls."), + nls.localize('showFoldingControls.never', "Never show the folding controls and reduce the gutter size."), + nls.localize('showFoldingControls.mouseover', "Only show the folding controls when the mouse is over the gutter."), + ], + description: nls.localize('showFoldingControls', "Controls when the folding controls on the gutter are shown.") + } + )), + showUnused: register(new EditorBooleanOption( + EditorOption.showUnused, 'showUnused', true, + { description: nls.localize('showUnused', "Controls fading out of unused code.") } + )), + showDeprecated: register(new EditorBooleanOption( + EditorOption.showDeprecated, 'showDeprecated', true, + { description: nls.localize('showDeprecated', "Controls strikethrough deprecated variables.") } + )), inlayHints: register(new EditorInlayHints()), - snippetSuggestions: register( - new EditorStringEnumOption( - EditorOption.snippetSuggestions, - "snippetSuggestions", - "inline" as "top" | "bottom" | "inline" | "none", - ["top", "bottom", "inline", "none"] as const, - { - enumDescriptions: [ - nls.localize( - "snippetSuggestions.top", - "Show snippet suggestions on top of other suggestions.", - ), - nls.localize( - "snippetSuggestions.bottom", - "Show snippet suggestions below other suggestions.", - ), - nls.localize( - "snippetSuggestions.inline", - "Show snippets suggestions with other suggestions.", - ), - nls.localize( - "snippetSuggestions.none", - "Do not show snippet suggestions.", - ), - ], - description: nls.localize( - "snippetSuggestions", - "Controls whether snippets are shown with other suggestions and how they are sorted.", - ), - }, - ), - ), + snippetSuggestions: register(new EditorStringEnumOption( + EditorOption.snippetSuggestions, 'snippetSuggestions', + 'inline' as 'top' | 'bottom' | 'inline' | 'none', + ['top', 'bottom', 'inline', 'none'] as const, + { + enumDescriptions: [ + nls.localize('snippetSuggestions.top', "Show snippet suggestions on top of other suggestions."), + nls.localize('snippetSuggestions.bottom', "Show snippet suggestions below other suggestions."), + nls.localize('snippetSuggestions.inline', "Show snippets suggestions with other suggestions."), + nls.localize('snippetSuggestions.none', "Do not show snippet suggestions."), + ], + description: nls.localize('snippetSuggestions', "Controls whether snippets are shown with other suggestions and how they are sorted.") + } + )), smartSelect: register(new SmartSelect()), - smoothScrolling: register( - new EditorBooleanOption( - EditorOption.smoothScrolling, - "smoothScrolling", - false, - { - description: nls.localize( - "smoothScrolling", - "Controls whether the editor will scroll using an animation.", - ), - }, - ), - ), - stopRenderingLineAfter: register( - new EditorIntOption( - EditorOption.stopRenderingLineAfter, - "stopRenderingLineAfter", - 10000, - -1, - Constants.MAX_SAFE_SMALL_INTEGER, - ), - ), + smoothScrolling: register(new EditorBooleanOption( + EditorOption.smoothScrolling, 'smoothScrolling', false, + { description: nls.localize('smoothScrolling', "Controls whether the editor will scroll using an animation.") } + )), + stopRenderingLineAfter: register(new EditorIntOption( + EditorOption.stopRenderingLineAfter, 'stopRenderingLineAfter', + 10000, -1, Constants.MAX_SAFE_SMALL_INTEGER, + )), suggest: register(new EditorSuggest()), inlineSuggest: register(new InlineEditorSuggest()), - inlineCompletionsAccessibilityVerbose: register( - new EditorBooleanOption( - EditorOption.inlineCompletionsAccessibilityVerbose, - "inlineCompletionsAccessibilityVerbose", - false, - { - description: nls.localize( - "inlineCompletionsAccessibilityVerbose", - "Controls whether the accessibility hint should be provided to screen reader users when an inline completion is shown.", - ), - }, - ), - ), - suggestFontSize: register( - new EditorIntOption( - EditorOption.suggestFontSize, - "suggestFontSize", - 0, - 0, - 1000, - { - markdownDescription: nls.localize( - "suggestFontSize", - "Font size for the suggest widget. When set to {0}, the value of {1} is used.", - "`0`", - "`#editor.fontSize#`", - ), - }, - ), - ), - suggestLineHeight: register( - new EditorIntOption( - EditorOption.suggestLineHeight, - "suggestLineHeight", - 0, - 0, - 1000, - { - markdownDescription: nls.localize( - "suggestLineHeight", - "Line height for the suggest widget. When set to {0}, the value of {1} is used. The minimum value is 8.", - "`0`", - "`#editor.lineHeight#`", - ), - }, - ), - ), - suggestOnTriggerCharacters: register( - new EditorBooleanOption( - EditorOption.suggestOnTriggerCharacters, - "suggestOnTriggerCharacters", - true, - { - description: nls.localize( - "suggestOnTriggerCharacters", - "Controls whether suggestions should automatically show up when typing trigger characters.", - ), - }, - ), - ), - suggestSelection: register( - new EditorStringEnumOption( - EditorOption.suggestSelection, - "suggestSelection", - "first" as "first" | "recentlyUsed" | "recentlyUsedByPrefix", - ["first", "recentlyUsed", "recentlyUsedByPrefix"] as const, - { - markdownEnumDescriptions: [ - nls.localize( - "suggestSelection.first", - "Always select the first suggestion.", - ), - nls.localize( - "suggestSelection.recentlyUsed", - "Select recent suggestions unless further typing selects one, e.g. `console.| -> console.log` because `log` has been completed recently.", - ), - nls.localize( - "suggestSelection.recentlyUsedByPrefix", - "Select suggestions based on previous prefixes that have completed those suggestions, e.g. `co -> console` and `con -> const`.", - ), - ], - description: nls.localize( - "suggestSelection", - "Controls how suggestions are pre-selected when showing the suggest list.", - ), - }, - ), - ), - tabCompletion: register( - new EditorStringEnumOption( - EditorOption.tabCompletion, - "tabCompletion", - "off" as "on" | "off" | "onlySnippets", - ["on", "off", "onlySnippets"] as const, - { - enumDescriptions: [ - nls.localize( - "tabCompletion.on", - "Tab complete will insert the best matching suggestion when pressing tab.", - ), - nls.localize( - "tabCompletion.off", - "Disable tab completions.", - ), - nls.localize( - "tabCompletion.onlySnippets", - "Tab complete snippets when their prefix match. Works best when 'quickSuggestions' aren't enabled.", - ), - ], - description: nls.localize( - "tabCompletion", - "Enables tab completions.", - ), - }, - ), - ), - tabIndex: register( - new EditorIntOption( - EditorOption.tabIndex, - "tabIndex", - 0, - -1, - Constants.MAX_SAFE_SMALL_INTEGER, - ), - ), + inlineCompletionsAccessibilityVerbose: register(new EditorBooleanOption(EditorOption.inlineCompletionsAccessibilityVerbose, 'inlineCompletionsAccessibilityVerbose', false, + { description: nls.localize('inlineCompletionsAccessibilityVerbose', "Controls whether the accessibility hint should be provided to screen reader users when an inline completion is shown.") })), + suggestFontSize: register(new EditorIntOption( + EditorOption.suggestFontSize, 'suggestFontSize', + 0, 0, 1000, + { markdownDescription: nls.localize('suggestFontSize', "Font size for the suggest widget. When set to {0}, the value of {1} is used.", '`0`', '`#editor.fontSize#`') } + )), + suggestLineHeight: register(new EditorIntOption( + EditorOption.suggestLineHeight, 'suggestLineHeight', + 0, 0, 1000, + { markdownDescription: nls.localize('suggestLineHeight', "Line height for the suggest widget. When set to {0}, the value of {1} is used. The minimum value is 8.", '`0`', '`#editor.lineHeight#`') } + )), + suggestOnTriggerCharacters: register(new EditorBooleanOption( + EditorOption.suggestOnTriggerCharacters, 'suggestOnTriggerCharacters', true, + { description: nls.localize('suggestOnTriggerCharacters', "Controls whether suggestions should automatically show up when typing trigger characters.") } + )), + suggestSelection: register(new EditorStringEnumOption( + EditorOption.suggestSelection, 'suggestSelection', + 'first' as 'first' | 'recentlyUsed' | 'recentlyUsedByPrefix', + ['first', 'recentlyUsed', 'recentlyUsedByPrefix'] as const, + { + markdownEnumDescriptions: [ + nls.localize('suggestSelection.first', "Always select the first suggestion."), + nls.localize('suggestSelection.recentlyUsed', "Select recent suggestions unless further typing selects one, e.g. `console.| -> console.log` because `log` has been completed recently."), + nls.localize('suggestSelection.recentlyUsedByPrefix', "Select suggestions based on previous prefixes that have completed those suggestions, e.g. `co -> console` and `con -> const`."), + ], + description: nls.localize('suggestSelection', "Controls how suggestions are pre-selected when showing the suggest list.") + } + )), + tabCompletion: register(new EditorStringEnumOption( + EditorOption.tabCompletion, 'tabCompletion', + 'off' as 'on' | 'off' | 'onlySnippets', + ['on', 'off', 'onlySnippets'] as const, + { + enumDescriptions: [ + nls.localize('tabCompletion.on', "Tab complete will insert the best matching suggestion when pressing tab."), + nls.localize('tabCompletion.off', "Disable tab completions."), + nls.localize('tabCompletion.onlySnippets', "Tab complete snippets when their prefix match. Works best when 'quickSuggestions' aren't enabled."), + ], + description: nls.localize('tabCompletion', "Enables tab completions.") + } + )), + tabIndex: register(new EditorIntOption( + EditorOption.tabIndex, 'tabIndex', + 0, -1, Constants.MAX_SAFE_SMALL_INTEGER + )), unicodeHighlight: register(new UnicodeHighlight()), - unusualLineTerminators: register( - new EditorStringEnumOption( - EditorOption.unusualLineTerminators, - "unusualLineTerminators", - "prompt" as "auto" | "off" | "prompt", - ["auto", "off", "prompt"] as const, - { - enumDescriptions: [ - nls.localize( - "unusualLineTerminators.auto", - "Unusual line terminators are automatically removed.", - ), - nls.localize( - "unusualLineTerminators.off", - "Unusual line terminators are ignored.", - ), - nls.localize( - "unusualLineTerminators.prompt", - "Unusual line terminators prompt to be removed.", - ), - ], - description: nls.localize( - "unusualLineTerminators", - "Remove unusual line terminators that might cause problems.", - ), - }, - ), - ), - useShadowDOM: register( - new EditorBooleanOption( - EditorOption.useShadowDOM, - "useShadowDOM", - true, - ), - ), - useTabStops: register( - new EditorBooleanOption(EditorOption.useTabStops, "useTabStops", true, { - description: nls.localize( - "useTabStops", - "Spaces and tabs are inserted and deleted in alignment with tab stops.", - ), - }), - ), - wordBreak: register( - new EditorStringEnumOption( - EditorOption.wordBreak, - "wordBreak", - "normal" as "normal" | "keepAll", - ["normal", "keepAll"] as const, - { - markdownEnumDescriptions: [ - nls.localize( - "wordBreak.normal", - "Use the default line break rule.", - ), - nls.localize( - "wordBreak.keepAll", - "Word breaks should not be used for Chinese/Japanese/Korean (CJK) text. Non-CJK text behavior is the same as for normal.", - ), - ], - description: nls.localize( - "wordBreak", - "Controls the word break rules used for Chinese/Japanese/Korean (CJK) text.", - ), - }, - ), - ), + unusualLineTerminators: register(new EditorStringEnumOption( + EditorOption.unusualLineTerminators, 'unusualLineTerminators', + 'prompt' as 'auto' | 'off' | 'prompt', + ['auto', 'off', 'prompt'] as const, + { + enumDescriptions: [ + nls.localize('unusualLineTerminators.auto', "Unusual line terminators are automatically removed."), + nls.localize('unusualLineTerminators.off', "Unusual line terminators are ignored."), + nls.localize('unusualLineTerminators.prompt', "Unusual line terminators prompt to be removed."), + ], + description: nls.localize('unusualLineTerminators', "Remove unusual line terminators that might cause problems.") + } + )), + useShadowDOM: register(new EditorBooleanOption( + EditorOption.useShadowDOM, 'useShadowDOM', true + )), + useTabStops: register(new EditorBooleanOption( + EditorOption.useTabStops, 'useTabStops', true, + { description: nls.localize('useTabStops', "Spaces and tabs are inserted and deleted in alignment with tab stops.") } + )), + wordBreak: register(new EditorStringEnumOption( + EditorOption.wordBreak, 'wordBreak', + 'normal' as 'normal' | 'keepAll', + ['normal', 'keepAll'] as const, + { + markdownEnumDescriptions: [ + nls.localize('wordBreak.normal', "Use the default line break rule."), + nls.localize('wordBreak.keepAll', "Word breaks should not be used for Chinese/Japanese/Korean (CJK) text. Non-CJK text behavior is the same as for normal."), + ], + description: nls.localize('wordBreak', "Controls the word break rules used for Chinese/Japanese/Korean (CJK) text.") + } + )), wordSegmenterLocales: register(new WordSegmenterLocales()), - wordSeparators: register( - new EditorStringOption( - EditorOption.wordSeparators, - "wordSeparators", - USUAL_WORD_SEPARATORS, - { - description: nls.localize( - "wordSeparators", - "Characters that will be used as word separators when doing word related navigations or operations.", - ), - }, - ), - ), - wordWrap: register( - new EditorStringEnumOption( - EditorOption.wordWrap, - "wordWrap", - "off" as "off" | "on" | "wordWrapColumn" | "bounded", - ["off", "on", "wordWrapColumn", "bounded"] as const, - { - markdownEnumDescriptions: [ - nls.localize("wordWrap.off", "Lines will never wrap."), - nls.localize( - "wordWrap.on", - "Lines will wrap at the viewport width.", - ), - nls.localize( - { - key: "wordWrap.wordWrapColumn", - comment: [ - "- `editor.wordWrapColumn` refers to a different setting and should not be localized.", - ], - }, - "Lines will wrap at `#editor.wordWrapColumn#`.", - ), - nls.localize( - { - key: "wordWrap.bounded", - comment: [ - "- viewport means the edge of the visible window size.", - "- `editor.wordWrapColumn` refers to a different setting and should not be localized.", - ], - }, - "Lines will wrap at the minimum of viewport and `#editor.wordWrapColumn#`.", - ), - ], - description: nls.localize( - { - key: "wordWrap", - comment: [ - "- 'off', 'on', 'wordWrapColumn' and 'bounded' refer to values the setting can take and should not be localized.", - "- `editor.wordWrapColumn` refers to a different setting and should not be localized.", - ], - }, - "Controls how lines should wrap.", - ), - }, - ), - ), - wordWrapBreakAfterCharacters: register( - new EditorStringOption( - EditorOption.wordWrapBreakAfterCharacters, - "wordWrapBreakAfterCharacters", - // allow-any-unicode-next-line - " \t})]?|/&.,;¢°′″‰℃、。。、¢,.:;?!%・・ゝゞヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻ァィゥェォャュョッー”〉》」』】〕)]}」", - ), - ), - wordWrapBreakBeforeCharacters: register( - new EditorStringOption( - EditorOption.wordWrapBreakBeforeCharacters, - "wordWrapBreakBeforeCharacters", - // allow-any-unicode-next-line - "([{‘“〈《「『【〔([{「£¥$£¥++", - ), - ), - wordWrapColumn: register( - new EditorIntOption( - EditorOption.wordWrapColumn, - "wordWrapColumn", - 80, - 1, - Constants.MAX_SAFE_SMALL_INTEGER, - { - markdownDescription: nls.localize( - { - key: "wordWrapColumn", - comment: [ - "- `editor.wordWrap` refers to a different setting and should not be localized.", - "- 'wordWrapColumn' and 'bounded' refer to values the different setting can take and should not be localized.", - ], - }, - "Controls the wrapping column of the editor when `#editor.wordWrap#` is `wordWrapColumn` or `bounded`.", - ), - }, - ), - ), - wordWrapOverride1: register( - new EditorStringEnumOption( - EditorOption.wordWrapOverride1, - "wordWrapOverride1", - "inherit" as "off" | "on" | "inherit", - ["off", "on", "inherit"] as const, - ), - ), - wordWrapOverride2: register( - new EditorStringEnumOption( - EditorOption.wordWrapOverride2, - "wordWrapOverride2", - "inherit" as "off" | "on" | "inherit", - ["off", "on", "inherit"] as const, - ), - ), + wordSeparators: register(new EditorStringOption( + EditorOption.wordSeparators, 'wordSeparators', USUAL_WORD_SEPARATORS, + { description: nls.localize('wordSeparators', "Characters that will be used as word separators when doing word related navigations or operations.") } + )), + wordWrap: register(new EditorStringEnumOption( + EditorOption.wordWrap, 'wordWrap', + 'off' as 'off' | 'on' | 'wordWrapColumn' | 'bounded', + ['off', 'on', 'wordWrapColumn', 'bounded'] as const, + { + markdownEnumDescriptions: [ + nls.localize('wordWrap.off', "Lines will never wrap."), + nls.localize('wordWrap.on', "Lines will wrap at the viewport width."), + nls.localize({ + key: 'wordWrap.wordWrapColumn', + comment: [ + '- `editor.wordWrapColumn` refers to a different setting and should not be localized.' + ] + }, "Lines will wrap at `#editor.wordWrapColumn#`."), + nls.localize({ + key: 'wordWrap.bounded', + comment: [ + '- viewport means the edge of the visible window size.', + '- `editor.wordWrapColumn` refers to a different setting and should not be localized.' + ] + }, "Lines will wrap at the minimum of viewport and `#editor.wordWrapColumn#`."), + ], + description: nls.localize({ + key: 'wordWrap', + comment: [ + '- \'off\', \'on\', \'wordWrapColumn\' and \'bounded\' refer to values the setting can take and should not be localized.', + '- `editor.wordWrapColumn` refers to a different setting and should not be localized.' + ] + }, "Controls how lines should wrap.") + } + )), + wordWrapBreakAfterCharacters: register(new EditorStringOption( + EditorOption.wordWrapBreakAfterCharacters, 'wordWrapBreakAfterCharacters', + // allow-any-unicode-next-line + ' \t})]?|/&.,;¢°′″‰℃、。。、¢,.:;?!%・・ゝゞヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻ァィゥェォャュョッー”〉》」』】〕)]}」', + )), + wordWrapBreakBeforeCharacters: register(new EditorStringOption( + EditorOption.wordWrapBreakBeforeCharacters, 'wordWrapBreakBeforeCharacters', + // allow-any-unicode-next-line + '([{‘“〈《「『【〔([{「£¥$£¥++' + )), + wordWrapColumn: register(new EditorIntOption( + EditorOption.wordWrapColumn, 'wordWrapColumn', + 80, 1, Constants.MAX_SAFE_SMALL_INTEGER, + { + markdownDescription: nls.localize({ + key: 'wordWrapColumn', + comment: [ + '- `editor.wordWrap` refers to a different setting and should not be localized.', + '- \'wordWrapColumn\' and \'bounded\' refer to values the different setting can take and should not be localized.' + ] + }, "Controls the wrapping column of the editor when `#editor.wordWrap#` is `wordWrapColumn` or `bounded`.") + } + )), + wordWrapOverride1: register(new EditorStringEnumOption( + EditorOption.wordWrapOverride1, 'wordWrapOverride1', + 'inherit' as 'off' | 'on' | 'inherit', + ['off', 'on', 'inherit'] as const + )), + wordWrapOverride2: register(new EditorStringEnumOption( + EditorOption.wordWrapOverride2, 'wordWrapOverride2', + 'inherit' as 'off' | 'on' | 'inherit', + ['off', 'on', 'inherit'] as const + )), // Leave these at the end (because they have dependencies!) editorClassName: register(new EditorClassName()), - defaultColorDecorators: register( - new EditorBooleanOption( - EditorOption.defaultColorDecorators, - "defaultColorDecorators", - true, - { - markdownDescription: nls.localize( - "defaultColorDecorators", - "Controls whether inline color decorations should be shown using the default document color provider", - ), - }, - ), - ), + defaultColorDecorators: register(new EditorBooleanOption( + EditorOption.defaultColorDecorators, 'defaultColorDecorators', true, + { markdownDescription: nls.localize('defaultColorDecorators', "Controls whether inline color decorations should be shown using the default document color provider") } + )), pixelRatio: register(new EditorPixelRatio()), - tabFocusMode: register( - new EditorBooleanOption( - EditorOption.tabFocusMode, - "tabFocusMode", - false, - { - markdownDescription: nls.localize( - "tabFocusMode", - "Controls whether the editor receives tabs or defers them to the workbench for navigation.", - ), - }, - ), - ), + tabFocusMode: register(new EditorBooleanOption(EditorOption.tabFocusMode, 'tabFocusMode', false, + { markdownDescription: nls.localize('tabFocusMode', "Controls whether the editor receives tabs or defers them to the workbench for navigation.") } + )), layoutInfo: register(new EditorLayoutInfoComputer()), wrappingInfo: register(new EditorWrappingInfoComputer()), wrappingIndent: register(new WrappingIndentOption()), - wrappingStrategy: register(new WrappingStrategy()), + wrappingStrategy: register(new WrappingStrategy()) }; type EditorOptionsType = typeof EditorOptions; -type FindEditorOptionsKeyById = { - [K in keyof EditorOptionsType]: EditorOptionsType[K]["id"] extends T - ? K - : never; -}[keyof EditorOptionsType]; -type ComputedEditorOptionValue> = - T extends IEditorOption ? R : never; -export type FindComputedEditorOptionValueById = - NonNullable< - ComputedEditorOptionValue< - EditorOptionsType[FindEditorOptionsKeyById] - > - >; +type FindEditorOptionsKeyById = { [K in keyof EditorOptionsType]: EditorOptionsType[K]['id'] extends T ? K : never }[keyof EditorOptionsType]; +type ComputedEditorOptionValue> = T extends IEditorOption ? R : never; +export type FindComputedEditorOptionValueById = NonNullable]>>; diff --git a/Source/vs/editor/common/languages.ts b/Source/vs/editor/common/languages.ts index 9a5ad7cf964db..c03a26de603c5 100644 --- a/Source/vs/editor/common/languages.ts +++ b/Source/vs/editor/common/languages.ts @@ -1958,7 +1958,14 @@ export interface IWorkspaceTextEdit { } export interface WorkspaceEdit { - edits: Array; + edits: Array; +} + +export interface ICustomEdit { + readonly resource: URI; + readonly metadata?: WorkspaceEditMetadata; + undo(): Promise | void; + redo(): Promise | void; } export interface Rejection { diff --git a/Source/vs/editor/common/services/markerDecorations.ts b/Source/vs/editor/common/services/markerDecorations.ts index e776371e2bd49..8ec7d94676af4 100644 --- a/Source/vs/editor/common/services/markerDecorations.ts +++ b/Source/vs/editor/common/services/markerDecorations.ts @@ -9,8 +9,16 @@ import { IMarker } from "../../../platform/markers/common/markers.js"; import { Range } from "../core/range.js"; import { IModelDecoration, ITextModel } from "../model.js"; -export const IMarkerDecorationsService = - createDecorator("markerDecorationsService"); +import { ITextModel, IModelDecoration } from '../model.js'; +import { createDecorator } from '../../../platform/instantiation/common/instantiation.js'; +import { IMarker } from '../../../platform/markers/common/markers.js'; +import { Event } from '../../../base/common/event.js'; +import { Range } from '../core/range.js'; +import { URI } from '../../../base/common/uri.js'; +import { IDisposable } from '../../../base/common/lifecycle.js'; + +export const IMarkerDecorationsService = createDecorator('markerDecorationsService'); + export interface IMarkerDecorationsService { readonly _serviceBrand: undefined; onDidChangeMarker: Event; @@ -18,4 +26,6 @@ export interface IMarkerDecorationsService { getMarker(uri: URI, decoration: IModelDecoration): IMarker | null; getLiveMarkers(uri: URI): [Range, IMarker][]; + + addMarkerSuppression(uri: URI, range: Range): IDisposable; } diff --git a/Source/vs/editor/common/services/markerDecorationsService.ts b/Source/vs/editor/common/services/markerDecorationsService.ts index cfd54c7a24c07..b58bceee09c2f 100644 --- a/Source/vs/editor/common/services/markerDecorationsService.ts +++ b/Source/vs/editor/common/services/markerDecorationsService.ts @@ -41,16 +41,33 @@ import { ClassName } from "../model/intervalTree.js"; import { IMarkerDecorationsService } from "./markerDecorations.js"; import { IModelService } from "./model.js"; -export class MarkerDecorationsService - extends Disposable - implements IMarkerDecorationsService -{ +import { IMarkerService, IMarker, MarkerSeverity, MarkerTag } from '../../../platform/markers/common/markers.js'; +import { Disposable, IDisposable, toDisposable } from '../../../base/common/lifecycle.js'; +import { URI } from '../../../base/common/uri.js'; +import { IModelDeltaDecoration, ITextModel, IModelDecorationOptions, TrackedRangeStickiness, OverviewRulerLane, IModelDecoration, MinimapPosition, IModelDecorationMinimapOptions } from '../model.js'; +import { ClassName } from '../model/intervalTree.js'; +import { themeColorFromId } from '../../../platform/theme/common/themeService.js'; +import { ThemeColor } from '../../../base/common/themables.js'; +import { overviewRulerWarning, overviewRulerInfo, overviewRulerError } from '../core/editorColorRegistry.js'; +import { IModelService } from './model.js'; +import { Range } from '../core/range.js'; +import { IMarkerDecorationsService } from './markerDecorations.js'; +import { Schemas } from '../../../base/common/network.js'; +import { Emitter, Event } from '../../../base/common/event.js'; +import { minimapInfo, minimapWarning, minimapError } from '../../../platform/theme/common/colorRegistry.js'; +import { BidirectionalMap, ResourceMap } from '../../../base/common/map.js'; +import { diffSets } from '../../../base/common/collections.js'; +import { Iterable } from '../../../base/common/iterator.js'; + +export class MarkerDecorationsService extends Disposable implements IMarkerDecorationsService { + declare readonly _serviceBrand: undefined; - private readonly _onDidChangeMarker = this._register( - new Emitter(), - ); - readonly onDidChangeMarker: Event = - this._onDidChangeMarker.event; + + private readonly _onDidChangeMarker = this._register(new Emitter()); + readonly onDidChangeMarker: Event = this._onDidChangeMarker.event; + + private readonly _suppressedRanges = new ResourceMap>(); + private readonly _markerDecorations = new ResourceMap(); constructor( @@ -84,6 +101,29 @@ export class MarkerDecorationsService return markerDecorations ? markerDecorations.getMarkers() : []; } + + addMarkerSuppression(uri: URI, range: Range): IDisposable { + + let suppressedRanges = this._suppressedRanges.get(uri); + if (!suppressedRanges) { + suppressedRanges = new Set(); + this._suppressedRanges.set(uri, suppressedRanges); + } + suppressedRanges.add(range); + this._handleMarkerChange([uri]); + + return toDisposable(() => { + const suppressedRanges = this._suppressedRanges.get(uri); + if (suppressedRanges) { + suppressedRanges.delete(range); + if (suppressedRanges.size === 0) { + this._suppressedRanges.delete(uri); + } + this._handleMarkerChange([uri]); + } + }); + } + private _handleMarkerChange(changedResources: readonly URI[]): void { changedResources.forEach((resource) => { const markerDecorations = this._markerDecorations.get(resource); @@ -121,10 +161,15 @@ export class MarkerDecorationsService } private _updateDecorations(markerDecorations: MarkerDecorations): void { // Limit to the first 500 errors/warnings - const markers = this._markerService.read({ - resource: markerDecorations.model.uri, - take: 500, - }); + let markers = this._markerService.read({ resource: markerDecorations.model.uri, take: 500 }); + + // filter markers from suppressed ranges + const suppressedRanges = this._suppressedRanges.get(markerDecorations.model.uri); + if (suppressedRanges) { + markers = markers.filter(marker => { + return !Iterable.some(suppressedRanges, candidate => Range.areIntersectingOrTouching(candidate, marker)); + }); + } if (markerDecorations.update(markers)) { this._onDidChangeMarker.fire(markerDecorations.model); diff --git a/Source/vs/editor/contrib/gpu/browser/gpuActions.ts b/Source/vs/editor/contrib/gpu/browser/gpuActions.ts index 029569bf404f2..58adf582bdd70 100644 --- a/Source/vs/editor/contrib/gpu/browser/gpuActions.ts +++ b/Source/vs/editor/contrib/gpu/browser/gpuActions.ts @@ -3,34 +3,29 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { VSBuffer } from "../../../../base/common/buffer.js"; -import { URI } from "../../../../base/common/uri.js"; -import { localize, localize2 } from "../../../../nls.js"; -import { IConfigurationService } from "../../../../platform/configuration/common/configuration.js"; -import { ContextKeyExpr } from "../../../../platform/contextkey/common/contextkey.js"; -import { IFileService } from "../../../../platform/files/common/files.js"; -import { IInstantiationService } from "../../../../platform/instantiation/common/instantiation.js"; -import { ILogService } from "../../../../platform/log/common/log.js"; -import { IQuickInputService } from "../../../../platform/quickinput/common/quickInput.js"; -import { IWorkspaceContextService } from "../../../../platform/workspace/common/workspace.js"; -import type { ICodeEditor } from "../../../browser/editorBrowser.js"; -import { - EditorAction, - registerEditorAction, - type ServicesAccessor, -} from "../../../browser/editorExtensions.js"; -import { ensureNonNullable } from "../../../browser/gpu/gpuUtils.js"; -import { GlyphRasterizer } from "../../../browser/gpu/raster/glyphRasterizer.js"; -import { ViewGpuContext } from "../../../browser/gpu/viewGpuContext.js"; +import { getActiveWindow } from '../../../../base/browser/dom.js'; +import { VSBuffer } from '../../../../base/common/buffer.js'; +import { URI } from '../../../../base/common/uri.js'; +import { localize, localize2 } from '../../../../nls.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; +import { IFileService } from '../../../../platform/files/common/files.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; +import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js'; +import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; +import type { ICodeEditor } from '../../../browser/editorBrowser.js'; +import { EditorAction, registerEditorAction, type ServicesAccessor } from '../../../browser/editorExtensions.js'; +import { ensureNonNullable } from '../../../browser/gpu/gpuUtils.js'; +import { GlyphRasterizer } from '../../../browser/gpu/raster/glyphRasterizer.js'; +import { ViewGpuContext } from '../../../browser/gpu/viewGpuContext.js'; class DebugEditorGpuRendererAction extends EditorAction { + constructor() { super({ - id: "editor.action.debugEditorGpuRenderer", - label: localize2( - "gpuDebug.label", - "Developer: Debug Editor GPU Renderer", - ), + id: 'editor.action.debugEditorGpuRenderer', + label: localize2('gpuDebug.label', "Developer: Debug Editor GPU Renderer"), // TODO: Why doesn't `ContextKeyExpr.equals('config:editor.experimentalGpuAcceleration', 'on')` work? precondition: ContextKeyExpr.true(), }); @@ -39,181 +34,109 @@ class DebugEditorGpuRendererAction extends EditorAction { async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { const instantiationService = accessor.get(IInstantiationService); const quickInputService = accessor.get(IQuickInputService); - const choice = await quickInputService.pick( - [ - { - label: localize( - "logTextureAtlasStats.label", - "Log Texture Atlas Stats", - ), - id: "logTextureAtlasStats", - }, - { - label: localize( - "saveTextureAtlas.label", - "Save Texture Atlas", - ), - id: "saveTextureAtlas", - }, - { - label: localize("drawGlyph.label", "Draw Glyph"), - id: "drawGlyph", - }, - ], - { canPickMany: false }, - ); + const choice = await quickInputService.pick([ + { + label: localize('logTextureAtlasStats.label', "Log Texture Atlas Stats"), + id: 'logTextureAtlasStats', + }, + { + label: localize('saveTextureAtlas.label', "Save Texture Atlas"), + id: 'saveTextureAtlas', + }, + { + label: localize('drawGlyph.label', "Draw Glyph"), + id: 'drawGlyph', + }, + ], { canPickMany: false }); if (!choice) { return; } switch (choice.id) { - case "logTextureAtlasStats": - instantiationService.invokeFunction((accessor) => { + case 'logTextureAtlasStats': + instantiationService.invokeFunction(accessor => { const logService = accessor.get(ILogService); const atlas = ViewGpuContext.atlas; if (!ViewGpuContext.atlas) { - logService.error("No texture atlas found"); + logService.error('No texture atlas found'); return; } const stats = atlas.getStats(); - logService.info( - ["Texture atlas stats", ...stats].join("\n\n"), - ); + logService.info(['Texture atlas stats', ...stats].join('\n\n')); }); break; - case "saveTextureAtlas": - instantiationService.invokeFunction(async (accessor) => { - const workspaceContextService = accessor.get( - IWorkspaceContextService, - ); + case 'saveTextureAtlas': + instantiationService.invokeFunction(async accessor => { + const workspaceContextService = accessor.get(IWorkspaceContextService); const fileService = accessor.get(IFileService); - const folders = - workspaceContextService.getWorkspace().folders; + const folders = workspaceContextService.getWorkspace().folders; if (folders.length > 0) { const atlas = ViewGpuContext.atlas; const promises = []; - for (const [ - layerIndex, - page, - ] of atlas.pages.entries()) { - promises.push( - ...[ - fileService.writeFile( - URI.joinPath( - folders[0].uri, - `textureAtlasPage${layerIndex}_actual.png`, - ), - VSBuffer.wrap( - new Uint8Array( - await ( - await page.source.convertToBlob() - ).arrayBuffer(), - ), - ), - ), - fileService.writeFile( - URI.joinPath( - folders[0].uri, - `textureAtlasPage${layerIndex}_usage.png`, - ), - VSBuffer.wrap( - new Uint8Array( - await ( - await page.getUsagePreview() - ).arrayBuffer(), - ), - ), - ), - ], - ); + for (const [layerIndex, page] of atlas.pages.entries()) { + promises.push(...[ + fileService.writeFile( + URI.joinPath(folders[0].uri, `textureAtlasPage${layerIndex}_actual.png`), + VSBuffer.wrap(new Uint8Array(await (await page.source.convertToBlob()).arrayBuffer())) + ), + fileService.writeFile( + URI.joinPath(folders[0].uri, `textureAtlasPage${layerIndex}_usage.png`), + VSBuffer.wrap(new Uint8Array(await (await page.getUsagePreview()).arrayBuffer())) + ), + ]); } await Promise.all(promises); } }); break; - case "drawGlyph": - instantiationService.invokeFunction(async (accessor) => { - const configurationService = accessor.get( - IConfigurationService, - ); + case 'drawGlyph': + instantiationService.invokeFunction(async accessor => { + const configurationService = accessor.get(IConfigurationService); const fileService = accessor.get(IFileService); const quickInputService = accessor.get(IQuickInputService); - const workspaceContextService = accessor.get( - IWorkspaceContextService, - ); + const workspaceContextService = accessor.get(IWorkspaceContextService); - const folders = - workspaceContextService.getWorkspace().folders; + const folders = workspaceContextService.getWorkspace().folders; if (folders.length === 0) { return; } const atlas = ViewGpuContext.atlas; - const fontFamily = - configurationService.getValue( - "editor.fontFamily", - ); - const fontSize = - configurationService.getValue( - "editor.fontSize", - ); - const rasterizer = new GlyphRasterizer( - fontSize, - fontFamily, - ); + const fontFamily = configurationService.getValue('editor.fontFamily'); + const fontSize = configurationService.getValue('editor.fontSize'); + const rasterizer = new GlyphRasterizer(fontSize, fontFamily, getActiveWindow().devicePixelRatio); let chars = await quickInputService.input({ - prompt: "Enter a character to draw (prefix with 0x for code point))", + prompt: 'Enter a character to draw (prefix with 0x for code point))' }); if (!chars) { return; } - const codePoint = chars.match(/0x(?[0-9a-f]+)/i) - ?.groups?.codePoint; + const codePoint = chars.match(/0x(?[0-9a-f]+)/i)?.groups?.codePoint; if (codePoint !== undefined) { chars = String.fromCodePoint(parseInt(codePoint, 16)); } const tokenMetadata = 0; const charMetadata = 0; - const rasterizedGlyph = atlas.getGlyph( - rasterizer, - chars, - tokenMetadata, - charMetadata, - ); + const rasterizedGlyph = atlas.getGlyph(rasterizer, chars, tokenMetadata, charMetadata); if (!rasterizedGlyph) { return; } - const imageData = atlas.pages[ - rasterizedGlyph.pageIndex - ].source - .getContext("2d") - ?.getImageData( - rasterizedGlyph.x, - rasterizedGlyph.y, - rasterizedGlyph.w, - rasterizedGlyph.h, - ); + const imageData = atlas.pages[rasterizedGlyph.pageIndex].source.getContext('2d')?.getImageData( + rasterizedGlyph.x, + rasterizedGlyph.y, + rasterizedGlyph.w, + rasterizedGlyph.h + ); if (!imageData) { return; } - const canvas = new OffscreenCanvas( - imageData.width, - imageData.height, - ); - const ctx = ensureNonNullable(canvas.getContext("2d")); + const canvas = new OffscreenCanvas(imageData.width, imageData.height); + const ctx = ensureNonNullable(canvas.getContext('2d')); ctx.putImageData(imageData, 0, 0); - const blob = await canvas.convertToBlob({ - type: "image/png", - }); - const resource = URI.joinPath( - folders[0].uri, - `glyph_${chars}_${tokenMetadata}_${fontSize}px_${fontFamily.replaceAll(/[,\\\/\.'\s]/g, "_")}.png`, - ); - await fileService.writeFile( - resource, - VSBuffer.wrap(new Uint8Array(await blob.arrayBuffer())), - ); + const blob = await canvas.convertToBlob({ type: 'image/png' }); + const resource = URI.joinPath(folders[0].uri, `glyph_${chars}_${tokenMetadata}_${fontSize}px_${fontFamily.replaceAll(/[,\\\/\.'\s]/g, '_')}.png`); + await fileService.writeFile(resource, VSBuffer.wrap(new Uint8Array(await blob.arrayBuffer()))); }); break; } diff --git a/Source/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys.ts b/Source/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys.ts index f28d1e76b0daf..faf6677079345 100644 --- a/Source/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys.ts +++ b/Source/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys.ts @@ -3,122 +3,31 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from "../../../../../base/common/lifecycle.js"; -import { autorun, IObservable } from "../../../../../base/common/observable.js"; -import { firstNonWhitespaceIndex } from "../../../../../base/common/strings.js"; -import { localize } from "../../../../../nls.js"; -import { - IContextKeyService, - RawContextKey, -} from "../../../../../platform/contextkey/common/contextkey.js"; -import { bindContextKey } from "../../../../../platform/observable/common/platformObservableUtils.js"; -import { CursorColumns } from "../../../../common/core/cursorColumns.js"; -import { InlineCompletionsModel } from "../model/inlineCompletionsModel.js"; +import { IObservable, autorun } from '../../../../../base/common/observable.js'; +import { InlineCompletionsModel } from '../model/inlineCompletionsModel.js'; +import { RawContextKey, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; +import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { localize } from '../../../../../nls.js'; +import { bindContextKey } from '../../../../../platform/observable/common/platformObservableUtils.js'; export class InlineCompletionContextKeys extends Disposable { - public static readonly inlineSuggestionVisible = new RawContextKey( - "inlineSuggestionVisible", - false, - localize( - "inlineSuggestionVisible", - "Whether an inline suggestion is visible", - ), - ); - public static readonly inlineSuggestionHasIndentation = - new RawContextKey( - "inlineSuggestionHasIndentation", - false, - localize( - "inlineSuggestionHasIndentation", - "Whether the inline suggestion starts with whitespace", - ), - ); - public static readonly inlineSuggestionHasIndentationLessThanTabSize = - new RawContextKey( - "inlineSuggestionHasIndentationLessThanTabSize", - true, - localize( - "inlineSuggestionHasIndentationLessThanTabSize", - "Whether the inline suggestion starts with whitespace that is less than what would be inserted by tab", - ), - ); - public static readonly suppressSuggestions = new RawContextKey< - boolean | undefined - >( - "inlineSuggestionSuppressSuggestions", - undefined, - localize( - "suppressSuggestions", - "Whether suggestions should be suppressed for the current suggestion", - ), - ); - public static readonly cursorInIndentation = new RawContextKey< - boolean | undefined - >( - "cursorInIndentation", - false, - localize("cursorInIndentation", "Whether the cursor is in indentation"), - ); - public static readonly hasSelection = new RawContextKey< - boolean | undefined - >( - "editor.hasSelection", - false, - localize("editor.hasSelection", "Whether the editor has a selection"), - ); - public static readonly cursorAtInlineEdit = new RawContextKey< - boolean | undefined - >( - "cursorAtInlineEdit", - false, - localize( - "cursorAtInlineEdit", - "Whether the cursor is at an inline edit", - ), - ); - public static readonly inlineEditVisible = new RawContextKey( - "inlineEditIsVisible", - false, - localize("inlineEditVisible", "Whether an inline edit is visible"), - ); - public static readonly tabShouldJumpToInlineEdit = new RawContextKey< - boolean | undefined - >( - "tabShouldJumpToInlineEdit", - false, - localize( - "tabShouldJumpToInlineEdit", - "Whether tab should jump to an inline edit.", - ), - ); - public static readonly tabShouldAcceptInlineEdit = new RawContextKey< - boolean | undefined - >( - "tabShouldAcceptInlineEdit", - false, - localize( - "tabShouldAcceptInlineEdit", - "Whether tab should accept the inline edit.", - ), - ); + public static readonly inlineSuggestionVisible = new RawContextKey('inlineSuggestionVisible', false, localize('inlineSuggestionVisible', "Whether an inline suggestion is visible")); + public static readonly inlineSuggestionHasIndentation = new RawContextKey('inlineSuggestionHasIndentation', false, localize('inlineSuggestionHasIndentation', "Whether the inline suggestion starts with whitespace")); + public static readonly inlineSuggestionHasIndentationLessThanTabSize = new RawContextKey('inlineSuggestionHasIndentationLessThanTabSize', true, localize('inlineSuggestionHasIndentationLessThanTabSize', "Whether the inline suggestion starts with whitespace that is less than what would be inserted by tab")); + public static readonly suppressSuggestions = new RawContextKey('inlineSuggestionSuppressSuggestions', undefined, localize('suppressSuggestions', "Whether suggestions should be suppressed for the current suggestion")); - public readonly inlineCompletionVisible = - InlineCompletionContextKeys.inlineSuggestionVisible.bindTo( - this.contextKeyService, - ); - public readonly inlineCompletionSuggestsIndentation = - InlineCompletionContextKeys.inlineSuggestionHasIndentation.bindTo( - this.contextKeyService, - ); - public readonly inlineCompletionSuggestsIndentationLessThanTabSize = - InlineCompletionContextKeys.inlineSuggestionHasIndentationLessThanTabSize.bindTo( - this.contextKeyService, - ); - public readonly suppressSuggestions = - InlineCompletionContextKeys.suppressSuggestions.bindTo( - this.contextKeyService, - ); + public static readonly cursorInIndentation = new RawContextKey('cursorInIndentation', false, localize('cursorInIndentation', "Whether the cursor is in indentation")); + public static readonly hasSelection = new RawContextKey('editor.hasSelection', false, localize('editor.hasSelection', "Whether the editor has a selection")); + public static readonly cursorAtInlineEdit = new RawContextKey('cursorAtInlineEdit', false, localize('cursorAtInlineEdit', "Whether the cursor is at an inline edit")); + public static readonly inlineEditVisible = new RawContextKey('inlineEditIsVisible', false, localize('inlineEditVisible', "Whether an inline edit is visible")); + public static readonly tabShouldJumpToInlineEdit = new RawContextKey('tabShouldJumpToInlineEdit', false, localize('tabShouldJumpToInlineEdit', "Whether tab should jump to an inline edit.")); + public static readonly tabShouldAcceptInlineEdit = new RawContextKey('tabShouldAcceptInlineEdit', false, localize('tabShouldAcceptInlineEdit', "Whether tab should accept the inline edit.")); + + public readonly inlineCompletionVisible = InlineCompletionContextKeys.inlineSuggestionVisible.bindTo(this.contextKeyService); + public readonly inlineCompletionSuggestsIndentation = InlineCompletionContextKeys.inlineSuggestionHasIndentation.bindTo(this.contextKeyService); + public readonly inlineCompletionSuggestsIndentationLessThanTabSize = InlineCompletionContextKeys.inlineSuggestionHasIndentationLessThanTabSize.bindTo(this.contextKeyService); + public readonly suppressSuggestions = InlineCompletionContextKeys.suppressSuggestions.bindTo(this.contextKeyService); constructor( private readonly contextKeyService: IContextKeyService, @@ -126,93 +35,31 @@ export class InlineCompletionContextKeys extends Disposable { ) { super(); - this._register( - bindContextKey( - InlineCompletionContextKeys.inlineEditVisible, - this.contextKeyService, - (reader) => - this.model.read(reader)?.inlineEditState.read(reader) !== - undefined, - ), - ); - - this._register( - autorun((reader) => { - /** @description update context key: inlineCompletionVisible, suppressSuggestions */ - const model = this.model.read(reader); - - const state = model?.inlineCompletionState.read(reader); - - const isInlineCompletionVisible = - !!state?.inlineCompletion && - state?.primaryGhostText !== undefined && - !state?.primaryGhostText.isEmpty(); - this.inlineCompletionVisible.set(isInlineCompletionVisible); - - if (state?.primaryGhostText && state?.inlineCompletion) { - this.suppressSuggestions.set( - state.inlineCompletion.inlineCompletion.source - .inlineCompletions.suppressSuggestions, - ); - } - }), - ); - - this._register( - autorun((reader) => { - /** @description update context key: inlineCompletionSuggestsIndentation, inlineCompletionSuggestsIndentationLessThanTabSize */ - const model = this.model.read(reader); - - let startsWithIndentation = false; - - let startsWithIndentationLessThanTabSize = true; - - const ghostText = model?.primaryGhostText.read(reader); - - if ( - !!model?.selectedSuggestItem && - ghostText && - ghostText.parts.length > 0 - ) { - const { column, lines } = ghostText.parts[0]; - - const firstLine = lines[0]; - - const indentationEndColumn = - model.textModel.getLineIndentColumn( - ghostText.lineNumber, - ); - - const inIndentation = column <= indentationEndColumn; - - if (inIndentation) { - let firstNonWsIdx = firstNonWhitespaceIndex(firstLine); - - if (firstNonWsIdx === -1) { - firstNonWsIdx = firstLine.length - 1; - } - startsWithIndentation = firstNonWsIdx > 0; - - const tabSize = model.textModel.getOptions().tabSize; - - const visibleColumnIndentation = - CursorColumns.visibleColumnFromColumn( - firstLine, - firstNonWsIdx + 1, - tabSize, - ); - startsWithIndentationLessThanTabSize = - visibleColumnIndentation < tabSize; - } - } - - this.inlineCompletionSuggestsIndentation.set( - startsWithIndentation, - ); - this.inlineCompletionSuggestsIndentationLessThanTabSize.set( - startsWithIndentationLessThanTabSize, - ); - }), - ); + this._register(bindContextKey( + InlineCompletionContextKeys.inlineEditVisible, + this.contextKeyService, + reader => this.model.read(reader)?.inlineEditState.read(reader) !== undefined + )); + + this._register(autorun(reader => { + /** @description update context key: inlineCompletionVisible, suppressSuggestions */ + const model = this.model.read(reader); + const state = model?.inlineCompletionState.read(reader); + + const isInlineCompletionVisible = !!state?.inlineCompletion && state?.primaryGhostText !== undefined && !state?.primaryGhostText.isEmpty(); + this.inlineCompletionVisible.set(isInlineCompletionVisible); + + if (state?.primaryGhostText && state?.inlineCompletion) { + this.suppressSuggestions.set(state.inlineCompletion.inlineCompletion.source.inlineCompletions.suppressSuggestions); + } + })); + + this._register(autorun(reader => { + /** @description update context key: inlineCompletionSuggestsIndentation, inlineCompletionSuggestsIndentationLessThanTabSize */ + const model = this.model.read(reader); + const result = model?.getIndentationInfo(reader); + this.inlineCompletionSuggestsIndentation.set(result?.startsWithIndentation ?? false); + this.inlineCompletionSuggestsIndentationLessThanTabSize.set(result?.startsWithIndentationLessThanTabSize ?? true); + })); } } diff --git a/Source/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts b/Source/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts index 0c33dadec84dc..f09dea308b17c 100644 --- a/Source/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts +++ b/Source/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts @@ -3,165 +3,124 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { mapFindFirst } from "../../../../../base/common/arraysFind.js"; -import { itemsEquals } from "../../../../../base/common/equals.js"; -import { - BugIndicatingError, - onUnexpectedError, - onUnexpectedExternalError, -} from "../../../../../base/common/errors.js"; -import { Disposable } from "../../../../../base/common/lifecycle.js"; -import { - autorun, - derived, - derivedHandleChanges, - derivedOpts, - IObservable, - IReader, - ITransaction, - observableSignal, - observableValue, - recomputeInitiallyAndOnChange, - subtransaction, - transaction, -} from "../../../../../base/common/observable.js"; -import { commonPrefixLength } from "../../../../../base/common/strings.js"; -import { isDefined } from "../../../../../base/common/types.js"; -import { ICommandService } from "../../../../../platform/commands/common/commands.js"; -import { IInstantiationService } from "../../../../../platform/instantiation/common/instantiation.js"; -import { ICodeEditor } from "../../../../browser/editorBrowser.js"; -import { observableCodeEditor } from "../../../../browser/observableCodeEditor.js"; -import { EditOperation } from "../../../../common/core/editOperation.js"; -import { LineRange } from "../../../../common/core/lineRange.js"; -import { Position } from "../../../../common/core/position.js"; -import { Range } from "../../../../common/core/range.js"; -import { Selection } from "../../../../common/core/selection.js"; -import { SingleTextEdit } from "../../../../common/core/textEdit.js"; -import { TextLength } from "../../../../common/core/textLength.js"; -import { ScrollType } from "../../../../common/editorCommon.js"; -import { - Command, - InlineCompletion, - InlineCompletionContext, - InlineCompletionTriggerKind, - PartialAcceptTriggerKind, -} from "../../../../common/languages.js"; -import { ILanguageConfigurationService } from "../../../../common/languages/languageConfigurationRegistry.js"; -import { EndOfLinePreference, ITextModel } from "../../../../common/model.js"; -import { IFeatureDebounceInformation } from "../../../../common/services/languageFeatureDebounce.js"; -import { IModelContentChangedEvent } from "../../../../common/textModelEvents.js"; -import { SnippetController2 } from "../../../snippet/browser/snippetController2.js"; -import { - addPositions, - getEndPositionsAfterApplying, - substringPos, - subtractPositions, -} from "../utils.js"; -import { computeGhostText } from "./computeGhostText.js"; -import { - GhostText, - GhostTextOrReplacement, - ghostTextOrReplacementEquals, - ghostTextsOrReplacementsEqual, -} from "./ghostText.js"; -import { - InlineCompletionsSource, - InlineCompletionWithUpdatedRange, -} from "./inlineCompletionsSource.js"; -import { InlineEdit } from "./inlineEdit.js"; -import { InlineCompletionItem } from "./provideInlineCompletions.js"; -import { - singleTextEditAugments, - singleTextRemoveCommonPrefix, -} from "./singleTextEditHelpers.js"; -import { SuggestItemInfo } from "./suggestWidgetAdapter.js"; +import { mapFindFirst } from '../../../../../base/common/arraysFind.js'; +import { itemsEquals } from '../../../../../base/common/equals.js'; +import { BugIndicatingError, onUnexpectedError, onUnexpectedExternalError } from '../../../../../base/common/errors.js'; +import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { IObservable, IReader, ITransaction, autorun, derived, derivedHandleChanges, derivedOpts, observableSignal, observableValue, recomputeInitiallyAndOnChange, subtransaction, transaction } from '../../../../../base/common/observable.js'; +import { commonPrefixLength, firstNonWhitespaceIndex } from '../../../../../base/common/strings.js'; +import { isDefined } from '../../../../../base/common/types.js'; +import { ICommandService } from '../../../../../platform/commands/common/commands.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { ICodeEditor } from '../../../../browser/editorBrowser.js'; +import { observableCodeEditor } from '../../../../browser/observableCodeEditor.js'; +import { CursorColumns } from '../../../../common/core/cursorColumns.js'; +import { EditOperation } from '../../../../common/core/editOperation.js'; +import { LineRange } from '../../../../common/core/lineRange.js'; +import { Position } from '../../../../common/core/position.js'; +import { Range } from '../../../../common/core/range.js'; +import { Selection } from '../../../../common/core/selection.js'; +import { SingleTextEdit } from '../../../../common/core/textEdit.js'; +import { TextLength } from '../../../../common/core/textLength.js'; +import { ScrollType } from '../../../../common/editorCommon.js'; +import { Command, InlineCompletion, InlineCompletionContext, InlineCompletionTriggerKind, PartialAcceptTriggerKind } from '../../../../common/languages.js'; +import { ILanguageConfigurationService } from '../../../../common/languages/languageConfigurationRegistry.js'; +import { EndOfLinePreference, ITextModel } from '../../../../common/model.js'; +import { IFeatureDebounceInformation } from '../../../../common/services/languageFeatureDebounce.js'; +import { IModelContentChangedEvent } from '../../../../common/textModelEvents.js'; +import { SnippetController2 } from '../../../snippet/browser/snippetController2.js'; +import { addPositions, getEndPositionsAfterApplying, substringPos, subtractPositions } from '../utils.js'; +import { computeGhostText } from './computeGhostText.js'; +import { GhostText, GhostTextOrReplacement, ghostTextOrReplacementEquals, ghostTextsOrReplacementsEqual } from './ghostText.js'; +import { InlineCompletionWithUpdatedRange, InlineCompletionsSource } from './inlineCompletionsSource.js'; +import { InlineEdit } from './inlineEdit.js'; +import { InlineCompletionItem } from './provideInlineCompletions.js'; +import { singleTextEditAugments, singleTextRemoveCommonPrefix } from './singleTextEditHelpers.js'; +import { SuggestItemInfo } from './suggestWidgetAdapter.js'; export class InlineCompletionsModel extends Disposable { - private readonly _source = this._register( - this._instantiationService.createInstance( - InlineCompletionsSource, - this.textModel, - this._textModelVersionId, - this._debounceValue, - ), - ); + private readonly _source = this._register(this._instantiationService.createInstance(InlineCompletionsSource, this.textModel, this._textModelVersionId, this._debounceValue)); private readonly _isActive = observableValue(this, false); private readonly _onlyRequestInlineEditsSignal = observableSignal(this); private readonly _forceUpdateExplicitlySignal = observableSignal(this); // We use a semantic id to keep the same inline completion selected even if the provider reorders the completions. - private readonly _selectedInlineCompletionId = observableValue< - string | undefined - >(this, undefined); - private readonly _primaryPosition = derived( - this, - (reader) => this._positions.read(reader)[0] ?? new Position(1, 1), - ); + private readonly _selectedInlineCompletionId = observableValue(this, undefined); + private readonly _primaryPosition = derived(this, reader => this._positions.read(reader)[0] ?? new Position(1, 1)); private _isAcceptingPartially = false; - public get isAcceptingPartially() { - return this._isAcceptingPartially; - } + public get isAcceptingPartially() { return this._isAcceptingPartially; } private readonly _editorObs = observableCodeEditor(this._editor); constructor( public readonly textModel: ITextModel, - public readonly selectedSuggestItem: IObservable< - SuggestItemInfo | undefined - >, - public readonly _textModelVersionId: IObservable< - number | null, - IModelContentChangedEvent | undefined - >, + private readonly _selectedSuggestItem: IObservable, + public readonly _textModelVersionId: IObservable, private readonly _positions: IObservable, private readonly _debounceValue: IFeatureDebounceInformation, private readonly _suggestPreviewEnabled: IObservable, - private readonly _suggestPreviewMode: IObservable< - "prefix" | "subword" | "subwordSmart" - >, - private readonly _inlineSuggestMode: IObservable< - "prefix" | "subword" | "subwordSmart" - >, + private readonly _suggestPreviewMode: IObservable<'prefix' | 'subword' | 'subwordSmart'>, + private readonly _inlineSuggestMode: IObservable<'prefix' | 'subword' | 'subwordSmart'>, private readonly _enabled: IObservable, private readonly _inlineEditsEnabled: IObservable, private readonly _editor: ICodeEditor, - @IInstantiationService - private readonly _instantiationService: IInstantiationService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, @ICommandService private readonly _commandService: ICommandService, - @ILanguageConfigurationService - private readonly _languageConfigurationService: ILanguageConfigurationService, + @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService, ) { super(); - this._register( - recomputeInitiallyAndOnChange(this._fetchInlineCompletionsPromise), - ); + this._register(recomputeInitiallyAndOnChange(this._fetchInlineCompletionsPromise)); let lastItem: InlineCompletionWithUpdatedRange | undefined = undefined; - this._register( - autorun((reader) => { - /** @description call handleItemDidShow */ - const item = this.inlineCompletionState.read(reader); - - const completion = item?.inlineCompletion; - - if (completion?.semanticId !== lastItem?.semanticId) { - lastItem = completion; - - if (completion) { - const i = completion.inlineCompletion; - - const src = i.source; - src.provider.handleItemDidShow?.( - src.inlineCompletions, - i.sourceInlineCompletion, - i.insertText, - ); - } + this._register(autorun(reader => { + /** @description call handleItemDidShow */ + const item = this.inlineCompletionState.read(reader); + const completion = item?.inlineCompletion; + if (completion?.semanticId !== lastItem?.semanticId) { + lastItem = completion; + if (completion) { + const i = completion.inlineCompletion; + const src = i.source; + src.provider.handleItemDidShow?.(src.inlineCompletions, i.sourceInlineCompletion, i.insertText); } - }), - ); + } + })); + } + + public debugGetSelectedSuggestItem(): IObservable { + return this._selectedSuggestItem; + } + + public getIndentationInfo(reader: IReader) { + let startsWithIndentation = false; + let startsWithIndentationLessThanTabSize = true; + const ghostText = this?.primaryGhostText.read(reader); + if (!!this?._selectedSuggestItem && ghostText && ghostText.parts.length > 0) { + const { column, lines } = ghostText.parts[0]; + + const firstLine = lines[0]; + + const indentationEndColumn = this.textModel.getLineIndentColumn(ghostText.lineNumber); + const inIndentation = column <= indentationEndColumn; + + if (inIndentation) { + let firstNonWsIdx = firstNonWhitespaceIndex(firstLine); + if (firstNonWsIdx === -1) { + firstNonWsIdx = firstLine.length - 1; + } + startsWithIndentation = firstNonWsIdx > 0; + + const tabSize = this.textModel.getOptions().tabSize; + const visibleColumnIndentation = CursorColumns.visibleColumnFromColumn(firstLine, firstNonWsIdx + 1, tabSize); + startsWithIndentationLessThanTabSize = visibleColumnIndentation < tabSize; + } + } + return { + startsWithIndentation, + startsWithIndentationLessThanTabSize, + }; } private readonly _preserveCurrentCompletionReasons = new Set([ @@ -170,129 +129,85 @@ export class InlineCompletionsModel extends Disposable { VersionIdChangeReason.AcceptWord, ]); - private _getReason( - e: IModelContentChangedEvent | undefined, - ): VersionIdChangeReason { - if (e?.isUndoing) { - return VersionIdChangeReason.Undo; - } - if (e?.isRedoing) { - return VersionIdChangeReason.Redo; - } - if (this.isAcceptingPartially) { - return VersionIdChangeReason.AcceptWord; - } + private _getReason(e: IModelContentChangedEvent | undefined): VersionIdChangeReason { + if (e?.isUndoing) { return VersionIdChangeReason.Undo; } + if (e?.isRedoing) { return VersionIdChangeReason.Redo; } + if (this.isAcceptingPartially) { return VersionIdChangeReason.AcceptWord; } return VersionIdChangeReason.Other; } public readonly dontRefetchSignal = observableSignal(this); - private readonly _fetchInlineCompletionsPromise = derivedHandleChanges( - { - owner: this, - createEmptyChangeSummary: () => ({ - dontRefetch: false, - preserveCurrentCompletion: false, - inlineCompletionTriggerKind: - InlineCompletionTriggerKind.Automatic, - onlyRequestInlineEdits: false, - }), - handleChange: (ctx, changeSummary) => { - /** @description fetch inline completions */ - if ( - ctx.didChange(this._textModelVersionId) && - this._preserveCurrentCompletionReasons.has( - this._getReason(ctx.change), - ) - ) { - changeSummary.preserveCurrentCompletion = true; - } else if (ctx.didChange(this._forceUpdateExplicitlySignal)) { - changeSummary.inlineCompletionTriggerKind = - InlineCompletionTriggerKind.Explicit; - } else if (ctx.didChange(this.dontRefetchSignal)) { - changeSummary.dontRefetch = true; - } else if (ctx.didChange(this._onlyRequestInlineEditsSignal)) { - changeSummary.onlyRequestInlineEdits = true; - } - return true; - }, - }, - (reader, changeSummary) => { - this.dontRefetchSignal.read(reader); - this._onlyRequestInlineEditsSignal.read(reader); - this._forceUpdateExplicitlySignal.read(reader); - - const shouldUpdate = - (this._enabled.read(reader) && - this.selectedSuggestItem.read(reader)) || - this._isActive.read(reader); - - if (!shouldUpdate) { - this._source.cancelUpdate(); - - return undefined; - } - - this._textModelVersionId.read(reader); // Refetch on text change - - const suggestWidgetInlineCompletions = - this._source.suggestWidgetInlineCompletions.get(); - - const suggestItem = this.selectedSuggestItem.read(reader); - - if (suggestWidgetInlineCompletions && !suggestItem) { - const inlineCompletions = this._source.inlineCompletions.get(); - transaction((tx) => { - /** @description Seed inline completions with (newer) suggest widget inline completions */ - if ( - !inlineCompletions || - suggestWidgetInlineCompletions.request.versionId > - inlineCompletions.request.versionId - ) { - this._source.inlineCompletions.set( - suggestWidgetInlineCompletions.clone(), - tx, - ); - } - this._source.clearSuggestWidgetInlineCompletions(tx); - }); - } - - const cursorPosition = this._primaryPosition.get(); - - if (changeSummary.dontRefetch) { - return Promise.resolve(true); + private readonly _fetchInlineCompletionsPromise = derivedHandleChanges({ + owner: this, + createEmptyChangeSummary: () => ({ + dontRefetch: false, + preserveCurrentCompletion: false, + inlineCompletionTriggerKind: InlineCompletionTriggerKind.Automatic, + onlyRequestInlineEdits: false, + }), + handleChange: (ctx, changeSummary) => { + /** @description fetch inline completions */ + if (ctx.didChange(this._textModelVersionId) && this._preserveCurrentCompletionReasons.has(this._getReason(ctx.change))) { + changeSummary.preserveCurrentCompletion = true; + } else if (ctx.didChange(this._forceUpdateExplicitlySignal)) { + changeSummary.inlineCompletionTriggerKind = InlineCompletionTriggerKind.Explicit; + } else if (ctx.didChange(this.dontRefetchSignal)) { + changeSummary.dontRefetch = true; + } else if (ctx.didChange(this._onlyRequestInlineEditsSignal)) { + changeSummary.onlyRequestInlineEdits = true; } + return true; + }, + }, (reader, changeSummary) => { + this.dontRefetchSignal.read(reader); + this._onlyRequestInlineEditsSignal.read(reader); + this._forceUpdateExplicitlySignal.read(reader); + const shouldUpdate = (this._enabled.read(reader) && this._selectedSuggestItem.read(reader)) || this._isActive.read(reader); + if (!shouldUpdate) { + this._source.cancelUpdate(); + return undefined; + } - const context: InlineCompletionContext = { - triggerKind: changeSummary.inlineCompletionTriggerKind, - selectedSuggestionInfo: suggestItem?.toSelectedSuggestionInfo(), - includeInlineCompletions: !changeSummary.onlyRequestInlineEdits, - includeInlineEdits: this._inlineEditsEnabled.read(reader), - }; + this._textModelVersionId.read(reader); // Refetch on text change - const itemToPreserveCandidate = this.selectedInlineCompletion.get(); + const suggestWidgetInlineCompletions = this._source.suggestWidgetInlineCompletions.get(); + const suggestItem = this._selectedSuggestItem.read(reader); + if (suggestWidgetInlineCompletions && !suggestItem) { + const inlineCompletions = this._source.inlineCompletions.get(); + transaction(tx => { + /** @description Seed inline completions with (newer) suggest widget inline completions */ + if (!inlineCompletions || suggestWidgetInlineCompletions.request.versionId > inlineCompletions.request.versionId) { + this._source.inlineCompletions.set(suggestWidgetInlineCompletions.clone(), tx); + } + this._source.clearSuggestWidgetInlineCompletions(tx); + }); + } - const itemToPreserve = - changeSummary.preserveCurrentCompletion || - itemToPreserveCandidate?.forwardStable - ? itemToPreserveCandidate - : undefined; + const cursorPosition = this._primaryPosition.get(); + if (changeSummary.dontRefetch) { + return Promise.resolve(true); + } - return this._source.fetch(cursorPosition, context, itemToPreserve); - }, - ); + const context: InlineCompletionContext = { + triggerKind: changeSummary.inlineCompletionTriggerKind, + selectedSuggestionInfo: suggestItem?.toSelectedSuggestionInfo(), + includeInlineCompletions: !changeSummary.onlyRequestInlineEdits, + includeInlineEdits: this._inlineEditsEnabled.read(reader), + }; + const itemToPreserveCandidate = this.selectedInlineCompletion.get(); + const itemToPreserve = changeSummary.preserveCurrentCompletion || itemToPreserveCandidate?.forwardStable + ? itemToPreserveCandidate : undefined; + return this._source.fetch(cursorPosition, context, itemToPreserve); + }); public async trigger(tx?: ITransaction): Promise { this._isActive.set(true, tx); await this._fetchInlineCompletionsPromise.get(); } - public async triggerExplicitly( - tx?: ITransaction, - onlyFetchInlineEdits: boolean = false, - ): Promise { - subtransaction(tx, (tx) => { + public async triggerExplicitly(tx?: ITransaction, onlyFetchInlineEdits: boolean = false): Promise { + subtransaction(tx, tx => { if (onlyFetchInlineEdits) { this._onlyRequestInlineEditsSignal.trigger(tx); } @@ -302,20 +217,12 @@ export class InlineCompletionsModel extends Disposable { await this._fetchInlineCompletionsPromise.get(); } - public stop( - stopReason: "explicitCancel" | "automatic" = "automatic", - tx?: ITransaction, - ): void { - subtransaction(tx, (tx) => { - if (stopReason === "explicitCancel") { - const completion = - this.state.get()?.inlineCompletion?.inlineCompletion; - + public stop(stopReason: 'explicitCancel' | 'automatic' = 'automatic', tx?: ITransaction): void { + subtransaction(tx, tx => { + if (stopReason === 'explicitCancel') { + const completion = this.state.get()?.inlineCompletion?.inlineCompletion; if (completion && completion.source.provider.handleRejection) { - completion.source.provider.handleRejection( - completion.source.inlineCompletions, - completion.sourceInlineCompletion, - ); + completion.source.provider.handleRejection(completion.source.inlineCompletions, completion.sourceInlineCompletion); } } @@ -324,379 +231,178 @@ export class InlineCompletionsModel extends Disposable { }); } - private readonly _collapsedInlineEditId = observableValue< - string | undefined - >(this, undefined); + private readonly _collapsedInlineEditId = observableValue(this, undefined); public collapseInlineEdit(): void { const currentInlineEdit = this.inlineEditState.get()?.inlineCompletion; - - if (!currentInlineEdit) { - return; - } - this._collapsedInlineEditId.set( - currentInlineEdit.semanticId, - undefined, - ); + if (!currentInlineEdit) { return; } + this._collapsedInlineEditId.set(currentInlineEdit.semanticId, undefined); } - private readonly _inlineCompletionItems = derivedOpts( - { owner: this }, - (reader) => { - const c = this._source.inlineCompletions.read(reader); - - if (!c) { - return undefined; - } - const cursorPosition = this._primaryPosition.read(reader); - - let inlineEdit: InlineCompletionWithUpdatedRange | undefined = - undefined; - - const visibleCompletions: InlineCompletionWithUpdatedRange[] = []; - - for (const completion of c.inlineCompletions) { - if ( - !completion.inlineCompletion.sourceInlineCompletion - .isInlineEdit - ) { - if ( - completion.isVisible( - this.textModel, - cursorPosition, - reader, - ) - ) { - visibleCompletions.push(completion); - } - } else { - inlineEdit = completion; + private readonly _inlineCompletionItems = derivedOpts({ owner: this }, reader => { + const c = this._source.inlineCompletions.read(reader); + if (!c) { return undefined; } + const cursorPosition = this._primaryPosition.read(reader); + let inlineEdit: InlineCompletionWithUpdatedRange | undefined = undefined; + const visibleCompletions: InlineCompletionWithUpdatedRange[] = []; + for (const completion of c.inlineCompletions) { + if (!completion.inlineCompletion.sourceInlineCompletion.isInlineEdit) { + if (completion.isVisible(this.textModel, cursorPosition, reader)) { + visibleCompletions.push(completion); } + } else { + inlineEdit = completion; } + } - if (visibleCompletions.length !== 0) { - // Don't show the inline edit if there is a visible completion - inlineEdit = undefined; - } - - return { - inlineCompletions: visibleCompletions, - inlineEdit, - }; - }, - ); - - private readonly _filteredInlineCompletionItems = derivedOpts( - { owner: this, equalsFn: itemsEquals() }, - (reader) => { - const c = this._inlineCompletionItems.read(reader); - - return c?.inlineCompletions ?? []; - }, - ); - - public readonly selectedInlineCompletionIndex = derived( - this, - (reader) => { - const selectedInlineCompletionId = - this._selectedInlineCompletionId.read(reader); - - const filteredCompletions = - this._filteredInlineCompletionItems.read(reader); - - const idx = - this._selectedInlineCompletionId === undefined - ? -1 - : filteredCompletions.findIndex( - (v) => v.semanticId === selectedInlineCompletionId, - ); + if (visibleCompletions.length !== 0) { + // Don't show the inline edit if there is a visible completion + inlineEdit = undefined; + } - if (idx === -1) { - // Reset the selection so that the selection does not jump back when it appears again - this._selectedInlineCompletionId.set(undefined, undefined); + return { + inlineCompletions: visibleCompletions, + inlineEdit, + }; + }); - return 0; - } - return idx; - }, - ); + private readonly _filteredInlineCompletionItems = derivedOpts({ owner: this, equalsFn: itemsEquals() }, reader => { + const c = this._inlineCompletionItems.read(reader); + return c?.inlineCompletions ?? []; + }); - public readonly selectedInlineCompletion = derived< - InlineCompletionWithUpdatedRange | undefined - >(this, (reader) => { - const filteredCompletions = - this._filteredInlineCompletionItems.read(reader); + public readonly selectedInlineCompletionIndex = derived(this, (reader) => { + const selectedInlineCompletionId = this._selectedInlineCompletionId.read(reader); + const filteredCompletions = this._filteredInlineCompletionItems.read(reader); + const idx = this._selectedInlineCompletionId === undefined ? -1 + : filteredCompletions.findIndex(v => v.semanticId === selectedInlineCompletionId); + if (idx === -1) { + // Reset the selection so that the selection does not jump back when it appears again + this._selectedInlineCompletionId.set(undefined, undefined); + return 0; + } + return idx; + }); + public readonly selectedInlineCompletion = derived(this, (reader) => { + const filteredCompletions = this._filteredInlineCompletionItems.read(reader); const idx = this.selectedInlineCompletionIndex.read(reader); - return filteredCompletions[idx]; }); - public readonly activeCommands = derivedOpts( - { owner: this, equalsFn: itemsEquals() }, - (r) => - this.selectedInlineCompletion.read(r)?.inlineCompletion.source - .inlineCompletions.commands ?? [], - ); - - public readonly lastTriggerKind: IObservable< - InlineCompletionTriggerKind | undefined - > = this._source.inlineCompletions.map( - this, - (v) => v?.request.context.triggerKind, + public readonly activeCommands = derivedOpts({ owner: this, equalsFn: itemsEquals() }, + r => this.selectedInlineCompletion.read(r)?.inlineCompletion.source.inlineCompletions.commands ?? [] ); - public readonly inlineCompletionsCount = derived( - this, - (reader) => { - if ( - this.lastTriggerKind.read(reader) === - InlineCompletionTriggerKind.Explicit - ) { - return this._filteredInlineCompletionItems.read(reader).length; - } else { - return undefined; - } - }, - ); - - public readonly state = derivedOpts< - | { - kind: "ghostText"; - edits: readonly SingleTextEdit[]; - primaryGhostText: GhostTextOrReplacement; - ghostTexts: readonly GhostTextOrReplacement[]; - suggestItem: SuggestItemInfo | undefined; - inlineCompletion: InlineCompletionWithUpdatedRange | undefined; - } - | { - kind: "inlineEdit"; - edits: readonly SingleTextEdit[]; - inlineEdit: InlineEdit; - inlineCompletion: InlineCompletionWithUpdatedRange; - cursorAtInlineEdit: boolean; - } - | undefined - >( - { - owner: this, - equalsFn: (a, b) => { - if (!a || !b) { - return a === b; - } - - if (a.kind === "ghostText" && b.kind === "ghostText") { - return ( - ghostTextsOrReplacementsEqual( - a.ghostTexts, - b.ghostTexts, - ) && - a.inlineCompletion === b.inlineCompletion && - a.suggestItem === b.suggestItem - ); - } else if (a.kind === "inlineEdit" && b.kind === "inlineEdit") { - return ( - a.inlineEdit.equals(b.inlineEdit) && - a.cursorAtInlineEdit === b.cursorAtInlineEdit - ); - } - return false; - }, - }, - (reader) => { - const model = this.textModel; - - const item = this._inlineCompletionItems.read(reader); - - if (item?.inlineEdit) { - let edit = item.inlineEdit.toSingleTextEdit(reader); - edit = singleTextRemoveCommonPrefix(edit, model); - - const cursorPos = this._primaryPosition.read(reader); - - const cursorAtInlineEdit = LineRange.fromRangeInclusive( - edit.range, - ) - .addMargin(1, 1) - .contains(cursorPos.lineNumber); - - const cursorDist = LineRange.fromRange( - edit.range, - ).distanceToLine(this._primaryPosition.read(reader).lineNumber); - - const disableCollapsing = true; - - const currentItemIsCollapsed = - !disableCollapsing && - cursorDist > 1 && - this._collapsedInlineEditId.read(reader) === - item.inlineEdit.semanticId; + public readonly lastTriggerKind: IObservable + = this._source.inlineCompletions.map(this, v => v?.request.context.triggerKind); - const commands = - item.inlineEdit.inlineCompletion.source.inlineCompletions - .commands; - - const renderExplicitly = this._jumpedTo.read(reader); - - const inlineEdit = new InlineEdit( - edit, - currentItemIsCollapsed, - renderExplicitly, - commands ?? [], - item.inlineEdit.inlineCompletion, - ); + public readonly inlineCompletionsCount = derived(this, reader => { + if (this.lastTriggerKind.read(reader) === InlineCompletionTriggerKind.Explicit) { + return this._filteredInlineCompletionItems.read(reader).length; + } else { + return undefined; + } + }); - return { - kind: "inlineEdit", - inlineEdit, - inlineCompletion: item.inlineEdit, - edits: [edit], - cursorAtInlineEdit, - }; + public readonly state = derivedOpts<{ + kind: 'ghostText'; + edits: readonly SingleTextEdit[]; + primaryGhostText: GhostTextOrReplacement; + ghostTexts: readonly GhostTextOrReplacement[]; + suggestItem: SuggestItemInfo | undefined; + inlineCompletion: InlineCompletionWithUpdatedRange | undefined; + } | { + kind: 'inlineEdit'; + edits: readonly SingleTextEdit[]; + inlineEdit: InlineEdit; + inlineCompletion: InlineCompletionWithUpdatedRange; + cursorAtInlineEdit: boolean; + } | undefined>({ + owner: this, + equalsFn: (a, b) => { + if (!a || !b) { return a === b; } + + if (a.kind === 'ghostText' && b.kind === 'ghostText') { + return ghostTextsOrReplacementsEqual(a.ghostTexts, b.ghostTexts) + && a.inlineCompletion === b.inlineCompletion + && a.suggestItem === b.suggestItem; + } else if (a.kind === 'inlineEdit' && b.kind === 'inlineEdit') { + return a.inlineEdit.equals(b.inlineEdit) && a.cursorAtInlineEdit === b.cursorAtInlineEdit; } + return false; + } + }, (reader) => { + const model = this.textModel; - this._jumpedTo.set(false, undefined); - - const suggestItem = this.selectedSuggestItem.read(reader); - - if (suggestItem) { - const suggestCompletionEdit = singleTextRemoveCommonPrefix( - suggestItem.toSingleTextEdit(), - model, - ); - - const augmentation = this._computeAugmentation( - suggestCompletionEdit, - reader, - ); - - const isSuggestionPreviewEnabled = - this._suggestPreviewEnabled.read(reader); - - if (!isSuggestionPreviewEnabled && !augmentation) { - return undefined; - } - - const fullEdit = augmentation?.edit ?? suggestCompletionEdit; - - const fullEditPreviewLength = augmentation - ? augmentation.edit.text.length - - suggestCompletionEdit.text.length - : 0; - - const mode = this._suggestPreviewMode.read(reader); - - const positions = this._positions.read(reader); - - const edits = [ - fullEdit, - ...getSecondaryEdits(this.textModel, positions, fullEdit), - ]; - - const ghostTexts = edits - .map((edit, idx) => - computeGhostText( - edit, - model, - mode, - positions[idx], - fullEditPreviewLength, - ), - ) - .filter(isDefined); - - const primaryGhostText = - ghostTexts[0] ?? - new GhostText(fullEdit.range.endLineNumber, []); - - return { - kind: "ghostText", - edits, - primaryGhostText, - ghostTexts, - inlineCompletion: augmentation?.completion, - suggestItem, - }; - } else { - if (!this._isActive.read(reader)) { - return undefined; - } - const inlineCompletion = - this.selectedInlineCompletion.read(reader); + const item = this._inlineCompletionItems.read(reader); + if (item?.inlineEdit) { + let edit = item.inlineEdit.toSingleTextEdit(reader); + edit = singleTextRemoveCommonPrefix(edit, model); - if (!inlineCompletion) { - return undefined; - } + const cursorPos = this._primaryPosition.read(reader); + const cursorAtInlineEdit = LineRange.fromRangeInclusive(edit.range).addMargin(1, 1).contains(cursorPos.lineNumber); - const replacement = inlineCompletion.toSingleTextEdit(reader); + const cursorDist = LineRange.fromRange(edit.range).distanceToLine(this._primaryPosition.read(reader).lineNumber); + const disableCollapsing = true; + const currentItemIsCollapsed = !disableCollapsing && (cursorDist > 1 && this._collapsedInlineEditId.read(reader) === item.inlineEdit.semanticId); - const mode = this._inlineSuggestMode.read(reader); + const commands = item.inlineEdit.inlineCompletion.source.inlineCompletions.commands; + const renderExplicitly = this._jumpedTo.read(reader); + const inlineEdit = new InlineEdit(edit, currentItemIsCollapsed, renderExplicitly, commands ?? [], item.inlineEdit.inlineCompletion); - const positions = this._positions.read(reader); + return { kind: 'inlineEdit', inlineEdit, inlineCompletion: item.inlineEdit, edits: [edit], cursorAtInlineEdit }; + } - const edits = [ - replacement, - ...getSecondaryEdits( - this.textModel, - positions, - replacement, - ), - ]; + this._jumpedTo.set(false, undefined); - const ghostTexts = edits - .map((edit, idx) => - computeGhostText(edit, model, mode, positions[idx], 0), - ) - .filter(isDefined); + const suggestItem = this._selectedSuggestItem.read(reader); + if (suggestItem) { + const suggestCompletionEdit = singleTextRemoveCommonPrefix(suggestItem.toSingleTextEdit(), model); + const augmentation = this._computeAugmentation(suggestCompletionEdit, reader); - if (!ghostTexts[0]) { - return undefined; - } - return { - kind: "ghostText", - edits, - primaryGhostText: ghostTexts[0], - ghostTexts, - inlineCompletion, - suggestItem: undefined, - }; - } - }, - ); + const isSuggestionPreviewEnabled = this._suggestPreviewEnabled.read(reader); + if (!isSuggestionPreviewEnabled && !augmentation) { return undefined; } - private readonly _alwaysShowInlineEdit = observableValue( - this, - false, - ); + const fullEdit = augmentation?.edit ?? suggestCompletionEdit; + const fullEditPreviewLength = augmentation ? augmentation.edit.text.length - suggestCompletionEdit.text.length : 0; - protected readonly _resetAlwaysShowInlineEdit = this._register( - autorun((reader) => { - this._primaryPosition.read(reader); - this._textModelVersionId.read(reader); - - this._alwaysShowInlineEdit.set(false, undefined); - }), - ); - - public readonly status = derived(this, (reader) => { - if (this._source.loading.read(reader)) { - return "loading"; - } - const s = this.state.read(reader); - - if (s?.kind === "ghostText") { - return "ghostText"; - } - if (s?.kind === "inlineEdit") { - return "inlineEdit"; + const mode = this._suggestPreviewMode.read(reader); + const positions = this._positions.read(reader); + const edits = [fullEdit, ...getSecondaryEdits(this.textModel, positions, fullEdit)]; + const ghostTexts = edits + .map((edit, idx) => computeGhostText(edit, model, mode, positions[idx], fullEditPreviewLength)) + .filter(isDefined); + const primaryGhostText = ghostTexts[0] ?? new GhostText(fullEdit.range.endLineNumber, []); + return { kind: 'ghostText', edits, primaryGhostText, ghostTexts, inlineCompletion: augmentation?.completion, suggestItem }; + } else { + if (!this._isActive.read(reader)) { return undefined; } + const inlineCompletion = this.selectedInlineCompletion.read(reader); + if (!inlineCompletion) { return undefined; } + + const replacement = inlineCompletion.toSingleTextEdit(reader); + const mode = this._inlineSuggestMode.read(reader); + const positions = this._positions.read(reader); + const edits = [replacement, ...getSecondaryEdits(this.textModel, positions, replacement)]; + const ghostTexts = edits + .map((edit, idx) => computeGhostText(edit, model, mode, positions[idx], 0)) + .filter(isDefined); + if (!ghostTexts[0]) { return undefined; } + return { kind: 'ghostText', edits, primaryGhostText: ghostTexts[0], ghostTexts, inlineCompletion, suggestItem: undefined }; } - return "noSuggestion"; }); - public readonly inlineCompletionState = derived((reader) => { + public readonly status = derived(this, reader => { + if (this._source.loading.read(reader)) { return 'loading'; } const s = this.state.read(reader); + if (s?.kind === 'ghostText') { return 'ghostText'; } + if (s?.kind === 'inlineEdit') { return 'inlineEdit'; } + return 'noSuggestion'; + }); - if (!s || s.kind !== "ghostText") { + public readonly inlineCompletionState = derived(reader => { + const s = this.state.read(reader); + if (!s || s.kind !== 'ghostText') { return undefined; } if (this._editorObs.inComposition.read(reader)) { @@ -705,173 +411,119 @@ export class InlineCompletionsModel extends Disposable { return s; }); - public readonly inlineEditState = derived((reader) => { + public readonly inlineEditState = derived(reader => { const s = this.state.read(reader); - - if (!s || s.kind !== "inlineEdit") { + if (!s || s.kind !== 'inlineEdit') { return undefined; } return s; }); - public readonly inlineEditAvailable = derived((reader) => { + public readonly inlineEditAvailable = derived(reader => { const s = this.inlineEditState.read(reader); - return !!s; }); - private _computeAugmentation( - suggestCompletion: SingleTextEdit, - reader: IReader | undefined, - ) { + private _computeAugmentation(suggestCompletion: SingleTextEdit, reader: IReader | undefined) { const model = this.textModel; - - const suggestWidgetInlineCompletions = - this._source.suggestWidgetInlineCompletions.read(reader); - + const suggestWidgetInlineCompletions = this._source.suggestWidgetInlineCompletions.read(reader); const candidateInlineCompletions = suggestWidgetInlineCompletions ? suggestWidgetInlineCompletions.inlineCompletions : [this.selectedInlineCompletion.read(reader)].filter(isDefined); - const augmentedCompletion = mapFindFirst( - candidateInlineCompletions, - (completion) => { - let r = completion.toSingleTextEdit(reader); - r = singleTextRemoveCommonPrefix( - r, - model, - Range.fromPositions( - r.range.getStartPosition(), - suggestCompletion.range.getEndPosition(), - ), - ); - - return singleTextEditAugments(r, suggestCompletion) - ? { completion, edit: r } - : undefined; - }, - ); + const augmentedCompletion = mapFindFirst(candidateInlineCompletions, completion => { + let r = completion.toSingleTextEdit(reader); + r = singleTextRemoveCommonPrefix( + r, + model, + Range.fromPositions(r.range.getStartPosition(), suggestCompletion.range.getEndPosition()) + ); + return singleTextEditAugments(r, suggestCompletion) ? { completion, edit: r } : undefined; + }); return augmentedCompletion; } - public readonly ghostTexts = derivedOpts( - { owner: this, equalsFn: ghostTextsOrReplacementsEqual }, - (reader) => { - const v = this.inlineCompletionState.read(reader); - - if (!v) { - return undefined; - } - return v.ghostTexts; - }, - ); - - public readonly primaryGhostText = derivedOpts( - { owner: this, equalsFn: ghostTextOrReplacementEquals }, - (reader) => { - const v = this.inlineCompletionState.read(reader); + public readonly ghostTexts = derivedOpts({ owner: this, equalsFn: ghostTextsOrReplacementsEqual }, reader => { + const v = this.inlineCompletionState.read(reader); + if (!v) { + return undefined; + } + return v.ghostTexts; + }); - if (!v) { - return undefined; - } - return v?.primaryGhostText; - }, - ); + public readonly primaryGhostText = derivedOpts({ owner: this, equalsFn: ghostTextOrReplacementEquals }, reader => { + const v = this.inlineCompletionState.read(reader); + if (!v) { + return undefined; + } + return v?.primaryGhostText; + }); - private readonly _tabShouldIndent = derived(this, (reader) => { + private readonly _tabShouldIndent = derived(this, reader => { function isMultiLine(range: Range): boolean { return range.startLineNumber !== range.endLineNumber; } - function getNonIndentationRange( - model: ITextModel, - lineNumber: number, - ): Range { + function getNonIndentationRange(model: ITextModel, lineNumber: number): Range { const columnStart = model.getLineIndentColumn(lineNumber); - - const lastNonWsColumn = - model.getLineLastNonWhitespaceColumn(lineNumber); - + const lastNonWsColumn = model.getLineLastNonWhitespaceColumn(lineNumber); const columnEnd = Math.max(lastNonWsColumn, columnStart); - return new Range(lineNumber, columnStart, lineNumber, columnEnd); } const selections = this._editorObs.selections.read(reader); - - return selections?.some((s) => { + return selections?.some(s => { if (s.isEmpty()) { return this.textModel.getLineLength(s.startLineNumber) === 0; } else { - return ( - isMultiLine(s) || - s.containsRange( - getNonIndentationRange( - this.textModel, - s.startLineNumber, - ), - ) - ); + return isMultiLine(s) || s.containsRange(getNonIndentationRange(this.textModel, s.startLineNumber)); } }); }); - public readonly tabShouldJumpToInlineEdit = derived(this, (reader) => { + public readonly tabShouldJumpToInlineEdit = derived(this, reader => { if (this._tabShouldIndent.read(reader)) { return false; } const s = this.inlineEditState.read(reader); - if (!s) { return false; } return !s.cursorAtInlineEdit; }); - public readonly tabShouldAcceptInlineEdit = derived(this, (reader) => { + public readonly tabShouldAcceptInlineEdit = derived(this, reader => { + if (this._jumpedTo.read(reader)) { + return true; + } if (this._tabShouldIndent.read(reader)) { return false; } const s = this.inlineEditState.read(reader); - if (!s) { return false; } return s.cursorAtInlineEdit; }); - private async _deltaSelectedInlineCompletionIndex( - delta: 1 | -1, - ): Promise { + private async _deltaSelectedInlineCompletionIndex(delta: 1 | -1): Promise { await this.triggerExplicitly(); const completions = this._filteredInlineCompletionItems.get() || []; - if (completions.length > 0) { - const newIdx = - (this.selectedInlineCompletionIndex.get() + - delta + - completions.length) % - completions.length; - this._selectedInlineCompletionId.set( - completions[newIdx].semanticId, - undefined, - ); + const newIdx = (this.selectedInlineCompletionIndex.get() + delta + completions.length) % completions.length; + this._selectedInlineCompletionId.set(completions[newIdx].semanticId, undefined); } else { this._selectedInlineCompletionId.set(undefined, undefined); } } - public async next(): Promise { - await this._deltaSelectedInlineCompletionIndex(1); - } + public async next(): Promise { await this._deltaSelectedInlineCompletionIndex(1); } - public async previous(): Promise { - await this._deltaSelectedInlineCompletionIndex(-1); - } + public async previous(): Promise { await this._deltaSelectedInlineCompletionIndex(-1); } public async accept(editor: ICodeEditor): Promise { if (editor.getModel() !== this.textModel) { @@ -881,17 +533,12 @@ export class InlineCompletionsModel extends Disposable { let completion: InlineCompletionItem; const state = this.state.get(); - - if (state?.kind === "ghostText") { - if ( - !state || - state.primaryGhostText.isEmpty() || - !state.inlineCompletion - ) { + if (state?.kind === 'ghostText') { + if (!state || state.primaryGhostText.isEmpty() || !state.inlineCompletion) { return; } completion = state.inlineCompletion.toInlineCompletion(undefined); - } else if (state?.kind === "inlineEdit") { + } else if (state?.kind === 'inlineEdit') { completion = state.inlineCompletion.toInlineCompletion(undefined); } else { return; @@ -903,33 +550,24 @@ export class InlineCompletionsModel extends Disposable { } editor.pushUndoStop(); - if (completion.snippetInfo) { - editor.executeEdits("inlineSuggestion.accept", [ - EditOperation.replace(completion.range, ""), - ...completion.additionalTextEdits, - ]); - editor.setPosition( - completion.snippetInfo.range.getStartPosition(), - "inlineCompletionAccept", - ); - SnippetController2.get(editor)?.insert( - completion.snippetInfo.snippet, - { undoStopBefore: false }, + editor.executeEdits( + 'inlineSuggestion.accept', + [ + EditOperation.replace(completion.range, ''), + ...completion.additionalTextEdits + ] ); + editor.setPosition(completion.snippetInfo.range.getStartPosition(), 'inlineCompletionAccept'); + SnippetController2.get(editor)?.insert(completion.snippetInfo.snippet, { undoStopBefore: false }); } else { const edits = state.edits; - - const selections = getEndPositionsAfterApplying(edits).map((p) => - Selection.fromPositions(p), - ); - editor.executeEdits("inlineSuggestion.accept", [ - ...edits.map((edit) => - EditOperation.replace(edit.range, edit.text), - ), - ...completion.additionalTextEdits, + const selections = getEndPositionsAfterApplying(edits).map(p => Selection.fromPositions(p)); + editor.executeEdits('inlineSuggestion.accept', [ + ...edits.map(edit => EditOperation.replace(edit.range, edit.text)), + ...completion.additionalTextEdits ]); - editor.setSelections(selections, "inlineCompletionAccept"); + editor.setSelections(selections, 'inlineCompletionAccept'); } // Reset before invoking the command, as the command might cause a follow up trigger (which we don't want to reset). @@ -937,209 +575,109 @@ export class InlineCompletionsModel extends Disposable { if (completion.command) { await this._commandService - .executeCommand( - completion.command.id, - ...(completion.command.arguments || []), - ) + .executeCommand(completion.command.id, ...(completion.command.arguments || [])) .then(undefined, onUnexpectedExternalError); completion.source.removeRef(); } - - this._alwaysShowInlineEdit.set(true, undefined); } public async acceptNextWord(editor: ICodeEditor): Promise { - await this._acceptNext( - editor, - (pos, text) => { - const langId = this.textModel.getLanguageIdAtPosition( - pos.lineNumber, - pos.column, - ); - - const config = - this._languageConfigurationService.getLanguageConfiguration( - langId, - ); - - const wordRegExp = new RegExp( - config.wordDefinition.source, - config.wordDefinition.flags.replace("g", ""), - ); - - const m1 = text.match(wordRegExp); - - let acceptUntilIndexExclusive = 0; - - if (m1 && m1.index !== undefined) { - if (m1.index === 0) { - acceptUntilIndexExclusive = m1[0].length; - } else { - acceptUntilIndexExclusive = m1.index; - } + await this._acceptNext(editor, (pos, text) => { + const langId = this.textModel.getLanguageIdAtPosition(pos.lineNumber, pos.column); + const config = this._languageConfigurationService.getLanguageConfiguration(langId); + const wordRegExp = new RegExp(config.wordDefinition.source, config.wordDefinition.flags.replace('g', '')); + + const m1 = text.match(wordRegExp); + let acceptUntilIndexExclusive = 0; + if (m1 && m1.index !== undefined) { + if (m1.index === 0) { + acceptUntilIndexExclusive = m1[0].length; } else { - acceptUntilIndexExclusive = text.length; + acceptUntilIndexExclusive = m1.index; } + } else { + acceptUntilIndexExclusive = text.length; + } - const wsRegExp = /\s+/g; - - const m2 = wsRegExp.exec(text); - - if (m2 && m2.index !== undefined) { - if (m2.index + m2[0].length < acceptUntilIndexExclusive) { - acceptUntilIndexExclusive = m2.index + m2[0].length; - } + const wsRegExp = /\s+/g; + const m2 = wsRegExp.exec(text); + if (m2 && m2.index !== undefined) { + if (m2.index + m2[0].length < acceptUntilIndexExclusive) { + acceptUntilIndexExclusive = m2.index + m2[0].length; } - return acceptUntilIndexExclusive; - }, - PartialAcceptTriggerKind.Word, - ); + } + return acceptUntilIndexExclusive; + }, PartialAcceptTriggerKind.Word); } public async acceptNextLine(editor: ICodeEditor): Promise { - await this._acceptNext( - editor, - (pos, text) => { - const m = text.match(/\n/); - - if (m && m.index !== undefined) { - return m.index + 1; - } - return text.length; - }, - PartialAcceptTriggerKind.Line, - ); + await this._acceptNext(editor, (pos, text) => { + const m = text.match(/\n/); + if (m && m.index !== undefined) { + return m.index + 1; + } + return text.length; + }, PartialAcceptTriggerKind.Line); } - private async _acceptNext( - editor: ICodeEditor, - getAcceptUntilIndex: (position: Position, text: string) => number, - kind: PartialAcceptTriggerKind, - ): Promise { + private async _acceptNext(editor: ICodeEditor, getAcceptUntilIndex: (position: Position, text: string) => number, kind: PartialAcceptTriggerKind): Promise { if (editor.getModel() !== this.textModel) { throw new BugIndicatingError(); } const state = this.inlineCompletionState.get(); - - if ( - !state || - state.primaryGhostText.isEmpty() || - !state.inlineCompletion - ) { + if (!state || state.primaryGhostText.isEmpty() || !state.inlineCompletion) { return; } const ghostText = state.primaryGhostText; - const completion = state.inlineCompletion.toInlineCompletion(undefined); - if ( - completion.snippetInfo || - completion.filterText !== completion.insertText - ) { + if (completion.snippetInfo || completion.filterText !== completion.insertText) { // not in WYSIWYG mode, partial commit might change completion, thus it is not supported await this.accept(editor); - return; } const firstPart = ghostText.parts[0]; - - const ghostTextPos = new Position( - ghostText.lineNumber, - firstPart.column, - ); - + const ghostTextPos = new Position(ghostText.lineNumber, firstPart.column); const ghostTextVal = firstPart.text; - - const acceptUntilIndexExclusive = getAcceptUntilIndex( - ghostTextPos, - ghostTextVal, - ); - - if ( - acceptUntilIndexExclusive === ghostTextVal.length && - ghostText.parts.length === 1 - ) { + const acceptUntilIndexExclusive = getAcceptUntilIndex(ghostTextPos, ghostTextVal); + if (acceptUntilIndexExclusive === ghostTextVal.length && ghostText.parts.length === 1) { this.accept(editor); - return; } - const partialGhostTextVal = ghostTextVal.substring( - 0, - acceptUntilIndexExclusive, - ); + const partialGhostTextVal = ghostTextVal.substring(0, acceptUntilIndexExclusive); const positions = this._positions.get(); - const cursorPosition = positions[0]; // Executing the edit might free the completion, so we have to hold a reference on it. completion.source.addRef(); - try { this._isAcceptingPartially = true; - try { editor.pushUndoStop(); - - const replaceRange = Range.fromPositions( - cursorPosition, - ghostTextPos, - ); - - const newText = - editor.getModel()!.getValueInRange(replaceRange) + - partialGhostTextVal; - + const replaceRange = Range.fromPositions(cursorPosition, ghostTextPos); + const newText = editor.getModel()!.getValueInRange(replaceRange) + partialGhostTextVal; const primaryEdit = new SingleTextEdit(replaceRange, newText); - - const edits = [ - primaryEdit, - ...getSecondaryEdits( - this.textModel, - positions, - primaryEdit, - ), - ]; - - const selections = getEndPositionsAfterApplying(edits).map( - (p) => Selection.fromPositions(p), - ); - editor.executeEdits( - "inlineSuggestion.accept", - edits.map((edit) => - EditOperation.replace(edit.range, edit.text), - ), - ); - editor.setSelections( - selections, - "inlineCompletionPartialAccept", - ); - editor.revealPositionInCenterIfOutsideViewport( - editor.getPosition()!, - ScrollType.Immediate, - ); + const edits = [primaryEdit, ...getSecondaryEdits(this.textModel, positions, primaryEdit)]; + const selections = getEndPositionsAfterApplying(edits).map(p => Selection.fromPositions(p)); + editor.executeEdits('inlineSuggestion.accept', edits.map(edit => EditOperation.replace(edit.range, edit.text))); + editor.setSelections(selections, 'inlineCompletionPartialAccept'); + editor.revealPositionInCenterIfOutsideViewport(editor.getPosition()!, ScrollType.Immediate); } finally { this._isAcceptingPartially = false; } if (completion.source.provider.handlePartialAccept) { - const acceptedRange = Range.fromPositions( - completion.range.getStartPosition(), - TextLength.ofText(partialGhostTextVal).addToPosition( - ghostTextPos, - ), - ); + const acceptedRange = Range.fromPositions(completion.range.getStartPosition(), TextLength.ofText(partialGhostTextVal).addToPosition(ghostTextPos)); // This assumes that the inline completion and the model use the same EOL style. - const text = editor - .getModel()! - .getValueInRange(acceptedRange, EndOfLinePreference.LF); + const text = editor.getModel()!.getValueInRange(acceptedRange, EndOfLinePreference.LF); completion.source.provider.handlePartialAccept( completion.source.inlineCompletions, completion.sourceInlineCompletion, text.length, - { kind }, + { kind, } ); } } finally { @@ -1148,39 +686,24 @@ export class InlineCompletionsModel extends Disposable { } public handleSuggestAccepted(item: SuggestItemInfo) { - const itemEdit = singleTextRemoveCommonPrefix( - item.toSingleTextEdit(), - this.textModel, - ); + const itemEdit = singleTextRemoveCommonPrefix(item.toSingleTextEdit(), this.textModel); + const augmentedCompletion = this._computeAugmentation(itemEdit, undefined); + if (!augmentedCompletion) { return; } - const augmentedCompletion = this._computeAugmentation( - itemEdit, - undefined, - ); - - if (!augmentedCompletion) { - return; - } - - const inlineCompletion = - augmentedCompletion.completion.inlineCompletion; + const inlineCompletion = augmentedCompletion.completion.inlineCompletion; inlineCompletion.source.provider.handlePartialAccept?.( inlineCompletion.source.inlineCompletions, inlineCompletion.sourceInlineCompletion, itemEdit.text.length, { kind: PartialAcceptTriggerKind.Suggest, - }, + } ); } public extractReproSample(): Repro { const value = this.textModel.getValue(); - - const item = this.state - .get() - ?.inlineCompletion?.toInlineCompletion(undefined); - + const item = this.state.get()?.inlineCompletion?.toInlineCompletion(undefined); return { documentValue: value, inlineCompletion: item?.sourceInlineCompletion, @@ -1191,26 +714,18 @@ export class InlineCompletionsModel extends Disposable { public jump(): void { const s = this.inlineEditState.get(); + if (!s) { return; } - if (!s) { - return; - } - - transaction((tx) => { + transaction(tx => { this._jumpedTo.set(true, tx); this.dontRefetchSignal.trigger(tx); - this._editor.setPosition( - s.inlineEdit.range.getStartPosition(), - "inlineCompletions.jump", - ); + this._editor.setPosition(s.inlineEdit.range.getStartPosition(), 'inlineCompletions.jump'); this._editor.revealLine(s.inlineEdit.range.startLineNumber); this._editor.focus(); }); } - public async handleInlineCompletionShown( - inlineCompletion: InlineCompletionItem, - ): Promise { + public async handleInlineCompletionShown(inlineCompletion: InlineCompletionItem): Promise { if (!inlineCompletion.shownCommand) { return; } @@ -1218,10 +733,7 @@ export class InlineCompletionsModel extends Disposable { return; } inlineCompletion.markAsShown(); - await this._commandService.executeCommand( - inlineCompletion.shownCommand.id, - ...(inlineCompletion.shownCommand.arguments || []), - ); + await this._commandService.executeCommand(inlineCompletion.shownCommand.id, ...(inlineCompletion.shownCommand.arguments || [])); } } @@ -1237,64 +749,34 @@ export enum VersionIdChangeReason { Other, } -export function getSecondaryEdits( - textModel: ITextModel, - positions: readonly Position[], - primaryEdit: SingleTextEdit, -): SingleTextEdit[] { +export function getSecondaryEdits(textModel: ITextModel, positions: readonly Position[], primaryEdit: SingleTextEdit): SingleTextEdit[] { if (positions.length === 1) { // No secondary cursor positions return []; } const primaryPosition = positions[0]; - const secondaryPositions = positions.slice(1); - const primaryEditStartPosition = primaryEdit.range.getStartPosition(); - const primaryEditEndPosition = primaryEdit.range.getEndPosition(); - const replacedTextAfterPrimaryCursor = textModel.getValueInRange( - Range.fromPositions(primaryPosition, primaryEditEndPosition), - ); - - const positionWithinTextEdit = subtractPositions( - primaryPosition, - primaryEditStartPosition, + Range.fromPositions(primaryPosition, primaryEditEndPosition) ); - + const positionWithinTextEdit = subtractPositions(primaryPosition, primaryEditStartPosition); if (positionWithinTextEdit.lineNumber < 1) { - onUnexpectedError( - new BugIndicatingError( - `positionWithinTextEdit line number should be bigger than 0. - Invalid subtraction between ${primaryPosition.toString()} and ${primaryEditStartPosition.toString()}`, - ), - ); - + onUnexpectedError(new BugIndicatingError( + `positionWithinTextEdit line number should be bigger than 0. + Invalid subtraction between ${primaryPosition.toString()} and ${primaryEditStartPosition.toString()}` + )); return []; } - const secondaryEditText = substringPos( - primaryEdit.text, - positionWithinTextEdit, - ); - - return secondaryPositions.map((pos) => { - const posEnd = addPositions( - subtractPositions(pos, primaryEditStartPosition), - primaryEditEndPosition, - ); - + const secondaryEditText = substringPos(primaryEdit.text, positionWithinTextEdit); + return secondaryPositions.map(pos => { + const posEnd = addPositions(subtractPositions(pos, primaryEditStartPosition), primaryEditEndPosition); const textAfterSecondaryCursor = textModel.getValueInRange( - Range.fromPositions(pos, posEnd), + Range.fromPositions(pos, posEnd) ); - - const l = commonPrefixLength( - replacedTextAfterPrimaryCursor, - textAfterSecondaryCursor, - ); - + const l = commonPrefixLength(replacedTextAfterPrimaryCursor, textAfterSecondaryCursor); const range = Range.fromPositions(pos, pos.delta(0, l)); - return new SingleTextEdit(range, secondaryEditText); }); } diff --git a/Source/vs/monaco.d.ts b/Source/vs/monaco.d.ts index 1075ec39246b6..e695161e33540 100644 --- a/Source/vs/monaco.d.ts +++ b/Source/vs/monaco.d.ts @@ -8887,7 +8887,14 @@ declare namespace monaco.languages { } export interface WorkspaceEdit { - edits: Array; + edits: Array; + } + + export interface ICustomEdit { + readonly resource: Uri; + readonly metadata?: WorkspaceEditMetadata; + undo(): Promise | void; + redo(): Promise | void; } export interface Rejection { diff --git a/Source/vs/platform/configuration/common/configurations.ts b/Source/vs/platform/configuration/common/configurations.ts index 55809c244b856..fb7962eefb7ee 100644 --- a/Source/vs/platform/configuration/common/configurations.ts +++ b/Source/vs/platform/configuration/common/configurations.ts @@ -2,26 +2,19 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { coalesce } from "../../../base/common/arrays.js"; -import { IStringDictionary } from "../../../base/common/collections.js"; -import { Emitter, Event } from "../../../base/common/event.js"; -import { Disposable } from "../../../base/common/lifecycle.js"; -import { equals } from "../../../base/common/objects.js"; -import { isEmptyObject } from "../../../base/common/types.js"; -import { ILogService, NullLogService } from "../../log/common/log.js"; -import { - IPolicyService, - PolicyDefinition, - PolicyName, - PolicyValue, -} from "../../policy/common/policy.js"; -import { Registry } from "../../registry/common/platform.js"; -import { ConfigurationModel } from "./configurationModels.js"; -import { - Extensions, - IConfigurationRegistry, - IRegisteredConfigurationPropertySchema, -} from "./configurationRegistry.js"; + +import { coalesce } from '../../../base/common/arrays.js'; +import { IStringDictionary } from '../../../base/common/collections.js'; +import { Emitter, Event } from '../../../base/common/event.js'; +import { Disposable } from '../../../base/common/lifecycle.js'; +import { equals } from '../../../base/common/objects.js'; +import { isEmptyObject, isString } from '../../../base/common/types.js'; +import { ConfigurationModel } from './configurationModels.js'; +import { Extensions, IConfigurationRegistry, IRegisteredConfigurationPropertySchema } from './configurationRegistry.js'; +import { ILogService, NullLogService } from '../../log/common/log.js'; +import { IPolicyService, PolicyDefinition, PolicyName } from '../../policy/common/policy.js'; +import { Registry } from '../../registry/common/platform.js'; +import { getErrorMessage } from '../../../base/common/errors.js'; export class DefaultConfiguration extends Disposable { private readonly _onDidChangeConfiguration = this._register( @@ -201,15 +194,12 @@ export class PolicyConfiguration continue; } if (config.policy) { - if (config.type !== "string" && config.type !== "number") { - this.logService.warn( - `Policy ${config.policy.name} has unsupported type ${config.type}`, - ); - + if (config.type !== 'string' && config.type !== 'number' && config.type !== 'array' && config.type !== 'object') { + this.logService.warn(`Policy ${config.policy.name} has unsupported type ${config.type}`); continue; } keys.push(key); - policyDefinitions[config.policy.name] = { type: config.type }; + policyDefinitions[config.policy.name] = { type: config.type === 'number' ? 'number' : 'string' }; } } if (!isEmptyObject(policyDefinitions)) { @@ -235,31 +225,25 @@ export class PolicyConfiguration this.update(keys, true); } private update(keys: string[], trigger: boolean): void { - this.logService.trace("PolicyConfiguration#update", keys); - - const configurationProperties = Registry.as( - Extensions.Configuration, - ).getConfigurationProperties(); - - const changed: [string, PolicyValue | undefined][] = []; - + this.logService.trace('PolicyConfiguration#update', keys); + const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); + const changed: [string, any][] = []; const wasEmpty = this._configurationModel.isEmpty(); for (const key of keys) { - const policyName = configurationProperties[key]?.policy?.name; - + const proprety = configurationProperties[key]; + const policyName = proprety?.policy?.name; if (policyName) { - const policyValue = - this.policyService.getPolicyValue(policyName); - - if ( - wasEmpty - ? policyValue !== undefined - : !equals( - this._configurationModel.getValue(key), - policyValue, - ) - ) { + let policyValue = this.policyService.getPolicyValue(policyName); + if (isString(policyValue) && proprety.type !== 'string') { + try { + policyValue = JSON.parse(policyValue); + } catch (e) { + this.logService.error(`Error parsing policy value ${policyName}:`, getErrorMessage(e)); + continue; + } + } + if (wasEmpty ? policyValue !== undefined : !equals(this._configurationModel.getValue(key), policyValue)) { changed.push([key, policyValue]); } } else { diff --git a/Source/vs/platform/dnd/browser/dnd.ts b/Source/vs/platform/dnd/browser/dnd.ts index 35b875b618877..b58fcb133fadb 100644 --- a/Source/vs/platform/dnd/browser/dnd.ts +++ b/Source/vs/platform/dnd/browser/dnd.ts @@ -2,29 +2,28 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DataTransfers } from "../../../base/browser/dnd.js"; -import { DragMouseEvent } from "../../../base/browser/mouseEvent.js"; -import { mainWindow } from "../../../base/browser/window.js"; -import { coalesce } from "../../../base/common/arrays.js"; -import { DeferredPromise } from "../../../base/common/async.js"; -import { VSBuffer } from "../../../base/common/buffer.js"; -import { ResourceMap } from "../../../base/common/map.js"; -import { parse } from "../../../base/common/marshalling.js"; -import { Schemas } from "../../../base/common/network.js"; -import { isNative, isWeb } from "../../../base/common/platform.js"; -import { URI } from "../../../base/common/uri.js"; -import { localize } from "../../../nls.js"; -import { IDialogService } from "../../dialogs/common/dialogs.js"; -import { IBaseTextResourceEditorInput } from "../../editor/common/editor.js"; -import { HTMLFileSystemProvider } from "../../files/browser/htmlFileSystemProvider.js"; -import { WebFileSystemAccess } from "../../files/browser/webFileSystemAccess.js"; -import { ByteSize, IFileService } from "../../files/common/files.js"; -import { - IInstantiationService, - ServicesAccessor, -} from "../../instantiation/common/instantiation.js"; -import { extractSelection } from "../../opener/common/opener.js"; -import { Registry } from "../../registry/common/platform.js"; + +import { DataTransfers } from '../../../base/browser/dnd.js'; +import { mainWindow } from '../../../base/browser/window.js'; +import { DragMouseEvent } from '../../../base/browser/mouseEvent.js'; +import { coalesce } from '../../../base/common/arrays.js'; +import { DeferredPromise } from '../../../base/common/async.js'; +import { VSBuffer } from '../../../base/common/buffer.js'; +import { ResourceMap } from '../../../base/common/map.js'; +import { parse } from '../../../base/common/marshalling.js'; +import { Schemas } from '../../../base/common/network.js'; +import { isNative, isWeb } from '../../../base/common/platform.js'; +import { URI } from '../../../base/common/uri.js'; +import { localize } from '../../../nls.js'; +import { IDialogService } from '../../dialogs/common/dialogs.js'; +import { IBaseTextResourceEditorInput, ITextEditorSelection } from '../../editor/common/editor.js'; +import { HTMLFileSystemProvider } from '../../files/browser/htmlFileSystemProvider.js'; +import { WebFileSystemAccess } from '../../files/browser/webFileSystemAccess.js'; +import { ByteSize, IFileService } from '../../files/common/files.js'; +import { IInstantiationService, ServicesAccessor } from '../../instantiation/common/instantiation.js'; +import { extractSelection } from '../../opener/common/opener.js'; +import { Registry } from '../../registry/common/platform.js'; + //#region Editor / Resources DND export const CodeDataTransfers = { @@ -363,8 +362,9 @@ export function containsDragType( } //#region DND contributions export interface IResourceStat { - resource: URI; - isDirectory?: boolean; + readonly resource: URI; + readonly isDirectory?: boolean; + readonly selection?: ITextEditorSelection; } export interface IDragAndDropContributionRegistry { /** @@ -477,6 +477,10 @@ export function extractSymbolDropData( return []; } +export function fillInSymbolsDragData(symbolsData: readonly DocumentSymbolTransferData[], e: DragEvent): void { + e.dataTransfer?.setData(CodeDataTransfers.SYMBOLS, JSON.stringify(symbolsData)); +} + /** * A helper to get access to Electrons `webUtils.getPathForFile` function * in a safe way without crashing the application when running in the web. diff --git a/Source/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/Source/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index 8972cbc45866b..d78d2ef9c7332 100644 --- a/Source/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/Source/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -3,90 +3,40 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { distinct, isNonEmptyArray } from "../../../base/common/arrays.js"; +import { distinct, isNonEmptyArray } from '../../../base/common/arrays.js'; +import { Barrier, CancelablePromise, createCancelablePromise } from '../../../base/common/async.js'; +import { CancellationToken } from '../../../base/common/cancellation.js'; +import { CancellationError, getErrorMessage, isCancellationError } from '../../../base/common/errors.js'; +import { Emitter, Event } from '../../../base/common/event.js'; +import { Disposable, toDisposable } from '../../../base/common/lifecycle.js'; +import { ResourceMap } from '../../../base/common/map.js'; +import { isWeb } from '../../../base/common/platform.js'; +import { URI } from '../../../base/common/uri.js'; +import * as nls from '../../../nls.js'; import { - Barrier, - CancelablePromise, - createCancelablePromise, -} from "../../../base/common/async.js"; -import { CancellationToken } from "../../../base/common/cancellation.js"; -import { - CancellationError, - getErrorMessage, - isCancellationError, -} from "../../../base/common/errors.js"; -import { Emitter, Event } from "../../../base/common/event.js"; -import { - IMarkdownString, - MarkdownString, -} from "../../../base/common/htmlContent.js"; -import { Disposable, toDisposable } from "../../../base/common/lifecycle.js"; -import { ResourceMap } from "../../../base/common/map.js"; -import { isWeb } from "../../../base/common/platform.js"; -import { URI } from "../../../base/common/uri.js"; -import * as nls from "../../../nls.js"; -import { - ExtensionType, - IExtensionManifest, - isApplicationScopedExtension, - TargetPlatform, -} from "../../extensions/common/extensions.js"; -import { areApiProposalsCompatible } from "../../extensions/common/extensionValidator.js"; -import { ILogService } from "../../log/common/log.js"; -import { IProductService } from "../../product/common/productService.js"; -import { ITelemetryService } from "../../telemetry/common/telemetry.js"; -import { IUriIdentityService } from "../../uriIdentity/common/uriIdentity.js"; -import { IUserDataProfilesService } from "../../userDataProfile/common/userDataProfile.js"; -import { - DidUninstallExtensionEvent, - DidUpdateExtensionMetadata, - EXTENSION_INSTALL_DEP_PACK_CONTEXT, + ExtensionManagementError, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementParticipant, IGalleryExtension, ILocalExtension, InstallOperation, + IExtensionsControlManifest, StatisticType, isTargetPlatformCompatible, TargetPlatformToString, ExtensionManagementErrorCode, + InstallOptions, UninstallOptions, Metadata, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, IExtensionManagementService, InstallExtensionInfo, EXTENSION_INSTALL_DEP_PACK_CONTEXT, ExtensionGalleryError, + IProductVersion, ExtensionGalleryErrorCode, EXTENSION_INSTALL_SOURCE_CONTEXT, - ExtensionGalleryError, - ExtensionGalleryErrorCode, - ExtensionManagementError, - ExtensionManagementErrorCode, - ExtensionSignatureVerificationCode, - IAllowedExtensionsService, - IExtensionGalleryService, - IExtensionIdentifier, - IExtensionManagementParticipant, - IExtensionManagementService, - IExtensionsControlManifest, - IGalleryExtension, - ILocalExtension, - InstallExtensionEvent, - InstallExtensionInfo, - InstallExtensionResult, - InstallOperation, - InstallOptions, - IProductVersion, - isTargetPlatformCompatible, - Metadata, - StatisticType, - TargetPlatformToString, - UninstallExtensionEvent, + DidUpdateExtensionMetadata, UninstallExtensionInfo, - UninstallOptions, -} from "./extensionManagement.js"; -import { - areSameExtensions, - ExtensionKey, - getGalleryExtensionId, - getGalleryExtensionTelemetryData, - getLocalExtensionTelemetryData, -} from "./extensionManagementUtil.js"; - -export type InstallableExtension = { - readonly manifest: IExtensionManifest; - extension: IGalleryExtension | URI; - options: InstallOptions; -}; - -export type InstallExtensionTaskOptions = InstallOptions & { - readonly profileLocation: URI; - readonly productVersion: IProductVersion; -}; + ExtensionSignatureVerificationCode, + IAllowedExtensionsService +} from './extensionManagement.js'; +import { areSameExtensions, ExtensionKey, getGalleryExtensionId, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from './extensionManagementUtil.js'; +import { ExtensionType, IExtensionManifest, isApplicationScopedExtension, TargetPlatform } from '../../extensions/common/extensions.js'; +import { areApiProposalsCompatible } from '../../extensions/common/extensionValidator.js'; +import { ILogService } from '../../log/common/log.js'; +import { IProductService } from '../../product/common/productService.js'; +import { ITelemetryService } from '../../telemetry/common/telemetry.js'; +import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js'; +import { IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js'; +import { IMarkdownString, MarkdownString } from '../../../base/common/htmlContent.js'; + +export type InstallableExtension = { readonly manifest: IExtensionManifest; extension: IGalleryExtension | URI; options: InstallOptions }; + +export type InstallExtensionTaskOptions = InstallOptions & { readonly profileLocation: URI; readonly productVersion: IProductVersion }; export interface IInstallExtensionTask { readonly manifest: IExtensionManifest; readonly identifier: IExtensionIdentifier; @@ -99,9 +49,7 @@ export interface IInstallExtensionTask { cancel(): void; } -export type UninstallExtensionTaskOptions = UninstallOptions & { - readonly profileLocation: URI; -}; +export type UninstallExtensionTaskOptions = UninstallOptions & { readonly profileLocation: URI }; export interface IUninstallExtensionTask { readonly options: UninstallExtensionTaskOptions; readonly extension: ILocalExtension; @@ -110,415 +58,192 @@ export interface IUninstallExtensionTask { cancel(): void; } -export abstract class CommontExtensionManagementService - extends Disposable - implements IExtensionManagementService -{ +export abstract class CommontExtensionManagementService extends Disposable implements IExtensionManagementService { + _serviceBrand: undefined; constructor( @IProductService protected readonly productService: IProductService, - @IAllowedExtensionsService - protected readonly allowedExtensionsService: IAllowedExtensionsService, + @IAllowedExtensionsService protected readonly allowedExtensionsService: IAllowedExtensionsService, ) { super(); } - async canInstall( - extension: IGalleryExtension, - ): Promise { - const allowedToInstall = this.allowedExtensionsService.isAllowed({ - id: extension.identifier.id, - }); + async canInstall(extension: IGalleryExtension): Promise { + const allowedToInstall = this.allowedExtensionsService.isAllowed({ id: extension.identifier.id, publisherDisplayName: extension.publisherDisplayName }); if (allowedToInstall !== true) { - return new MarkdownString( - nls.localize( - "not allowed to install", - "This extension cannot be installed because {0}", - allowedToInstall.value, - ), - ); + return new MarkdownString(nls.localize('not allowed to install', "This extension cannot be installed because {0}", allowedToInstall.value)); } if (!(await this.isExtensionPlatformCompatible(extension))) { - const productName = isWeb - ? nls.localize( - "VS Code for Web", - "{0} for the Web", - this.productService.nameLong, - ) - : this.productService.nameLong; - const learnLink = isWeb - ? "https://aka.ms/vscode-web-extensions-guide" - : "https://aka.ms/vscode-platform-specific-extensions"; - return new MarkdownString( - `${nls.localize( - "incompatible platform", - "The '{0}' extension is not available in {1} for {2}.", - extension.displayName ?? extension.identifier.id, - productName, - TargetPlatformToString(await this.getTargetPlatform()), - )} [${nls.localize("learn why", "Learn Why")}](${learnLink})`, - ); + const productName = isWeb ? nls.localize('VS Code for Web', "{0} for the Web", this.productService.nameLong) : this.productService.nameLong; + const learnLink = isWeb ? 'https://aka.ms/vscode-web-extensions-guide' : 'https://aka.ms/vscode-platform-specific-extensions'; + return new MarkdownString(`${nls.localize('incompatible platform', "The '{0}' extension is not available in {1} for {2}.", + extension.displayName ?? extension.identifier.id, productName, TargetPlatformToString(await this.getTargetPlatform()))} [${nls.localize('learn why', "Learn Why")}](${learnLink})`); } return true; } - protected async isExtensionPlatformCompatible( - extension: IGalleryExtension, - ): Promise { + protected async isExtensionPlatformCompatible(extension: IGalleryExtension): Promise { const currentTargetPlatform = await this.getTargetPlatform(); - return extension.allTargetPlatforms.some((targetPlatform) => - isTargetPlatformCompatible( - targetPlatform, - extension.allTargetPlatforms, - currentTargetPlatform, - ), - ); + return extension.allTargetPlatforms.some(targetPlatform => isTargetPlatformCompatible(targetPlatform, extension.allTargetPlatforms, currentTargetPlatform)); } abstract readonly onInstallExtension: Event; - abstract readonly onDidInstallExtensions: Event< - readonly InstallExtensionResult[] - >; + abstract readonly onDidInstallExtensions: Event; abstract readonly onUninstallExtension: Event; abstract readonly onDidUninstallExtension: Event; abstract readonly onDidUpdateExtensionMetadata: Event; - abstract installFromGallery( - extension: IGalleryExtension, - options?: InstallOptions, - ): Promise; - abstract installGalleryExtensions( - extensions: InstallExtensionInfo[], - ): Promise; - abstract uninstall( - extension: ILocalExtension, - options?: UninstallOptions, - ): Promise; - abstract uninstallExtensions( - extensions: UninstallExtensionInfo[], - ): Promise; - abstract toggleAppliationScope( - extension: ILocalExtension, - fromProfileLocation: URI, - ): Promise; + abstract installFromGallery(extension: IGalleryExtension, options?: InstallOptions): Promise; + abstract installGalleryExtensions(extensions: InstallExtensionInfo[]): Promise; + abstract uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise; + abstract uninstallExtensions(extensions: UninstallExtensionInfo[]): Promise; + abstract toggleAppliationScope(extension: ILocalExtension, fromProfileLocation: URI): Promise; abstract getExtensionsControlManifest(): Promise; - abstract resetPinnedStateForAllUserExtensions( - pinned: boolean, - ): Promise; - abstract registerParticipant( - pariticipant: IExtensionManagementParticipant, - ): void; + abstract resetPinnedStateForAllUserExtensions(pinned: boolean): Promise; + abstract registerParticipant(pariticipant: IExtensionManagementParticipant): void; abstract getTargetPlatform(): Promise; abstract zip(extension: ILocalExtension): Promise; abstract getManifest(vsix: URI): Promise; - abstract install( - vsix: URI, - options?: InstallOptions, - ): Promise; - abstract installFromLocation( - location: URI, - profileLocation: URI, - ): Promise; - abstract installExtensionsFromProfile( - extensions: IExtensionIdentifier[], - fromProfileLocation: URI, - toProfileLocation: URI, - ): Promise; - abstract getInstalled( - type?: ExtensionType, - profileLocation?: URI, - productVersion?: IProductVersion, - ): Promise; - abstract copyExtensions( - fromProfileLocation: URI, - toProfileLocation: URI, - ): Promise; - abstract download( - extension: IGalleryExtension, - operation: InstallOperation, - donotVerifySignature: boolean, - ): Promise; - abstract reinstallFromGallery( - extension: ILocalExtension, - ): Promise; + abstract install(vsix: URI, options?: InstallOptions): Promise; + abstract installFromLocation(location: URI, profileLocation: URI): Promise; + abstract installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise; + abstract getInstalled(type?: ExtensionType, profileLocation?: URI, productVersion?: IProductVersion): Promise; + abstract copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise; + abstract download(extension: IGalleryExtension, operation: InstallOperation, donotVerifySignature: boolean): Promise; + abstract reinstallFromGallery(extension: ILocalExtension): Promise; abstract cleanUp(): Promise; - abstract updateMetadata( - local: ILocalExtension, - metadata: Partial, - profileLocation: URI, - ): Promise; + abstract updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation: URI): Promise; } -export abstract class AbstractExtensionManagementService - extends CommontExtensionManagementService - implements IExtensionManagementService -{ +export abstract class AbstractExtensionManagementService extends CommontExtensionManagementService implements IExtensionManagementService { + declare readonly _serviceBrand: undefined; - private extensionsControlManifest: - | Promise - | undefined; + private extensionsControlManifest: Promise | undefined; private lastReportTimestamp = 0; - private readonly installingExtensions = new Map< - string, - { task: IInstallExtensionTask; waitingTasks: IInstallExtensionTask[] } - >(); - private readonly uninstallingExtensions = new Map< - string, - IUninstallExtensionTask - >(); - - private readonly _onInstallExtension = this._register( - new Emitter(), - ); - get onInstallExtension() { - return this._onInstallExtension.event; - } + private readonly installingExtensions = new Map(); + private readonly uninstallingExtensions = new Map(); - protected readonly _onDidInstallExtensions = this._register( - new Emitter(), - ); - get onDidInstallExtensions() { - return this._onDidInstallExtensions.event; - } + private readonly _onInstallExtension = this._register(new Emitter()); + get onInstallExtension() { return this._onInstallExtension.event; } - protected readonly _onUninstallExtension = this._register( - new Emitter(), - ); - get onUninstallExtension() { - return this._onUninstallExtension.event; - } + protected readonly _onDidInstallExtensions = this._register(new Emitter()); + get onDidInstallExtensions() { return this._onDidInstallExtensions.event; } - protected _onDidUninstallExtension = this._register( - new Emitter(), - ); - get onDidUninstallExtension() { - return this._onDidUninstallExtension.event; - } + protected readonly _onUninstallExtension = this._register(new Emitter()); + get onUninstallExtension() { return this._onUninstallExtension.event; } - protected readonly _onDidUpdateExtensionMetadata = this._register( - new Emitter(), - ); - get onDidUpdateExtensionMetadata() { - return this._onDidUpdateExtensionMetadata.event; - } + protected _onDidUninstallExtension = this._register(new Emitter()); + get onDidUninstallExtension() { return this._onDidUninstallExtension.event; } + + protected readonly _onDidUpdateExtensionMetadata = this._register(new Emitter()); + get onDidUpdateExtensionMetadata() { return this._onDidUpdateExtensionMetadata.event; } private readonly participants: IExtensionManagementParticipant[] = []; constructor( - @IExtensionGalleryService - protected readonly galleryService: IExtensionGalleryService, - @ITelemetryService - protected readonly telemetryService: ITelemetryService, - @IUriIdentityService - protected readonly uriIdentityService: IUriIdentityService, + @IExtensionGalleryService protected readonly galleryService: IExtensionGalleryService, + @ITelemetryService protected readonly telemetryService: ITelemetryService, + @IUriIdentityService protected readonly uriIdentityService: IUriIdentityService, @ILogService protected readonly logService: ILogService, @IProductService productService: IProductService, - @IAllowedExtensionsService - allowedExtensionsService: IAllowedExtensionsService, - @IUserDataProfilesService - protected readonly userDataProfilesService: IUserDataProfilesService, + @IAllowedExtensionsService allowedExtensionsService: IAllowedExtensionsService, + @IUserDataProfilesService protected readonly userDataProfilesService: IUserDataProfilesService, ) { super(productService, allowedExtensionsService); - this._register( - toDisposable(() => { - this.installingExtensions.forEach(({ task }) => task.cancel()); - this.uninstallingExtensions.forEach((promise) => - promise.cancel(), - ); - this.installingExtensions.clear(); - this.uninstallingExtensions.clear(); - }), - ); + this._register(toDisposable(() => { + this.installingExtensions.forEach(({ task }) => task.cancel()); + this.uninstallingExtensions.forEach(promise => promise.cancel()); + this.installingExtensions.clear(); + this.uninstallingExtensions.clear(); + })); } - async installFromGallery( - extension: IGalleryExtension, - options: InstallOptions = {}, - ): Promise { + async installFromGallery(extension: IGalleryExtension, options: InstallOptions = {}): Promise { try { - const results = await this.installGalleryExtensions([ - { extension, options }, - ]); - const result = results.find(({ identifier }) => - areSameExtensions(identifier, extension.identifier), - ); + const results = await this.installGalleryExtensions([{ extension, options }]); + const result = results.find(({ identifier }) => areSameExtensions(identifier, extension.identifier)); if (result?.local) { return result?.local; } if (result?.error) { throw result.error; } - throw new ExtensionManagementError( - `Unknown error while installing extension ${extension.identifier.id}`, - ExtensionManagementErrorCode.Unknown, - ); + throw new ExtensionManagementError(`Unknown error while installing extension ${extension.identifier.id}`, ExtensionManagementErrorCode.Unknown); } catch (error) { throw toExtensionManagementError(error); } } - async installGalleryExtensions( - extensions: InstallExtensionInfo[], - ): Promise { + async installGalleryExtensions(extensions: InstallExtensionInfo[]): Promise { if (!this.galleryService.isEnabled()) { - throw new ExtensionManagementError( - nls.localize( - "MarketPlaceDisabled", - "Marketplace is not enabled", - ), - ExtensionManagementErrorCode.NotAllowed, - ); + throw new ExtensionManagementError(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"), ExtensionManagementErrorCode.NotAllowed); } const results: InstallExtensionResult[] = []; const installableExtensions: InstallableExtension[] = []; - await Promise.allSettled( - extensions.map(async ({ extension, options }) => { - try { - const compatible = await this.checkAndGetCompatibleVersion( - extension, - !!options?.installGivenVersion, - !!options?.installPreReleaseVersion, - options.productVersion ?? { - version: this.productService.version, - date: this.productService.date, - }, - ); - installableExtensions.push({ ...compatible, options }); - } catch (error) { - results.push({ - identifier: extension.identifier, - operation: InstallOperation.Install, - source: extension, - error, - profileLocation: - options.profileLocation ?? - this.getCurrentExtensionsManifestLocation(), - }); - } - }), - ); + await Promise.allSettled(extensions.map(async ({ extension, options }) => { + try { + const compatible = await this.checkAndGetCompatibleVersion(extension, !!options?.installGivenVersion, !!options?.installPreReleaseVersion, options.productVersion ?? { version: this.productService.version, date: this.productService.date }); + installableExtensions.push({ ...compatible, options }); + } catch (error) { + results.push({ identifier: extension.identifier, operation: InstallOperation.Install, source: extension, error, profileLocation: options.profileLocation ?? this.getCurrentExtensionsManifestLocation() }); + } + })); if (installableExtensions.length) { - results.push( - ...(await this.installExtensions(installableExtensions)), - ); + results.push(...await this.installExtensions(installableExtensions)); } return results; } - async uninstall( - extension: ILocalExtension, - options?: UninstallOptions, - ): Promise { - this.logService.trace( - "ExtensionManagementService#uninstall", - extension.identifier.id, - ); + async uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise { + this.logService.trace('ExtensionManagementService#uninstall', extension.identifier.id); return this.uninstallExtensions([{ extension, options }]); } - async toggleAppliationScope( - extension: ILocalExtension, - fromProfileLocation: URI, - ): Promise { - if ( - isApplicationScopedExtension(extension.manifest) || - extension.isBuiltin - ) { + async toggleAppliationScope(extension: ILocalExtension, fromProfileLocation: URI): Promise { + if (isApplicationScopedExtension(extension.manifest) || extension.isBuiltin) { return extension; } if (extension.isApplicationScoped) { - let local = await this.updateMetadata( - extension, - { isApplicationScoped: false }, - this.userDataProfilesService.defaultProfile.extensionsResource, - ); - if ( - !this.uriIdentityService.extUri.isEqual( - fromProfileLocation, - this.userDataProfilesService.defaultProfile - .extensionsResource, - ) - ) { - local = await this.copyExtension( - extension, - this.userDataProfilesService.defaultProfile - .extensionsResource, - fromProfileLocation, - ); + let local = await this.updateMetadata(extension, { isApplicationScoped: false }, this.userDataProfilesService.defaultProfile.extensionsResource); + if (!this.uriIdentityService.extUri.isEqual(fromProfileLocation, this.userDataProfilesService.defaultProfile.extensionsResource)) { + local = await this.copyExtension(extension, this.userDataProfilesService.defaultProfile.extensionsResource, fromProfileLocation); } for (const profile of this.userDataProfilesService.profiles) { - const existing = ( - await this.getInstalled( - ExtensionType.User, - profile.extensionsResource, - ) - ).find((e) => - areSameExtensions(e.identifier, extension.identifier), - ); + const existing = (await this.getInstalled(ExtensionType.User, profile.extensionsResource)) + .find(e => areSameExtensions(e.identifier, extension.identifier)); if (existing) { - this._onDidUpdateExtensionMetadata.fire({ - local: existing, - profileLocation: profile.extensionsResource, - }); + this._onDidUpdateExtensionMetadata.fire({ local: existing, profileLocation: profile.extensionsResource }); } else { - this._onDidUninstallExtension.fire({ - identifier: extension.identifier, - profileLocation: profile.extensionsResource, - }); + this._onDidUninstallExtension.fire({ identifier: extension.identifier, profileLocation: profile.extensionsResource }); } } return local; - } else { - const local = this.uriIdentityService.extUri.isEqual( - fromProfileLocation, - this.userDataProfilesService.defaultProfile.extensionsResource, - ) - ? await this.updateMetadata( - extension, - { isApplicationScoped: true }, - this.userDataProfilesService.defaultProfile - .extensionsResource, - ) - : await this.copyExtension( - extension, - fromProfileLocation, - this.userDataProfilesService.defaultProfile - .extensionsResource, - { isApplicationScoped: true }, - ); - - this._onDidInstallExtensions.fire([ - { - identifier: local.identifier, - operation: InstallOperation.Install, - local, - profileLocation: - this.userDataProfilesService.defaultProfile - .extensionsResource, - applicationScoped: true, - }, - ]); + } + + else { + const local = this.uriIdentityService.extUri.isEqual(fromProfileLocation, this.userDataProfilesService.defaultProfile.extensionsResource) + ? await this.updateMetadata(extension, { isApplicationScoped: true }, this.userDataProfilesService.defaultProfile.extensionsResource) + : await this.copyExtension(extension, fromProfileLocation, this.userDataProfilesService.defaultProfile.extensionsResource, { isApplicationScoped: true }); + + this._onDidInstallExtensions.fire([{ identifier: local.identifier, operation: InstallOperation.Install, local, profileLocation: this.userDataProfilesService.defaultProfile.extensionsResource, applicationScoped: true }]); return local; } + } getExtensionsControlManifest(): Promise { const now = new Date().getTime(); - if ( - !this.extensionsControlManifest || - now - this.lastReportTimestamp > 1000 * 60 * 5 - ) { - // 5 minute cache freshness + if (!this.extensionsControlManifest || now - this.lastReportTimestamp > 1000 * 60 * 5) { // 5 minute cache freshness this.extensionsControlManifest = this.updateControlCache(); this.lastReportTimestamp = now; } @@ -532,448 +257,173 @@ export abstract class AbstractExtensionManagementService async resetPinnedStateForAllUserExtensions(pinned: boolean): Promise { try { - await this.joinAllSettled( - this.userDataProfilesService.profiles.map(async (profile) => { - const extensions = await this.getInstalled( - ExtensionType.User, - profile.extensionsResource, - ); - await this.joinAllSettled( - extensions.map(async (extension) => { + await this.joinAllSettled(this.userDataProfilesService.profiles.map( + async profile => { + const extensions = await this.getInstalled(ExtensionType.User, profile.extensionsResource); + await this.joinAllSettled(extensions.map( + async extension => { if (extension.pinned !== pinned) { - await this.updateMetadata( - extension, - { pinned }, - profile.extensionsResource, - ); + await this.updateMetadata(extension, { pinned }, profile.extensionsResource); } - }), - ); - }), - ); + })); + })); } catch (error) { - this.logService.error( - "Error while resetting pinned state for all user extensions", - getErrorMessage(error), - ); + this.logService.error('Error while resetting pinned state for all user extensions', getErrorMessage(error)); throw error; } } - protected async installExtensions( - extensions: InstallableExtension[], - ): Promise { - const installExtensionResultsMap = new Map< - string, - InstallExtensionResult & { profileLocation: URI } - >(); - const installingExtensionsMap = new Map< - string, - { - task: IInstallExtensionTask; - root: IInstallExtensionTask | undefined; - } - >(); + protected async installExtensions(extensions: InstallableExtension[]): Promise { + const installExtensionResultsMap = new Map(); + const installingExtensionsMap = new Map(); const alreadyRequestedInstallations: Promise[] = []; - const getInstallExtensionTaskKey = ( - extension: IGalleryExtension, - profileLocation: URI, - ) => - `${ExtensionKey.create(extension).toString()}-${profileLocation.toString()}`; - const createInstallExtensionTask = ( - manifest: IExtensionManifest, - extension: IGalleryExtension | URI, - options: InstallExtensionTaskOptions, - root: IInstallExtensionTask | undefined, - ): void => { + const getInstallExtensionTaskKey = (extension: IGalleryExtension, profileLocation: URI) => `${ExtensionKey.create(extension).toString()}-${profileLocation.toString()}`; + const createInstallExtensionTask = (manifest: IExtensionManifest, extension: IGalleryExtension | URI, options: InstallExtensionTaskOptions, root: IInstallExtensionTask | undefined): void => { if (!URI.isUri(extension)) { - if ( - installingExtensionsMap.has( - `${extension.identifier.id.toLowerCase()}-${options.profileLocation.toString()}`, - ) - ) { + if (installingExtensionsMap.has(`${extension.identifier.id.toLowerCase()}-${options.profileLocation.toString()}`)) { return; } - const existingInstallingExtension = - this.installingExtensions.get( - getInstallExtensionTaskKey( - extension, - options.profileLocation, - ), - ); + const existingInstallingExtension = this.installingExtensions.get(getInstallExtensionTaskKey(extension, options.profileLocation)); if (existingInstallingExtension) { - if ( - root && - this.canWaitForTask( - root, - existingInstallingExtension.task, - ) - ) { - const identifier = - existingInstallingExtension.task.identifier; - this.logService.info( - "Waiting for already requested installing extension", - identifier.id, - root.identifier.id, - options.profileLocation.toString(), - ); + if (root && this.canWaitForTask(root, existingInstallingExtension.task)) { + const identifier = existingInstallingExtension.task.identifier; + this.logService.info('Waiting for already requested installing extension', identifier.id, root.identifier.id, options.profileLocation.toString()); existingInstallingExtension.waitingTasks.push(root); // add promise that waits until the extension is completely installed, ie., onDidInstallExtensions event is triggered for this extension alreadyRequestedInstallations.push( Event.toPromise( - Event.filter( - this.onDidInstallExtensions, - (results) => - results.some((result) => - areSameExtensions( - result.identifier, - identifier, - ), - ), - ), - ).then((results) => { - this.logService.info( - "Finished waiting for already requested installing extension", - identifier.id, - root.identifier.id, - options.profileLocation.toString(), - ); - const result = results.find((result) => - areSameExtensions( - result.identifier, - identifier, - ), - ); + Event.filter(this.onDidInstallExtensions, results => results.some(result => areSameExtensions(result.identifier, identifier))) + ).then(results => { + this.logService.info('Finished waiting for already requested installing extension', identifier.id, root.identifier.id, options.profileLocation.toString()); + const result = results.find(result => areSameExtensions(result.identifier, identifier)); if (!result?.local) { // Extension failed to install - throw new Error( - `Extension ${identifier.id} is not installed`, - ); + throw new Error(`Extension ${identifier.id} is not installed`); } - }), - ); + })); } return; } } - const installExtensionTask = this.createInstallExtensionTask( - manifest, - extension, - options, - ); + const installExtensionTask = this.createInstallExtensionTask(manifest, extension, options); const key = `${getGalleryExtensionId(manifest.publisher, manifest.name)}-${options.profileLocation.toString()}`; - installingExtensionsMap.set(key, { - task: installExtensionTask, - root, - }); - this._onInstallExtension.fire({ - identifier: installExtensionTask.identifier, - source: extension, - profileLocation: options.profileLocation, - }); - this.logService.info( - "Installing extension:", - installExtensionTask.identifier.id, - options, - ); + installingExtensionsMap.set(key, { task: installExtensionTask, root }); + this._onInstallExtension.fire({ identifier: installExtensionTask.identifier, source: extension, profileLocation: options.profileLocation }); + this.logService.info('Installing extension:', installExtensionTask.identifier.id, options); // only cache gallery extensions tasks if (!URI.isUri(extension)) { - this.installingExtensions.set( - getInstallExtensionTaskKey( - extension, - options.profileLocation, - ), - { task: installExtensionTask, waitingTasks: [] }, - ); + this.installingExtensions.set(getInstallExtensionTaskKey(extension, options.profileLocation), { task: installExtensionTask, waitingTasks: [] }); } }; try { // Start installing extensions for (const { manifest, extension, options } of extensions) { - const isApplicationScoped = - options.isApplicationScoped || - options.isBuiltin || - isApplicationScopedExtension(manifest); - const installExtensionTaskOptions: InstallExtensionTaskOptions = - { - ...options, - installOnlyNewlyAddedFromExtensionPack: - options.installOnlyNewlyAddedFromExtensionPack ?? - !URI.isUri( - extension, - ) /* always true for gallery extensions */, - isApplicationScoped, - profileLocation: isApplicationScoped - ? this.userDataProfilesService.defaultProfile - .extensionsResource - : (options.profileLocation ?? - this.getCurrentExtensionsManifestLocation()), - productVersion: options.productVersion ?? { - version: this.productService.version, - date: this.productService.date, - }, - }; - - const existingInstallExtensionTask = !URI.isUri(extension) - ? this.installingExtensions.get( - getInstallExtensionTaskKey( - extension, - installExtensionTaskOptions.profileLocation, - ), - ) - : undefined; + const isApplicationScoped = options.isApplicationScoped || options.isBuiltin || isApplicationScopedExtension(manifest); + const installExtensionTaskOptions: InstallExtensionTaskOptions = { + ...options, + installOnlyNewlyAddedFromExtensionPack: options.installOnlyNewlyAddedFromExtensionPack ?? !URI.isUri(extension) /* always true for gallery extensions */, + isApplicationScoped, + profileLocation: isApplicationScoped ? this.userDataProfilesService.defaultProfile.extensionsResource : options.profileLocation ?? this.getCurrentExtensionsManifestLocation(), + productVersion: options.productVersion ?? { version: this.productService.version, date: this.productService.date } + }; + + const existingInstallExtensionTask = !URI.isUri(extension) ? this.installingExtensions.get(getInstallExtensionTaskKey(extension, installExtensionTaskOptions.profileLocation)) : undefined; if (existingInstallExtensionTask) { - this.logService.info( - "Extension is already requested to install", - existingInstallExtensionTask.task.identifier.id, - installExtensionTaskOptions.profileLocation.toString(), - ); - alreadyRequestedInstallations.push( - existingInstallExtensionTask.task.waitUntilTaskIsFinished(), - ); + this.logService.info('Extension is already requested to install', existingInstallExtensionTask.task.identifier.id, installExtensionTaskOptions.profileLocation.toString()); + alreadyRequestedInstallations.push(existingInstallExtensionTask.task.waitUntilTaskIsFinished()); } else { - createInstallExtensionTask( - manifest, - extension, - installExtensionTaskOptions, - undefined, - ); + createInstallExtensionTask(manifest, extension, installExtensionTaskOptions, undefined); } } // collect and start installing all dependencies and pack extensions - await Promise.all( - [...installingExtensionsMap.values()].map(async ({ task }) => { - if (task.options.donotIncludePackAndDependencies) { - this.logService.info( - "Installing the extension without checking dependencies and pack", - task.identifier.id, - ); - } else { - try { - const allDepsAndPackExtensionsToInstall = - await this.getAllDepsAndPackExtensions( - task.identifier, - task.manifest, - !!task.options - .installOnlyNewlyAddedFromExtensionPack, - !!task.options.installPreReleaseVersion, - task.options.profileLocation, - task.options.productVersion, - ); - const installed = await this.getInstalled( - undefined, - task.options.profileLocation, - task.options.productVersion, - ); - const options: InstallExtensionTaskOptions = { - ...task.options, - context: { - ...task.options.context, - [EXTENSION_INSTALL_DEP_PACK_CONTEXT]: true, - }, - }; - for (const { gallery, manifest } of distinct( - allDepsAndPackExtensionsToInstall, - ({ gallery }) => gallery.identifier.id, - )) { - if ( - installed.some(({ identifier }) => - areSameExtensions( - identifier, - gallery.identifier, - ), - ) - ) { - continue; - } - createInstallExtensionTask( - manifest, - gallery, - options, - task, - ); + await Promise.all([...installingExtensionsMap.values()].map(async ({ task }) => { + if (task.options.donotIncludePackAndDependencies) { + this.logService.info('Installing the extension without checking dependencies and pack', task.identifier.id); + } else { + try { + const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensions(task.identifier, task.manifest, !!task.options.installOnlyNewlyAddedFromExtensionPack, !!task.options.installPreReleaseVersion, task.options.profileLocation, task.options.productVersion); + const installed = await this.getInstalled(undefined, task.options.profileLocation, task.options.productVersion); + const options: InstallExtensionTaskOptions = { ...task.options, context: { ...task.options.context, [EXTENSION_INSTALL_DEP_PACK_CONTEXT]: true } }; + for (const { gallery, manifest } of distinct(allDepsAndPackExtensionsToInstall, ({ gallery }) => gallery.identifier.id)) { + if (installed.some(({ identifier }) => areSameExtensions(identifier, gallery.identifier))) { + continue; } - } catch (error) { - // Installing through VSIX - if (URI.isUri(task.source)) { - // Ignore installing dependencies and packs - if ( - isNonEmptyArray( - task.manifest.extensionDependencies, - ) - ) { - this.logService.warn( - `Cannot install dependencies of extension:`, - task.identifier.id, - error.message, - ); - } - if ( - isNonEmptyArray(task.manifest.extensionPack) - ) { - this.logService.warn( - `Cannot install packed extensions of extension:`, - task.identifier.id, - error.message, - ); - } - } else { - this.logService.error( - "Error while preparing to install dependencies and extension packs of the extension:", - task.identifier.id, - ); - throw error; + createInstallExtensionTask(manifest, gallery, options, task); + } + } catch (error) { + // Installing through VSIX + if (URI.isUri(task.source)) { + // Ignore installing dependencies and packs + if (isNonEmptyArray(task.manifest.extensionDependencies)) { + this.logService.warn(`Cannot install dependencies of extension:`, task.identifier.id, error.message); } + if (isNonEmptyArray(task.manifest.extensionPack)) { + this.logService.warn(`Cannot install packed extensions of extension:`, task.identifier.id, error.message); + } + } else { + this.logService.error('Error while preparing to install dependencies and extension packs of the extension:', task.identifier.id); + throw error; } } - }), - ); + } + })); - const otherProfilesToUpdate = - await this.getOtherProfilesToUpdateExtension( - [...installingExtensionsMap.values()].map( - ({ task }) => task, - ), - ); + const otherProfilesToUpdate = await this.getOtherProfilesToUpdateExtension([...installingExtensionsMap.values()].map(({ task }) => task)); for (const [profileLocation, task] of otherProfilesToUpdate) { - createInstallExtensionTask( - task.manifest, - task.source, - { ...task.options, profileLocation }, - undefined, - ); + createInstallExtensionTask(task.manifest, task.source, { ...task.options, profileLocation }, undefined); } // Install extensions in parallel and wait until all extensions are installed / failed - await this.joinAllSettled( - [...installingExtensionsMap.entries()].map( - async ([key, { task }]) => { - const startTime = new Date().getTime(); - let local: ILocalExtension; - try { - local = await task.run(); - await this.joinAllSettled( - this.participants.map((participant) => - participant.postInstall( - local, - task.source, - task.options, - CancellationToken.None, - ), - ), - ExtensionManagementErrorCode.PostInstall, - ); - } catch (e) { - const error = toExtensionManagementError(e); - if (!URI.isUri(task.source)) { - reportTelemetry( - this.telemetryService, - task.operation === InstallOperation.Update - ? "extensionGallery:update" - : "extensionGallery:install", - { - extensionData: - getGalleryExtensionTelemetryData( - task.source, - ), - error, - source: task.options.context?.[ - EXTENSION_INSTALL_SOURCE_CONTEXT - ], - }, - ); - } - installExtensionResultsMap.set(key, { - error, - identifier: task.identifier, - operation: task.operation, - source: task.source, - context: task.options.context, - profileLocation: task.options.profileLocation, - applicationScoped: - task.options.isApplicationScoped, - }); - this.logService.error( - "Error while installing the extension", - task.identifier.id, - getErrorMessage(error), - task.options.profileLocation.toString(), - ); - throw error; - } - if (!URI.isUri(task.source)) { - const isUpdate = - task.operation === InstallOperation.Update; - const durationSinceUpdate = isUpdate - ? undefined - : (new Date().getTime() - - task.source.lastUpdated) / - 1000; - reportTelemetry( - this.telemetryService, - isUpdate - ? "extensionGallery:update" - : "extensionGallery:install", - { - extensionData: - getGalleryExtensionTelemetryData( - task.source, - ), - verificationStatus: task.verificationStatus, - duration: new Date().getTime() - startTime, - durationSinceUpdate, - source: task.options.context?.[ - EXTENSION_INSTALL_SOURCE_CONTEXT - ], - }, - ); - // In web, report extension install statistics explicitly. In Desktop, statistics are automatically updated while downloading the VSIX. - if ( - isWeb && - task.operation !== InstallOperation.Update - ) { - try { - await this.galleryService.reportStatistic( - local.manifest.publisher, - local.manifest.name, - local.manifest.version, - StatisticType.Install, - ); - } catch (error) { - /* ignore */ - } - } - } - installExtensionResultsMap.set(key, { - local, - identifier: task.identifier, - operation: task.operation, - source: task.source, - context: task.options.context, - profileLocation: task.options.profileLocation, - applicationScoped: local.isApplicationScoped, + await this.joinAllSettled([...installingExtensionsMap.entries()].map(async ([key, { task }]) => { + const startTime = new Date().getTime(); + let local: ILocalExtension; + try { + local = await task.run(); + await this.joinAllSettled(this.participants.map(participant => participant.postInstall(local, task.source, task.options, CancellationToken.None)), ExtensionManagementErrorCode.PostInstall); + } catch (e) { + const error = toExtensionManagementError(e); + if (!URI.isUri(task.source)) { + reportTelemetry(this.telemetryService, task.operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install', { + extensionData: getGalleryExtensionTelemetryData(task.source), + error, + source: task.options.context?.[EXTENSION_INSTALL_SOURCE_CONTEXT] }); - }, - ), - ); + } + installExtensionResultsMap.set(key, { error, identifier: task.identifier, operation: task.operation, source: task.source, context: task.options.context, profileLocation: task.options.profileLocation, applicationScoped: task.options.isApplicationScoped }); + this.logService.error('Error while installing the extension', task.identifier.id, getErrorMessage(error), task.options.profileLocation.toString()); + throw error; + } + if (!URI.isUri(task.source)) { + const isUpdate = task.operation === InstallOperation.Update; + const durationSinceUpdate = isUpdate ? undefined : (new Date().getTime() - task.source.lastUpdated) / 1000; + reportTelemetry(this.telemetryService, isUpdate ? 'extensionGallery:update' : 'extensionGallery:install', { + extensionData: getGalleryExtensionTelemetryData(task.source), + verificationStatus: task.verificationStatus, + duration: new Date().getTime() - startTime, + durationSinceUpdate, + source: task.options.context?.[EXTENSION_INSTALL_SOURCE_CONTEXT] + }); + // In web, report extension install statistics explicitly. In Desktop, statistics are automatically updated while downloading the VSIX. + if (isWeb && task.operation !== InstallOperation.Update) { + try { + await this.galleryService.reportStatistic(local.manifest.publisher, local.manifest.name, local.manifest.version, StatisticType.Install); + } catch (error) { /* ignore */ } + } + } + installExtensionResultsMap.set(key, { local, identifier: task.identifier, operation: task.operation, source: task.source, context: task.options.context, profileLocation: task.options.profileLocation, applicationScoped: local.isApplicationScoped }); + })); if (alreadyRequestedInstallations.length) { await this.joinAllSettled(alreadyRequestedInstallations); } } catch (error) { - const getAllDepsAndPacks = ( - extension: ILocalExtension, - profileLocation: URI, - allDepsOrPacks: string[], - ) => { + const getAllDepsAndPacks = (extension: ILocalExtension, profileLocation: URI, allDepsOrPacks: string[]) => { const depsOrPacks = []; if (extension.manifest.extensionDependencies?.length) { - depsOrPacks.push( - ...extension.manifest.extensionDependencies, - ); + depsOrPacks.push(...extension.manifest.extensionDependencies); } if (extension.manifest.extensionPack?.length) { depsOrPacks.push(...extension.manifest.extensionPack); @@ -983,27 +433,14 @@ export abstract class AbstractExtensionManagementService continue; } allDepsOrPacks.push(id.toLowerCase()); - const installed = installExtensionResultsMap.get( - `${id.toLowerCase()}-${profileLocation.toString()}`, - ); + const installed = installExtensionResultsMap.get(`${id.toLowerCase()}-${profileLocation.toString()}`); if (installed?.local) { - allDepsOrPacks = getAllDepsAndPacks( - installed.local, - profileLocation, - allDepsOrPacks, - ); + allDepsOrPacks = getAllDepsAndPacks(installed.local, profileLocation, allDepsOrPacks); } } return allDepsOrPacks; }; - const getErrorResult = (task: IInstallExtensionTask) => ({ - identifier: task.identifier, - operation: InstallOperation.Install, - source: task.source, - context: task.options.context, - profileLocation: task.options.profileLocation, - error, - }); + const getErrorResult = (task: IInstallExtensionTask) => ({ identifier: task.identifier, operation: InstallOperation.Install, source: task.source, context: task.options.context, profileLocation: task.options.profileLocation, error }); const rollbackTasks: IUninstallExtensionTask[] = []; for (const [key, { task, root }] of installingExtensionsMap) { @@ -1013,19 +450,8 @@ export abstract class AbstractExtensionManagementService installExtensionResultsMap.set(key, getErrorResult(task)); } // If the extension is installed by a root task and the root task is failed, then uninstall the extension - else if ( - result.local && - root && - !installExtensionResultsMap.get( - `${root.identifier.id.toLowerCase()}-${task.options.profileLocation.toString()}`, - )?.local - ) { - rollbackTasks.push( - this.createUninstallExtensionTask(result.local, { - versionOnly: true, - profileLocation: task.options.profileLocation, - }), - ); + else if (result.local && root && !installExtensionResultsMap.get(`${root.identifier.id.toLowerCase()}-${task.options.profileLocation.toString()}`)?.local) { + rollbackTasks.push(this.createUninstallExtensionTask(result.local, { versionOnly: true, profileLocation: task.options.profileLocation })); installExtensionResultsMap.set(key, getErrorResult(task)); } } @@ -1037,173 +463,98 @@ export abstract class AbstractExtensionManagementService if (task.options.donotIncludePackAndDependencies) { continue; } - const depsOrPacks = getAllDepsAndPacks( - result.local, - task.options.profileLocation, - [result.local.identifier.id.toLowerCase()], - ).slice(1); - if ( - depsOrPacks.some( - (depOrPack) => - installingExtensionsMap.has( - `${depOrPack.toLowerCase()}-${task.options.profileLocation.toString()}`, - ) && - !installExtensionResultsMap.get( - `${depOrPack.toLowerCase()}-${task.options.profileLocation.toString()}`, - )?.local, - ) - ) { - rollbackTasks.push( - this.createUninstallExtensionTask(result.local, { - versionOnly: true, - profileLocation: task.options.profileLocation, - }), - ); + const depsOrPacks = getAllDepsAndPacks(result.local, task.options.profileLocation, [result.local.identifier.id.toLowerCase()]).slice(1); + if (depsOrPacks.some(depOrPack => installingExtensionsMap.has(`${depOrPack.toLowerCase()}-${task.options.profileLocation.toString()}`) && !installExtensionResultsMap.get(`${depOrPack.toLowerCase()}-${task.options.profileLocation.toString()}`)?.local)) { + rollbackTasks.push(this.createUninstallExtensionTask(result.local, { versionOnly: true, profileLocation: task.options.profileLocation })); installExtensionResultsMap.set(key, getErrorResult(task)); } } if (rollbackTasks.length) { - await Promise.allSettled( - rollbackTasks.map(async (rollbackTask) => { - try { - await rollbackTask.run(); - this.logService.info( - "Rollback: Uninstalled extension", - rollbackTask.extension.identifier.id, - ); - } catch (error) { - this.logService.warn( - "Rollback: Error while uninstalling extension", - rollbackTask.extension.identifier.id, - getErrorMessage(error), - ); - } - }), - ); + await Promise.allSettled(rollbackTasks.map(async rollbackTask => { + try { + await rollbackTask.run(); + this.logService.info('Rollback: Uninstalled extension', rollbackTask.extension.identifier.id); + } catch (error) { + this.logService.warn('Rollback: Error while uninstalling extension', rollbackTask.extension.identifier.id, getErrorMessage(error)); + } + })); } } finally { // Finally, remove all the tasks from the cache for (const { task } of installingExtensionsMap.values()) { if (task.source && !URI.isUri(task.source)) { - this.installingExtensions.delete( - getInstallExtensionTaskKey( - task.source, - task.options.profileLocation, - ), - ); + this.installingExtensions.delete(getInstallExtensionTaskKey(task.source, task.options.profileLocation)); } } } const results = [...installExtensionResultsMap.values()]; for (const result of results) { if (result.local) { - this.logService.info( - `Extension installed successfully:`, - result.identifier.id, - result.profileLocation.toString(), - ); + this.logService.info(`Extension installed successfully:`, result.identifier.id, result.profileLocation.toString()); } } this._onDidInstallExtensions.fire(results); return results; } - private async getOtherProfilesToUpdateExtension( - tasks: IInstallExtensionTask[], - ): Promise<[URI, IInstallExtensionTask][]> { + private async getOtherProfilesToUpdateExtension(tasks: IInstallExtensionTask[]): Promise<[URI, IInstallExtensionTask][]> { const otherProfilesToUpdate: [URI, IInstallExtensionTask][] = []; const profileExtensionsCache = new ResourceMap(); for (const task of tasks) { - if ( - task.operation !== InstallOperation.Update || - task.options.isApplicationScoped || - task.options.pinned || - task.options.installGivenVersion || - URI.isUri(task.source) + if (task.operation !== InstallOperation.Update + || task.options.isApplicationScoped + || task.options.pinned + || task.options.installGivenVersion + || URI.isUri(task.source) ) { continue; } for (const profile of this.userDataProfilesService.profiles) { - if ( - this.uriIdentityService.extUri.isEqual( - profile.extensionsResource, - task.options.profileLocation, - ) - ) { + if (this.uriIdentityService.extUri.isEqual(profile.extensionsResource, task.options.profileLocation)) { continue; } - let installedExtensions = profileExtensionsCache.get( - profile.extensionsResource, - ); + let installedExtensions = profileExtensionsCache.get(profile.extensionsResource); if (!installedExtensions) { - installedExtensions = await this.getInstalled( - ExtensionType.User, - profile.extensionsResource, - ); - profileExtensionsCache.set( - profile.extensionsResource, - installedExtensions, - ); + installedExtensions = await this.getInstalled(ExtensionType.User, profile.extensionsResource); + profileExtensionsCache.set(profile.extensionsResource, installedExtensions); } - const installedExtension = installedExtensions.find((e) => - areSameExtensions(e.identifier, task.identifier), - ); + const installedExtension = installedExtensions.find(e => areSameExtensions(e.identifier, task.identifier)); if (installedExtension && !installedExtension.pinned) { - otherProfilesToUpdate.push([ - profile.extensionsResource, - task, - ]); + otherProfilesToUpdate.push([profile.extensionsResource, task]); } } } return otherProfilesToUpdate; } - private canWaitForTask( - taskToWait: IInstallExtensionTask, - taskToWaitFor: IInstallExtensionTask, - ): boolean { - for (const [ - , - { task, waitingTasks }, - ] of this.installingExtensions.entries()) { + private canWaitForTask(taskToWait: IInstallExtensionTask, taskToWaitFor: IInstallExtensionTask): boolean { + for (const [, { task, waitingTasks }] of this.installingExtensions.entries()) { if (task === taskToWait) { // Cannot be waited, If taskToWaitFor is waiting for taskToWait if (waitingTasks.includes(taskToWaitFor)) { return false; } // Cannot be waited, If taskToWaitFor is waiting for tasks waiting for taskToWait - if ( - waitingTasks.some((waitingTask) => - this.canWaitForTask(waitingTask, taskToWaitFor), - ) - ) { + if (waitingTasks.some(waitingTask => this.canWaitForTask(waitingTask, taskToWaitFor))) { return false; } } // Cannot be waited, if the taskToWait cannot be waited for the task created the taskToWaitFor // Because, the task waits for the tasks it created - if ( - task === taskToWaitFor && - waitingTasks[0] && - !this.canWaitForTask(taskToWait, waitingTasks[0]) - ) { + if (task === taskToWaitFor && waitingTasks[0] && !this.canWaitForTask(taskToWait, waitingTasks[0])) { return false; } } return true; } - private async joinAllSettled( - promises: Promise[], - errorCode?: ExtensionManagementErrorCode, - ): Promise { + private async joinAllSettled(promises: Promise[], errorCode?: ExtensionManagementErrorCode): Promise { const results: T[] = []; const errors: ExtensionManagementError[] = []; const promiseResults = await Promise.allSettled(promises); for (const r of promiseResults) { - if (r.status === "fulfilled") { + if (r.status === 'fulfilled') { results.push(r.value); } else { errors.push(toExtensionManagementError(r.reason, errorCode)); @@ -1219,86 +570,35 @@ export abstract class AbstractExtensionManagementService throw errors[0]; } - let error = new ExtensionManagementError( - "", - ExtensionManagementErrorCode.Unknown, - ); + let error = new ExtensionManagementError('', ExtensionManagementErrorCode.Unknown); for (const current of errors) { error = new ExtensionManagementError( - error.message - ? `${error.message}, ${current.message}` - : current.message, - current.code !== ExtensionManagementErrorCode.Unknown && - current.code !== ExtensionManagementErrorCode.Internal - ? current.code - : error.code, + error.message ? `${error.message}, ${current.message}` : current.message, + current.code !== ExtensionManagementErrorCode.Unknown && current.code !== ExtensionManagementErrorCode.Internal ? current.code : error.code ); } throw error; } - private async getAllDepsAndPackExtensions( - extensionIdentifier: IExtensionIdentifier, - manifest: IExtensionManifest, - getOnlyNewlyAddedFromExtensionPack: boolean, - installPreRelease: boolean, - profile: URI | undefined, - productVersion: IProductVersion, - ): Promise<{ gallery: IGalleryExtension; manifest: IExtensionManifest }[]> { + private async getAllDepsAndPackExtensions(extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest, getOnlyNewlyAddedFromExtensionPack: boolean, installPreRelease: boolean, profile: URI | undefined, productVersion: IProductVersion): Promise<{ gallery: IGalleryExtension; manifest: IExtensionManifest }[]> { if (!this.galleryService.isEnabled()) { return []; } - const installed = await this.getInstalled( - undefined, - profile, - productVersion, - ); + const installed = await this.getInstalled(undefined, profile, productVersion); const knownIdentifiers: IExtensionIdentifier[] = []; - const allDependenciesAndPacks: { - gallery: IGalleryExtension; - manifest: IExtensionManifest; - }[] = []; - const collectDependenciesAndPackExtensionsToInstall = async ( - extensionIdentifier: IExtensionIdentifier, - manifest: IExtensionManifest, - ): Promise => { + const allDependenciesAndPacks: { gallery: IGalleryExtension; manifest: IExtensionManifest }[] = []; + const collectDependenciesAndPackExtensionsToInstall = async (extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest): Promise => { knownIdentifiers.push(extensionIdentifier); const dependecies: string[] = manifest.extensionDependencies || []; const dependenciesAndPackExtensions = [...dependecies]; if (manifest.extensionPack) { - const existing = getOnlyNewlyAddedFromExtensionPack - ? installed.find((e) => - areSameExtensions( - e.identifier, - extensionIdentifier, - ), - ) - : undefined; + const existing = getOnlyNewlyAddedFromExtensionPack ? installed.find(e => areSameExtensions(e.identifier, extensionIdentifier)) : undefined; for (const extension of manifest.extensionPack) { // add only those extensions which are new in currently installed extension - if ( - !( - existing && - existing.manifest.extensionPack && - existing.manifest.extensionPack.some((old) => - areSameExtensions( - { id: old }, - { id: extension }, - ), - ) - ) - ) { - if ( - dependenciesAndPackExtensions.every( - (e) => - !areSameExtensions( - { id: e }, - { id: extension }, - ), - ) - ) { + if (!(existing && existing.manifest.extensionPack && existing.manifest.extensionPack.some(old => areSameExtensions({ id: old }, { id: extension })))) { + if (dependenciesAndPackExtensions.every(e => !areSameExtensions({ id: e }, { id: extension }))) { dependenciesAndPackExtensions.push(extension); } } @@ -1307,380 +607,129 @@ export abstract class AbstractExtensionManagementService if (dependenciesAndPackExtensions.length) { // filter out known extensions - const ids = dependenciesAndPackExtensions.filter((id) => - knownIdentifiers.every( - (galleryIdentifier) => - !areSameExtensions(galleryIdentifier, { id }), - ), - ); + const ids = dependenciesAndPackExtensions.filter(id => knownIdentifiers.every(galleryIdentifier => !areSameExtensions(galleryIdentifier, { id }))); if (ids.length) { - const galleryExtensions = - await this.galleryService.getExtensions( - ids.map((id) => ({ - id, - preRelease: installPreRelease, - })), - CancellationToken.None, - ); + const galleryExtensions = await this.galleryService.getExtensions(ids.map(id => ({ id, preRelease: installPreRelease })), CancellationToken.None); for (const galleryExtension of galleryExtensions) { - if ( - knownIdentifiers.find((identifier) => - areSameExtensions( - identifier, - galleryExtension.identifier, - ), - ) - ) { + if (knownIdentifiers.find(identifier => areSameExtensions(identifier, galleryExtension.identifier))) { continue; } - const isDependency = dependecies.some((id) => - areSameExtensions( - { id }, - galleryExtension.identifier, - ), - ); + const isDependency = dependecies.some(id => areSameExtensions({ id }, galleryExtension.identifier)); let compatible; try { - compatible = - await this.checkAndGetCompatibleVersion( - galleryExtension, - false, - installPreRelease, - productVersion, - ); + compatible = await this.checkAndGetCompatibleVersion(galleryExtension, false, installPreRelease, productVersion); } catch (error) { if (!isDependency) { - this.logService.info( - "Skipping the packed extension as it cannot be installed", - galleryExtension.identifier.id, - getErrorMessage(error), - ); + this.logService.info('Skipping the packed extension as it cannot be installed', galleryExtension.identifier.id, getErrorMessage(error)); continue; } else { throw error; } } - allDependenciesAndPacks.push({ - gallery: compatible.extension, - manifest: compatible.manifest, - }); - await collectDependenciesAndPackExtensionsToInstall( - compatible.extension.identifier, - compatible.manifest, - ); + allDependenciesAndPacks.push({ gallery: compatible.extension, manifest: compatible.manifest }); + await collectDependenciesAndPackExtensionsToInstall(compatible.extension.identifier, compatible.manifest); } } } }; - await collectDependenciesAndPackExtensionsToInstall( - extensionIdentifier, - manifest, - ); + await collectDependenciesAndPackExtensionsToInstall(extensionIdentifier, manifest); return allDependenciesAndPacks; } - private async checkAndGetCompatibleVersion( - extension: IGalleryExtension, - sameVersion: boolean, - installPreRelease: boolean, - productVersion: IProductVersion, - ): Promise<{ extension: IGalleryExtension; manifest: IExtensionManifest }> { + private async checkAndGetCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, installPreRelease: boolean, productVersion: IProductVersion): Promise<{ extension: IGalleryExtension; manifest: IExtensionManifest }> { let compatibleExtension: IGalleryExtension | null; - const extensionsControlManifest = - await this.getExtensionsControlManifest(); - if ( - extensionsControlManifest.malicious.some((identifier) => - areSameExtensions(extension.identifier, identifier), - ) - ) { - throw new ExtensionManagementError( - nls.localize( - "malicious extension", - "Can't install '{0}' extension since it was reported to be problematic.", - extension.identifier.id, - ), - ExtensionManagementErrorCode.Malicious, - ); + const extensionsControlManifest = await this.getExtensionsControlManifest(); + if (extensionsControlManifest.malicious.some(identifier => areSameExtensions(extension.identifier, identifier))) { + throw new ExtensionManagementError(nls.localize('malicious extension', "Can't install '{0}' extension since it was reported to be problematic.", extension.identifier.id), ExtensionManagementErrorCode.Malicious); } - const deprecationInfo = - extensionsControlManifest.deprecated[ - extension.identifier.id.toLowerCase() - ]; + const deprecationInfo = extensionsControlManifest.deprecated[extension.identifier.id.toLowerCase()]; if (deprecationInfo?.extension?.autoMigrate) { - this.logService.info( - `The '${extension.identifier.id}' extension is deprecated, fetching the compatible '${deprecationInfo.extension.id}' extension instead.`, - ); - compatibleExtension = ( - await this.galleryService.getExtensions( - [ - { - id: deprecationInfo.extension.id, - preRelease: deprecationInfo.extension.preRelease, - }, - ], - { - targetPlatform: await this.getTargetPlatform(), - compatible: true, - productVersion, - }, - CancellationToken.None, - ) - )[0]; + this.logService.info(`The '${extension.identifier.id}' extension is deprecated, fetching the compatible '${deprecationInfo.extension.id}' extension instead.`); + compatibleExtension = (await this.galleryService.getExtensions([{ id: deprecationInfo.extension.id, preRelease: deprecationInfo.extension.preRelease }], { targetPlatform: await this.getTargetPlatform(), compatible: true, productVersion }, CancellationToken.None))[0]; if (!compatibleExtension) { - throw new ExtensionManagementError( - nls.localize( - "notFoundDeprecatedReplacementExtension", - "Can't install '{0}' extension since it was deprecated and the replacement extension '{1}' can't be found.", - extension.identifier.id, - deprecationInfo.extension.id, - ), - ExtensionManagementErrorCode.Deprecated, - ); + throw new ExtensionManagementError(nls.localize('notFoundDeprecatedReplacementExtension', "Can't install '{0}' extension since it was deprecated and the replacement extension '{1}' can't be found.", extension.identifier.id, deprecationInfo.extension.id), ExtensionManagementErrorCode.Deprecated); } - } else { - if ((await this.canInstall(extension)) !== true) { + } + + else { + if (await this.canInstall(extension) !== true) { const targetPlatform = await this.getTargetPlatform(); - throw new ExtensionManagementError( - nls.localize( - "incompatible platform", - "The '{0}' extension is not available in {1} for {2}.", - extension.identifier.id, - this.productService.nameLong, - TargetPlatformToString(targetPlatform), - ), - ExtensionManagementErrorCode.IncompatibleTargetPlatform, - ); + throw new ExtensionManagementError(nls.localize('incompatible platform', "The '{0}' extension is not available in {1} for {2}.", extension.identifier.id, this.productService.nameLong, TargetPlatformToString(targetPlatform)), ExtensionManagementErrorCode.IncompatibleTargetPlatform); } - compatibleExtension = await this.getCompatibleVersion( - extension, - sameVersion, - installPreRelease, - productVersion, - ); + compatibleExtension = await this.getCompatibleVersion(extension, sameVersion, installPreRelease, productVersion); if (!compatibleExtension) { const incompatibleApiProposalsMessages: string[] = []; - if ( - !areApiProposalsCompatible( - extension.properties.enabledApiProposals ?? [], - incompatibleApiProposalsMessages, - ) - ) { - throw new ExtensionManagementError( - nls.localize( - "incompatibleAPI", - "Can't install '{0}' extension. {1}", - extension.displayName ?? extension.identifier.id, - incompatibleApiProposalsMessages[0], - ), - ExtensionManagementErrorCode.IncompatibleApi, - ); + if (!areApiProposalsCompatible(extension.properties.enabledApiProposals ?? [], incompatibleApiProposalsMessages)) { + throw new ExtensionManagementError(nls.localize('incompatibleAPI', "Can't install '{0}' extension. {1}", extension.displayName ?? extension.identifier.id, incompatibleApiProposalsMessages[0]), ExtensionManagementErrorCode.IncompatibleApi); } /** If no compatible release version is found, check if the extension has a release version or not and throw relevant error */ - if ( - !installPreRelease && - extension.properties.isPreReleaseVersion && - ( - await this.galleryService.getExtensions( - [extension.identifier], - CancellationToken.None, - ) - )[0] - ) { - throw new ExtensionManagementError( - nls.localize( - "notFoundReleaseExtension", - "Can't install release version of '{0}' extension because it has no release version.", - extension.displayName ?? extension.identifier.id, - ), - ExtensionManagementErrorCode.ReleaseVersionNotFound, - ); + if (!installPreRelease && extension.properties.isPreReleaseVersion && (await this.galleryService.getExtensions([extension.identifier], CancellationToken.None))[0]) { + throw new ExtensionManagementError(nls.localize('notFoundReleaseExtension', "Can't install release version of '{0}' extension because it has no release version.", extension.displayName ?? extension.identifier.id), ExtensionManagementErrorCode.ReleaseVersionNotFound); } - throw new ExtensionManagementError( - nls.localize( - "notFoundCompatibleDependency", - "Can't install '{0}' extension because it is not compatible with the current version of {1} (version {2}).", - extension.identifier.id, - this.productService.nameLong, - this.productService.version, - ), - ExtensionManagementErrorCode.Incompatible, - ); + throw new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Can't install '{0}' extension because it is not compatible with the current version of {1} (version {2}).", extension.identifier.id, this.productService.nameLong, this.productService.version), ExtensionManagementErrorCode.Incompatible); } } - this.logService.info( - "Getting Manifest...", - compatibleExtension.identifier.id, - ); - const manifest = await this.galleryService.getManifest( - compatibleExtension, - CancellationToken.None, - ); + this.logService.info('Getting Manifest...', compatibleExtension.identifier.id); + const manifest = await this.galleryService.getManifest(compatibleExtension, CancellationToken.None); if (manifest === null) { - throw new ExtensionManagementError( - `Missing manifest for extension ${compatibleExtension.identifier.id}`, - ExtensionManagementErrorCode.Invalid, - ); + throw new ExtensionManagementError(`Missing manifest for extension ${compatibleExtension.identifier.id}`, ExtensionManagementErrorCode.Invalid); } if (manifest.version !== compatibleExtension.version) { - throw new ExtensionManagementError( - `Cannot install '${compatibleExtension.identifier.id}' extension because of version mismatch in Marketplace`, - ExtensionManagementErrorCode.Invalid, - ); + throw new ExtensionManagementError(`Cannot install '${compatibleExtension.identifier.id}' extension because of version mismatch in Marketplace`, ExtensionManagementErrorCode.Invalid); } return { extension: compatibleExtension, manifest }; } - protected async getCompatibleVersion( - extension: IGalleryExtension, - sameVersion: boolean, - includePreRelease: boolean, - productVersion: IProductVersion, - ): Promise { + protected async getCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, includePreRelease: boolean, productVersion: IProductVersion): Promise { const targetPlatform = await this.getTargetPlatform(); let compatibleExtension: IGalleryExtension | null = null; - if ( - !sameVersion && - extension.hasPreReleaseVersion && - extension.properties.isPreReleaseVersion !== includePreRelease - ) { - compatibleExtension = - ( - await this.galleryService.getExtensions( - [ - { - ...extension.identifier, - preRelease: includePreRelease, - }, - ], - { targetPlatform, compatible: true, productVersion }, - CancellationToken.None, - ) - )[0] || null; + if (!sameVersion && extension.hasPreReleaseVersion && extension.properties.isPreReleaseVersion !== includePreRelease) { + compatibleExtension = (await this.galleryService.getExtensions([{ ...extension.identifier, preRelease: includePreRelease }], { targetPlatform, compatible: true, productVersion }, CancellationToken.None))[0] || null; } - if ( - !compatibleExtension && - (await this.galleryService.isExtensionCompatible( - extension, - includePreRelease, - targetPlatform, - productVersion, - )) - ) { + if (!compatibleExtension && await this.galleryService.isExtensionCompatible(extension, includePreRelease, targetPlatform, productVersion)) { compatibleExtension = extension; } if (!compatibleExtension) { if (sameVersion) { - compatibleExtension = - ( - await this.galleryService.getExtensions( - [ - { - ...extension.identifier, - version: extension.version, - }, - ], - { - targetPlatform, - compatible: true, - productVersion, - }, - CancellationToken.None, - ) - )[0] || null; + compatibleExtension = (await this.galleryService.getExtensions([{ ...extension.identifier, version: extension.version }], { targetPlatform, compatible: true, productVersion }, CancellationToken.None))[0] || null; } else { - compatibleExtension = - await this.galleryService.getCompatibleExtension( - extension, - includePreRelease, - targetPlatform, - productVersion, - ); + compatibleExtension = await this.galleryService.getCompatibleExtension(extension, includePreRelease, targetPlatform, productVersion); } } return compatibleExtension; } - async uninstallExtensions( - extensions: UninstallExtensionInfo[], - ): Promise { - const getUninstallExtensionTaskKey = ( - extension: ILocalExtension, - uninstallOptions: UninstallExtensionTaskOptions, - ) => - `${extension.identifier.id.toLowerCase()}${uninstallOptions.versionOnly ? `-${extension.manifest.version}` : ""}@${uninstallOptions.profileLocation.toString()}`; - - const createUninstallExtensionTask = ( - extension: ILocalExtension, - uninstallOptions: UninstallExtensionTaskOptions, - ): IUninstallExtensionTask => { - const uninstallExtensionTask = this.createUninstallExtensionTask( - extension, - uninstallOptions, - ); - this.uninstallingExtensions.set( - getUninstallExtensionTaskKey( - uninstallExtensionTask.extension, - uninstallOptions, - ), - uninstallExtensionTask, - ); - this.logService.info( - "Uninstalling extension from the profile:", - `${extension.identifier.id}@${extension.manifest.version}`, - uninstallOptions.profileLocation.toString(), - ); - this._onUninstallExtension.fire({ - identifier: extension.identifier, - profileLocation: uninstallOptions.profileLocation, - applicationScoped: extension.isApplicationScoped, - }); + async uninstallExtensions(extensions: UninstallExtensionInfo[]): Promise { + + const getUninstallExtensionTaskKey = (extension: ILocalExtension, uninstallOptions: UninstallExtensionTaskOptions) => `${extension.identifier.id.toLowerCase()}${uninstallOptions.versionOnly ? `-${extension.manifest.version}` : ''}@${uninstallOptions.profileLocation.toString()}`; + + const createUninstallExtensionTask = (extension: ILocalExtension, uninstallOptions: UninstallExtensionTaskOptions): IUninstallExtensionTask => { + const uninstallExtensionTask = this.createUninstallExtensionTask(extension, uninstallOptions); + this.uninstallingExtensions.set(getUninstallExtensionTaskKey(uninstallExtensionTask.extension, uninstallOptions), uninstallExtensionTask); + this.logService.info('Uninstalling extension from the profile:', `${extension.identifier.id}@${extension.manifest.version}`, uninstallOptions.profileLocation.toString()); + this._onUninstallExtension.fire({ identifier: extension.identifier, profileLocation: uninstallOptions.profileLocation, applicationScoped: extension.isApplicationScoped }); return uninstallExtensionTask; }; - const postUninstallExtension = ( - extension: ILocalExtension, - uninstallOptions: UninstallExtensionTaskOptions, - error?: ExtensionManagementError, - ): void => { + const postUninstallExtension = (extension: ILocalExtension, uninstallOptions: UninstallExtensionTaskOptions, error?: ExtensionManagementError): void => { if (error) { - this.logService.error( - "Failed to uninstall extension from the profile:", - `${extension.identifier.id}@${extension.manifest.version}`, - uninstallOptions.profileLocation.toString(), - error.message, - ); + this.logService.error('Failed to uninstall extension from the profile:', `${extension.identifier.id}@${extension.manifest.version}`, uninstallOptions.profileLocation.toString(), error.message); } else { - this.logService.info( - "Successfully uninstalled extension from the profile", - `${extension.identifier.id}@${extension.manifest.version}`, - uninstallOptions.profileLocation.toString(), - ); + this.logService.info('Successfully uninstalled extension from the profile', `${extension.identifier.id}@${extension.manifest.version}`, uninstallOptions.profileLocation.toString()); } - reportTelemetry( - this.telemetryService, - "extensionGallery:uninstall", - { - extensionData: getLocalExtensionTelemetryData(extension), - error, - }, - ); - this._onDidUninstallExtension.fire({ - identifier: extension.identifier, - error: error?.code, - profileLocation: uninstallOptions.profileLocation, - applicationScoped: extension.isApplicationScoped, - }); + reportTelemetry(this.telemetryService, 'extensionGallery:uninstall', { extensionData: getLocalExtensionTelemetryData(extension), error }); + this._onDidUninstallExtension.fire({ identifier: extension.identifier, error: error?.code, profileLocation: uninstallOptions.profileLocation, applicationScoped: extension.isApplicationScoped }); }; const allTasks: IUninstallExtensionTask[] = []; @@ -1692,133 +741,62 @@ export abstract class AbstractExtensionManagementService for (const { extension, options } of extensions) { const uninstallOptions: UninstallExtensionTaskOptions = { ...options, - profileLocation: extension.isApplicationScoped - ? this.userDataProfilesService.defaultProfile - .extensionsResource - : (options?.profileLocation ?? - this.getCurrentExtensionsManifestLocation()), + profileLocation: extension.isApplicationScoped ? this.userDataProfilesService.defaultProfile.extensionsResource : options?.profileLocation ?? this.getCurrentExtensionsManifestLocation() }; - const uninstallExtensionTask = this.uninstallingExtensions.get( - getUninstallExtensionTaskKey(extension, uninstallOptions), - ); + const uninstallExtensionTask = this.uninstallingExtensions.get(getUninstallExtensionTaskKey(extension, uninstallOptions)); if (uninstallExtensionTask) { - this.logService.info( - "Extensions is already requested to uninstall", - extension.identifier.id, - ); - alreadyRequestedUninstalls.push( - uninstallExtensionTask.waitUntilTaskIsFinished(), - ); + this.logService.info('Extensions is already requested to uninstall', extension.identifier.id); + alreadyRequestedUninstalls.push(uninstallExtensionTask.waitUntilTaskIsFinished()); } else { - allTasks.push( - createUninstallExtensionTask(extension, uninstallOptions), - ); + allTasks.push(createUninstallExtensionTask(extension, uninstallOptions)); } } try { for (const task of allTasks.slice(0)) { - let installed = installedExtensionsMap.get( - task.options.profileLocation, - ); + let installed = installedExtensionsMap.get(task.options.profileLocation); if (!installed) { - installedExtensionsMap.set( - task.options.profileLocation, - (installed = await this.getInstalled( - ExtensionType.User, - task.options.profileLocation, - )), - ); + installedExtensionsMap.set(task.options.profileLocation, installed = await this.getInstalled(ExtensionType.User, task.options.profileLocation)); } if (task.options.donotIncludePack) { - this.logService.info( - "Uninstalling the extension without including packed extension", - `${task.extension.identifier.id}@${task.extension.manifest.version}`, - ); + this.logService.info('Uninstalling the extension without including packed extension', `${task.extension.identifier.id}@${task.extension.manifest.version}`); } else { - const packedExtensions = - this.getAllPackExtensionsToUninstall( - task.extension, - installed, - ); + const packedExtensions = this.getAllPackExtensionsToUninstall(task.extension, installed); for (const packedExtension of packedExtensions) { - if ( - this.uninstallingExtensions.has( - getUninstallExtensionTaskKey( - packedExtension, - task.options, - ), - ) - ) { - this.logService.info( - "Extensions is already requested to uninstall", - packedExtension.identifier.id, - ); + if (this.uninstallingExtensions.has(getUninstallExtensionTaskKey(packedExtension, task.options))) { + this.logService.info('Extensions is already requested to uninstall', packedExtension.identifier.id); } else { - allTasks.push( - createUninstallExtensionTask( - packedExtension, - task.options, - ), - ); + allTasks.push(createUninstallExtensionTask(packedExtension, task.options)); } } } if (task.options.donotCheckDependents) { - this.logService.info( - "Uninstalling the extension without checking dependents", - `${task.extension.identifier.id}@${task.extension.manifest.version}`, - ); + this.logService.info('Uninstalling the extension without checking dependents', `${task.extension.identifier.id}@${task.extension.manifest.version}`); } else { - this.checkForDependents( - allTasks.map((task) => task.extension), - installed, - task.extension, - ); + this.checkForDependents(allTasks.map(task => task.extension), installed, task.extension); } } // Uninstall extensions in parallel and wait until all extensions are uninstalled / failed - await this.joinAllSettled( - allTasks.map(async (task) => { - try { - await task.run(); - await this.joinAllSettled( - this.participants.map((participant) => - participant.postUninstall( - task.extension, - task.options, - CancellationToken.None, - ), - ), - ); - // only report if extension has a mapped gallery extension. UUID identifies the gallery extension. - if (task.extension.identifier.uuid) { - try { - await this.galleryService.reportStatistic( - task.extension.manifest.publisher, - task.extension.manifest.name, - task.extension.manifest.version, - StatisticType.Uninstall, - ); - } catch (error) { - /* ignore */ - } - } - } catch (e) { - const error = toExtensionManagementError(e); - postUninstallExtension( - task.extension, - task.options, - error, - ); - throw error; - } finally { - processedTasks.push(task); + await this.joinAllSettled(allTasks.map(async task => { + try { + await task.run(); + await this.joinAllSettled(this.participants.map(participant => participant.postUninstall(task.extension, task.options, CancellationToken.None))); + // only report if extension has a mapped gallery extension. UUID identifies the gallery extension. + if (task.extension.identifier.uuid) { + try { + await this.galleryService.reportStatistic(task.extension.manifest.publisher, task.extension.manifest.name, task.extension.manifest.version, StatisticType.Uninstall); + } catch (error) { /* ignore */ } } - }), - ); + } catch (e) { + const error = toExtensionManagementError(e); + postUninstallExtension(task.extension, task.options, error); + throw error; + } finally { + processedTasks.push(task); + } + })); if (alreadyRequestedUninstalls.length) { await this.joinAllSettled(alreadyRequestedUninstalls); @@ -1831,11 +809,7 @@ export abstract class AbstractExtensionManagementService const error = toExtensionManagementError(e); for (const task of allTasks) { // cancel the tasks - try { - task.cancel(); - } catch (error) { - /* ignore */ - } + try { task.cancel(); } catch (error) { /* ignore */ } if (!processedTasks.includes(task)) { postUninstallExtension(task.extension, task.options, error); } @@ -1844,248 +818,113 @@ export abstract class AbstractExtensionManagementService } finally { // Remove tasks from cache for (const task of allTasks) { - if ( - !this.uninstallingExtensions.delete( - getUninstallExtensionTaskKey( - task.extension, - task.options, - ), - ) - ) { - this.logService.warn( - "Uninstallation task is not found in the cache", - task.extension.identifier.id, - ); + if (!this.uninstallingExtensions.delete(getUninstallExtensionTaskKey(task.extension, task.options))) { + this.logService.warn('Uninstallation task is not found in the cache', task.extension.identifier.id); } } } } - private checkForDependents( - extensionsToUninstall: ILocalExtension[], - installed: ILocalExtension[], - extensionToUninstall: ILocalExtension, - ): void { + private checkForDependents(extensionsToUninstall: ILocalExtension[], installed: ILocalExtension[], extensionToUninstall: ILocalExtension): void { for (const extension of extensionsToUninstall) { const dependents = this.getDependents(extension, installed); if (dependents.length) { - const remainingDependents = dependents.filter( - (dependent) => - !extensionsToUninstall.some((e) => - areSameExtensions( - e.identifier, - dependent.identifier, - ), - ), - ); + const remainingDependents = dependents.filter(dependent => !extensionsToUninstall.some(e => areSameExtensions(e.identifier, dependent.identifier))); if (remainingDependents.length) { - throw new Error( - this.getDependentsErrorMessage( - extension, - remainingDependents, - extensionToUninstall, - ), - ); + throw new Error(this.getDependentsErrorMessage(extension, remainingDependents, extensionToUninstall)); } } } } - private getDependentsErrorMessage( - dependingExtension: ILocalExtension, - dependents: ILocalExtension[], - extensionToUninstall: ILocalExtension, - ): string { + private getDependentsErrorMessage(dependingExtension: ILocalExtension, dependents: ILocalExtension[], extensionToUninstall: ILocalExtension): string { if (extensionToUninstall === dependingExtension) { if (dependents.length === 1) { - return nls.localize( - "singleDependentError", - "Cannot uninstall '{0}' extension. '{1}' extension depends on this.", - extensionToUninstall.manifest.displayName || - extensionToUninstall.manifest.name, - dependents[0].manifest.displayName || - dependents[0].manifest.name, - ); + return nls.localize('singleDependentError', "Cannot uninstall '{0}' extension. '{1}' extension depends on this.", + extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name); } if (dependents.length === 2) { - return nls.localize( - "twoDependentsError", - "Cannot uninstall '{0}' extension. '{1}' and '{2}' extensions depend on this.", - extensionToUninstall.manifest.displayName || - extensionToUninstall.manifest.name, - dependents[0].manifest.displayName || - dependents[0].manifest.name, - dependents[1].manifest.displayName || - dependents[1].manifest.name, - ); + return nls.localize('twoDependentsError', "Cannot uninstall '{0}' extension. '{1}' and '{2}' extensions depend on this.", + extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name); } - return nls.localize( - "multipleDependentsError", - "Cannot uninstall '{0}' extension. '{1}', '{2}' and other extension depend on this.", - extensionToUninstall.manifest.displayName || - extensionToUninstall.manifest.name, - dependents[0].manifest.displayName || - dependents[0].manifest.name, - dependents[1].manifest.displayName || - dependents[1].manifest.name, - ); + return nls.localize('multipleDependentsError', "Cannot uninstall '{0}' extension. '{1}', '{2}' and other extension depend on this.", + extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name); } if (dependents.length === 1) { - return nls.localize( - "singleIndirectDependentError", - "Cannot uninstall '{0}' extension . It includes uninstalling '{1}' extension and '{2}' extension depends on this.", - extensionToUninstall.manifest.displayName || - extensionToUninstall.manifest.name, - dependingExtension.manifest.displayName || - dependingExtension.manifest.name, - dependents[0].manifest.displayName || - dependents[0].manifest.name, - ); + return nls.localize('singleIndirectDependentError', "Cannot uninstall '{0}' extension . It includes uninstalling '{1}' extension and '{2}' extension depends on this.", + extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependingExtension.manifest.displayName + || dependingExtension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name); } if (dependents.length === 2) { - return nls.localize( - "twoIndirectDependentsError", - "Cannot uninstall '{0}' extension. It includes uninstalling '{1}' extension and '{2}' and '{3}' extensions depend on this.", - extensionToUninstall.manifest.displayName || - extensionToUninstall.manifest.name, - dependingExtension.manifest.displayName || - dependingExtension.manifest.name, - dependents[0].manifest.displayName || - dependents[0].manifest.name, - dependents[1].manifest.displayName || - dependents[1].manifest.name, - ); + return nls.localize('twoIndirectDependentsError', "Cannot uninstall '{0}' extension. It includes uninstalling '{1}' extension and '{2}' and '{3}' extensions depend on this.", + extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependingExtension.manifest.displayName + || dependingExtension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name); } - return nls.localize( - "multipleIndirectDependentsError", - "Cannot uninstall '{0}' extension. It includes uninstalling '{1}' extension and '{2}', '{3}' and other extensions depend on this.", - extensionToUninstall.manifest.displayName || - extensionToUninstall.manifest.name, - dependingExtension.manifest.displayName || - dependingExtension.manifest.name, - dependents[0].manifest.displayName || dependents[0].manifest.name, - dependents[1].manifest.displayName || dependents[1].manifest.name, - ); + return nls.localize('multipleIndirectDependentsError', "Cannot uninstall '{0}' extension. It includes uninstalling '{1}' extension and '{2}', '{3}' and other extensions depend on this.", + extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependingExtension.manifest.displayName + || dependingExtension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name); + } - private getAllPackExtensionsToUninstall( - extension: ILocalExtension, - installed: ILocalExtension[], - checked: ILocalExtension[] = [], - ): ILocalExtension[] { + private getAllPackExtensionsToUninstall(extension: ILocalExtension, installed: ILocalExtension[], checked: ILocalExtension[] = []): ILocalExtension[] { if (checked.indexOf(extension) !== -1) { return []; } checked.push(extension); - const extensionsPack = extension.manifest.extensionPack - ? extension.manifest.extensionPack - : []; + const extensionsPack = extension.manifest.extensionPack ? extension.manifest.extensionPack : []; if (extensionsPack.length) { - const packedExtensions = installed.filter( - (i) => - !i.isBuiltin && - extensionsPack.some((id) => - areSameExtensions({ id }, i.identifier), - ), - ); + const packedExtensions = installed.filter(i => !i.isBuiltin && extensionsPack.some(id => areSameExtensions({ id }, i.identifier))); const packOfPackedExtensions: ILocalExtension[] = []; for (const packedExtension of packedExtensions) { - packOfPackedExtensions.push( - ...this.getAllPackExtensionsToUninstall( - packedExtension, - installed, - checked, - ), - ); + packOfPackedExtensions.push(...this.getAllPackExtensionsToUninstall(packedExtension, installed, checked)); } return [...packedExtensions, ...packOfPackedExtensions]; } return []; } - private getDependents( - extension: ILocalExtension, - installed: ILocalExtension[], - ): ILocalExtension[] { - return installed.filter( - (e) => - e.manifest.extensionDependencies && - e.manifest.extensionDependencies.some((id) => - areSameExtensions({ id }, extension.identifier), - ), - ); + private getDependents(extension: ILocalExtension, installed: ILocalExtension[]): ILocalExtension[] { + return installed.filter(e => e.manifest.extensionDependencies && e.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.identifier))); } private async updateControlCache(): Promise { try { - this.logService.trace( - "ExtensionManagementService.updateControlCache", - ); + this.logService.trace('ExtensionManagementService.updateControlCache'); return await this.galleryService.getExtensionsControlManifest(); } catch (err) { - this.logService.trace( - "ExtensionManagementService.refreshControlCache - failed to get extension control manifest", - getErrorMessage(err), - ); + this.logService.trace('ExtensionManagementService.refreshControlCache - failed to get extension control manifest', getErrorMessage(err)); return { malicious: [], deprecated: {}, search: [] }; } } protected abstract getCurrentExtensionsManifestLocation(): URI; - protected abstract createInstallExtensionTask( - manifest: IExtensionManifest, - extension: URI | IGalleryExtension, - options: InstallExtensionTaskOptions, - ): IInstallExtensionTask; - protected abstract createUninstallExtensionTask( - extension: ILocalExtension, - options: UninstallExtensionTaskOptions, - ): IUninstallExtensionTask; - protected abstract copyExtension( - extension: ILocalExtension, - fromProfileLocation: URI, - toProfileLocation: URI, - metadata?: Partial, - ): Promise; + protected abstract createInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallExtensionTaskOptions): IInstallExtensionTask; + protected abstract createUninstallExtensionTask(extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask; + protected abstract copyExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata?: Partial): Promise; } -export function toExtensionManagementError( - error: Error, - code?: ExtensionManagementErrorCode, -): ExtensionManagementError { +export function toExtensionManagementError(error: Error, code?: ExtensionManagementErrorCode): ExtensionManagementError { if (error instanceof ExtensionManagementError) { return error; } let extensionManagementError: ExtensionManagementError; if (error instanceof ExtensionGalleryError) { - extensionManagementError = new ExtensionManagementError( - error.message, - error.code === ExtensionGalleryErrorCode.DownloadFailedWriting - ? ExtensionManagementErrorCode.DownloadFailedWriting - : ExtensionManagementErrorCode.Gallery, - ); + extensionManagementError = new ExtensionManagementError(error.message, error.code === ExtensionGalleryErrorCode.DownloadFailedWriting ? ExtensionManagementErrorCode.DownloadFailedWriting : ExtensionManagementErrorCode.Gallery); } else { - extensionManagementError = new ExtensionManagementError( - error.message, - isCancellationError(error) - ? ExtensionManagementErrorCode.Cancelled - : (code ?? ExtensionManagementErrorCode.Internal), - ); + extensionManagementError = new ExtensionManagementError(error.message, isCancellationError(error) ? ExtensionManagementErrorCode.Cancelled : (code ?? ExtensionManagementErrorCode.Internal)); } extensionManagementError.stack = error.stack; return extensionManagementError; } -function reportTelemetry( - telemetryService: ITelemetryService, - eventName: string, +function reportTelemetry(telemetryService: ITelemetryService, eventName: string, { extensionData, verificationStatus, duration, error, source, - durationSinceUpdate, + durationSinceUpdate }: { extensionData: any; verificationStatus?: ExtensionSignatureVerificationCode; @@ -2093,8 +932,8 @@ function reportTelemetry( durationSinceUpdate?: number; source?: string; error?: ExtensionManagementError | ExtensionGalleryError; - }, -): void { + }): void { + /* __GDPR__ "extensionGallery:install" : { "owner": "sandy081", @@ -2141,14 +980,12 @@ function reportTelemetry( durationSinceUpdate, success: !error, errorcode: error?.code, - verificationStatus: - verificationStatus === ExtensionSignatureVerificationCode.Success - ? "Verified" - : (verificationStatus ?? "Unverified"), + verificationStatus: verificationStatus === ExtensionSignatureVerificationCode.Success ? 'Verified' : (verificationStatus ?? 'Unverified') }); } export abstract class AbstractExtensionTask { + private readonly barrier = new Barrier(); private cancellablePromise: CancelablePromise | undefined; @@ -2159,9 +996,7 @@ export abstract class AbstractExtensionTask { run(): Promise { if (!this.cancellablePromise) { - this.cancellablePromise = createCancelablePromise((token) => - this.doRun(token), - ); + this.cancellablePromise = createCancelablePromise(token => this.doRun(token)); } this.barrier.open(); return this.cancellablePromise; @@ -2169,7 +1004,7 @@ export abstract class AbstractExtensionTask { cancel(): void { if (!this.cancellablePromise) { - this.cancellablePromise = createCancelablePromise((token) => { + this.cancellablePromise = createCancelablePromise(token => { return new Promise((c, e) => { const disposable = token.onCancellationRequested(() => { disposable.dispose(); diff --git a/Source/vs/platform/extensionManagement/common/allowedExtensionsService.ts b/Source/vs/platform/extensionManagement/common/allowedExtensionsService.ts index d5fd056632781..0974d293a8d5f 100644 --- a/Source/vs/platform/extensionManagement/common/allowedExtensionsService.ts +++ b/Source/vs/platform/extensionManagement/common/allowedExtensionsService.ts @@ -3,232 +3,140 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IStringDictionary } from "../../../base/common/collections.js"; -import { Emitter } from "../../../base/common/event.js"; -import { - IMarkdownString, - MarkdownString, -} from "../../../base/common/htmlContent.js"; -import { Disposable } from "../../../base/common/lifecycle.js"; -import { - isBoolean, - isObject, - isUndefined, -} from "../../../base/common/types.js"; -import { URI } from "../../../base/common/uri.js"; -import * as nls from "../../../nls.js"; -import { IConfigurationService } from "../../configuration/common/configuration.js"; -import { - ExtensionType, - IExtension, - TargetPlatform, -} from "../../extensions/common/extensions.js"; -import { IProductService } from "../../product/common/productService.js"; -import { - AllowedExtensionsConfigKey, - IAllowedExtensionsService, - IGalleryExtension, -} from "./extensionManagement.js"; +import { Disposable } from '../../../base/common/lifecycle.js'; +import { URI } from '../../../base/common/uri.js'; +import * as nls from '../../../nls.js'; +import { IGalleryExtension, AllowedExtensionsConfigKey, IAllowedExtensionsService } from './extensionManagement.js'; +import { ExtensionType, IExtension, TargetPlatform } from '../../extensions/common/extensions.js'; +import { IProductService } from '../../product/common/productService.js'; +import { IMarkdownString, MarkdownString } from '../../../base/common/htmlContent.js'; +import { IConfigurationService } from '../../configuration/common/configuration.js'; +import { IStringDictionary } from '../../../base/common/collections.js'; +import { isBoolean, isObject, isUndefined } from '../../../base/common/types.js'; +import { Emitter } from '../../../base/common/event.js'; function isGalleryExtension(extension: any): extension is IGalleryExtension { - return extension.type === "gallery"; + return extension.type === 'gallery'; } function isIExtension(extension: any): extension is IExtension { - return ( - extension.type === ExtensionType.User || - extension.type === ExtensionType.System - ); + return extension.type === ExtensionType.User || extension.type === ExtensionType.System; } + const VersionRegex = /^(?\d+\.\d+\.\d+(-.*)?)(@(?.+))?$/; -type AllowedExtensionsConfigValueType = IStringDictionary< - boolean | string | string[] ->; +type AllowedExtensionsConfigValueType = IStringDictionary; + +export class AllowedExtensionsService extends Disposable implements IAllowedExtensionsService { -export class AllowedExtensionsService - extends Disposable - implements IAllowedExtensionsService -{ _serviceBrand: undefined; private allowedExtensions: AllowedExtensionsConfigValueType | undefined; - private readonly publisherMappings: IStringDictionary = {}; + private readonly publisherOrgs: string[]; private _onDidChangeAllowedExtensions = this._register(new Emitter()); - readonly onDidChangeAllowedExtensions = - this._onDidChangeAllowedExtensions.event; + readonly onDidChangeAllowedExtensions = this._onDidChangeAllowedExtensions.event; constructor( @IProductService productService: IProductService, - @IConfigurationService - protected readonly configurationService: IConfigurationService, + @IConfigurationService protected readonly configurationService: IConfigurationService ) { super(); - for (const key in productService.extensionPublisherMappings) { - this.publisherMappings[key.toLowerCase()] = - productService.extensionPublisherMappings[key].toLowerCase(); - } + this.publisherOrgs = productService.extensionPublisherOrgs?.map(p => p.toLowerCase()) ?? []; this.allowedExtensions = this.getAllowedExtensionsValue(); - this._register( - this.configurationService.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration(AllowedExtensionsConfigKey)) { - this.allowedExtensions = this.getAllowedExtensionsValue(); - this._onDidChangeAllowedExtensions.fire(); - } - }), - ); + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(AllowedExtensionsConfigKey)) { + this.allowedExtensions = this.getAllowedExtensionsValue(); + this._onDidChangeAllowedExtensions.fire(); + } + })); } - private getAllowedExtensionsValue(): - | AllowedExtensionsConfigValueType - | undefined { - const value = this.configurationService.getValue< - AllowedExtensionsConfigValueType | undefined - >(AllowedExtensionsConfigKey); + private getAllowedExtensionsValue(): AllowedExtensionsConfigValueType | undefined { + const value = this.configurationService.getValue(AllowedExtensionsConfigKey); if (!isObject(value) || Array.isArray(value)) { return undefined; } - const entries = Object.entries(value).map(([key, value]) => [ - key.toLowerCase(), - value, - ]); - if ( - entries.length === 1 && - entries[0][0] === "*" && - entries[0][1] === true - ) { + const entries = Object.entries(value).map(([key, value]) => [key.toLowerCase(), value]); + if (entries.length === 1 && entries[0][0] === '*' && entries[0][1] === true) { return undefined; } return Object.fromEntries(entries); } - isAllowed( - extension: - | IGalleryExtension - | IExtension - | { - id: string; - version?: string; - prerelease?: boolean; - targetPlatform?: TargetPlatform; - }, - ): true | IMarkdownString { + isAllowed(extension: IGalleryExtension | IExtension | { id: string; publisherDisplayName: string | undefined; version?: string; prerelease?: boolean; targetPlatform?: TargetPlatform }): true | IMarkdownString { if (!this.allowedExtensions) { return true; } - let id: string, - version: string, - targetPlatform: TargetPlatform, - prerelease: boolean, - publisher: string; + let id: string, version: string, targetPlatform: TargetPlatform, prerelease: boolean, publisher: string, publisherDisplayName: string | undefined; if (isGalleryExtension(extension)) { id = extension.identifier.id.toLowerCase(); version = extension.version; prerelease = extension.properties.isPreReleaseVersion; publisher = extension.publisher.toLowerCase(); + publisherDisplayName = extension.publisherDisplayName.toLowerCase(); targetPlatform = extension.properties.targetPlatform; } else if (isIExtension(extension)) { id = extension.identifier.id.toLowerCase(); version = extension.manifest.version; prerelease = extension.preRelease; publisher = extension.manifest.publisher.toLowerCase(); + publisherDisplayName = extension.publisherDisplayName?.toLowerCase(); targetPlatform = extension.targetPlatform; } else { id = extension.id.toLowerCase(); - version = extension.version ?? "*"; - targetPlatform = - extension.targetPlatform ?? TargetPlatform.UNIVERSAL; + version = extension.version ?? '*'; + targetPlatform = extension.targetPlatform ?? TargetPlatform.UNIVERSAL; prerelease = extension.prerelease ?? false; - publisher = extension.id - .substring(0, extension.id.indexOf(".")) - .toLowerCase(); + publisher = extension.id.substring(0, extension.id.indexOf('.')).toLowerCase(); + publisherDisplayName = extension.publisherDisplayName?.toLowerCase(); } - const settingsCommandLink = URI.parse( - `command:workbench.action.openSettings?${encodeURIComponent(JSON.stringify({ query: `@id:${AllowedExtensionsConfigKey}` }))}`, - ).toString(); + const settingsCommandLink = URI.parse(`command:workbench.action.openSettings?${encodeURIComponent(JSON.stringify({ query: `@id:${AllowedExtensionsConfigKey}` }))}`).toString(); const extensionValue = this.allowedExtensions[id]; - const extensionReason = new MarkdownString( - nls.localize( - "specific extension not allowed", - "it is not in the [allowed list]({0})", - settingsCommandLink, - ), - ); + const extensionReason = new MarkdownString(nls.localize('specific extension not allowed', "it is not in the [allowed list]({0})", settingsCommandLink)); if (!isUndefined(extensionValue)) { if (isBoolean(extensionValue)) { return extensionValue ? true : extensionReason; } - if (extensionValue === "release" && prerelease) { - return new MarkdownString( - nls.localize( - "extension prerelease not allowed", - "the pre-release versions of this extension are not in the [allowed list]({0})", - settingsCommandLink, - ), - ); + if (extensionValue === 'release' && prerelease) { + return new MarkdownString(nls.localize('extension prerelease not allowed', "the pre-release versions of this extension are not in the [allowed list]({0})", settingsCommandLink)); } - if ( - version !== "*" && - Array.isArray(extensionValue) && - !extensionValue.some((v) => { - const match = VersionRegex.exec(v); - if (match && match.groups) { - const { platform: p, version: v } = match.groups; - if (v !== version) { - return false; - } - if ( - targetPlatform !== TargetPlatform.UNIVERSAL && - p && - targetPlatform !== p - ) { - return false; - } - return true; + if (version !== '*' && Array.isArray(extensionValue) && !extensionValue.some(v => { + const match = VersionRegex.exec(v); + if (match && match.groups) { + const { platform: p, version: v } = match.groups; + if (v !== version) { + return false; + } + if (targetPlatform !== TargetPlatform.UNIVERSAL && p && targetPlatform !== p) { + return false; } - return false; - }) - ) { - return extensionReason; + return true; + } + return false; + })) { + return new MarkdownString(nls.localize('specific version of extension not allowed', "the version {0} of this extension is not in the [allowed list]({1})", version, settingsCommandLink)); } return true; } - publisher = - this.publisherMappings[publisher]?.toLowerCase() ?? publisher; - const publisherValue = this.allowedExtensions[publisher]; + const publisherKey = publisherDisplayName && this.publisherOrgs.includes(publisherDisplayName) ? publisherDisplayName : publisher; + const publisherValue = this.allowedExtensions[publisherKey]; if (!isUndefined(publisherValue)) { if (isBoolean(publisherValue)) { - return publisherValue - ? true - : new MarkdownString( - nls.localize( - "publisher not allowed", - "the extensions from this publisher are not in the [allowed list]({1})", - publisher, - settingsCommandLink, - ), - ); + return publisherValue ? true : new MarkdownString(nls.localize('publisher not allowed', "the extensions from this publisher are not in the [allowed list]({1})", publisherKey, settingsCommandLink)); } - if (publisherValue === "release" && prerelease) { - return new MarkdownString( - nls.localize( - "prerelease versions from this publisher not allowed", - "the pre-release versions from this publisher are not in the [allowed list]({1})", - publisher, - settingsCommandLink, - ), - ); + if (publisherValue === 'release' && prerelease) { + return new MarkdownString(nls.localize('prerelease versions from this publisher not allowed', "the pre-release versions from this publisher are not in the [allowed list]({1})", publisherKey, settingsCommandLink)); } return true; } - if (this.allowedExtensions["*"] === true) { + if (this.allowedExtensions['*'] === true) { return true; } diff --git a/Source/vs/platform/extensionManagement/common/extensionGalleryService.ts b/Source/vs/platform/extensionManagement/common/extensionGalleryService.ts index eeb40d22192a2..2fd54906e5eb1 100644 --- a/Source/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/Source/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -3,89 +3,34 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { distinct } from "../../../base/common/arrays.js"; -import { CancellationToken } from "../../../base/common/cancellation.js"; -import { IStringDictionary } from "../../../base/common/collections.js"; -import { - CancellationError, - getErrorMessage, - isCancellationError, -} from "../../../base/common/errors.js"; -import { IPager } from "../../../base/common/paging.js"; -import { isWeb, platform } from "../../../base/common/platform.js"; -import { arch } from "../../../base/common/process.js"; -import { StopWatch } from "../../../base/common/stopwatch.js"; -import { format2 } from "../../../base/common/strings.js"; -import { isBoolean } from "../../../base/common/types.js"; -import { URI } from "../../../base/common/uri.js"; -import { - IHeaders, - IRequestContext, - IRequestOptions, - isOfflineError, -} from "../../../base/parts/request/common/request.js"; -import { IConfigurationService } from "../../configuration/common/configuration.js"; -import { IEnvironmentService } from "../../environment/common/environment.js"; -import { - IExtensionManifest, - TargetPlatform, -} from "../../extensions/common/extensions.js"; -import { - areApiProposalsCompatible, - isEngineValid, -} from "../../extensions/common/extensionValidator.js"; -import { resolveMarketplaceHeaders } from "../../externalServices/common/marketplace.js"; -import { IFileService } from "../../files/common/files.js"; -import { ILogService } from "../../log/common/log.js"; -import { IProductService } from "../../product/common/productService.js"; -import { - asJson, - asTextOrError, - IRequestService, - isSuccess, -} from "../../request/common/request.js"; -import { IStorageService } from "../../storage/common/storage.js"; -import { ITelemetryService } from "../../telemetry/common/telemetry.js"; -import { - ExtensionGalleryError, - ExtensionGalleryErrorCode, - getTargetPlatform, - IAllowedExtensionsService, - IDeprecationInfo, - IExtensionGalleryService, - IExtensionIdentifier, - IExtensionInfo, - IExtensionQueryOptions, - IExtensionsControlManifest, - IGalleryExtension, - IGalleryExtensionAsset, - IGalleryExtensionAssets, - IGalleryExtensionVersion, - InstallOperation, - IProductVersion, - IQueryOptions, - ISearchPrefferedResults, - isNotWebExtensionInWebTargetPlatform, - isTargetPlatformCompatible, - ITranslation, - SortBy, - SortOrder, - StatisticType, - toTargetPlatform, - UseUnpkgResourceApiConfigKey, - WEB_EXTENSION_TAG, -} from "./extensionManagement.js"; -import { - adoptToGalleryExtensionId, - areSameExtensions, - getGalleryExtensionId, - getGalleryExtensionTelemetryData, -} from "./extensionManagementUtil.js"; - -const CURRENT_TARGET_PLATFORM = isWeb - ? TargetPlatform.WEB - : getTargetPlatform(platform, arch); -const ACTIVITY_HEADER_NAME = "X-Market-Search-Activity-Id"; +import { distinct } from '../../../base/common/arrays.js'; +import { CancellationToken } from '../../../base/common/cancellation.js'; +import { IStringDictionary } from '../../../base/common/collections.js'; +import { CancellationError, getErrorMessage, isCancellationError } from '../../../base/common/errors.js'; +import { IPager } from '../../../base/common/paging.js'; +import { isWeb, platform } from '../../../base/common/platform.js'; +import { arch } from '../../../base/common/process.js'; +import { isBoolean } from '../../../base/common/types.js'; +import { URI } from '../../../base/common/uri.js'; +import { IHeaders, IRequestContext, IRequestOptions, isOfflineError } from '../../../base/parts/request/common/request.js'; +import { IConfigurationService } from '../../configuration/common/configuration.js'; +import { IEnvironmentService } from '../../environment/common/environment.js'; +import { getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionInfo, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IExtensionsControlManifest, isNotWebExtensionInWebTargetPlatform, isTargetPlatformCompatible, ITranslation, SortBy, SortOrder, StatisticType, toTargetPlatform, WEB_EXTENSION_TAG, IExtensionQueryOptions, IDeprecationInfo, ISearchPrefferedResults, ExtensionGalleryError, ExtensionGalleryErrorCode, IProductVersion, UseUnpkgResourceApiConfigKey, IAllowedExtensionsService } from './extensionManagement.js'; +import { adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, getGalleryExtensionTelemetryData } from './extensionManagementUtil.js'; +import { IExtensionManifest, TargetPlatform } from '../../extensions/common/extensions.js'; +import { areApiProposalsCompatible, isEngineValid } from '../../extensions/common/extensionValidator.js'; +import { IFileService } from '../../files/common/files.js'; +import { ILogService } from '../../log/common/log.js'; +import { IProductService } from '../../product/common/productService.js'; +import { asJson, asTextOrError, IRequestService, isSuccess } from '../../request/common/request.js'; +import { resolveMarketplaceHeaders } from '../../externalServices/common/marketplace.js'; +import { IStorageService } from '../../storage/common/storage.js'; +import { ITelemetryService } from '../../telemetry/common/telemetry.js'; +import { StopWatch } from '../../../base/common/stopwatch.js'; +import { format2 } from '../../../base/common/strings.js'; + +const CURRENT_TARGET_PLATFORM = isWeb ? TargetPlatform.WEB : getTargetPlatform(platform, arch); +const ACTIVITY_HEADER_NAME = 'X-Market-Search-Activity-Id'; interface IRawGalleryExtensionFile { readonly assetType: string; @@ -156,6 +101,7 @@ interface IRawGalleryQueryResult { } enum Flags { + /** * None is used to retrieve only the basic extension details. */ @@ -245,31 +191,31 @@ enum FilterType { Target = 8, Featured = 9, SearchText = 10, - ExcludeWithFlags = 12, + ExcludeWithFlags = 12 } const AssetType = { - Icon: "Microsoft.VisualStudio.Services.Icons.Default", - Details: "Microsoft.VisualStudio.Services.Content.Details", - Changelog: "Microsoft.VisualStudio.Services.Content.Changelog", - Manifest: "Microsoft.VisualStudio.Code.Manifest", - VSIX: "Microsoft.VisualStudio.Services.VSIXPackage", - License: "Microsoft.VisualStudio.Services.Content.License", - Repository: "Microsoft.VisualStudio.Services.Links.Source", - Signature: "Microsoft.VisualStudio.Services.VsixSignature", + Icon: 'Microsoft.VisualStudio.Services.Icons.Default', + Details: 'Microsoft.VisualStudio.Services.Content.Details', + Changelog: 'Microsoft.VisualStudio.Services.Content.Changelog', + Manifest: 'Microsoft.VisualStudio.Code.Manifest', + VSIX: 'Microsoft.VisualStudio.Services.VSIXPackage', + License: 'Microsoft.VisualStudio.Services.Content.License', + Repository: 'Microsoft.VisualStudio.Services.Links.Source', + Signature: 'Microsoft.VisualStudio.Services.VsixSignature' }; const PropertyType = { - Dependency: "Microsoft.VisualStudio.Code.ExtensionDependencies", - ExtensionPack: "Microsoft.VisualStudio.Code.ExtensionPack", - Engine: "Microsoft.VisualStudio.Code.Engine", - PreRelease: "Microsoft.VisualStudio.Code.PreRelease", - EnabledApiProposals: "Microsoft.VisualStudio.Code.EnabledApiProposals", - LocalizedLanguages: "Microsoft.VisualStudio.Code.LocalizedLanguages", - WebExtension: "Microsoft.VisualStudio.Code.WebExtension", - SponsorLink: "Microsoft.VisualStudio.Code.SponsorLink", - SupportLink: "Microsoft.VisualStudio.Services.Links.Support", - ExecutesCode: "Microsoft.VisualStudio.Code.ExecutesCode", + Dependency: 'Microsoft.VisualStudio.Code.ExtensionDependencies', + ExtensionPack: 'Microsoft.VisualStudio.Code.ExtensionPack', + Engine: 'Microsoft.VisualStudio.Code.Engine', + PreRelease: 'Microsoft.VisualStudio.Code.PreRelease', + EnabledApiProposals: 'Microsoft.VisualStudio.Code.EnabledApiProposals', + LocalizedLanguages: 'Microsoft.VisualStudio.Code.LocalizedLanguages', + WebExtension: 'Microsoft.VisualStudio.Code.WebExtension', + SponsorLink: 'Microsoft.VisualStudio.Code.SponsorLink', + SupportLink: 'Microsoft.VisualStudio.Services.Links.Support', + ExecutesCode: 'Microsoft.VisualStudio.Code.ExecutesCode', }; interface ICriterium { @@ -297,83 +243,26 @@ const DefaultQueryState: IQueryState = { sortOrder: SortOrder.Default, flags: Flags.None, criteria: [], - assetTypes: [], + assetTypes: [] }; type GalleryServiceQueryClassification = { - owner: "sandy081"; - comment: "Information about Marketplace query and its response"; - readonly filterTypes: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "Filter types used in the query."; - }; - readonly flags: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "Flags passed in the query."; - }; - readonly sortBy: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "sorted by option passed in the query"; - }; - readonly sortOrder: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "sort order option passed in the query"; - }; - readonly pageNumber: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "requested page number in the query"; - }; - readonly duration: { - classification: "SystemMetaData"; - purpose: "PerformanceAndHealth"; - "isMeasurement": true; - comment: "amount of time taken by the query request"; - }; - readonly success: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "whether the query reques is success or not"; - }; - readonly requestBodySize: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "size of the request body"; - }; - readonly responseBodySize?: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "size of the response body"; - }; - readonly statusCode?: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "status code of the response"; - }; - readonly errorCode?: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "error code of the response"; - }; - readonly count?: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "total number of extensions matching the query"; - }; - readonly source?: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "source that requested this query, eg., recommendations, viewlet"; - }; - readonly searchTextLength?: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "length of the search text in the query"; - }; + owner: 'sandy081'; + comment: 'Information about Marketplace query and its response'; + readonly filterTypes: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Filter types used in the query.' }; + readonly flags: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Flags passed in the query.' }; + readonly sortBy: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'sorted by option passed in the query' }; + readonly sortOrder: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'sort order option passed in the query' }; + readonly pageNumber: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'requested page number in the query' }; + readonly duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; 'isMeasurement': true; comment: 'amount of time taken by the query request' }; + readonly success: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'whether the query reques is success or not' }; + readonly requestBodySize: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'size of the request body' }; + readonly responseBodySize?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'size of the response body' }; + readonly statusCode?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'status code of the response' }; + readonly errorCode?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'error code of the response' }; + readonly count?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'total number of extensions matching the query' }; + readonly source?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'source that requested this query, eg., recommendations, viewlet' }; + readonly searchTextLength?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'length of the search text in the query' }; }; type QueryTelemetryData = { @@ -397,19 +286,10 @@ type GalleryServiceQueryEvent = QueryTelemetryData & { }; type GalleryServiceAdditionalQueryClassification = { - owner: "sandy081"; - comment: "Response information about the additional query to the Marketplace for fetching all versions to get release version"; - readonly duration: { - classification: "SystemMetaData"; - purpose: "PerformanceAndHealth"; - "isMeasurement": true; - comment: "Amount of time taken by the additional query"; - }; - readonly count: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "Total number of extensions returned by this additional query"; - }; + owner: 'sandy081'; + comment: 'Response information about the additional query to the Marketplace for fetching all versions to get release version'; + readonly duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; 'isMeasurement': true; comment: 'Amount of time taken by the additional query' }; + readonly count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Total number of extensions returned by this additional query' }; }; type GalleryServiceAdditionalQueryEvent = { @@ -421,47 +301,29 @@ interface IExtensionCriteria { readonly productVersion: IProductVersion; readonly targetPlatform: TargetPlatform; readonly compatible: boolean; - readonly includePreRelease: - | boolean - | (IExtensionIdentifier & { includePreRelease: boolean })[]; + readonly includePreRelease: boolean | (IExtensionIdentifier & { includePreRelease: boolean })[]; readonly versions?: (IExtensionIdentifier & { version: string })[]; } class Query { - constructor(private state = DefaultQueryState) {} - get pageNumber(): number { - return this.state.pageNumber; - } - get pageSize(): number { - return this.state.pageSize; - } - get sortBy(): number { - return this.state.sortBy; - } - get sortOrder(): number { - return this.state.sortOrder; - } - get flags(): number { - return this.state.flags; - } - get criteria(): ICriterium[] { - return this.state.criteria; - } + constructor(private state = DefaultQueryState) { } + + get pageNumber(): number { return this.state.pageNumber; } + get pageSize(): number { return this.state.pageSize; } + get sortBy(): number { return this.state.sortBy; } + get sortOrder(): number { return this.state.sortOrder; } + get flags(): number { return this.state.flags; } + get criteria(): ICriterium[] { return this.state.criteria; } - withPage( - pageNumber: number, - pageSize: number = this.state.pageSize, - ): Query { + withPage(pageNumber: number, pageSize: number = this.state.pageSize): Query { return new Query({ ...this.state, pageNumber, pageSize }); } withFilter(filterType: FilterType, ...values: string[]): Query { const criteria = [ ...this.state.criteria, - ...(values.length - ? values.map((value) => ({ filterType, value })) - : [{ filterType }]), + ...values.length ? values.map(value => ({ filterType, value })) : [{ filterType }] ]; return new Query({ ...this.state, criteria }); @@ -476,10 +338,7 @@ class Query { } withFlags(...flags: Flags[]): Query { - return new Query({ - ...this.state, - flags: flags.reduce((r, f) => r | f, 0), - }); + return new Query({ ...this.state, flags: flags.reduce((r, f) => r | f, 0) }); } withAssetTypes(...assetTypes: string[]): Query { @@ -491,210 +350,130 @@ class Query { } get raw() { - const { - criteria, - pageNumber, - pageSize, - sortBy, - sortOrder, - flags, - assetTypes, - } = this.state; + const { criteria, pageNumber, pageSize, sortBy, sortOrder, flags, assetTypes } = this.state; const filters = [{ criteria, pageNumber, pageSize, sortBy, sortOrder }]; return { filters, assetTypes, flags }; } get searchText(): string { - const criterium = this.state.criteria.filter( - (criterium) => criterium.filterType === FilterType.SearchText, - )[0]; - return criterium && criterium.value ? criterium.value : ""; + const criterium = this.state.criteria.filter(criterium => criterium.filterType === FilterType.SearchText)[0]; + return criterium && criterium.value ? criterium.value : ''; } get telemetryData(): QueryTelemetryData { return { - filterTypes: this.state.criteria.map((criterium) => - String(criterium.filterType), - ), + filterTypes: this.state.criteria.map(criterium => String(criterium.filterType)), flags: this.state.flags, sortBy: String(this.sortBy), sortOrder: String(this.sortOrder), pageNumber: String(this.pageNumber), source: this.state.source, - searchTextLength: this.searchText.length, + searchTextLength: this.searchText.length }; } } -function getStatistic( - statistics: IRawGalleryExtensionStatistics[], - name: string, -): number { - const result = (statistics || []).filter( - (s) => s.statisticName === name, - )[0]; +function getStatistic(statistics: IRawGalleryExtensionStatistics[], name: string): number { + const result = (statistics || []).filter(s => s.statisticName === name)[0]; return result ? result.value : 0; } -function getCoreTranslationAssets( - version: IRawGalleryExtensionVersion, -): [string, IGalleryExtensionAsset][] { - const coreTranslationAssetPrefix = - "Microsoft.VisualStudio.Code.Translation."; - const result = version.files.filter( - (f) => f.assetType.indexOf(coreTranslationAssetPrefix) === 0, - ); +function getCoreTranslationAssets(version: IRawGalleryExtensionVersion): [string, IGalleryExtensionAsset][] { + const coreTranslationAssetPrefix = 'Microsoft.VisualStudio.Code.Translation.'; + const result = version.files.filter(f => f.assetType.indexOf(coreTranslationAssetPrefix) === 0); return result.reduce<[string, IGalleryExtensionAsset][]>((result, file) => { const asset = getVersionAsset(version, file.assetType); if (asset) { - result.push([ - file.assetType.substring(coreTranslationAssetPrefix.length), - asset, - ]); + result.push([file.assetType.substring(coreTranslationAssetPrefix.length), asset]); } return result; }, []); } -function getRepositoryAsset( - version: IRawGalleryExtensionVersion, -): IGalleryExtensionAsset | null { +function getRepositoryAsset(version: IRawGalleryExtensionVersion): IGalleryExtensionAsset | null { if (version.properties) { - const results = version.properties.filter( - (p) => p.key === AssetType.Repository, - ); - const gitRegExp = new RegExp( - "((git|ssh|http(s)?)|(git@[\\w.]+))(:(//)?)([\\w.@:/\\-~]+)(.git)(/)?", - ); - - const uri = results.filter((r) => gitRegExp.test(r.value))[0]; + const results = version.properties.filter(p => p.key === AssetType.Repository); + const gitRegExp = new RegExp('((git|ssh|http(s)?)|(git@[\\w.]+))(:(//)?)([\\w.@:/\\-~]+)(.git)(/)?'); + + const uri = results.filter(r => gitRegExp.test(r.value))[0]; return uri ? { uri: uri.value, fallbackUri: uri.value } : null; } return getVersionAsset(version, AssetType.Repository); } -function getDownloadAsset( - version: IRawGalleryExtensionVersion, -): IGalleryExtensionAsset { +function getDownloadAsset(version: IRawGalleryExtensionVersion): IGalleryExtensionAsset { return { // always use fallbackAssetUri for download asset to hit the Marketplace API so that downloads are counted - uri: `${version.fallbackAssetUri}/${AssetType.VSIX}?redirect=true${version.targetPlatform ? `&targetPlatform=${version.targetPlatform}` : ""}`, - fallbackUri: `${version.fallbackAssetUri}/${AssetType.VSIX}${version.targetPlatform ? `?targetPlatform=${version.targetPlatform}` : ""}`, + uri: `${version.fallbackAssetUri}/${AssetType.VSIX}?redirect=true${version.targetPlatform ? `&targetPlatform=${version.targetPlatform}` : ''}`, + fallbackUri: `${version.fallbackAssetUri}/${AssetType.VSIX}${version.targetPlatform ? `?targetPlatform=${version.targetPlatform}` : ''}` }; } -function getVersionAsset( - version: IRawGalleryExtensionVersion, - type: string, -): IGalleryExtensionAsset | null { - const result = version.files.filter((f) => f.assetType === type)[0]; - return result - ? { - uri: `${version.assetUri}/${type}${version.targetPlatform ? `?targetPlatform=${version.targetPlatform}` : ""}`, - fallbackUri: `${version.fallbackAssetUri}/${type}${version.targetPlatform ? `?targetPlatform=${version.targetPlatform}` : ""}`, - } - : null; +function getVersionAsset(version: IRawGalleryExtensionVersion, type: string): IGalleryExtensionAsset | null { + const result = version.files.filter(f => f.assetType === type)[0]; + return result ? { + uri: `${version.assetUri}/${type}${version.targetPlatform ? `?targetPlatform=${version.targetPlatform}` : ''}`, + fallbackUri: `${version.fallbackAssetUri}/${type}${version.targetPlatform ? `?targetPlatform=${version.targetPlatform}` : ''}` + } : null; } -function getExtensions( - version: IRawGalleryExtensionVersion, - property: string, -): string[] { - const values = version.properties - ? version.properties.filter((p) => p.key === property) - : []; +function getExtensions(version: IRawGalleryExtensionVersion, property: string): string[] { + const values = version.properties ? version.properties.filter(p => p.key === property) : []; const value = values.length > 0 && values[0].value; - return value - ? value.split(",").map((v) => adoptToGalleryExtensionId(v)) - : []; + return value ? value.split(',').map(v => adoptToGalleryExtensionId(v)) : []; } function getEngine(version: IRawGalleryExtensionVersion): string { - const values = version.properties - ? version.properties.filter((p) => p.key === PropertyType.Engine) - : []; - return (values.length > 0 && values[0].value) || ""; + const values = version.properties ? version.properties.filter(p => p.key === PropertyType.Engine) : []; + return (values.length > 0 && values[0].value) || ''; } function isPreReleaseVersion(version: IRawGalleryExtensionVersion): boolean { - const values = version.properties - ? version.properties.filter((p) => p.key === PropertyType.PreRelease) - : []; - return values.length > 0 && values[0].value === "true"; + const values = version.properties ? version.properties.filter(p => p.key === PropertyType.PreRelease) : []; + return values.length > 0 && values[0].value === 'true'; } -function executesCode( - version: IRawGalleryExtensionVersion, -): boolean | undefined { - const values = version.properties - ? version.properties.filter((p) => p.key === PropertyType.ExecutesCode) - : []; - return values.length > 0 ? values[0].value === "true" : undefined; +function executesCode(version: IRawGalleryExtensionVersion): boolean | undefined { + const values = version.properties ? version.properties.filter(p => p.key === PropertyType.ExecutesCode) : []; + return values.length > 0 ? values[0].value === 'true' : undefined; } -function getEnabledApiProposals( - version: IRawGalleryExtensionVersion, -): string[] { - const values = version.properties - ? version.properties.filter( - (p) => p.key === PropertyType.EnabledApiProposals, - ) - : []; - const value = (values.length > 0 && values[0].value) || ""; - return value ? value.split(",") : []; +function getEnabledApiProposals(version: IRawGalleryExtensionVersion): string[] { + const values = version.properties ? version.properties.filter(p => p.key === PropertyType.EnabledApiProposals) : []; + const value = (values.length > 0 && values[0].value) || ''; + return value ? value.split(',') : []; } function getLocalizedLanguages(version: IRawGalleryExtensionVersion): string[] { - const values = version.properties - ? version.properties.filter( - (p) => p.key === PropertyType.LocalizedLanguages, - ) - : []; - const value = (values.length > 0 && values[0].value) || ""; - return value ? value.split(",") : []; + const values = version.properties ? version.properties.filter(p => p.key === PropertyType.LocalizedLanguages) : []; + const value = (values.length > 0 && values[0].value) || ''; + return value ? value.split(',') : []; } -function getSponsorLink( - version: IRawGalleryExtensionVersion, -): string | undefined { - return version.properties?.find((p) => p.key === PropertyType.SponsorLink) - ?.value; +function getSponsorLink(version: IRawGalleryExtensionVersion): string | undefined { + return version.properties?.find(p => p.key === PropertyType.SponsorLink)?.value; } -function getSupportLink( - version: IRawGalleryExtensionVersion, -): string | undefined { - return version.properties?.find((p) => p.key === PropertyType.SupportLink) - ?.value; +function getSupportLink(version: IRawGalleryExtensionVersion): string | undefined { + return version.properties?.find(p => p.key === PropertyType.SupportLink)?.value; } function getIsPreview(flags: string): boolean { - return flags.indexOf("preview") !== -1; + return flags.indexOf('preview') !== -1; } -function getTargetPlatformForExtensionVersion( - version: IRawGalleryExtensionVersion, -): TargetPlatform { - return version.targetPlatform - ? toTargetPlatform(version.targetPlatform) - : TargetPlatform.UNDEFINED; +function getTargetPlatformForExtensionVersion(version: IRawGalleryExtensionVersion): TargetPlatform { + return version.targetPlatform ? toTargetPlatform(version.targetPlatform) : TargetPlatform.UNDEFINED; } -function getAllTargetPlatforms( - rawGalleryExtension: IRawGalleryExtension, -): TargetPlatform[] { - const allTargetPlatforms = distinct( - rawGalleryExtension.versions.map(getTargetPlatformForExtensionVersion), - ); +function getAllTargetPlatforms(rawGalleryExtension: IRawGalleryExtension): TargetPlatform[] { + const allTargetPlatforms = distinct(rawGalleryExtension.versions.map(getTargetPlatformForExtensionVersion)); // Is a web extension only if it has WEB_EXTENSION_TAG - const isWebExtension = - !!rawGalleryExtension.tags?.includes(WEB_EXTENSION_TAG); + const isWebExtension = !!rawGalleryExtension.tags?.includes(WEB_EXTENSION_TAG); // Include Web Target Platform only if it is a web extension - const webTargetPlatformIndex = allTargetPlatforms.indexOf( - TargetPlatform.WEB, - ); + const webTargetPlatformIndex = allTargetPlatforms.indexOf(TargetPlatform.WEB); if (isWebExtension) { if (webTargetPlatformIndex === -1) { // Web extension but does not has web target platform -> add it @@ -710,25 +489,16 @@ function getAllTargetPlatforms( return allTargetPlatforms; } -export function sortExtensionVersions( - versions: IRawGalleryExtensionVersion[], - preferredTargetPlatform: TargetPlatform, -): IRawGalleryExtensionVersion[] { +export function sortExtensionVersions(versions: IRawGalleryExtensionVersion[], preferredTargetPlatform: TargetPlatform): IRawGalleryExtensionVersion[] { /* It is expected that versions from Marketplace are sorted by version. So we are just sorting by preferred targetPlatform */ for (let index = 0; index < versions.length; index++) { const version = versions[index]; if (version.version === versions[index - 1]?.version) { let insertionIndex = index; - const versionTargetPlatform = - getTargetPlatformForExtensionVersion(version); + const versionTargetPlatform = getTargetPlatformForExtensionVersion(version); /* put it at the beginning */ if (versionTargetPlatform === preferredTargetPlatform) { - while ( - insertionIndex > 0 && - versions[insertionIndex - 1].version === version.version - ) { - insertionIndex--; - } + while (insertionIndex > 0 && versions[insertionIndex - 1].version === version.version) { insertionIndex--; } } if (insertionIndex !== index) { versions.splice(index, 1); @@ -739,11 +509,7 @@ export function sortExtensionVersions( return versions; } -function setTelemetry( - extension: IGalleryExtension, - index: number, - querySource?: string, -): void { +function setTelemetry(extension: IGalleryExtension, index: number, querySource?: string): void { /* __GDPR__FRAGMENT__ "GalleryExtensionTelemetryData2" : { "index" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -751,19 +517,10 @@ function setTelemetry( "queryActivityId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ - extension.telemetryData = { - index, - querySource, - queryActivityId: extension.queryContext?.[ACTIVITY_HEADER_NAME], - }; + extension.telemetryData = { index, querySource, queryActivityId: extension.queryContext?.[ACTIVITY_HEADER_NAME] }; } -function toExtension( - galleryExtension: IRawGalleryExtension, - version: IRawGalleryExtensionVersion, - allTargetPlatforms: TargetPlatform[], - queryContext?: IStringDictionary, -): IGalleryExtension { +function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGalleryExtensionVersion, allTargetPlatforms: TargetPlatform[], queryContext?: IStringDictionary): IGalleryExtension { const latestVersion = galleryExtension.versions[0]; const assets: IGalleryExtensionAssets = { manifest: getVersionAsset(version, AssetType.Manifest), @@ -774,17 +531,14 @@ function toExtension( download: getDownloadAsset(version), icon: getVersionAsset(version, AssetType.Icon), signature: getVersionAsset(version, AssetType.Signature), - coreTranslations: getCoreTranslationAssets(version), + coreTranslations: getCoreTranslationAssets(version) }; return { - type: "gallery", + type: 'gallery', identifier: { - id: getGalleryExtensionId( - galleryExtension.publisher.publisherName, - galleryExtension.extensionName, - ), - uuid: galleryExtension.extensionId, + id: getGalleryExtensionId(galleryExtension.publisher.publisherName, galleryExtension.extensionName), + uuid: galleryExtension.extensionId }, name: galleryExtension.extensionName, version: version.version, @@ -792,17 +546,12 @@ function toExtension( publisherId: galleryExtension.publisher.publisherId, publisher: galleryExtension.publisher.publisherName, publisherDisplayName: galleryExtension.publisher.displayName, - publisherDomain: galleryExtension.publisher.domain - ? { - link: galleryExtension.publisher.domain, - verified: !!galleryExtension.publisher.isDomainVerified, - } - : undefined, + publisherDomain: galleryExtension.publisher.domain ? { link: galleryExtension.publisher.domain, verified: !!galleryExtension.publisher.isDomainVerified } : undefined, publisherSponsorLink: getSponsorLink(latestVersion), - description: galleryExtension.shortDescription ?? "", - installCount: getStatistic(galleryExtension.statistics, "install"), - rating: getStatistic(galleryExtension.statistics, "averagerating"), - ratingCount: getStatistic(galleryExtension.statistics, "ratingcount"), + description: galleryExtension.shortDescription ?? '', + installCount: getStatistic(galleryExtension.statistics, 'install'), + rating: getStatistic(galleryExtension.statistics, 'averagerating'), + ratingCount: getStatistic(galleryExtension.statistics, 'ratingcount'), categories: galleryExtension.categories || [], tags: galleryExtension.tags || [], releaseDate: Date.parse(galleryExtension.releaseDate), @@ -817,14 +566,14 @@ function toExtension( localizedLanguages: getLocalizedLanguages(version), targetPlatform: getTargetPlatformForExtensionVersion(version), isPreReleaseVersion: isPreReleaseVersion(version), - executesCode: executesCode(version), + executesCode: executesCode(version) }, hasPreReleaseVersion: isPreReleaseVersion(latestVersion), hasReleaseVersion: true, preview: getIsPreview(galleryExtension.flags), isSigned: !!assets.signature, queryContext, - supportLink: getSupportLink(latestVersion), + supportLink: getSupportLink(latestVersion) }; } @@ -836,25 +585,21 @@ interface IRawExtensionsControlManifest { migrateStorage?: boolean; engine?: string; }>; - deprecated?: IStringDictionary< - | boolean - | { - disallowInstall?: boolean; - extension?: { - id: string; - displayName: string; - }; - settings?: string[]; - additionalInfo?: string; - } - >; + deprecated?: IStringDictionary; search?: ISearchPrefferedResults[]; extensionsEnabledWithPreRelease?: string[]; } -abstract class AbstractExtensionGalleryService - implements IExtensionGalleryService -{ +abstract class AbstractExtensionGalleryService implements IExtensionGalleryService { + declare readonly _serviceBrand: undefined; private readonly extensionsGalleryUrl: string | undefined; @@ -869,32 +614,20 @@ abstract class AbstractExtensionGalleryService storageService: IStorageService | undefined, @IRequestService private readonly requestService: IRequestService, @ILogService private readonly logService: ILogService, - @IEnvironmentService - private readonly environmentService: IEnvironmentService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IFileService private readonly fileService: IFileService, @IProductService private readonly productService: IProductService, - @IConfigurationService - private readonly configurationService: IConfigurationService, - @IAllowedExtensionsService - private readonly allowedExtensionsService: IAllowedExtensionsService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService, ) { const config = productService.extensionsGallery; - const isPPEEnabled = - config?.servicePPEUrl && - configurationService.getValue("_extensionsGallery.enablePPE"); - this.extensionsGalleryUrl = isPPEEnabled - ? config.servicePPEUrl - : config?.serviceUrl; - this.extensionsGallerySearchUrl = isPPEEnabled - ? undefined - : config?.searchUrl; + const isPPEEnabled = config?.servicePPEUrl && configurationService.getValue('_extensionsGallery.enablePPE'); + this.extensionsGalleryUrl = isPPEEnabled ? config.servicePPEUrl : config?.serviceUrl; + this.extensionsGallerySearchUrl = isPPEEnabled ? undefined : config?.searchUrl; this.extensionsControlUrl = config?.controlUrl; this.extensionUrlTemplate = config?.extensionUrlTemplate; - this.extensionsEnabledWithApiProposalVersion = - productService.extensionsEnabledWithApiProposalVersion?.map((id) => - id.toLowerCase(), - ) ?? []; + this.extensionsEnabledWithApiProposalVersion = productService.extensionsEnabledWithApiProposalVersion?.map(id => id.toLowerCase()) ?? []; this.commonHeadersPromise = resolveMarketplaceHeaders( productService.version, productService, @@ -902,11 +635,10 @@ abstract class AbstractExtensionGalleryService this.configurationService, this.fileService, storageService, - this.telemetryService, - ); + this.telemetryService); } - private api(path = ""): string { + private api(path = ''): string { return `${this.extensionsGalleryUrl}${path}`; } @@ -914,44 +646,22 @@ abstract class AbstractExtensionGalleryService return !!this.extensionsGalleryUrl; } - getExtensions( - extensionInfos: ReadonlyArray, - token: CancellationToken, - ): Promise; - getExtensions( - extensionInfos: ReadonlyArray, - options: IExtensionQueryOptions, - token: CancellationToken, - ): Promise; - async getExtensions( - extensionInfos: ReadonlyArray, - arg1: any, - arg2?: any, - ): Promise { + getExtensions(extensionInfos: ReadonlyArray, token: CancellationToken): Promise; + getExtensions(extensionInfos: ReadonlyArray, options: IExtensionQueryOptions, token: CancellationToken): Promise; + async getExtensions(extensionInfos: ReadonlyArray, arg1: any, arg2?: any): Promise { if (!this.isEnabled()) { - throw new Error("No extension gallery service configured."); + throw new Error('No extension gallery service configured.'); } - const options = CancellationToken.isCancellationToken(arg1) - ? {} - : (arg1 as IExtensionQueryOptions); - const token = CancellationToken.isCancellationToken(arg1) - ? arg1 - : (arg2 as CancellationToken); + const options = CancellationToken.isCancellationToken(arg1) ? {} : arg1 as IExtensionQueryOptions; + const token = CancellationToken.isCancellationToken(arg1) ? arg1 : arg2 as CancellationToken; - const useResourceApi = - options.preferResourceApi && - (this.configurationService.getValue(UseUnpkgResourceApiConfigKey) ?? - false); + const useResourceApi = options.preferResourceApi && (this.configurationService.getValue(UseUnpkgResourceApiConfigKey) ?? false); const result = useResourceApi - ? await this.getExtensionsUsingResourceApi( - extensionInfos, - options, - token, - ) + ? await this.getExtensionsUsingResourceApi(extensionInfos, options, token) : await this.doGetExtensions(extensionInfos, options, token); - const uuids = result.map((r) => r.identifier.uuid); + const uuids = result.map(r => r.identifier.uuid); const extensionInfosByName: IExtensionInfo[] = []; for (const e of extensionInfos) { if (e.uuid && !uuids.includes(e.uuid)) { @@ -964,40 +674,22 @@ abstract class AbstractExtensionGalleryService this.telemetryService.publicLog2< { count: number }, { - owner: "sandy081"; - comment: "Report the query to the the Marketplace for fetching extensions by name"; - readonly count: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "Number of extensions to fetch"; - }; - } - >("galleryService:additionalQueryByName", { - count: extensionInfosByName.length, - }); + owner: 'sandy081'; + comment: 'Report the query to the the Marketplace for fetching extensions by name'; + readonly count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of extensions to fetch' }; + }>('galleryService:additionalQueryByName', { + count: extensionInfosByName.length + }); - const extensions = await this.doGetExtensions( - extensionInfosByName, - options, - token, - ); + const extensions = await this.doGetExtensions(extensionInfosByName, options, token); result.push(...extensions); } return result; } - private async doGetExtensions( - extensionInfos: ReadonlyArray, - options: IExtensionQueryOptions, - token: CancellationToken, - ): Promise { - const names: string[] = []; - const ids: string[] = [], - includePreReleases: (IExtensionIdentifier & { - includePreRelease: boolean; - })[] = [], - versions: (IExtensionIdentifier & { version: string })[] = []; + private async doGetExtensions(extensionInfos: ReadonlyArray, options: IExtensionQueryOptions, token: CancellationToken): Promise { + const names: string[] = []; const ids: string[] = [], includePreReleases: (IExtensionIdentifier & { includePreRelease: boolean })[] = [], versions: (IExtensionIdentifier & { version: string })[] = []; let isQueryForReleaseVersionFromPreReleaseVersion = true; for (const extensionInfo of extensionInfos) { if (extensionInfo.uuid) { @@ -1006,25 +698,12 @@ abstract class AbstractExtensionGalleryService names.push(extensionInfo.id); } // Set includePreRelease to true if version is set, because the version can be a pre-release version - const includePreRelease = !!( - extensionInfo.version || extensionInfo.preRelease - ); - includePreReleases.push({ - id: extensionInfo.id, - uuid: extensionInfo.uuid, - includePreRelease, - }); + const includePreRelease = !!(extensionInfo.version || extensionInfo.preRelease); + includePreReleases.push({ id: extensionInfo.id, uuid: extensionInfo.uuid, includePreRelease }); if (extensionInfo.version) { - versions.push({ - id: extensionInfo.id, - uuid: extensionInfo.uuid, - version: extensionInfo.version, - }); + versions.push({ id: extensionInfo.id, uuid: extensionInfo.uuid, version: extensionInfo.version }); } - isQueryForReleaseVersionFromPreReleaseVersion = - isQueryForReleaseVersionFromPreReleaseVersion && - !!extensionInfo.hasPreRelease && - !includePreRelease; + isQueryForReleaseVersionFromPreReleaseVersion = isQueryForReleaseVersionFromPreReleaseVersion && (!!extensionInfo.hasPreRelease && !includePreRelease); } if (!ids.length && !names.length) { @@ -1038,135 +717,81 @@ abstract class AbstractExtensionGalleryService if (names.length) { query = query.withFilter(FilterType.ExtensionName, ...names); } - if ( - options.queryAllVersions || - isQueryForReleaseVersionFromPreReleaseVersion /* Inlcude all versions if every requested extension is for release version and has pre-release version */ - ) { + if (options.queryAllVersions || isQueryForReleaseVersionFromPreReleaseVersion /* Inlcude all versions if every requested extension is for release version and has pre-release version */) { query = query.withFlags(query.flags, Flags.IncludeVersions); } if (options.source) { query = query.withSource(options.source); } - const { extensions } = await this.queryGalleryExtensions( - query, - { - targetPlatform: - options.targetPlatform ?? CURRENT_TARGET_PLATFORM, - includePreRelease: includePreReleases, - versions, - compatible: !!options.compatible, - productVersion: options.productVersion ?? { - version: this.productService.version, - date: this.productService.date, - }, - }, - token, - ); + const { extensions } = await this.queryGalleryExtensions(query, { targetPlatform: options.targetPlatform ?? CURRENT_TARGET_PLATFORM, includePreRelease: includePreReleases, versions, compatible: !!options.compatible, productVersion: options.productVersion ?? { version: this.productService.version, date: this.productService.date } }, token); if (options.source) { - extensions.forEach((e, index) => - setTelemetry(e, index, options.source), - ); + extensions.forEach((e, index) => setTelemetry(e, index, options.source)); } return extensions; } - private async getExtensionsUsingResourceApi( - extensionInfos: ReadonlyArray, - options: IExtensionQueryOptions, - token: CancellationToken, - ): Promise { + private async getExtensionsUsingResourceApi(extensionInfos: ReadonlyArray, options: IExtensionQueryOptions, token: CancellationToken): Promise { + const toQuery: IExtensionInfo[] = []; const result: IGalleryExtension[] = []; - await Promise.allSettled( - extensionInfos.map(async (extensionInfo) => { - if (extensionInfo.version) { + await Promise.allSettled(extensionInfos.map(async extensionInfo => { + if (extensionInfo.version) { + toQuery.push(extensionInfo); + return; + } + + try { + const rawGalleryExtension = await this.getLatestRawGalleryExtension(extensionInfo.id, token); + if (!rawGalleryExtension) { toQuery.push(extensionInfo); return; } - try { - const rawGalleryExtension = - await this.getLatestRawGalleryExtension( - extensionInfo.id, - token, - ); - if (!rawGalleryExtension) { - toQuery.push(extensionInfo); - return; + const extension = await this.toGalleryExtensionWithCriteria(rawGalleryExtension, { + targetPlatform: options.targetPlatform ?? CURRENT_TARGET_PLATFORM, + includePreRelease: !!extensionInfo.preRelease, + compatible: !!options.compatible, + productVersion: options.productVersion ?? { + version: this.productService.version, + date: this.productService.date } + }); - const extension = await this.toGalleryExtensionWithCriteria( - rawGalleryExtension, + if (extension) { + result.push(extension); + } + + // report telemetry + else { + this.telemetryService.publicLog2< { - targetPlatform: - options.targetPlatform ?? - CURRENT_TARGET_PLATFORM, - includePreRelease: !!extensionInfo.preRelease, - compatible: !!options.compatible, - productVersion: options.productVersion ?? { - version: this.productService.version, - date: this.productService.date, - }, + extension: string; + preRelease: boolean; + compatible: boolean; }, - ); - - if (extension) { - result.push(extension); - } - - // report telemetry - else { - this.telemetryService.publicLog2< - { - extension: string; - preRelease: boolean; - compatible: boolean; - }, - { - owner: "sandy081"; - comment: "Report the fallback to the Marketplace query for fetching extensions"; - extension: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "Extension id"; - }; - preRelease: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "Get pre-release version"; - }; - compatible: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "Get compatible version"; - }; - } - >("galleryService:fallbacktoquery", { + { + owner: 'sandy081'; + comment: 'Report the fallback to the Marketplace query for fetching extensions'; + extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension id' }; + preRelease: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Get pre-release version' }; + compatible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Get compatible version' }; + }>('galleryService:fallbacktoquery', { extension: extensionInfo.id, preRelease: !!extensionInfo.preRelease, - compatible: !!options.compatible, + compatible: !!options.compatible }); - if ( - !options.compatible || - this.allowedExtensionsService.isAllowed({ - id: extensionInfo.id, - }) === true - ) { - toQuery.push(extensionInfo); - } + if (!options.compatible || this.allowedExtensionsService.isAllowed({ id: extensionInfo.id, publisherDisplayName: rawGalleryExtension.publisher.displayName }) === true) { + toQuery.push(extensionInfo); } - } catch (error) { - // Skip if there is an error while getting the latest version - this.logService.error( - `Error while getting the latest version for the extension ${extensionInfo.id}.`, - getErrorMessage(error), - ); - toQuery.push(extensionInfo); } - }), - ); + } catch (error) { + // Skip if there is an error while getting the latest version + this.logService.error(`Error while getting the latest version for the extension ${extensionInfo.id}.`, getErrorMessage(error)); + toQuery.push(extensionInfo); + } + })); const extensions = await this.doGetExtensions(toQuery, options, token); result.push(...extensions); @@ -1174,76 +799,30 @@ abstract class AbstractExtensionGalleryService return result; } - async getCompatibleExtension( - extension: IGalleryExtension, - includePreRelease: boolean, - targetPlatform: TargetPlatform, - productVersion: IProductVersion = { - version: this.productService.version, - date: this.productService.date, - }, - ): Promise { - if ( - isNotWebExtensionInWebTargetPlatform( - extension.allTargetPlatforms, - targetPlatform, - ) - ) { + async getCompatibleExtension(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise { + if (isNotWebExtensionInWebTargetPlatform(extension.allTargetPlatforms, targetPlatform)) { return null; } - if ( - await this.isExtensionCompatible( - extension, - includePreRelease, - targetPlatform, - ) - ) { + if (await this.isExtensionCompatible(extension, includePreRelease, targetPlatform)) { return extension; } - if ( - this.allowedExtensionsService.isAllowed({ - id: extension.identifier.id, - }) !== true - ) { + if (this.allowedExtensionsService.isAllowed({ id: extension.identifier.id, publisherDisplayName: extension.publisherDisplayName }) !== true) { return null; } const query = new Query() .withFlags(Flags.IncludeVersions) .withPage(1, 1) .withFilter(FilterType.ExtensionId, extension.identifier.uuid); - const { extensions } = await this.queryGalleryExtensions( - query, - { - targetPlatform, - compatible: true, - includePreRelease, - productVersion, - }, - CancellationToken.None, - ); + const { extensions } = await this.queryGalleryExtensions(query, { targetPlatform, compatible: true, includePreRelease, productVersion }, CancellationToken.None); return extensions[0] || null; } - async isExtensionCompatible( - extension: IGalleryExtension, - includePreRelease: boolean, - targetPlatform: TargetPlatform, - productVersion: IProductVersion = { - version: this.productService.version, - date: this.productService.date, - }, - ): Promise { + async isExtensionCompatible(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise { if (this.allowedExtensionsService.isAllowed(extension) !== true) { return false; } - if ( - !isTargetPlatformCompatible( - extension.properties.targetPlatform, - extension.allTargetPlatforms, - targetPlatform, - ) - ) { + if (!isTargetPlatformCompatible(extension.properties.targetPlatform, extension.allTargetPlatforms, targetPlatform)) { return false; } @@ -1254,117 +833,55 @@ abstract class AbstractExtensionGalleryService let engine = extension.properties.engine; if (!engine) { - const manifest = await this.getManifest( - extension, - CancellationToken.None, - ); + const manifest = await this.getManifest(extension, CancellationToken.None); if (!manifest) { - throw new Error("Manifest was not found"); + throw new Error('Manifest was not found'); } engine = manifest.engines.vscode; } - if ( - !isEngineValid(engine, productVersion.version, productVersion.date) - ) { + if (!isEngineValid(engine, productVersion.version, productVersion.date)) { return false; } - if ( - !this.areApiProposalsCompatible( - extension.identifier, - extension.properties.enabledApiProposals, - ) - ) { + if (!this.areApiProposalsCompatible(extension.identifier, extension.properties.enabledApiProposals)) { return false; } return true; } - private areApiProposalsCompatible( - extensionIdentifier: IExtensionIdentifier, - enabledApiProposals: string[] | undefined, - ): boolean { + private areApiProposalsCompatible(extensionIdentifier: IExtensionIdentifier, enabledApiProposals: string[] | undefined): boolean { if (!enabledApiProposals) { return true; } - if ( - !this.extensionsEnabledWithApiProposalVersion.includes( - extensionIdentifier.id.toLowerCase(), - ) - ) { + if (!this.extensionsEnabledWithApiProposalVersion.includes(extensionIdentifier.id.toLowerCase())) { return true; } return areApiProposalsCompatible(enabledApiProposals); } - private async isValidVersion( - extension: string, - rawGalleryExtensionVersion: IRawGalleryExtensionVersion, - versionType: "release" | "prerelease" | "any", - compatible: boolean, - allTargetPlatforms: TargetPlatform[], - targetPlatform: TargetPlatform, - productVersion: IProductVersion = { - version: this.productService.version, - date: this.productService.date, - }, - ): Promise { - const targetPlatformForExtension = getTargetPlatformForExtensionVersion( - rawGalleryExtensionVersion, - ); - if ( - !isTargetPlatformCompatible( - targetPlatformForExtension, - allTargetPlatforms, - targetPlatform, - ) - ) { + private async isValidVersion(extension: string, rawGalleryExtensionVersion: IRawGalleryExtensionVersion, publisherDisplayName: string, versionType: 'release' | 'prerelease' | 'any', compatible: boolean, allTargetPlatforms: TargetPlatform[], targetPlatform: TargetPlatform, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise { + const targetPlatformForExtension = getTargetPlatformForExtensionVersion(rawGalleryExtensionVersion); + if (!isTargetPlatformCompatible(targetPlatformForExtension, allTargetPlatforms, targetPlatform)) { return false; } - if ( - versionType !== "any" && - isPreReleaseVersion(rawGalleryExtensionVersion) !== - (versionType === "prerelease") - ) { + if (versionType !== 'any' && isPreReleaseVersion(rawGalleryExtensionVersion) !== (versionType === 'prerelease')) { return false; } if (compatible) { - if ( - this.allowedExtensionsService.isAllowed({ - id: extension, - version: rawGalleryExtensionVersion.version, - prerelease: - versionType === "any" - ? undefined - : isPreReleaseVersion(rawGalleryExtensionVersion), - targetPlatform: targetPlatformForExtension, - }) !== true - ) { + if (this.allowedExtensionsService.isAllowed({ id: extension, publisherDisplayName, version: rawGalleryExtensionVersion.version, prerelease: versionType === 'any' ? undefined : isPreReleaseVersion(rawGalleryExtensionVersion), targetPlatform: targetPlatformForExtension }) !== true) { return false; } try { - const engine = await this.getEngine( - extension, - rawGalleryExtensionVersion, - ); - if ( - !isEngineValid( - engine, - productVersion.version, - productVersion.date, - ) - ) { + const engine = await this.getEngine(extension, rawGalleryExtensionVersion); + if (!isEngineValid(engine, productVersion.version, productVersion.date)) { return false; } } catch (error) { - this.logService.error( - `Error while getting the engine for the version ${rawGalleryExtensionVersion.version}.`, - getErrorMessage(error), - ); + this.logService.error(`Error while getting the engine for the version ${rawGalleryExtensionVersion.version}.`, getErrorMessage(error)); return false; } } @@ -1372,41 +889,30 @@ abstract class AbstractExtensionGalleryService return true; } - async query( - options: IQueryOptions, - token: CancellationToken, - ): Promise> { - let text = options.text || ""; + async query(options: IQueryOptions, token: CancellationToken): Promise> { + let text = options.text || ''; const pageSize = options.pageSize ?? 50; - let query = new Query().withPage(1, pageSize); + let query = new Query() + .withPage(1, pageSize); if (text) { // Use category filter instead of "category:themes" - text = text.replace( - /\bcategory:("([^"]*)"|([^"]\S*))(\s+|\b|$)/g, - (_, quotedCategory, category) => { - query = query.withFilter( - FilterType.Category, - category || quotedCategory, - ); - return ""; - }, - ); + text = text.replace(/\bcategory:("([^"]*)"|([^"]\S*))(\s+|\b|$)/g, (_, quotedCategory, category) => { + query = query.withFilter(FilterType.Category, category || quotedCategory); + return ''; + }); // Use tag filter instead of "tag:debuggers" - text = text.replace( - /\btag:("([^"]*)"|([^"]\S*))(\s+|\b|$)/g, - (_, quotedTag, tag) => { - query = query.withFilter(FilterType.Tag, tag || quotedTag); - return ""; - }, - ); + text = text.replace(/\btag:("([^"]*)"|([^"]\S*))(\s+|\b|$)/g, (_, quotedTag, tag) => { + query = query.withFilter(FilterType.Tag, tag || quotedTag); + return ''; + }); // Use featured filter text = text.replace(/\bfeatured(\s+|\b|$)/g, () => { query = query.withFilter(FilterType.Featured); - return ""; + return ''; }); text = text.trim(); @@ -1421,11 +927,11 @@ abstract class AbstractExtensionGalleryService query = query.withSortBy(SortBy.InstallCount); } - if (typeof options.sortBy === "number") { + if (typeof options.sortBy === 'number') { query = query.withSortBy(options.sortBy); } - if (typeof options.sortOrder === "number") { + if (typeof options.sortOrder === 'number') { query = query.withSortOrder(options.sortOrder); } @@ -1434,26 +940,8 @@ abstract class AbstractExtensionGalleryService } const runQuery = async (query: Query, token: CancellationToken) => { - const { extensions, total } = await this.queryGalleryExtensions( - query, - { - targetPlatform: CURRENT_TARGET_PLATFORM, - compatible: false, - includePreRelease: !!options.includePreRelease, - productVersion: options.productVersion ?? { - version: this.productService.version, - date: this.productService.date, - }, - }, - token, - ); - extensions.forEach((e, index) => - setTelemetry( - e, - (query.pageNumber - 1) * query.pageSize + index, - options.source, - ), - ); + const { extensions, total } = await this.queryGalleryExtensions(query, { targetPlatform: CURRENT_TARGET_PLATFORM, compatible: false, includePreRelease: !!options.includePreRelease, productVersion: options.productVersion ?? { version: this.productService.version, date: this.productService.date } }, token); + extensions.forEach((e, index) => setTelemetry(e, ((query.pageNumber - 1) * query.pageSize) + index, options.source)); return { extensions, total }; }; const { extensions, total } = await runQuery(query, token); @@ -1461,92 +949,48 @@ abstract class AbstractExtensionGalleryService if (ct.isCancellationRequested) { throw new CancellationError(); } - const { extensions } = await runQuery( - query.withPage(pageIndex + 1), - ct, - ); + const { extensions } = await runQuery(query.withPage(pageIndex + 1), ct); return extensions; }; - return { - firstPage: extensions, - total, - pageSize: query.pageSize, - getPage, - }; + return { firstPage: extensions, total, pageSize: query.pageSize, getPage }; } - private async queryGalleryExtensions( - query: Query, - criteria: IExtensionCriteria, - token: CancellationToken, - ): Promise<{ extensions: IGalleryExtension[]; total: number }> { + private async queryGalleryExtensions(query: Query, criteria: IExtensionCriteria, token: CancellationToken): Promise<{ extensions: IGalleryExtension[]; total: number }> { const flags = query.flags; /** * If both version flags (IncludeLatestVersionOnly and IncludeVersions) are included, then only include latest versions (IncludeLatestVersionOnly) flag. */ - if ( - !!(query.flags & Flags.IncludeLatestVersionOnly) && - !!(query.flags & Flags.IncludeVersions) - ) { - query = query.withFlags( - query.flags & ~Flags.IncludeVersions, - Flags.IncludeLatestVersionOnly, - ); + if (!!(query.flags & Flags.IncludeLatestVersionOnly) && !!(query.flags & Flags.IncludeVersions)) { + query = query.withFlags(query.flags & ~Flags.IncludeVersions, Flags.IncludeLatestVersionOnly); } /** * If version flags (IncludeLatestVersionOnly and IncludeVersions) are not included, default is to query for latest versions (IncludeLatestVersionOnly). */ - if ( - !(query.flags & Flags.IncludeLatestVersionOnly) && - !(query.flags & Flags.IncludeVersions) - ) { - query = query.withFlags( - query.flags, - Flags.IncludeLatestVersionOnly, - ); + if (!(query.flags & Flags.IncludeLatestVersionOnly) && !(query.flags & Flags.IncludeVersions)) { + query = query.withFlags(query.flags, Flags.IncludeLatestVersionOnly); } /** * If versions criteria exist, then remove IncludeLatestVersionOnly flag and add IncludeVersions flag. */ if (criteria.versions?.length) { - query = query.withFlags( - query.flags & ~Flags.IncludeLatestVersionOnly, - Flags.IncludeVersions, - ); + query = query.withFlags(query.flags & ~Flags.IncludeLatestVersionOnly, Flags.IncludeVersions); } /** * Add necessary extension flags */ - query = query.withFlags( - query.flags, - Flags.IncludeAssetUri, - Flags.IncludeCategoryAndTags, - Flags.IncludeFiles, - Flags.IncludeStatistics, - Flags.IncludeVersionProperties, - ); - const { - galleryExtensions: rawGalleryExtensions, - total, - context, - } = await this.queryRawGalleryExtensions(query, token); - - const hasAllVersions: boolean = !( - query.flags & Flags.IncludeLatestVersionOnly - ); + query = query.withFlags(query.flags, Flags.IncludeAssetUri, Flags.IncludeCategoryAndTags, Flags.IncludeFiles, Flags.IncludeStatistics, Flags.IncludeVersionProperties); + const { galleryExtensions: rawGalleryExtensions, total, context } = await this.queryRawGalleryExtensions(query, token); + + const hasAllVersions: boolean = !(query.flags & Flags.IncludeLatestVersionOnly); if (hasAllVersions) { const extensions: IGalleryExtension[] = []; for (const rawGalleryExtension of rawGalleryExtensions) { - const extension = await this.toGalleryExtensionWithCriteria( - rawGalleryExtension, - criteria, - context, - ); + const extension = await this.toGalleryExtensionWithCriteria(rawGalleryExtension, criteria, context); if (extension) { extensions.push(extension); } @@ -1558,69 +1002,37 @@ abstract class AbstractExtensionGalleryService const needAllVersions = new Map(); for (let index = 0; index < rawGalleryExtensions.length; index++) { const rawGalleryExtension = rawGalleryExtensions[index]; - const extensionIdentifier = { - id: getGalleryExtensionId( - rawGalleryExtension.publisher.publisherName, - rawGalleryExtension.extensionName, - ), - uuid: rawGalleryExtension.extensionId, - }; - const includePreRelease = isBoolean(criteria.includePreRelease) - ? criteria.includePreRelease - : !!criteria.includePreRelease.find( - (extensionIdentifierWithPreRelease) => - areSameExtensions( - extensionIdentifierWithPreRelease, - extensionIdentifier, - ), - )?.includePreRelease; + const extensionIdentifier = { id: getGalleryExtensionId(rawGalleryExtension.publisher.publisherName, rawGalleryExtension.extensionName), uuid: rawGalleryExtension.extensionId }; + const includePreRelease = isBoolean(criteria.includePreRelease) ? criteria.includePreRelease : !!criteria.includePreRelease.find(extensionIdentifierWithPreRelease => areSameExtensions(extensionIdentifierWithPreRelease, extensionIdentifier))?.includePreRelease; if (criteria.compatible) { /** Skip if requested for a web-compatible extension and it is not a web extension. * All versions are not needed in this case - */ - if ( - isNotWebExtensionInWebTargetPlatform( - getAllTargetPlatforms(rawGalleryExtension), - criteria.targetPlatform, - ) - ) { + */ + if (isNotWebExtensionInWebTargetPlatform(getAllTargetPlatforms(rawGalleryExtension), criteria.targetPlatform)) { continue; } /** * Skip if the extension is not allowed. * All versions are not needed in this case */ - if ( - this.allowedExtensionsService.isAllowed({ - id: extensionIdentifier.id, - }) !== true - ) { + if (this.allowedExtensionsService.isAllowed({ id: extensionIdentifier.id, publisherDisplayName: rawGalleryExtension.publisher.displayName }) !== true) { continue; } } - const extension = await this.toGalleryExtensionWithCriteria( - rawGalleryExtension, - criteria, - context, - ); - if ( - !extension || + const extension = await this.toGalleryExtensionWithCriteria(rawGalleryExtension, criteria, context); + if (!extension /** Need all versions if the extension is a pre-release version but * - the query is to look for a release version or * - the extension has no release version * Get all versions to get or check the release version - */ - (extension.properties.isPreReleaseVersion && - (!includePreRelease || !extension.hasReleaseVersion)) || + */ + || (extension.properties.isPreReleaseVersion && (!includePreRelease || !extension.hasReleaseVersion)) /** * Need all versions if the extension is a release version with a different target platform than requested and also has a pre-release version * Because, this is a platform specific extension and can have a newer release version supporting this platform. * See https://github.com/microsoft/vscode/issues/139628 - */ - (!extension.properties.isPreReleaseVersion && - extension.properties.targetPlatform !== - criteria.targetPlatform && - extension.hasPreReleaseVersion) + */ + || (!extension.properties.isPreReleaseVersion && extension.properties.targetPlatform !== criteria.targetPlatform && extension.hasPreReleaseVersion) ) { needAllVersions.set(rawGalleryExtension.extensionId, index); } else { @@ -1631,23 +1043,13 @@ abstract class AbstractExtensionGalleryService if (needAllVersions.size) { const stopWatch = new StopWatch(); const query = new Query() - .withFlags( - flags & ~Flags.IncludeLatestVersionOnly, - Flags.IncludeVersions, - ) + .withFlags(flags & ~Flags.IncludeLatestVersionOnly, Flags.IncludeVersions) .withPage(1, needAllVersions.size) .withFilter(FilterType.ExtensionId, ...needAllVersions.keys()); - const { extensions } = await this.queryGalleryExtensions( - query, - criteria, - token, - ); - this.telemetryService.publicLog2< - GalleryServiceAdditionalQueryEvent, - GalleryServiceAdditionalQueryClassification - >("galleryService:additionalQuery", { + const { extensions } = await this.queryGalleryExtensions(query, criteria, token); + this.telemetryService.publicLog2('galleryService:additionalQuery', { duration: stopWatch.elapsed(), - count: needAllVersions.size, + count: needAllVersions.size }); for (const extension of extensions) { const index = needAllVersions.get(extension.identifier.uuid)!; @@ -1655,95 +1057,41 @@ abstract class AbstractExtensionGalleryService } } - return { - extensions: result - .sort((a, b) => a[0] - b[0]) - .map(([, extension]) => extension), - total, - }; + return { extensions: result.sort((a, b) => a[0] - b[0]).map(([, extension]) => extension), total }; } - private async toGalleryExtensionWithCriteria( - rawGalleryExtension: IRawGalleryExtension, - criteria: IExtensionCriteria, - queryContext?: IStringDictionary, - ): Promise { - const extensionIdentifier = { - id: getGalleryExtensionId( - rawGalleryExtension.publisher.publisherName, - rawGalleryExtension.extensionName, - ), - uuid: rawGalleryExtension.extensionId, - }; - const version = criteria.versions?.find( - (extensionIdentifierWithVersion) => - areSameExtensions( - extensionIdentifierWithVersion, - extensionIdentifier, - ), - )?.version; - const includePreRelease = isBoolean(criteria.includePreRelease) - ? criteria.includePreRelease - : !!criteria.includePreRelease.find( - (extensionIdentifierWithPreRelease) => - areSameExtensions( - extensionIdentifierWithPreRelease, - extensionIdentifier, - ), - )?.includePreRelease; + private async toGalleryExtensionWithCriteria(rawGalleryExtension: IRawGalleryExtension, criteria: IExtensionCriteria, queryContext?: IStringDictionary): Promise { + + const extensionIdentifier = { id: getGalleryExtensionId(rawGalleryExtension.publisher.publisherName, rawGalleryExtension.extensionName), uuid: rawGalleryExtension.extensionId }; + const version = criteria.versions?.find(extensionIdentifierWithVersion => areSameExtensions(extensionIdentifierWithVersion, extensionIdentifier))?.version; + const includePreRelease = isBoolean(criteria.includePreRelease) ? criteria.includePreRelease : !!criteria.includePreRelease.find(extensionIdentifierWithPreRelease => areSameExtensions(extensionIdentifierWithPreRelease, extensionIdentifier))?.includePreRelease; const allTargetPlatforms = getAllTargetPlatforms(rawGalleryExtension); - const rawGalleryExtensionVersions = sortExtensionVersions( - rawGalleryExtension.versions, - criteria.targetPlatform, - ); - - if ( - criteria.compatible && - isNotWebExtensionInWebTargetPlatform( - allTargetPlatforms, - criteria.targetPlatform, - ) - ) { + const rawGalleryExtensionVersions = sortExtensionVersions(rawGalleryExtension.versions, criteria.targetPlatform); + + if (criteria.compatible && isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, criteria.targetPlatform)) { return null; } - for ( - let index = 0; - index < rawGalleryExtensionVersions.length; - index++ - ) { - const rawGalleryExtensionVersion = - rawGalleryExtensionVersions[index]; + for (let index = 0; index < rawGalleryExtensionVersions.length; index++) { + const rawGalleryExtensionVersion = rawGalleryExtensionVersions[index]; if (version && rawGalleryExtensionVersion.version !== version) { continue; } // Allow any version if includePreRelease flag is set otherwise only release versions are allowed - if ( - await this.isValidVersion( - extensionIdentifier.id, - rawGalleryExtensionVersion, - includePreRelease ? "any" : "release", - criteria.compatible, - allTargetPlatforms, - criteria.targetPlatform, - criteria.productVersion, - ) + if (await this.isValidVersion( + extensionIdentifier.id, + rawGalleryExtensionVersion, + rawGalleryExtension.publisher.displayName, + includePreRelease ? 'any' : 'release', + criteria.compatible, + allTargetPlatforms, + criteria.targetPlatform, + criteria.productVersion) ) { - if ( - criteria.compatible && - !this.areApiProposalsCompatible( - extensionIdentifier, - getEnabledApiProposals(rawGalleryExtensionVersion), - ) - ) { + if (criteria.compatible && !this.areApiProposalsCompatible(extensionIdentifier, getEnabledApiProposals(rawGalleryExtensionVersion))) { continue; } - return toExtension( - rawGalleryExtension, - rawGalleryExtensionVersion, - allTargetPlatforms, - queryContext, - ); + return toExtension(rawGalleryExtension, rawGalleryExtensionVersion, allTargetPlatforms, queryContext); } if (version && rawGalleryExtensionVersion.version === version) { return null; @@ -1758,68 +1106,43 @@ abstract class AbstractExtensionGalleryService * Fallback: Return the latest version * This can happen when the extension does not have a release version or does not have a version compatible with the given target platform. */ - return toExtension( - rawGalleryExtension, - rawGalleryExtension.versions[0], - allTargetPlatforms, - ); + return toExtension(rawGalleryExtension, rawGalleryExtension.versions[0], allTargetPlatforms); } - private async queryRawGalleryExtensions( - query: Query, - token: CancellationToken, - ): Promise { + private async queryRawGalleryExtensions(query: Query, token: CancellationToken): Promise { if (!this.isEnabled()) { - throw new Error("No extension gallery service configured."); + throw new Error('No extension gallery service configured.'); } query = query /* Always exclude non validated extensions */ .withFlags(query.flags, Flags.ExcludeNonValidated) - .withFilter(FilterType.Target, "Microsoft.VisualStudio.Code") + .withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code') /* Always exclude unpublished extensions */ - .withFilter( - FilterType.ExcludeWithFlags, - flagsToString(Flags.Unpublished), - ); + .withFilter(FilterType.ExcludeWithFlags, flagsToString(Flags.Unpublished)); const commonHeaders = await this.commonHeadersPromise; const data = JSON.stringify(query.raw); const headers = { ...commonHeaders, - "Content-Type": "application/json", - "Accept": "application/json;api-version=3.0-preview.1", - "Accept-Encoding": "gzip", - "Content-Length": String(data.length), + 'Content-Type': 'application/json', + 'Accept': 'application/json;api-version=3.0-preview.1', + 'Accept-Encoding': 'gzip', + 'Content-Length': String(data.length), }; const stopWatch = new StopWatch(); - let context: IRequestContext | undefined, - errorCode: ExtensionGalleryErrorCode | undefined, - total: number = 0; + let context: IRequestContext | undefined, errorCode: ExtensionGalleryErrorCode | undefined, total: number = 0; try { - context = await this.requestService.request( - { - type: "POST", - url: - this.extensionsGallerySearchUrl && - query.criteria.some( - (c) => c.filterType === FilterType.SearchText, - ) - ? this.extensionsGallerySearchUrl - : this.api("/extensionquery"), - data, - headers, - }, - token, - ); - - if ( - context.res.statusCode && - context.res.statusCode >= 400 && - context.res.statusCode < 500 - ) { + context = await this.requestService.request({ + type: 'POST', + url: this.extensionsGallerySearchUrl && query.criteria.some(c => c.filterType === FilterType.SearchText) ? this.extensionsGallerySearchUrl : this.api('/extensionquery'), + data, + headers + }, token); + + if (context.res.statusCode && context.res.statusCode >= 400 && context.res.statusCode < 500) { return { galleryExtensions: [], total }; } @@ -1827,30 +1150,19 @@ abstract class AbstractExtensionGalleryService if (result) { const r = result.results[0]; const galleryExtensions = r.extensions; - const resultCount = - r.resultMetadata && - r.resultMetadata.filter( - (m) => m.metadataType === "ResultCount", - )[0]; - total = - (resultCount && - resultCount.metadataItems.filter( - (i) => i.name === "TotalCount", - )[0].count) || - 0; + const resultCount = r.resultMetadata && r.resultMetadata.filter(m => m.metadataType === 'ResultCount')[0]; + total = resultCount && resultCount.metadataItems.filter(i => i.name === 'TotalCount')[0].count || 0; return { galleryExtensions, total, - context: context.res.headers["activityid"] - ? { - [ACTIVITY_HEADER_NAME]: - context.res.headers["activityid"], - } - : {}, + context: context.res.headers['activityid'] ? { + [ACTIVITY_HEADER_NAME]: context.res.headers['activityid'] + } : {} }; } return { galleryExtensions: [], total }; + } catch (e) { if (isCancellationError(e)) { errorCode = ExtensionGalleryErrorCode.Cancelled; @@ -1859,71 +1171,54 @@ abstract class AbstractExtensionGalleryService const errorMessage = getErrorMessage(e); errorCode = isOfflineError(e) ? ExtensionGalleryErrorCode.Offline - : errorMessage.startsWith("XHR timeout") + : errorMessage.startsWith('XHR timeout') ? ExtensionGalleryErrorCode.Timeout : ExtensionGalleryErrorCode.Failed; throw new ExtensionGalleryError(errorMessage, errorCode); } } finally { - this.telemetryService.publicLog2< - GalleryServiceQueryEvent, - GalleryServiceQueryClassification - >("galleryService:query", { + this.telemetryService.publicLog2('galleryService:query', { ...query.telemetryData, requestBodySize: String(data.length), duration: stopWatch.elapsed(), success: !!context && isSuccess(context), - responseBodySize: context?.res.headers["Content-Length"], - statusCode: context - ? String(context.res.statusCode) - : undefined, + responseBodySize: context?.res.headers['Content-Length'], + statusCode: context ? String(context.res.statusCode) : undefined, errorCode, - count: String(total), + count: String(total) }); } } - private async getLatestRawGalleryExtension( - extensionId: string, - token: CancellationToken, - ): Promise { + private async getLatestRawGalleryExtension(extensionId: string, token: CancellationToken): Promise { let errorCode: string | undefined; const stopWatch = new StopWatch(); try { - const [publisher, name] = extensionId.split("."); + const [publisher, name] = extensionId.split('.'); if (!publisher || !name) { - errorCode = "InvalidExtensionId"; + errorCode = 'InvalidExtensionId'; return undefined; } - const uri = URI.parse( - format2(this.extensionUrlTemplate!, { publisher, name }), - ); + const uri = URI.parse(format2(this.extensionUrlTemplate!, { publisher, name })); const commonHeaders = await this.commonHeadersPromise; const headers = { ...commonHeaders, - "Content-Type": "application/json", - "Accept": "application/json;api-version=3.0-preview.1", - "Accept-Encoding": "gzip", + 'Content-Type': 'application/json', + 'Accept': 'application/json;api-version=7.2-preview', + 'Accept-Encoding': 'gzip', }; - const context = await this.requestService.request( - { - type: "GET", - url: uri.toString(true), - headers, - timeout: 10000 /*10s*/, - }, - token, - ); + const context = await this.requestService.request({ + type: 'GET', + url: uri.toString(true), + headers, + timeout: 10000 /*10s*/ + }, token); if (context.res.statusCode && context.res.statusCode !== 200) { errorCode = `GalleryServiceError:` + context.res.statusCode; - this.logService.warn( - "Error getting latest version of the extension", - extensionId, - context.res.statusCode, - ); + this.logService.warn('Error getting latest version of the extension', extensionId, context.res.statusCode); return undefined; } @@ -1932,138 +1227,75 @@ abstract class AbstractExtensionGalleryService return result; } - errorCode = "NoData"; - this.logService.warn( - "Error getting latest version of the extension", - extensionId, - errorCode, - ); - } catch (error) { + errorCode = 'NoData'; + this.logService.warn('Error getting latest version of the extension', extensionId, errorCode); + + } + + catch (error) { if (isCancellationError(error)) { errorCode = ExtensionGalleryErrorCode.Cancelled; } else { const errorMessage = getErrorMessage(error); errorCode = isOfflineError(error) ? ExtensionGalleryErrorCode.Offline - : errorMessage.startsWith("XHR timeout") + : errorMessage.startsWith('XHR timeout') ? ExtensionGalleryErrorCode.Timeout : ExtensionGalleryErrorCode.Failed; } - } finally { + } + + finally { type GalleryServiceGetLatestEventClassification = { - owner: "sandy081"; - comment: "Report the query to the the Marketplace for fetching latest version of an extension"; - extension: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "The identifier of the extension"; - }; - duration: { - classification: "SystemMetaData"; - purpose: "PerformanceAndHealth"; - isMeasurement: true; - comment: "Duration in ms for the query"; - }; - errorCode?: { - classification: "SystemMetaData"; - purpose: "PerformanceAndHealth"; - comment: "The error code in case of error"; - }; + owner: 'sandy081'; + comment: 'Report the query to the the Marketplace for fetching latest version of an extension'; + extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the extension' }; + duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Duration in ms for the query' }; + errorCode?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error code in case of error' }; }; type GalleryServiceGetLatestEvent = { extension: string; duration: number; errorCode?: string; }; - this.telemetryService.publicLog2< - GalleryServiceGetLatestEvent, - GalleryServiceGetLatestEventClassification - >("galleryService:getLatest", { - extension: extensionId, - duration: stopWatch.elapsed(), - errorCode, - }); + this.telemetryService.publicLog2('galleryService:getLatest', { extension: extensionId, duration: stopWatch.elapsed(), errorCode }); } return undefined; } - async reportStatistic( - publisher: string, - name: string, - version: string, - type: StatisticType, - ): Promise { + async reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise { if (!this.isEnabled()) { return undefined; } - const url = isWeb - ? this.api( - `/itemName/${publisher}.${name}/version/${version}/statType/${type === StatisticType.Install ? "1" : "3"}/vscodewebextension`, - ) - : this.api( - `/publishers/${publisher}/extensions/${name}/${version}/stats?statType=${type}`, - ); - const Accept = isWeb - ? "api-version=6.1-preview.1" - : "*/*;api-version=4.0-preview.1"; + const url = isWeb ? this.api(`/itemName/${publisher}.${name}/version/${version}/statType/${type === StatisticType.Install ? '1' : '3'}/vscodewebextension`) : this.api(`/publishers/${publisher}/extensions/${name}/${version}/stats?statType=${type}`); + const Accept = isWeb ? 'api-version=6.1-preview.1' : '*/*;api-version=4.0-preview.1'; const commonHeaders = await this.commonHeadersPromise; const headers = { ...commonHeaders, Accept }; try { - await this.requestService.request( - { - type: "POST", - url, - headers, - }, - CancellationToken.None, - ); - } catch (error) { - /* Ignore */ - } + await this.requestService.request({ + type: 'POST', + url, + headers + }, CancellationToken.None); + } catch (error) { /* Ignore */ } } - async download( - extension: IGalleryExtension, - location: URI, - operation: InstallOperation, - ): Promise { - this.logService.trace( - "ExtensionGalleryService#download", - extension.identifier.id, - ); + async download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise { + this.logService.trace('ExtensionGalleryService#download', extension.identifier.id); const data = getGalleryExtensionTelemetryData(extension); const startTime = new Date().getTime(); - const operationParam = - operation === InstallOperation.Install - ? "install" - : operation === InstallOperation.Update - ? "update" - : ""; - const downloadAsset = operationParam - ? { - uri: `${extension.assets.download.uri}${URI.parse(extension.assets.download.uri).query ? "&" : "?"}${operationParam}=true`, - fallbackUri: `${extension.assets.download.fallbackUri}${URI.parse(extension.assets.download.fallbackUri).query ? "&" : "?"}${operationParam}=true`, - } - : extension.assets.download; - - const headers: IHeaders | undefined = extension.queryContext?.[ - ACTIVITY_HEADER_NAME - ] - ? { - [ACTIVITY_HEADER_NAME]: - extension.queryContext[ACTIVITY_HEADER_NAME], - } - : undefined; - const context = await this.getAsset( - extension.identifier.id, - downloadAsset, - AssetType.VSIX, - headers ? { headers } : undefined, - ); + const operationParam = operation === InstallOperation.Install ? 'install' : operation === InstallOperation.Update ? 'update' : ''; + const downloadAsset = operationParam ? { + uri: `${extension.assets.download.uri}${URI.parse(extension.assets.download.uri).query ? '&' : '?'}${operationParam}=true`, + fallbackUri: `${extension.assets.download.fallbackUri}${URI.parse(extension.assets.download.fallbackUri).query ? '&' : '?'}${operationParam}=true` + } : extension.assets.download; + + const headers: IHeaders | undefined = extension.queryContext?.[ACTIVITY_HEADER_NAME] ? { [ACTIVITY_HEADER_NAME]: extension.queryContext[ACTIVITY_HEADER_NAME] } : undefined; + const context = await this.getAsset(extension.identifier.id, downloadAsset, AssetType.VSIX, headers ? { headers } : undefined); try { await this.fileService.writeFile(location, context.stream); @@ -2072,15 +1304,9 @@ abstract class AbstractExtensionGalleryService await this.fileService.del(location); } catch (e) { /* ignore */ - this.logService.warn( - `Error while deleting the file ${location.toString()}`, - getErrorMessage(e), - ); + this.logService.warn(`Error while deleting the file ${location.toString()}`, getErrorMessage(e)); } - throw new ExtensionGalleryError( - getErrorMessage(error), - ExtensionGalleryErrorCode.DownloadFailedWriting, - ); + throw new ExtensionGalleryError(getErrorMessage(error), ExtensionGalleryErrorCode.DownloadFailedWriting); } /* __GDPR__ @@ -2092,30 +1318,17 @@ abstract class AbstractExtensionGalleryService ] } */ - this.telemetryService.publicLog("galleryService:downloadVSIX", { - ...data, - duration: new Date().getTime() - startTime, - }); + this.telemetryService.publicLog('galleryService:downloadVSIX', { ...data, duration: new Date().getTime() - startTime }); } - async downloadSignatureArchive( - extension: IGalleryExtension, - location: URI, - ): Promise { + async downloadSignatureArchive(extension: IGalleryExtension, location: URI): Promise { if (!extension.assets.signature) { - throw new Error("No signature asset found"); + throw new Error('No signature asset found'); } - this.logService.trace( - "ExtensionGalleryService#downloadSignatureArchive", - extension.identifier.id, - ); + this.logService.trace('ExtensionGalleryService#downloadSignatureArchive', extension.identifier.id); - const context = await this.getAsset( - extension.identifier.id, - extension.assets.signature, - AssetType.Signature, - ); + const context = await this.getAsset(extension.identifier.id, extension.assets.signature, AssetType.Signature); try { await this.fileService.writeFile(location, context.stream); } catch (error) { @@ -2123,211 +1336,115 @@ abstract class AbstractExtensionGalleryService await this.fileService.del(location); } catch (e) { /* ignore */ - this.logService.warn( - `Error while deleting the file ${location.toString()}`, - getErrorMessage(e), - ); + this.logService.warn(`Error while deleting the file ${location.toString()}`, getErrorMessage(e)); } - throw new ExtensionGalleryError( - getErrorMessage(error), - ExtensionGalleryErrorCode.DownloadFailedWriting, - ); + throw new ExtensionGalleryError(getErrorMessage(error), ExtensionGalleryErrorCode.DownloadFailedWriting); } + } - async getReadme( - extension: IGalleryExtension, - token: CancellationToken, - ): Promise { + async getReadme(extension: IGalleryExtension, token: CancellationToken): Promise { if (extension.assets.readme) { - const context = await this.getAsset( - extension.identifier.id, - extension.assets.readme, - AssetType.Details, - {}, - token, - ); + const context = await this.getAsset(extension.identifier.id, extension.assets.readme, AssetType.Details, {}, token); const content = await asTextOrError(context); - return content || ""; + return content || ''; } - return ""; + return ''; } - async getManifest( - extension: IGalleryExtension, - token: CancellationToken, - ): Promise { + async getManifest(extension: IGalleryExtension, token: CancellationToken): Promise { if (extension.assets.manifest) { - const context = await this.getAsset( - extension.identifier.id, - extension.assets.manifest, - AssetType.Manifest, - {}, - token, - ); + const context = await this.getAsset(extension.identifier.id, extension.assets.manifest, AssetType.Manifest, {}, token); const text = await asTextOrError(context); return text ? JSON.parse(text) : null; } return null; } - private async getManifestFromRawExtensionVersion( - extension: string, - rawExtensionVersion: IRawGalleryExtensionVersion, - token: CancellationToken, - ): Promise { - const manifestAsset = getVersionAsset( - rawExtensionVersion, - AssetType.Manifest, - ); + private async getManifestFromRawExtensionVersion(extension: string, rawExtensionVersion: IRawGalleryExtensionVersion, token: CancellationToken): Promise { + const manifestAsset = getVersionAsset(rawExtensionVersion, AssetType.Manifest); if (!manifestAsset) { - throw new Error("Manifest was not found"); - } - const headers = { "Accept-Encoding": "gzip" }; - const context = await this.getAsset( - extension, - manifestAsset, - AssetType.Manifest, - { headers }, - ); + throw new Error('Manifest was not found'); + } + const headers = { 'Accept-Encoding': 'gzip' }; + const context = await this.getAsset(extension, manifestAsset, AssetType.Manifest, { headers }); return await asJson(context); } - async getCoreTranslation( - extension: IGalleryExtension, - languageId: string, - ): Promise { - const asset = extension.assets.coreTranslations.filter( - (t) => t[0] === languageId.toUpperCase(), - )[0]; + async getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise { + const asset = extension.assets.coreTranslations.filter(t => t[0] === languageId.toUpperCase())[0]; if (asset) { - const context = await this.getAsset( - extension.identifier.id, - asset[1], - asset[0], - ); + const context = await this.getAsset(extension.identifier.id, asset[1], asset[0]); const text = await asTextOrError(context); return text ? JSON.parse(text) : null; } return null; } - async getChangelog( - extension: IGalleryExtension, - token: CancellationToken, - ): Promise { + async getChangelog(extension: IGalleryExtension, token: CancellationToken): Promise { if (extension.assets.changelog) { - const context = await this.getAsset( - extension.identifier.id, - extension.assets.changelog, - AssetType.Changelog, - {}, - token, - ); + const context = await this.getAsset(extension.identifier.id, extension.assets.changelog, AssetType.Changelog, {}, token); const content = await asTextOrError(context); - return content || ""; + return content || ''; } - return ""; + return ''; } - async getAllCompatibleVersions( - extensionIdentifier: IExtensionIdentifier, - includePreRelease: boolean, - targetPlatform: TargetPlatform, - ): Promise { + async getAllCompatibleVersions(extensionIdentifier: IExtensionIdentifier, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise { let query = new Query() - .withFlags( - Flags.IncludeVersions, - Flags.IncludeCategoryAndTags, - Flags.IncludeFiles, - Flags.IncludeVersionProperties, - ) + .withFlags(Flags.IncludeVersions, Flags.IncludeCategoryAndTags, Flags.IncludeFiles, Flags.IncludeVersionProperties) .withPage(1, 1); if (extensionIdentifier.uuid) { - query = query.withFilter( - FilterType.ExtensionId, - extensionIdentifier.uuid, - ); + query = query.withFilter(FilterType.ExtensionId, extensionIdentifier.uuid); } else { - query = query.withFilter( - FilterType.ExtensionName, - extensionIdentifier.id, - ); + query = query.withFilter(FilterType.ExtensionName, extensionIdentifier.id); } - const { galleryExtensions } = await this.queryRawGalleryExtensions( - query, - CancellationToken.None, - ); + const { galleryExtensions } = await this.queryRawGalleryExtensions(query, CancellationToken.None); if (!galleryExtensions.length) { return []; } const allTargetPlatforms = getAllTargetPlatforms(galleryExtensions[0]); - if ( - isNotWebExtensionInWebTargetPlatform( - allTargetPlatforms, - targetPlatform, - ) - ) { + if (isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, targetPlatform)) { return []; } const validVersions: IRawGalleryExtensionVersion[] = []; - await Promise.all( - galleryExtensions[0].versions.map(async (version) => { - try { - if ( - (await this.isValidVersion( - extensionIdentifier.id, - version, - includePreRelease ? "any" : "release", - true, - allTargetPlatforms, - targetPlatform, - )) && - this.areApiProposalsCompatible( - extensionIdentifier, - getEnabledApiProposals(version), - ) - ) { - validVersions.push(version); - } - } catch (error) { - /* Ignore error and skip version */ + await Promise.all(galleryExtensions[0].versions.map(async (version) => { + try { + if ( + (await this.isValidVersion( + extensionIdentifier.id, + version, + galleryExtensions[0].publisher.displayName, + includePreRelease ? 'any' : 'release', + true, + allTargetPlatforms, + targetPlatform)) + && this.areApiProposalsCompatible(extensionIdentifier, getEnabledApiProposals(version)) + ) { + validVersions.push(version); } - }), - ); + } catch (error) { /* Ignore error and skip version */ } + })); const result: IGalleryExtensionVersion[] = []; const seen = new Set(); - for (const version of sortExtensionVersions( - validVersions, - targetPlatform, - )) { + for (const version of sortExtensionVersions(validVersions, targetPlatform)) { if (!seen.has(version.version)) { seen.add(version.version); - result.push({ - version: version.version, - date: version.lastUpdated, - isPreReleaseVersion: isPreReleaseVersion(version), - }); + result.push({ version: version.version, date: version.lastUpdated, isPreReleaseVersion: isPreReleaseVersion(version) }); } } return result; } - private async getAsset( - extension: string, - asset: IGalleryExtensionAsset, - assetType: string, - options: IRequestOptions = {}, - token: CancellationToken = CancellationToken.None, - ): Promise { + private async getAsset(extension: string, asset: IGalleryExtensionAsset, assetType: string, options: IRequestOptions = {}, token: CancellationToken = CancellationToken.None): Promise { const commonHeaders = await this.commonHeadersPromise; - const baseOptions = { type: "GET" }; + const baseOptions = { type: 'GET' }; const headers = { ...commonHeaders, ...(options.headers || {}) }; options = { ...options, ...baseOptions, headers }; @@ -2336,17 +1453,12 @@ abstract class AbstractExtensionGalleryService const firstOptions = { ...options, url }; try { - const context = await this.requestService.request( - firstOptions, - token, - ); + const context = await this.requestService.request(firstOptions, token); if (context.res.statusCode === 200) { return context; } const message = await asTextOrError(context); - throw new Error( - `Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`, - ); + throw new Error(`Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`); } catch (err) { if (isCancellationError(err)) { throw err; @@ -2354,77 +1466,41 @@ abstract class AbstractExtensionGalleryService const message = getErrorMessage(err); type GalleryServiceCDNFallbackClassification = { - owner: "sandy081"; - comment: "Fallback request information when the primary asset request to CDN fails"; - extension: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "extension name"; - }; - assetType: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "asset that failed"; - }; - message: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "error message"; - }; + owner: 'sandy081'; + comment: 'Fallback request information when the primary asset request to CDN fails'; + extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'extension name' }; + assetType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'asset that failed' }; + message: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'error message' }; }; type GalleryServiceCDNFallbackEvent = { extension: string; assetType: string; message: string; }; - this.telemetryService.publicLog2< - GalleryServiceCDNFallbackEvent, - GalleryServiceCDNFallbackClassification - >("galleryService:cdnFallback", { extension, assetType, message }); + this.telemetryService.publicLog2('galleryService:cdnFallback', { extension, assetType, message }); const fallbackOptions = { ...options, url: fallbackUrl }; return this.requestService.request(fallbackOptions, token); } } - private async getEngine( - extension: string, - rawExtensionVersion: IRawGalleryExtensionVersion, - ): Promise { + private async getEngine(extension: string, rawExtensionVersion: IRawGalleryExtensionVersion): Promise { let engine = getEngine(rawExtensionVersion); if (!engine) { type GalleryServiceEngineFallbackClassification = { - owner: "sandy081"; - comment: "Fallback request when engine is not found in properties of an extension version"; - extension: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "extension name"; - }; - version: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "version"; - }; + owner: 'sandy081'; + comment: 'Fallback request when engine is not found in properties of an extension version'; + extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'extension name' }; + version: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'version' }; }; type GalleryServiceEngineFallbackEvent = { extension: string; version: string; }; - this.telemetryService.publicLog2< - GalleryServiceEngineFallbackEvent, - GalleryServiceEngineFallbackClassification - >("galleryService:engineFallback", { - extension, - version: rawExtensionVersion.version, - }); - const manifest = await this.getManifestFromRawExtensionVersion( - extension, - rawExtensionVersion, - CancellationToken.None, - ); + this.telemetryService.publicLog2('galleryService:engineFallback', { extension, version: rawExtensionVersion.version }); + const manifest = await this.getManifestFromRawExtensionVersion(extension, rawExtensionVersion, CancellationToken.None); if (!manifest) { - throw new Error("Manifest was not found"); + throw new Error('Manifest was not found'); } engine = manifest.engines.vscode; } @@ -2433,19 +1509,16 @@ abstract class AbstractExtensionGalleryService async getExtensionsControlManifest(): Promise { if (!this.isEnabled()) { - throw new Error("No extension gallery service configured."); + throw new Error('No extension gallery service configured.'); } if (!this.extensionsControlUrl) { return { malicious: [], deprecated: {}, search: [] }; } - const context = await this.requestService.request( - { type: "GET", url: this.extensionsControlUrl }, - CancellationToken.None, - ); + const context = await this.requestService.request({ type: 'GET', url: this.extensionsControlUrl }, CancellationToken.None); if (context.res.statusCode !== 200) { - throw new Error("Could not get extensions report."); + throw new Error('Could not get extensions report.'); } const result = await asJson(context); @@ -2458,44 +1531,24 @@ abstract class AbstractExtensionGalleryService malicious.push({ id }); } if (result.migrateToPreRelease) { - for (const [ - unsupportedPreReleaseExtensionId, - preReleaseExtensionInfo, - ] of Object.entries(result.migrateToPreRelease)) { - if ( - !preReleaseExtensionInfo.engine || - isEngineValid( - preReleaseExtensionInfo.engine, - this.productService.version, - this.productService.date, - ) - ) { - deprecated[ - unsupportedPreReleaseExtensionId.toLowerCase() - ] = { + for (const [unsupportedPreReleaseExtensionId, preReleaseExtensionInfo] of Object.entries(result.migrateToPreRelease)) { + if (!preReleaseExtensionInfo.engine || isEngineValid(preReleaseExtensionInfo.engine, this.productService.version, this.productService.date)) { + deprecated[unsupportedPreReleaseExtensionId.toLowerCase()] = { disallowInstall: true, extension: { id: preReleaseExtensionInfo.id, - displayName: - preReleaseExtensionInfo.displayName, - autoMigrate: { - storage: - !!preReleaseExtensionInfo.migrateStorage, - }, - preRelease: true, - }, + displayName: preReleaseExtensionInfo.displayName, + autoMigrate: { storage: !!preReleaseExtensionInfo.migrateStorage }, + preRelease: true + } }; } } } if (result.deprecated) { - for (const [ - deprecatedExtensionId, - deprecationInfo, - ] of Object.entries(result.deprecated)) { + for (const [deprecatedExtensionId, deprecationInfo] of Object.entries(result.deprecated)) { if (deprecationInfo) { - deprecated[deprecatedExtensionId.toLowerCase()] = - isBoolean(deprecationInfo) ? {} : deprecationInfo; + deprecated[deprecatedExtensionId.toLowerCase()] = isBoolean(deprecationInfo) ? {} : deprecationInfo; } } } @@ -2511,16 +1564,12 @@ abstract class AbstractExtensionGalleryService } } - return { - malicious, - deprecated, - search, - extensionsEnabledWithPreRelease, - }; + return { malicious, deprecated, search, extensionsEnabledWithPreRelease }; } } export class ExtensionGalleryService extends AbstractExtensionGalleryService { + constructor( @IStorageService storageService: IStorageService, @IRequestService requestService: IRequestService, @@ -2530,24 +1579,14 @@ export class ExtensionGalleryService extends AbstractExtensionGalleryService { @IFileService fileService: IFileService, @IProductService productService: IProductService, @IConfigurationService configurationService: IConfigurationService, - @IAllowedExtensionsService - allowedExtensionsService: IAllowedExtensionsService, + @IAllowedExtensionsService allowedExtensionsService: IAllowedExtensionsService, ) { - super( - storageService, - requestService, - logService, - environmentService, - telemetryService, - fileService, - productService, - configurationService, - allowedExtensionsService, - ); + super(storageService, requestService, logService, environmentService, telemetryService, fileService, productService, configurationService, allowedExtensionsService); } } export class ExtensionGalleryServiceWithNoStorageService extends AbstractExtensionGalleryService { + constructor( @IRequestService requestService: IRequestService, @ILogService logService: ILogService, @@ -2556,19 +1595,8 @@ export class ExtensionGalleryServiceWithNoStorageService extends AbstractExtensi @IFileService fileService: IFileService, @IProductService productService: IProductService, @IConfigurationService configurationService: IConfigurationService, - @IAllowedExtensionsService - allowedExtensionsService: IAllowedExtensionsService, + @IAllowedExtensionsService allowedExtensionsService: IAllowedExtensionsService, ) { - super( - undefined, - requestService, - logService, - environmentService, - telemetryService, - fileService, - productService, - configurationService, - allowedExtensionsService, - ); + super(undefined, requestService, logService, environmentService, telemetryService, fileService, productService, configurationService, allowedExtensionsService); } } diff --git a/Source/vs/platform/extensionManagement/common/extensionManagement.ts b/Source/vs/platform/extensionManagement/common/extensionManagement.ts index 3898775f55455..2d6713d409d09 100644 --- a/Source/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/Source/vs/platform/extensionManagement/common/extensionManagement.ts @@ -3,39 +3,29 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken } from "../../../base/common/cancellation.js"; -import { IStringDictionary } from "../../../base/common/collections.js"; -import { Event } from "../../../base/common/event.js"; -import { IMarkdownString } from "../../../base/common/htmlContent.js"; -import { IPager } from "../../../base/common/paging.js"; -import { Platform } from "../../../base/common/platform.js"; -import { URI } from "../../../base/common/uri.js"; -import { localize2 } from "../../../nls.js"; -import { - ExtensionType, - IExtension, - IExtensionManifest, - TargetPlatform, -} from "../../extensions/common/extensions.js"; -import { IFileService } from "../../files/common/files.js"; -import { createDecorator } from "../../instantiation/common/instantiation.js"; - -export const EXTENSION_IDENTIFIER_PATTERN = - "^([a-z0-9A-Z][a-z0-9-A-Z]*)\\.([a-z0-9A-Z][a-z0-9-A-Z]*)$"; -export const EXTENSION_IDENTIFIER_REGEX = new RegExp( - EXTENSION_IDENTIFIER_PATTERN, -); -export const WEB_EXTENSION_TAG = "__web_extension"; -export const EXTENSION_INSTALL_SKIP_WALKTHROUGH_CONTEXT = "skipWalkthrough"; -export const EXTENSION_INSTALL_SOURCE_CONTEXT = "extensionInstallSource"; -export const EXTENSION_INSTALL_DEP_PACK_CONTEXT = - "dependecyOrPackExtensionInstall"; -export const EXTENSION_INSTALL_CLIENT_TARGET_PLATFORM_CONTEXT = - "clientTargetPlatform"; +import { CancellationToken } from '../../../base/common/cancellation.js'; +import { IStringDictionary } from '../../../base/common/collections.js'; +import { Event } from '../../../base/common/event.js'; +import { IMarkdownString } from '../../../base/common/htmlContent.js'; +import { IPager } from '../../../base/common/paging.js'; +import { Platform } from '../../../base/common/platform.js'; +import { URI } from '../../../base/common/uri.js'; +import { localize2 } from '../../../nls.js'; +import { ExtensionType, IExtension, IExtensionManifest, TargetPlatform } from '../../extensions/common/extensions.js'; +import { IFileService } from '../../files/common/files.js'; +import { createDecorator } from '../../instantiation/common/instantiation.js'; + +export const EXTENSION_IDENTIFIER_PATTERN = '^([a-z0-9A-Z][a-z0-9-A-Z]*)\\.([a-z0-9A-Z][a-z0-9-A-Z]*)$'; +export const EXTENSION_IDENTIFIER_REGEX = new RegExp(EXTENSION_IDENTIFIER_PATTERN); +export const WEB_EXTENSION_TAG = '__web_extension'; +export const EXTENSION_INSTALL_SKIP_WALKTHROUGH_CONTEXT = 'skipWalkthrough'; +export const EXTENSION_INSTALL_SOURCE_CONTEXT = 'extensionInstallSource'; +export const EXTENSION_INSTALL_DEP_PACK_CONTEXT = 'dependecyOrPackExtensionInstall'; +export const EXTENSION_INSTALL_CLIENT_TARGET_PLATFORM_CONTEXT = 'clientTargetPlatform'; export const enum ExtensionInstallSource { - COMMAND = "command", - SETTINGS_SYNC = "settingsSync", + COMMAND = 'command', + SETTINGS_SYNC = 'settingsSync', } export interface IProductVersion { @@ -45,146 +35,102 @@ export interface IProductVersion { export function TargetPlatformToString(targetPlatform: TargetPlatform) { switch (targetPlatform) { - case TargetPlatform.WIN32_X64: - return "Windows 64 bit"; - case TargetPlatform.WIN32_ARM64: - return "Windows ARM"; - - case TargetPlatform.LINUX_X64: - return "Linux 64 bit"; - case TargetPlatform.LINUX_ARM64: - return "Linux ARM 64"; - case TargetPlatform.LINUX_ARMHF: - return "Linux ARM"; - - case TargetPlatform.ALPINE_X64: - return "Alpine Linux 64 bit"; - case TargetPlatform.ALPINE_ARM64: - return "Alpine ARM 64"; - - case TargetPlatform.DARWIN_X64: - return "Mac"; - case TargetPlatform.DARWIN_ARM64: - return "Mac Silicon"; - - case TargetPlatform.WEB: - return "Web"; - - case TargetPlatform.UNIVERSAL: - return TargetPlatform.UNIVERSAL; - case TargetPlatform.UNKNOWN: - return TargetPlatform.UNKNOWN; - case TargetPlatform.UNDEFINED: - return TargetPlatform.UNDEFINED; + case TargetPlatform.WIN32_X64: return 'Windows 64 bit'; + case TargetPlatform.WIN32_ARM64: return 'Windows ARM'; + + case TargetPlatform.LINUX_X64: return 'Linux 64 bit'; + case TargetPlatform.LINUX_ARM64: return 'Linux ARM 64'; + case TargetPlatform.LINUX_ARMHF: return 'Linux ARM'; + + case TargetPlatform.ALPINE_X64: return 'Alpine Linux 64 bit'; + case TargetPlatform.ALPINE_ARM64: return 'Alpine ARM 64'; + + case TargetPlatform.DARWIN_X64: return 'Mac'; + case TargetPlatform.DARWIN_ARM64: return 'Mac Silicon'; + + case TargetPlatform.WEB: return 'Web'; + + case TargetPlatform.UNIVERSAL: return TargetPlatform.UNIVERSAL; + case TargetPlatform.UNKNOWN: return TargetPlatform.UNKNOWN; + case TargetPlatform.UNDEFINED: return TargetPlatform.UNDEFINED; } } export function toTargetPlatform(targetPlatform: string): TargetPlatform { switch (targetPlatform) { - case TargetPlatform.WIN32_X64: - return TargetPlatform.WIN32_X64; - case TargetPlatform.WIN32_ARM64: - return TargetPlatform.WIN32_ARM64; - - case TargetPlatform.LINUX_X64: - return TargetPlatform.LINUX_X64; - case TargetPlatform.LINUX_ARM64: - return TargetPlatform.LINUX_ARM64; - case TargetPlatform.LINUX_ARMHF: - return TargetPlatform.LINUX_ARMHF; - - case TargetPlatform.ALPINE_X64: - return TargetPlatform.ALPINE_X64; - case TargetPlatform.ALPINE_ARM64: - return TargetPlatform.ALPINE_ARM64; - - case TargetPlatform.DARWIN_X64: - return TargetPlatform.DARWIN_X64; - case TargetPlatform.DARWIN_ARM64: - return TargetPlatform.DARWIN_ARM64; - - case TargetPlatform.WEB: - return TargetPlatform.WEB; - - case TargetPlatform.UNIVERSAL: - return TargetPlatform.UNIVERSAL; - default: - return TargetPlatform.UNKNOWN; + case TargetPlatform.WIN32_X64: return TargetPlatform.WIN32_X64; + case TargetPlatform.WIN32_ARM64: return TargetPlatform.WIN32_ARM64; + + case TargetPlatform.LINUX_X64: return TargetPlatform.LINUX_X64; + case TargetPlatform.LINUX_ARM64: return TargetPlatform.LINUX_ARM64; + case TargetPlatform.LINUX_ARMHF: return TargetPlatform.LINUX_ARMHF; + + case TargetPlatform.ALPINE_X64: return TargetPlatform.ALPINE_X64; + case TargetPlatform.ALPINE_ARM64: return TargetPlatform.ALPINE_ARM64; + + case TargetPlatform.DARWIN_X64: return TargetPlatform.DARWIN_X64; + case TargetPlatform.DARWIN_ARM64: return TargetPlatform.DARWIN_ARM64; + + case TargetPlatform.WEB: return TargetPlatform.WEB; + + case TargetPlatform.UNIVERSAL: return TargetPlatform.UNIVERSAL; + default: return TargetPlatform.UNKNOWN; } } -export function getTargetPlatform( - platform: Platform | "alpine", - arch: string | undefined, -): TargetPlatform { +export function getTargetPlatform(platform: Platform | 'alpine', arch: string | undefined): TargetPlatform { switch (platform) { case Platform.Windows: - if (arch === "x64") { + if (arch === 'x64') { return TargetPlatform.WIN32_X64; } - if (arch === "arm64") { + if (arch === 'arm64') { return TargetPlatform.WIN32_ARM64; } return TargetPlatform.UNKNOWN; case Platform.Linux: - if (arch === "x64") { + if (arch === 'x64') { return TargetPlatform.LINUX_X64; } - if (arch === "arm64") { + if (arch === 'arm64') { return TargetPlatform.LINUX_ARM64; } - if (arch === "arm") { + if (arch === 'arm') { return TargetPlatform.LINUX_ARMHF; } return TargetPlatform.UNKNOWN; - case "alpine": - if (arch === "x64") { + case 'alpine': + if (arch === 'x64') { return TargetPlatform.ALPINE_X64; } - if (arch === "arm64") { + if (arch === 'arm64') { return TargetPlatform.ALPINE_ARM64; } return TargetPlatform.UNKNOWN; case Platform.Mac: - if (arch === "x64") { + if (arch === 'x64') { return TargetPlatform.DARWIN_X64; } - if (arch === "arm64") { + if (arch === 'arm64') { return TargetPlatform.DARWIN_ARM64; } return TargetPlatform.UNKNOWN; - case Platform.Web: - return TargetPlatform.WEB; + case Platform.Web: return TargetPlatform.WEB; } } -export function isNotWebExtensionInWebTargetPlatform( - allTargetPlatforms: TargetPlatform[], - productTargetPlatform: TargetPlatform, -): boolean { +export function isNotWebExtensionInWebTargetPlatform(allTargetPlatforms: TargetPlatform[], productTargetPlatform: TargetPlatform): boolean { // Not a web extension in web target platform - return ( - productTargetPlatform === TargetPlatform.WEB && - !allTargetPlatforms.includes(TargetPlatform.WEB) - ); + return productTargetPlatform === TargetPlatform.WEB && !allTargetPlatforms.includes(TargetPlatform.WEB); } -export function isTargetPlatformCompatible( - extensionTargetPlatform: TargetPlatform, - allTargetPlatforms: TargetPlatform[], - productTargetPlatform: TargetPlatform, -): boolean { +export function isTargetPlatformCompatible(extensionTargetPlatform: TargetPlatform, allTargetPlatforms: TargetPlatform[], productTargetPlatform: TargetPlatform): boolean { // Not compatible when extension is not a web extension in web target platform - if ( - isNotWebExtensionInWebTargetPlatform( - allTargetPlatforms, - productTargetPlatform, - ) - ) { + if (isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, productTargetPlatform)) { return false; } @@ -239,15 +185,11 @@ export interface IGalleryExtensionAssets { coreTranslations: [string, IGalleryExtensionAsset][]; } -export function isIExtensionIdentifier( - thing: any, -): thing is IExtensionIdentifier { - return ( - thing && - typeof thing === "object" && - typeof thing.id === "string" && - (!thing.uuid || typeof thing.uuid === "string") - ); +export function isIExtensionIdentifier(thing: any): thing is IExtensionIdentifier { + return thing + && typeof thing === 'object' + && typeof thing.id === 'string' + && (!thing.uuid || typeof thing.uuid === 'string'); } export interface IExtensionIdentifier { @@ -266,7 +208,7 @@ export interface IGalleryExtensionVersion { } export interface IGalleryExtension { - type: "gallery"; + type: 'gallery'; name: string; identifier: IGalleryExtensionIdentifier; version: string; @@ -296,7 +238,7 @@ export interface IGalleryExtension { supportLink?: string; } -export type InstallSource = "gallery" | "vsix" | "resource"; +export type InstallSource = 'gallery' | 'vsix' | 'resource'; export interface IGalleryMetadata { id: string; @@ -306,21 +248,19 @@ export interface IGalleryMetadata { targetPlatform?: TargetPlatform; } -export type Metadata = Partial< - IGalleryMetadata & { - isApplicationScoped: boolean; - isMachineScoped: boolean; - isBuiltin: boolean; - isSystem: boolean; - updated: boolean; - preRelease: boolean; - hasPreReleaseVersion: boolean; - installedTimestamp: number; - pinned: boolean; - source: InstallSource; - size: number; - } ->; +export type Metadata = Partial; export interface ILocalExtension extends IExtension { isWorkspaceScoped: boolean; @@ -345,13 +285,13 @@ export const enum SortBy { InstallCount = 4, PublishedDate = 10, AverageRating = 6, - WeightedRating = 12, + WeightedRating = 12 } export const enum SortOrder { Default = 0, Ascending = 1, - Descending = 2, + Descending = 2 } export interface IQueryOptions { @@ -366,8 +306,8 @@ export interface IQueryOptions { } export const enum StatisticType { - Install = "install", - Uninstall = "uninstall", + Install = 'install', + Uninstall = 'uninstall' } export interface IDeprecationInfo { @@ -420,8 +360,7 @@ export interface IExtensionQueryOptions { preferResourceApi?: boolean; } -export const IExtensionGalleryService = - createDecorator("extensionGalleryService"); +export const IExtensionGalleryService = createDecorator('extensionGalleryService'); /** * Service to interact with the Visual Studio Code Marketplace to get extensions. @@ -430,67 +369,19 @@ export const IExtensionGalleryService = export interface IExtensionGalleryService { readonly _serviceBrand: undefined; isEnabled(): boolean; - query( - options: IQueryOptions, - token: CancellationToken, - ): Promise>; - getExtensions( - extensionInfos: ReadonlyArray, - token: CancellationToken, - ): Promise; - getExtensions( - extensionInfos: ReadonlyArray, - options: IExtensionQueryOptions, - token: CancellationToken, - ): Promise; - isExtensionCompatible( - extension: IGalleryExtension, - includePreRelease: boolean, - targetPlatform: TargetPlatform, - productVersion?: IProductVersion, - ): Promise; - getCompatibleExtension( - extension: IGalleryExtension, - includePreRelease: boolean, - targetPlatform: TargetPlatform, - productVersion?: IProductVersion, - ): Promise; - getAllCompatibleVersions( - extensionIdentifier: IExtensionIdentifier, - includePreRelease: boolean, - targetPlatform: TargetPlatform, - ): Promise; - download( - extension: IGalleryExtension, - location: URI, - operation: InstallOperation, - ): Promise; - downloadSignatureArchive( - extension: IGalleryExtension, - location: URI, - ): Promise; - reportStatistic( - publisher: string, - name: string, - version: string, - type: StatisticType, - ): Promise; - getReadme( - extension: IGalleryExtension, - token: CancellationToken, - ): Promise; - getManifest( - extension: IGalleryExtension, - token: CancellationToken, - ): Promise; - getChangelog( - extension: IGalleryExtension, - token: CancellationToken, - ): Promise; - getCoreTranslation( - extension: IGalleryExtension, - languageId: string, - ): Promise; + query(options: IQueryOptions, token: CancellationToken): Promise>; + getExtensions(extensionInfos: ReadonlyArray, token: CancellationToken): Promise; + getExtensions(extensionInfos: ReadonlyArray, options: IExtensionQueryOptions, token: CancellationToken): Promise; + isExtensionCompatible(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion?: IProductVersion): Promise; + getCompatibleExtension(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion?: IProductVersion): Promise; + getAllCompatibleVersions(extensionIdentifier: IExtensionIdentifier, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise; + download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise; + downloadSignatureArchive(extension: IGalleryExtension, location: URI): Promise; + reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise; + getReadme(extension: IGalleryExtension, token: CancellationToken): Promise; + getManifest(extension: IGalleryExtension, token: CancellationToken): Promise; + getChangelog(extension: IGalleryExtension, token: CancellationToken): Promise; + getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise; getExtensionsControlManifest(): Promise; } @@ -535,93 +426,88 @@ export interface DidUpdateExtensionMetadata { } export const enum ExtensionGalleryErrorCode { - Timeout = "Timeout", - Cancelled = "Cancelled", - Failed = "Failed", - DownloadFailedWriting = "DownloadFailedWriting", - Offline = "Offline", + Timeout = 'Timeout', + Cancelled = 'Cancelled', + Failed = 'Failed', + DownloadFailedWriting = 'DownloadFailedWriting', + Offline = 'Offline', } export class ExtensionGalleryError extends Error { - constructor( - message: string, - readonly code: ExtensionGalleryErrorCode, - ) { + constructor(message: string, readonly code: ExtensionGalleryErrorCode) { super(message); this.name = code; } } export const enum ExtensionManagementErrorCode { - Unsupported = "Unsupported", - Deprecated = "Deprecated", - Malicious = "Malicious", - Incompatible = "Incompatible", - IncompatibleApi = "IncompatibleApi", - IncompatibleTargetPlatform = "IncompatibleTargetPlatform", - ReleaseVersionNotFound = "ReleaseVersionNotFound", - Invalid = "Invalid", - Download = "Download", - DownloadSignature = "DownloadSignature", + Unsupported = 'Unsupported', + Deprecated = 'Deprecated', + Malicious = 'Malicious', + Incompatible = 'Incompatible', + IncompatibleApi = 'IncompatibleApi', + IncompatibleTargetPlatform = 'IncompatibleTargetPlatform', + ReleaseVersionNotFound = 'ReleaseVersionNotFound', + Invalid = 'Invalid', + Download = 'Download', + DownloadSignature = 'DownloadSignature', DownloadFailedWriting = ExtensionGalleryErrorCode.DownloadFailedWriting, - UpdateMetadata = "UpdateMetadata", - Extract = "Extract", - Scanning = "Scanning", - ScanningExtension = "ScanningExtension", - ReadUninstalled = "ReadUninstalled", - UnsetUninstalled = "UnsetUninstalled", - Delete = "Delete", - Rename = "Rename", - IntializeDefaultProfile = "IntializeDefaultProfile", - AddToProfile = "AddToProfile", - InstalledExtensionNotFound = "InstalledExtensionNotFound", - PostInstall = "PostInstall", - CorruptZip = "CorruptZip", - IncompleteZip = "IncompleteZip", - PackageNotSigned = "PackageNotSigned", - SignatureVerificationInternal = "SignatureVerificationInternal", - SignatureVerificationFailed = "SignatureVerificationFailed", - NotAllowed = "NotAllowed", - Gallery = "Gallery", - Cancelled = "Cancelled", - Unknown = "Unknown", - Internal = "Internal", + UpdateMetadata = 'UpdateMetadata', + Extract = 'Extract', + Scanning = 'Scanning', + ScanningExtension = 'ScanningExtension', + ReadUninstalled = 'ReadUninstalled', + UnsetUninstalled = 'UnsetUninstalled', + Delete = 'Delete', + Rename = 'Rename', + IntializeDefaultProfile = 'IntializeDefaultProfile', + AddToProfile = 'AddToProfile', + InstalledExtensionNotFound = 'InstalledExtensionNotFound', + PostInstall = 'PostInstall', + CorruptZip = 'CorruptZip', + IncompleteZip = 'IncompleteZip', + PackageNotSigned = 'PackageNotSigned', + SignatureVerificationInternal = 'SignatureVerificationInternal', + SignatureVerificationFailed = 'SignatureVerificationFailed', + NotAllowed = 'NotAllowed', + Gallery = 'Gallery', + Cancelled = 'Cancelled', + Unknown = 'Unknown', + Internal = 'Internal', } export enum ExtensionSignatureVerificationCode { - "Success" = "Success", - "RequiredArgumentMissing" = "RequiredArgumentMissing", // A required argument is missing. - "InvalidArgument" = "InvalidArgument", // An argument is invalid. - "PackageIsUnreadable" = "PackageIsUnreadable", // The extension package is unreadable. - "UnhandledException" = "UnhandledException", // An unhandled exception occurred. - "SignatureManifestIsMissing" = "SignatureManifestIsMissing", // The extension is missing a signature manifest file (.signature.manifest). - "SignatureManifestIsUnreadable" = "SignatureManifestIsUnreadable", // The signature manifest is unreadable. - "SignatureIsMissing" = "SignatureIsMissing", // The extension is missing a signature file (.signature.p7s). - "SignatureIsUnreadable" = "SignatureIsUnreadable", // The signature is unreadable. - "CertificateIsUnreadable" = "CertificateIsUnreadable", // The certificate is unreadable. - "SignatureArchiveIsUnreadable" = "SignatureArchiveIsUnreadable", - "FileAlreadyExists" = "FileAlreadyExists", // The output file already exists. - "SignatureArchiveIsInvalidZip" = "SignatureArchiveIsInvalidZip", - "SignatureArchiveHasSameSignatureFile" = "SignatureArchiveHasSameSignatureFile", // The signature archive has the same signature file. - "PackageIntegrityCheckFailed" = "PackageIntegrityCheckFailed", // The package integrity check failed. - "SignatureIsInvalid" = "SignatureIsInvalid", // The extension has an invalid signature file (.signature.p7s). - "SignatureManifestIsInvalid" = "SignatureManifestIsInvalid", // The extension has an invalid signature manifest file (.signature.manifest). - "SignatureIntegrityCheckFailed" = "SignatureIntegrityCheckFailed", // The extension's signature integrity check failed. Extension integrity is suspect. - "EntryIsMissing" = "EntryIsMissing", // An entry referenced in the signature manifest was not found in the extension. - "EntryIsTampered" = "EntryIsTampered", // The integrity check for an entry referenced in the signature manifest failed. - "Untrusted" = "Untrusted", // An X.509 certificate in the extension signature is untrusted. - "CertificateRevoked" = "CertificateRevoked", // An X.509 certificate in the extension signature has been revoked. - "SignatureIsNotValid" = "SignatureIsNotValid", // The extension signature is invalid. - "UnknownError" = "UnknownError", // An unknown error occurred. - "PackageIsInvalidZip" = "PackageIsInvalidZip", // The extension package is not valid ZIP format. - "SignatureArchiveHasTooManyEntries" = "SignatureArchiveHasTooManyEntries", // The signature archive has too many entries. + 'NotSigned' = 'NotSigned', + 'Success' = 'Success', + 'RequiredArgumentMissing' = 'RequiredArgumentMissing', // A required argument is missing. + 'InvalidArgument' = 'InvalidArgument', // An argument is invalid. + 'PackageIsUnreadable' = 'PackageIsUnreadable', // The extension package is unreadable. + 'UnhandledException' = 'UnhandledException', // An unhandled exception occurred. + 'SignatureManifestIsMissing' = 'SignatureManifestIsMissing', // The extension is missing a signature manifest file (.signature.manifest). + 'SignatureManifestIsUnreadable' = 'SignatureManifestIsUnreadable', // The signature manifest is unreadable. + 'SignatureIsMissing' = 'SignatureIsMissing', // The extension is missing a signature file (.signature.p7s). + 'SignatureIsUnreadable' = 'SignatureIsUnreadable', // The signature is unreadable. + 'CertificateIsUnreadable' = 'CertificateIsUnreadable', // The certificate is unreadable. + 'SignatureArchiveIsUnreadable' = 'SignatureArchiveIsUnreadable', + 'FileAlreadyExists' = 'FileAlreadyExists', // The output file already exists. + 'SignatureArchiveIsInvalidZip' = 'SignatureArchiveIsInvalidZip', + 'SignatureArchiveHasSameSignatureFile' = 'SignatureArchiveHasSameSignatureFile', // The signature archive has the same signature file. + 'PackageIntegrityCheckFailed' = 'PackageIntegrityCheckFailed', // The package integrity check failed. + 'SignatureIsInvalid' = 'SignatureIsInvalid', // The extension has an invalid signature file (.signature.p7s). + 'SignatureManifestIsInvalid' = 'SignatureManifestIsInvalid', // The extension has an invalid signature manifest file (.signature.manifest). + 'SignatureIntegrityCheckFailed' = 'SignatureIntegrityCheckFailed', // The extension's signature integrity check failed. Extension integrity is suspect. + 'EntryIsMissing' = 'EntryIsMissing', // An entry referenced in the signature manifest was not found in the extension. + 'EntryIsTampered' = 'EntryIsTampered', // The integrity check for an entry referenced in the signature manifest failed. + 'Untrusted' = 'Untrusted', // An X.509 certificate in the extension signature is untrusted. + 'CertificateRevoked' = 'CertificateRevoked', // An X.509 certificate in the extension signature has been revoked. + 'SignatureIsNotValid' = 'SignatureIsNotValid', // The extension signature is invalid. + 'UnknownError' = 'UnknownError', // An unknown error occurred. + 'PackageIsInvalidZip' = 'PackageIsInvalidZip', // The extension package is not valid ZIP format. + 'SignatureArchiveHasTooManyEntries' = 'SignatureArchiveHasTooManyEntries', // The signature archive has too many entries. } export class ExtensionManagementError extends Error { - constructor( - message: string, - readonly code: ExtensionManagementErrorCode, - ) { + constructor(message: string, readonly code: ExtensionManagementErrorCode) { super(message); this.name = code; } @@ -658,30 +544,14 @@ export type UninstallOptions = { }; export interface IExtensionManagementParticipant { - postInstall( - local: ILocalExtension, - source: URI | IGalleryExtension, - options: InstallOptions, - token: CancellationToken, - ): Promise; - postUninstall( - local: ILocalExtension, - options: UninstallOptions, - token: CancellationToken, - ): Promise; -} - -export type InstallExtensionInfo = { - readonly extension: IGalleryExtension; - readonly options: InstallOptions; -}; -export type UninstallExtensionInfo = { - readonly extension: ILocalExtension; - readonly options?: UninstallOptions; -}; + postInstall(local: ILocalExtension, source: URI | IGalleryExtension, options: InstallOptions, token: CancellationToken): Promise; + postUninstall(local: ILocalExtension, options: UninstallOptions, token: CancellationToken): Promise; +} -export const IExtensionManagementService = - createDecorator("extensionManagementService"); +export type InstallExtensionInfo = { readonly extension: IGalleryExtension; readonly options: InstallOptions }; +export type UninstallExtensionInfo = { readonly extension: ILocalExtension; readonly options?: UninstallOptions }; + +export const IExtensionManagementService = createDecorator('extensionManagementService'); export interface IExtensionManagementService { readonly _serviceBrand: undefined; @@ -695,54 +565,21 @@ export interface IExtensionManagementService { getManifest(vsix: URI): Promise; install(vsix: URI, options?: InstallOptions): Promise; canInstall(extension: IGalleryExtension): Promise; - installFromGallery( - extension: IGalleryExtension, - options?: InstallOptions, - ): Promise; - installGalleryExtensions( - extensions: InstallExtensionInfo[], - ): Promise; - installFromLocation( - location: URI, - profileLocation: URI, - ): Promise; - installExtensionsFromProfile( - extensions: IExtensionIdentifier[], - fromProfileLocation: URI, - toProfileLocation: URI, - ): Promise; - uninstall( - extension: ILocalExtension, - options?: UninstallOptions, - ): Promise; + installFromGallery(extension: IGalleryExtension, options?: InstallOptions): Promise; + installGalleryExtensions(extensions: InstallExtensionInfo[]): Promise; + installFromLocation(location: URI, profileLocation: URI): Promise; + installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise; + uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise; uninstallExtensions(extensions: UninstallExtensionInfo[]): Promise; - toggleAppliationScope( - extension: ILocalExtension, - fromProfileLocation: URI, - ): Promise; + toggleAppliationScope(extension: ILocalExtension, fromProfileLocation: URI): Promise; reinstallFromGallery(extension: ILocalExtension): Promise; - getInstalled( - type?: ExtensionType, - profileLocation?: URI, - productVersion?: IProductVersion, - ): Promise; + getInstalled(type?: ExtensionType, profileLocation?: URI, productVersion?: IProductVersion): Promise; getExtensionsControlManifest(): Promise; - copyExtensions( - fromProfileLocation: URI, - toProfileLocation: URI, - ): Promise; - updateMetadata( - local: ILocalExtension, - metadata: Partial, - profileLocation: URI, - ): Promise; + copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise; + updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation: URI): Promise; resetPinnedStateForAllUserExtensions(pinned: boolean): Promise; - download( - extension: IGalleryExtension, - operation: InstallOperation, - donotVerifySignature: boolean, - ): Promise; + download(extension: IGalleryExtension, operation: InstallOperation, donotVerifySignature: boolean): Promise; registerParticipant(pariticipant: IExtensionManagementParticipant): void; getTargetPlatform(): Promise; @@ -750,30 +587,18 @@ export interface IExtensionManagementService { cleanUp(): Promise; } -export const DISABLED_EXTENSIONS_STORAGE_PATH = - "extensionsIdentifiers/disabled"; -export const ENABLED_EXTENSIONS_STORAGE_PATH = "extensionsIdentifiers/enabled"; -export const IGlobalExtensionEnablementService = - createDecorator( - "IGlobalExtensionEnablementService", - ); +export const DISABLED_EXTENSIONS_STORAGE_PATH = 'extensionsIdentifiers/disabled'; +export const ENABLED_EXTENSIONS_STORAGE_PATH = 'extensionsIdentifiers/enabled'; +export const IGlobalExtensionEnablementService = createDecorator('IGlobalExtensionEnablementService'); export interface IGlobalExtensionEnablementService { readonly _serviceBrand: undefined; - readonly onDidChangeEnablement: Event<{ - readonly extensions: IExtensionIdentifier[]; - readonly source?: string; - }>; + readonly onDidChangeEnablement: Event<{ readonly extensions: IExtensionIdentifier[]; readonly source?: string }>; getDisabledExtensions(): IExtensionIdentifier[]; - enableExtension( - extension: IExtensionIdentifier, - source?: string, - ): Promise; - disableExtension( - extension: IExtensionIdentifier, - source?: string, - ): Promise; + enableExtension(extension: IExtensionIdentifier, source?: string): Promise; + disableExtension(extension: IExtensionIdentifier, source?: string): Promise; + } export type IConfigBasedExtensionTip = { @@ -795,9 +620,7 @@ export type IExecutableBasedExtensionTip = { readonly whenNotInstalled?: string[]; }; -export const IExtensionTipsService = createDecorator( - "IExtensionTipsService", -); +export const IExtensionTipsService = createDecorator('IExtensionTipsService'); export interface IExtensionTipsService { readonly _serviceBrand: undefined; @@ -806,39 +629,26 @@ export interface IExtensionTipsService { getOtherExecutableBasedTips(): Promise; } -export const IAllowedExtensionsService = - createDecorator("IAllowedExtensionsService"); +export const IAllowedExtensionsService = createDecorator('IAllowedExtensionsService'); export interface IAllowedExtensionsService { readonly _serviceBrand: undefined; readonly onDidChangeAllowedExtensions: Event; - isAllowed( - extension: - | IGalleryExtension - | IExtension - | { id: string; version?: string; prerelease?: boolean }, - ): true | IMarkdownString; -} - -export async function computeSize( - location: URI, - fileService: IFileService, -): Promise { + + isAllowed(extension: IGalleryExtension | IExtension): true | IMarkdownString; + isAllowed(extension: { id: string; publisherDisplayName: string | undefined; version?: string; prerelease?: boolean; targetPlatform?: TargetPlatform }): true | IMarkdownString; +} + +export async function computeSize(location: URI, fileService: IFileService): Promise { const stat = await fileService.resolve(location); if (stat.children) { - const sizes = await Promise.all( - stat.children.map((c) => computeSize(c.resource, fileService)), - ); + const sizes = await Promise.all(stat.children.map(c => computeSize(c.resource, fileService))); return sizes.reduce((r, s) => r + s, 0); } return stat.size ?? 0; } -export const ExtensionsLocalizedLabel = localize2("extensions", "Extensions"); -export const PreferencesLocalizedLabel = localize2( - "preferences", - "Preferences", -); -export const UseUnpkgResourceApiConfigKey = - "extensions.gallery.useUnpkgResourceApi"; -export const AllowedExtensionsConfigKey = "extensions.allowed"; +export const ExtensionsLocalizedLabel = localize2('extensions', "Extensions"); +export const PreferencesLocalizedLabel = localize2('preferences', 'Preferences'); +export const UseUnpkgResourceApiConfigKey = 'extensions.gallery.useUnpkgResourceApi'; +export const AllowedExtensionsConfigKey = 'extensions.allowed'; diff --git a/Source/vs/platform/extensionManagement/common/extensionManagementUtil.ts b/Source/vs/platform/extensionManagement/common/extensionManagementUtil.ts index 09cf18fe50a72..39d008ba8be85 100644 --- a/Source/vs/platform/extensionManagement/common/extensionManagementUtil.ts +++ b/Source/vs/platform/extensionManagement/common/extensionManagementUtil.ts @@ -2,31 +2,19 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getErrorMessage } from "../../../base/common/errors.js"; -import { isLinux, platform } from "../../../base/common/platform.js"; -import { arch } from "../../../base/common/process.js"; -import { compareIgnoreCase } from "../../../base/common/strings.js"; -import { URI } from "../../../base/common/uri.js"; -import { - ExtensionIdentifier, - IExtension, - TargetPlatform, - UNDEFINED_PUBLISHER, -} from "../../extensions/common/extensions.js"; -import { IFileService } from "../../files/common/files.js"; -import { ILogService } from "../../log/common/log.js"; -import { TelemetryTrustedValue } from "../../telemetry/common/telemetryUtils.js"; -import { - getTargetPlatform, - IExtensionIdentifier, - IGalleryExtension, - ILocalExtension, -} from "./extensionManagement.js"; -export function areSameExtensions( - a: IExtensionIdentifier, - b: IExtensionIdentifier, -): boolean { +import { compareIgnoreCase } from '../../../base/common/strings.js'; +import { IExtensionIdentifier, IGalleryExtension, ILocalExtension, getTargetPlatform } from './extensionManagement.js'; +import { ExtensionIdentifier, IExtension, TargetPlatform, UNDEFINED_PUBLISHER } from '../../extensions/common/extensions.js'; +import { IFileService } from '../../files/common/files.js'; +import { isLinux, platform } from '../../../base/common/platform.js'; +import { URI } from '../../../base/common/uri.js'; +import { getErrorMessage } from '../../../base/common/errors.js'; +import { ILogService } from '../../log/common/log.js'; +import { arch } from '../../../base/common/process.js'; +import { TelemetryTrustedValue } from '../../telemetry/common/telemetryUtils.js'; + +export function areSameExtensions(a: IExtensionIdentifier, b: IExtensionIdentifier): boolean { if (a.uuid && b.uuid) { return a.uuid === b.uuid; } @@ -35,30 +23,22 @@ export function areSameExtensions( } return compareIgnoreCase(a.id, b.id) === 0; } + const ExtensionKeyRegex = /^([^.]+\..+)-(\d+\.\d+\.\d+)(-(.+))?$/; -export class ExtensionKey { - static create(extension: IExtension | IGalleryExtension): ExtensionKey { - const version = (extension as IExtension).manifest - ? (extension as IExtension).manifest.version - : (extension as IGalleryExtension).version; - const targetPlatform = (extension as IExtension).manifest - ? (extension as IExtension).targetPlatform - : (extension as IGalleryExtension).properties.targetPlatform; +export class ExtensionKey { + static create(extension: IExtension | IGalleryExtension): ExtensionKey { + const version = (extension as IExtension).manifest ? (extension as IExtension).manifest.version : (extension as IGalleryExtension).version; + const targetPlatform = (extension as IExtension).manifest ? (extension as IExtension).targetPlatform : (extension as IGalleryExtension).properties.targetPlatform; return new ExtensionKey(extension.identifier, version, targetPlatform); } + static parse(key: string): ExtensionKey | null { const matches = ExtensionKeyRegex.exec(key); - - return matches && matches[1] && matches[2] - ? new ExtensionKey( - { id: matches[1] }, - matches[2], - (matches[4] as TargetPlatform) || undefined, - ) - : null; + return matches && matches[1] && matches[2] ? new ExtensionKey({ id: matches[1] }, matches[2], matches[4] as TargetPlatform || undefined) : null; } + readonly id: string; constructor( @@ -68,69 +48,52 @@ export class ExtensionKey { ) { this.id = identifier.id; } + toString(): string { - return `${this.id}-${this.version}${this.targetPlatform !== TargetPlatform.UNDEFINED ? `-${this.targetPlatform}` : ""}`; + return `${this.id}-${this.version}${this.targetPlatform !== TargetPlatform.UNDEFINED ? `-${this.targetPlatform}` : ''}`; } + equals(o: any): boolean { if (!(o instanceof ExtensionKey)) { return false; } - return ( - areSameExtensions(this, o) && - this.version === o.version && - this.targetPlatform === o.targetPlatform - ); + return areSameExtensions(this, o) && this.version === o.version && this.targetPlatform === o.targetPlatform; } } -const EXTENSION_IDENTIFIER_WITH_VERSION_REGEX = - /^([^.]+\..+)@((prerelease)|(\d+\.\d+\.\d+(-.*)?))$/; + +const EXTENSION_IDENTIFIER_WITH_VERSION_REGEX = /^([^.]+\..+)@((prerelease)|(\d+\.\d+\.\d+(-.*)?))$/; export function getIdAndVersion(id: string): [string, string | undefined] { const matches = EXTENSION_IDENTIFIER_WITH_VERSION_REGEX.exec(id); - if (matches && matches[1]) { return [adoptToGalleryExtensionId(matches[1]), matches[2]]; } return [adoptToGalleryExtensionId(id), undefined]; } + export function getExtensionId(publisher: string, name: string): string { return `${publisher}.${name}`; } + export function adoptToGalleryExtensionId(id: string): string { return id.toLowerCase(); } -export function getGalleryExtensionId( - publisher: string | undefined, - name: string, -): string { - return adoptToGalleryExtensionId( - getExtensionId(publisher ?? UNDEFINED_PUBLISHER, name), - ); + +export function getGalleryExtensionId(publisher: string | undefined, name: string): string { + return adoptToGalleryExtensionId(getExtensionId(publisher ?? UNDEFINED_PUBLISHER, name)); } -export function groupByExtension( - extensions: T[], - getExtensionIdentifier: (t: T) => IExtensionIdentifier, -): T[][] { - const byExtension: T[][] = []; +export function groupByExtension(extensions: T[], getExtensionIdentifier: (t: T) => IExtensionIdentifier): T[][] { + const byExtension: T[][] = []; const findGroup = (extension: T) => { for (const group of byExtension) { - if ( - group.some((e) => - areSameExtensions( - getExtensionIdentifier(e), - getExtensionIdentifier(extension), - ), - ) - ) { + if (group.some(e => areSameExtensions(getExtensionIdentifier(e), getExtensionIdentifier(extension)))) { return group; } } return null; }; - for (const extension of extensions) { const group = findGroup(extension); - if (group) { group.push(extension); } else { @@ -139,6 +102,7 @@ export function groupByExtension( } return byExtension; } + export function getLocalExtensionTelemetryData(extension: ILocalExtension) { return { id: extension.identifier.id, @@ -147,120 +111,88 @@ export function getLocalExtensionTelemetryData(extension: ILocalExtension) { publisherId: extension.publisherId, publisherName: extension.manifest.publisher, publisherDisplayName: extension.publisherDisplayName, - dependencies: - extension.manifest.extensionDependencies && - extension.manifest.extensionDependencies.length > 0, + dependencies: extension.manifest.extensionDependencies && extension.manifest.extensionDependencies.length > 0 }; } + + /* __GDPR__FRAGMENT__ - "GalleryExtensionTelemetryData" : { - "id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "name": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "version": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "galleryId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "publisherId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "publisherName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "publisherDisplayName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "isPreReleaseVersion": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "dependencies": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "isSigned": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "${include}": [ - "${GalleryExtensionTelemetryData2}" - ] - } + "GalleryExtensionTelemetryData" : { + "id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "name": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "extensionVersion": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "galleryId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "publisherId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "publisherName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "publisherDisplayName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "isPreReleaseVersion": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "dependencies": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "isSigned": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "${include}": [ + "${GalleryExtensionTelemetryData2}" + ] + } */ export function getGalleryExtensionTelemetryData(extension: IGalleryExtension) { return { id: new TelemetryTrustedValue(extension.identifier.id), name: new TelemetryTrustedValue(extension.name), - version: extension.version, + extensionVersion: extension.version, galleryId: extension.identifier.uuid, publisherId: extension.publisherId, publisherName: extension.publisher, publisherDisplayName: extension.publisherDisplayName, isPreReleaseVersion: extension.properties.isPreReleaseVersion, - dependencies: !!( - extension.properties.dependencies && - extension.properties.dependencies.length > 0 - ), + dependencies: !!(extension.properties.dependencies && extension.properties.dependencies.length > 0), isSigned: extension.isSigned, - ...extension.telemetryData, + ...extension.telemetryData }; } -export const BetterMergeId = new ExtensionIdentifier("pprice.better-merge"); -export function getExtensionDependencies( - installedExtensions: ReadonlyArray, - extension: IExtension, -): IExtension[] { - const dependencies: IExtension[] = []; +export const BetterMergeId = new ExtensionIdentifier('pprice.better-merge'); + +export function getExtensionDependencies(installedExtensions: ReadonlyArray, extension: IExtension): IExtension[] { + const dependencies: IExtension[] = []; const extensions = extension.manifest.extensionDependencies?.slice(0) ?? []; while (extensions.length) { const id = extensions.shift(); - if ( - id && - dependencies.every((e) => !areSameExtensions(e.identifier, { id })) - ) { - const ext = installedExtensions.filter((e) => - areSameExtensions(e.identifier, { id }), - ); - + if (id && dependencies.every(e => !areSameExtensions(e.identifier, { id }))) { + const ext = installedExtensions.filter(e => areSameExtensions(e.identifier, { id })); if (ext.length === 1) { dependencies.push(ext[0]); - extensions.push( - ...(ext[0].manifest.extensionDependencies?.slice(0) ?? []), - ); + extensions.push(...ext[0].manifest.extensionDependencies?.slice(0) ?? []); } } } + return dependencies; } -async function isAlpineLinux( - fileService: IFileService, - logService: ILogService, -): Promise { + +async function isAlpineLinux(fileService: IFileService, logService: ILogService): Promise { if (!isLinux) { return false; } let content: string | undefined; - try { - const fileContent = await fileService.readFile( - URI.file("/etc/os-release"), - ); + const fileContent = await fileService.readFile(URI.file('/etc/os-release')); content = fileContent.value.toString(); } catch (error) { try { - const fileContent = await fileService.readFile( - URI.file("/usr/lib/os-release"), - ); + const fileContent = await fileService.readFile(URI.file('/usr/lib/os-release')); content = fileContent.value.toString(); } catch (error) { /* Ignore */ - logService.debug( - `Error while getting the os-release file.`, - getErrorMessage(error), - ); + logService.debug(`Error while getting the os-release file.`, getErrorMessage(error)); } } - return ( - !!content && - (content.match(/^ID=([^\u001b\r\n]*)/m) || [])[1] === "alpine" - ); + return !!content && (content.match(/^ID=([^\u001b\r\n]*)/m) || [])[1] === 'alpine'; } -export async function computeTargetPlatform( - fileService: IFileService, - logService: ILogService, -): Promise { - const alpineLinux = await isAlpineLinux(fileService, logService); - - const targetPlatform = getTargetPlatform( - alpineLinux ? "alpine" : platform, - arch, - ); - logService.debug("ComputeTargetPlatform:", targetPlatform); +export async function computeTargetPlatform(fileService: IFileService, logService: ILogService): Promise { + const alpineLinux = await isAlpineLinux(fileService, logService); + const targetPlatform = getTargetPlatform(alpineLinux ? 'alpine' : platform, arch); + logService.debug('ComputeTargetPlatform:', targetPlatform); return targetPlatform; } diff --git a/Source/vs/platform/extensionManagement/node/extensionDownloader.ts b/Source/vs/platform/extensionManagement/node/extensionDownloader.ts index 1bb310d1e63d3..249e705867d8e 100644 --- a/Source/vs/platform/extensionManagement/node/extensionDownloader.ts +++ b/Source/vs/platform/extensionManagement/node/extensionDownloader.ts @@ -3,57 +3,33 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Promises } from "../../../base/common/async.js"; -import { getErrorMessage } from "../../../base/common/errors.js"; -import { Disposable } from "../../../base/common/lifecycle.js"; -import { Schemas } from "../../../base/common/network.js"; -import { joinPath } from "../../../base/common/resources.js"; -import * as semver from "../../../base/common/semver/semver.js"; -import { URI } from "../../../base/common/uri.js"; -import { generateUuid } from "../../../base/common/uuid.js"; -import { Promises as FSPromises } from "../../../base/node/pfs.js"; -import { buffer, CorruptZipMessage } from "../../../base/node/zip.js"; -import { INativeEnvironmentService } from "../../environment/common/environment.js"; -import { TargetPlatform } from "../../extensions/common/extensions.js"; -import { - FileOperationResult, - IFileService, - IFileStatWithMetadata, - toFileOperationResult, -} from "../../files/common/files.js"; -import { ILogService } from "../../log/common/log.js"; -import { ITelemetryService } from "../../telemetry/common/telemetry.js"; -import { IUriIdentityService } from "../../uriIdentity/common/uriIdentity.js"; -import { toExtensionManagementError } from "../common/abstractExtensionManagementService.js"; -import { - ExtensionManagementError, - ExtensionManagementErrorCode, - ExtensionSignatureVerificationCode, - IExtensionGalleryService, - IGalleryExtension, - InstallOperation, -} from "../common/extensionManagement.js"; -import { - ExtensionKey, - groupByExtension, -} from "../common/extensionManagementUtil.js"; -import { fromExtractError } from "./extensionManagementUtil.js"; -import { IExtensionSignatureVerificationService } from "./extensionSignatureVerificationService.js"; +import { Promises } from '../../../base/common/async.js'; +import { getErrorMessage } from '../../../base/common/errors.js'; +import { Disposable } from '../../../base/common/lifecycle.js'; +import { Schemas } from '../../../base/common/network.js'; +import { joinPath } from '../../../base/common/resources.js'; +import * as semver from '../../../base/common/semver/semver.js'; +import { URI } from '../../../base/common/uri.js'; +import { generateUuid } from '../../../base/common/uuid.js'; +import { Promises as FSPromises } from '../../../base/node/pfs.js'; +import { buffer, CorruptZipMessage } from '../../../base/node/zip.js'; +import { INativeEnvironmentService } from '../../environment/common/environment.js'; +import { toExtensionManagementError } from '../common/abstractExtensionManagementService.js'; +import { ExtensionManagementError, ExtensionManagementErrorCode, ExtensionSignatureVerificationCode, IExtensionGalleryService, IGalleryExtension, InstallOperation } from '../common/extensionManagement.js'; +import { ExtensionKey, groupByExtension } from '../common/extensionManagementUtil.js'; +import { fromExtractError } from './extensionManagementUtil.js'; +import { IExtensionSignatureVerificationService } from './extensionSignatureVerificationService.js'; +import { TargetPlatform } from '../../extensions/common/extensions.js'; +import { FileOperationResult, IFileService, IFileStatWithMetadata, toFileOperationResult } from '../../files/common/files.js'; +import { ILogService } from '../../log/common/log.js'; +import { ITelemetryService } from '../../telemetry/common/telemetry.js'; +import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js'; type RetryDownloadClassification = { - owner: "sandy081"; - comment: "Event reporting the retry of downloading"; - extensionId: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "Extension Id"; - }; - attempts: { - classification: "SystemMetaData"; - purpose: "PerformanceAndHealth"; - isMeasurement: true; - comment: "Number of Attempts"; - }; + owner: 'sandy081'; + comment: 'Event reporting the retry of downloading'; + extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension Id' }; + attempts: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Number of Attempts' }; }; type RetryDownloadEvent = { extensionId: string; @@ -61,7 +37,8 @@ type RetryDownloadEvent = { }; export class ExtensionsDownloader extends Disposable { - private static readonly SignatureArchiveExtension = ".sigzip"; + + private static readonly SignatureArchiveExtension = '.sigzip'; readonly extensionsDownloadDir: URI; private readonly extensionsTrashDir: URI; @@ -69,80 +46,46 @@ export class ExtensionsDownloader extends Disposable { private readonly cleanUpPromise: Promise; constructor( - @INativeEnvironmentService - environmentService: INativeEnvironmentService, + @INativeEnvironmentService environmentService: INativeEnvironmentService, @IFileService private readonly fileService: IFileService, - @IExtensionGalleryService - private readonly extensionGalleryService: IExtensionGalleryService, - @IExtensionSignatureVerificationService - private readonly extensionSignatureVerificationService: IExtensionSignatureVerificationService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, + @IExtensionSignatureVerificationService private readonly extensionSignatureVerificationService: IExtensionSignatureVerificationService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IUriIdentityService - private readonly uriIdentityService: IUriIdentityService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @ILogService private readonly logService: ILogService, ) { super(); - this.extensionsDownloadDir = - environmentService.extensionsDownloadLocation; - this.extensionsTrashDir = uriIdentityService.extUri.joinPath( - environmentService.extensionsDownloadLocation, - `.trash`, - ); + this.extensionsDownloadDir = environmentService.extensionsDownloadLocation; + this.extensionsTrashDir = uriIdentityService.extUri.joinPath(environmentService.extensionsDownloadLocation, `.trash`); this.cache = 20; // Cache 20 downloaded VSIX files this.cleanUpPromise = this.cleanUp(); } - async download( - extension: IGalleryExtension, - operation: InstallOperation, - verifySignature: boolean, - clientTargetPlatform?: TargetPlatform, - ): Promise<{ - readonly location: URI; - readonly verificationStatus: - | ExtensionSignatureVerificationCode - | undefined; - }> { + async download(extension: IGalleryExtension, operation: InstallOperation, verifySignature: boolean, clientTargetPlatform?: TargetPlatform): Promise<{ readonly location: URI; readonly verificationStatus: ExtensionSignatureVerificationCode | undefined }> { await this.cleanUpPromise; const location = await this.downloadVSIX(extension, operation); - if (!verifySignature || !extension.isSigned) { + if (!verifySignature) { return { location, verificationStatus: undefined }; } - let signatureArchiveLocation; + if (!extension.isSigned) { + return { location, verificationStatus: ExtensionSignatureVerificationCode.NotSigned }; + } + let signatureArchiveLocation; try { - signatureArchiveLocation = - await this.downloadSignatureArchive(extension); - - const verificationStatus = ( - await this.extensionSignatureVerificationService.verify( - extension.identifier.id, - extension.version, - location.fsPath, - signatureArchiveLocation.fsPath, - clientTargetPlatform, - ) - )?.code; - - if ( - verificationStatus === - ExtensionSignatureVerificationCode.PackageIsInvalidZip || - verificationStatus === - ExtensionSignatureVerificationCode.SignatureArchiveIsInvalidZip - ) { + signatureArchiveLocation = await this.downloadSignatureArchive(extension); + const verificationStatus = (await this.extensionSignatureVerificationService.verify(extension.identifier.id, extension.version, location.fsPath, signatureArchiveLocation.fsPath, clientTargetPlatform))?.code; + if (verificationStatus === ExtensionSignatureVerificationCode.PackageIsInvalidZip || verificationStatus === ExtensionSignatureVerificationCode.SignatureArchiveIsInvalidZip) { try { // Delete the downloaded vsix if VSIX or signature archive is invalid await this.delete(location); } catch (error) { this.logService.error(error); } - throw new ExtensionManagementError( - CorruptZipMessage, - ExtensionManagementErrorCode.CorruptZip, - ); + throw new ExtensionManagementError(CorruptZipMessage, ExtensionManagementErrorCode.CorruptZip); } return { location, verificationStatus }; } catch (error) { @@ -165,126 +108,67 @@ export class ExtensionsDownloader extends Disposable { } } - private async downloadVSIX( - extension: IGalleryExtension, - operation: InstallOperation, - ): Promise { + private async downloadVSIX(extension: IGalleryExtension, operation: InstallOperation): Promise { try { - const location = joinPath( - this.extensionsDownloadDir, - this.getName(extension), - ); - - const attempts = await this.doDownload( - extension, - "vsix", - async () => { - await this.downloadFile(extension, location, (location) => - this.extensionGalleryService.download( - extension, - location, - operation, - ), - ); - + const location = joinPath(this.extensionsDownloadDir, this.getName(extension)); + const attempts = await this.doDownload(extension, 'vsix', async () => { + await this.downloadFile(extension, location, location => this.extensionGalleryService.download(extension, location, operation)); + try { + await this.validate(location.fsPath, 'extension/package.json'); + } catch (error) { try { - await this.validate( - location.fsPath, - "extension/package.json", - ); - } catch (error) { - try { - await this.fileService.del(location); - } catch (e) { - this.logService.warn( - `Error while deleting: ${location.path}`, - getErrorMessage(e), - ); - } - throw error; + await this.fileService.del(location); + } catch (e) { + this.logService.warn(`Error while deleting: ${location.path}`, getErrorMessage(e)); } - }, - 2, - ); + throw error; + } + }, 2); if (attempts > 1) { - this.telemetryService.publicLog2< - RetryDownloadEvent, - RetryDownloadClassification - >("extensiongallery:downloadvsix:retry", { + this.telemetryService.publicLog2('extensiongallery:downloadvsix:retry', { extensionId: extension.identifier.id, - attempts, + attempts }); } return location; } catch (e) { - throw toExtensionManagementError( - e, - ExtensionManagementErrorCode.Download, - ); + throw toExtensionManagementError(e, ExtensionManagementErrorCode.Download); } } - private async downloadSignatureArchive( - extension: IGalleryExtension, - ): Promise { + private async downloadSignatureArchive(extension: IGalleryExtension): Promise { try { - const location = joinPath( - this.extensionsDownloadDir, - `${this.getName(extension)}${ExtensionsDownloader.SignatureArchiveExtension}`, - ); - - const attempts = await this.doDownload( - extension, - "sigzip", - async () => { - await this.extensionGalleryService.downloadSignatureArchive( - extension, - location, - ); - + const location = joinPath(this.extensionsDownloadDir, `${this.getName(extension)}${ExtensionsDownloader.SignatureArchiveExtension}`); + const attempts = await this.doDownload(extension, 'sigzip', async () => { + await this.extensionGalleryService.downloadSignatureArchive(extension, location); + try { + await this.validate(location.fsPath, '.signature.p7s'); + } catch (error) { try { - await this.validate(location.fsPath, ".signature.p7s"); - } catch (error) { - try { - await this.fileService.del(location); - } catch (e) { - this.logService.warn( - `Error while deleting: ${location.path}`, - getErrorMessage(e), - ); - } - throw error; + await this.fileService.del(location); + } catch (e) { + this.logService.warn(`Error while deleting: ${location.path}`, getErrorMessage(e)); } - }, - 2, - ); + throw error; + } + }, 2); if (attempts > 1) { - this.telemetryService.publicLog2< - RetryDownloadEvent, - RetryDownloadClassification - >("extensiongallery:downloadsigzip:retry", { + this.telemetryService.publicLog2('extensiongallery:downloadsigzip:retry', { extensionId: extension.identifier.id, - attempts, + attempts }); } return location; } catch (e) { - throw toExtensionManagementError( - e, - ExtensionManagementErrorCode.DownloadSignature, - ); + throw toExtensionManagementError(e, ExtensionManagementErrorCode.DownloadSignature); } } - private async downloadFile( - extension: IGalleryExtension, - location: URI, - downloadFn: (location: URI) => Promise, - ): Promise { + private async downloadFile(extension: IGalleryExtension, location: URI, downloadFn: (location: URI) => Promise): Promise { // Do not download if exists if (await this.fileService.exists(location)) { return; @@ -293,85 +177,47 @@ export class ExtensionsDownloader extends Disposable { // Download directly if locaiton is not file scheme if (location.scheme !== Schemas.file) { await downloadFn(location); - return; } // Download to temporary location first only if file does not exist - const tempLocation = joinPath( - this.extensionsDownloadDir, - `.${generateUuid()}`, - ); - + const tempLocation = joinPath(this.extensionsDownloadDir, `.${generateUuid()}`); try { await downloadFn(tempLocation); } catch (error) { try { await this.fileService.del(tempLocation); - } catch (e) { - /* ignore */ - } + } catch (e) { /* ignore */ } throw error; } try { // Rename temp location to original - await FSPromises.rename( - tempLocation.fsPath, - location.fsPath, - 2 * 60 * 1000 /* Retry for 2 minutes */, - ); + await FSPromises.rename(tempLocation.fsPath, location.fsPath, 2 * 60 * 1000 /* Retry for 2 minutes */); } catch (error) { - try { - await this.fileService.del(tempLocation); - } catch (e) { - /* ignore */ - } + try { await this.fileService.del(tempLocation); } catch (e) { /* ignore */ } let exists = false; - - try { - exists = await this.fileService.exists(location); - } catch (e) { - /* ignore */ - } + try { exists = await this.fileService.exists(location); } catch (e) { /* ignore */ } if (exists) { - this.logService.info( - `Rename failed because the file was downloaded by another source. So ignoring renaming.`, - extension.identifier.id, - location.path, - ); + this.logService.info(`Rename failed because the file was downloaded by another source. So ignoring renaming.`, extension.identifier.id, location.path); } else { - this.logService.info( - `Rename failed because of ${getErrorMessage(error)}. Deleted the file from downloaded location`, - tempLocation.path, - ); - + this.logService.info(`Rename failed because of ${getErrorMessage(error)}. Deleted the file from downloaded location`, tempLocation.path); throw error; } } } - private async doDownload( - extension: IGalleryExtension, - name: string, - downloadFn: () => Promise, - retries: number, - ): Promise { + private async doDownload(extension: IGalleryExtension, name: string, downloadFn: () => Promise, retries: number): Promise { let attempts = 1; - while (true) { try { await downloadFn(); - return attempts; } catch (e) { if (attempts++ > retries) { throw e; } - this.logService.warn( - `Failed downloading ${name}. ${getErrorMessage(e)}. Retry again...`, - extension.identifier.id, - ); + this.logService.warn(`Failed downloading ${name}. ${getErrorMessage(e)}. Retry again...`, extension.identifier.id); } } } @@ -386,21 +232,9 @@ export class ExtensionsDownloader extends Disposable { async delete(location: URI): Promise { await this.cleanUpPromise; - - const trashRelativePath = this.uriIdentityService.extUri.relativePath( - this.extensionsDownloadDir, - location, - ); - + const trashRelativePath = this.uriIdentityService.extUri.relativePath(this.extensionsDownloadDir, location); if (trashRelativePath) { - await this.fileService.move( - location, - this.uriIdentityService.extUri.joinPath( - this.extensionsTrashDir, - trashRelativePath, - ), - true, - ); + await this.fileService.move(location, this.uriIdentityService.extUri.joinPath(this.extensionsTrashDir, trashRelativePath), true); } else { await this.fileService.del(location); } @@ -409,86 +243,50 @@ export class ExtensionsDownloader extends Disposable { private async cleanUp(): Promise { try { if (!(await this.fileService.exists(this.extensionsDownloadDir))) { - this.logService.trace( - "Extension VSIX downloads cache dir does not exist", - ); - + this.logService.trace('Extension VSIX downloads cache dir does not exist'); return; } try { - await this.fileService.del(this.extensionsTrashDir, { - recursive: true, - }); + await this.fileService.del(this.extensionsTrashDir, { recursive: true }); } catch (error) { - if ( - toFileOperationResult(error) !== - FileOperationResult.FILE_NOT_FOUND - ) { + if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) { this.logService.error(error); } } - const folderStat = await this.fileService.resolve( - this.extensionsDownloadDir, - { resolveMetadata: true }, - ); - + const folderStat = await this.fileService.resolve(this.extensionsDownloadDir, { resolveMetadata: true }); if (folderStat.children) { const toDelete: URI[] = []; - const vsixs: [ExtensionKey, IFileStatWithMetadata][] = []; - const signatureArchives: URI[] = []; for (const stat of folderStat.children) { - if ( - stat.name.endsWith( - ExtensionsDownloader.SignatureArchiveExtension, - ) - ) { + if (stat.name.endsWith(ExtensionsDownloader.SignatureArchiveExtension)) { signatureArchives.push(stat.resource); } else { const extension = ExtensionKey.parse(stat.name); - if (extension) { vsixs.push([extension, stat]); } } } - const byExtension = groupByExtension( - vsixs, - ([extension]) => extension, - ); - + const byExtension = groupByExtension(vsixs, ([extension]) => extension); const distinct: IFileStatWithMetadata[] = []; - for (const p of byExtension) { - p.sort((a, b) => - semver.rcompare(a[0].version, b[0].version), - ); - toDelete.push(...p.slice(1).map((e) => e[1].resource)); // Delete outdated extensions + p.sort((a, b) => semver.rcompare(a[0].version, b[0].version)); + toDelete.push(...p.slice(1).map(e => e[1].resource)); // Delete outdated extensions distinct.push(p[0][1]); } distinct.sort((a, b) => a.mtime - b.mtime); // sort by modified time - toDelete.push( - ...distinct - .slice(0, Math.max(0, distinct.length - this.cache)) - .map((s) => s.resource), - ); // Retain minimum cacheSize and delete the rest + toDelete.push(...distinct.slice(0, Math.max(0, distinct.length - this.cache)).map(s => s.resource)); // Retain minimum cacheSize and delete the rest toDelete.push(...signatureArchives); // Delete all signature archives - await Promises.settled( - toDelete.map((resource) => { - this.logService.trace( - "Deleting from cache", - resource.path, - ); - - return this.fileService.del(resource); - }), - ); + await Promises.settled(toDelete.map(resource => { + this.logService.trace('Deleting from cache', resource.path); + return this.fileService.del(resource); + })); } } catch (e) { this.logService.error(e); @@ -498,4 +296,5 @@ export class ExtensionsDownloader extends Disposable { private getName(extension: IGalleryExtension): string { return ExtensionKey.create(extension).toString().toLowerCase(); } + } diff --git a/Source/vs/platform/extensionManagement/node/extensionManagementService.ts b/Source/vs/platform/extensionManagement/node/extensionManagementService.ts index 74b9337b17757..a55978c612f26 100644 --- a/Source/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/Source/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -3,240 +3,118 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as fs from "fs"; - -import { Promises, Queue } from "../../../base/common/async.js"; -import { VSBuffer } from "../../../base/common/buffer.js"; -import { CancellationToken } from "../../../base/common/cancellation.js"; -import { IStringDictionary } from "../../../base/common/collections.js"; -import { toErrorMessage } from "../../../base/common/errorMessage.js"; -import { - CancellationError, - getErrorMessage, -} from "../../../base/common/errors.js"; -import { Emitter } from "../../../base/common/event.js"; -import { hash } from "../../../base/common/hash.js"; -import { Disposable } from "../../../base/common/lifecycle.js"; -import { ResourceMap, ResourceSet } from "../../../base/common/map.js"; -import { Schemas } from "../../../base/common/network.js"; -import * as path from "../../../base/common/path.js"; -import { isLinux } from "../../../base/common/platform.js"; -import { joinPath } from "../../../base/common/resources.js"; -import * as semver from "../../../base/common/semver/semver.js"; -import { isBoolean } from "../../../base/common/types.js"; -import { URI } from "../../../base/common/uri.js"; -import { generateUuid } from "../../../base/common/uuid.js"; -import * as pfs from "../../../base/node/pfs.js"; -import { extract, IFile, zip } from "../../../base/node/zip.js"; -import * as nls from "../../../nls.js"; -import { IConfigurationService } from "../../configuration/common/configuration.js"; -import { IDownloadService } from "../../download/common/download.js"; -import { INativeEnvironmentService } from "../../environment/common/environment.js"; -import { - ExtensionType, - IExtension, - IExtensionManifest, - TargetPlatform, -} from "../../extensions/common/extensions.js"; -import { isEngineValid } from "../../extensions/common/extensionValidator.js"; -import { - FileChangesEvent, - FileChangeType, - FileOperationResult, - IFileService, - toFileOperationResult, -} from "../../files/common/files.js"; -import { - IInstantiationService, - refineServiceDecorator, -} from "../../instantiation/common/instantiation.js"; -import { ILogService } from "../../log/common/log.js"; -import { IProductService } from "../../product/common/productService.js"; -import { ITelemetryService } from "../../telemetry/common/telemetry.js"; -import { IUriIdentityService } from "../../uriIdentity/common/uriIdentity.js"; -import { IUserDataProfilesService } from "../../userDataProfile/common/userDataProfile.js"; +import * as fs from 'fs'; +import { Promises, Queue } from '../../../base/common/async.js'; +import { VSBuffer } from '../../../base/common/buffer.js'; +import { CancellationToken } from '../../../base/common/cancellation.js'; +import { IStringDictionary } from '../../../base/common/collections.js'; +import { toErrorMessage } from '../../../base/common/errorMessage.js'; +import { CancellationError, getErrorMessage } from '../../../base/common/errors.js'; +import { Emitter } from '../../../base/common/event.js'; +import { hash } from '../../../base/common/hash.js'; +import { Disposable } from '../../../base/common/lifecycle.js'; +import { ResourceMap, ResourceSet } from '../../../base/common/map.js'; +import { Schemas } from '../../../base/common/network.js'; +import * as path from '../../../base/common/path.js'; +import { joinPath } from '../../../base/common/resources.js'; +import * as semver from '../../../base/common/semver/semver.js'; +import { isBoolean } from '../../../base/common/types.js'; +import { URI } from '../../../base/common/uri.js'; +import { generateUuid } from '../../../base/common/uuid.js'; +import * as pfs from '../../../base/node/pfs.js'; +import { extract, IFile, zip } from '../../../base/node/zip.js'; +import * as nls from '../../../nls.js'; +import { IDownloadService } from '../../download/common/download.js'; +import { INativeEnvironmentService } from '../../environment/common/environment.js'; +import { AbstractExtensionManagementService, AbstractExtensionTask, IInstallExtensionTask, InstallExtensionTaskOptions, IUninstallExtensionTask, toExtensionManagementError, UninstallExtensionTaskOptions } from '../common/abstractExtensionManagementService.js'; import { - AbstractExtensionManagementService, - AbstractExtensionTask, - IInstallExtensionTask, - InstallExtensionTaskOptions, - IUninstallExtensionTask, - toExtensionManagementError, - UninstallExtensionTaskOptions, -} from "../common/abstractExtensionManagementService.js"; -import { - computeSize, + ExtensionManagementError, ExtensionManagementErrorCode, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, IGalleryExtension, ILocalExtension, InstallOperation, + Metadata, InstallOptions, + IProductVersion, EXTENSION_INSTALL_CLIENT_TARGET_PLATFORM_CONTEXT, - ExtensionManagementError, - ExtensionManagementErrorCode, ExtensionSignatureVerificationCode, + computeSize, IAllowedExtensionsService, - IExtensionGalleryService, - IExtensionIdentifier, - IExtensionManagementService, - IGalleryExtension, - ILocalExtension, - InstallOperation, - InstallOptions, - IProductVersion, - Metadata, -} from "../common/extensionManagement.js"; -import { - areSameExtensions, - computeTargetPlatform, - ExtensionKey, - getGalleryExtensionId, - groupByExtension, -} from "../common/extensionManagementUtil.js"; -import { - IExtensionsProfileScannerService, - IScannedProfileExtension, -} from "../common/extensionsProfileScannerService.js"; -import { - IExtensionsScannerService, - IScannedExtension, - ScanOptions, -} from "../common/extensionsScannerService.js"; -import { ExtensionsDownloader } from "./extensionDownloader.js"; -import { ExtensionsLifecycle } from "./extensionLifecycle.js"; -import { fromExtractError, getManifest } from "./extensionManagementUtil.js"; -import { ExtensionsManifestCache } from "./extensionsManifestCache.js"; -import { - DidChangeProfileExtensionsEvent, - ExtensionsWatcher, -} from "./extensionsWatcher.js"; - -export const INativeServerExtensionManagementService = refineServiceDecorator< - IExtensionManagementService, - INativeServerExtensionManagementService ->(IExtensionManagementService); -export interface INativeServerExtensionManagementService - extends IExtensionManagementService { +} from '../common/extensionManagement.js'; +import { areSameExtensions, computeTargetPlatform, ExtensionKey, getGalleryExtensionId, groupByExtension } from '../common/extensionManagementUtil.js'; +import { IExtensionsProfileScannerService, IScannedProfileExtension } from '../common/extensionsProfileScannerService.js'; +import { IExtensionsScannerService, IScannedExtension, ScanOptions } from '../common/extensionsScannerService.js'; +import { ExtensionsDownloader } from './extensionDownloader.js'; +import { ExtensionsLifecycle } from './extensionLifecycle.js'; +import { fromExtractError, getManifest } from './extensionManagementUtil.js'; +import { ExtensionsManifestCache } from './extensionsManifestCache.js'; +import { DidChangeProfileExtensionsEvent, ExtensionsWatcher } from './extensionsWatcher.js'; +import { ExtensionType, IExtension, IExtensionManifest, TargetPlatform } from '../../extensions/common/extensions.js'; +import { isEngineValid } from '../../extensions/common/extensionValidator.js'; +import { FileChangesEvent, FileChangeType, FileOperationResult, IFileService, toFileOperationResult } from '../../files/common/files.js'; +import { IInstantiationService, refineServiceDecorator } from '../../instantiation/common/instantiation.js'; +import { ILogService } from '../../log/common/log.js'; +import { IProductService } from '../../product/common/productService.js'; +import { ITelemetryService } from '../../telemetry/common/telemetry.js'; +import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js'; +import { IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js'; +import { IConfigurationService } from '../../configuration/common/configuration.js'; +import { isLinux } from '../../../base/common/platform.js'; + +export const INativeServerExtensionManagementService = refineServiceDecorator(IExtensionManagementService); +export interface INativeServerExtensionManagementService extends IExtensionManagementService { readonly _serviceBrand: undefined; scanAllUserInstalledExtensions(): Promise; - scanInstalledExtensionAtLocation( - location: URI, - ): Promise; + scanInstalledExtensionAtLocation(location: URI): Promise; markAsUninstalled(...extensions: IExtension[]): Promise; } -type ExtractExtensionResult = { - readonly local: ILocalExtension; - readonly verificationStatus?: ExtensionSignatureVerificationCode; -}; +type ExtractExtensionResult = { readonly local: ILocalExtension; readonly verificationStatus?: ExtensionSignatureVerificationCode }; + +const DELETED_FOLDER_POSTFIX = '.vsctmp'; -const DELETED_FOLDER_POSTFIX = ".vsctmp"; +export class ExtensionManagementService extends AbstractExtensionManagementService implements INativeServerExtensionManagementService { -export class ExtensionManagementService - extends AbstractExtensionManagementService - implements INativeServerExtensionManagementService -{ private readonly extensionsScanner: ExtensionsScanner; private readonly manifestCache: ExtensionsManifestCache; private readonly extensionsDownloader: ExtensionsDownloader; - private readonly extractingGalleryExtensions = new Map< - string, - Promise - >(); + private readonly extractingGalleryExtensions = new Map>(); constructor( @IExtensionGalleryService galleryService: IExtensionGalleryService, @ITelemetryService telemetryService: ITelemetryService, @ILogService logService: ILogService, - @INativeEnvironmentService - private readonly environmentService: INativeEnvironmentService, - @IExtensionsScannerService - private readonly extensionsScannerService: IExtensionsScannerService, - @IExtensionsProfileScannerService - private readonly extensionsProfileScannerService: IExtensionsProfileScannerService, + @INativeEnvironmentService private readonly environmentService: INativeEnvironmentService, + @IExtensionsScannerService private readonly extensionsScannerService: IExtensionsScannerService, + @IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService, @IDownloadService private downloadService: IDownloadService, - @IInstantiationService - private readonly instantiationService: IInstantiationService, + @IInstantiationService private readonly instantiationService: IInstantiationService, @IFileService private readonly fileService: IFileService, - @IConfigurationService - private readonly configurationService: IConfigurationService, + @IConfigurationService private readonly configurationService: IConfigurationService, @IProductService productService: IProductService, - @IAllowedExtensionsService - allowedExtensionsService: IAllowedExtensionsService, + @IAllowedExtensionsService allowedExtensionsService: IAllowedExtensionsService, @IUriIdentityService uriIdentityService: IUriIdentityService, - @IUserDataProfilesService - userDataProfilesService: IUserDataProfilesService, + @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService ) { - super( - galleryService, - telemetryService, - uriIdentityService, - logService, - productService, - allowedExtensionsService, - userDataProfilesService, - ); - const extensionLifecycle = this._register( - instantiationService.createInstance(ExtensionsLifecycle), - ); - this.extensionsScanner = this._register( - instantiationService.createInstance( - ExtensionsScanner, - (extension) => extensionLifecycle.postUninstall(extension), - ), - ); - this.manifestCache = this._register( - new ExtensionsManifestCache( - userDataProfilesService, - fileService, - uriIdentityService, - this, - this.logService, - ), - ); - this.extensionsDownloader = this._register( - instantiationService.createInstance(ExtensionsDownloader), - ); - - const extensionsWatcher = this._register( - new ExtensionsWatcher( - this, - this.extensionsScannerService, - userDataProfilesService, - extensionsProfileScannerService, - uriIdentityService, - fileService, - logService, - ), - ); - this._register( - extensionsWatcher.onDidChangeExtensionsByAnotherSource((e) => - this.onDidChangeExtensionsFromAnotherSource(e), - ), - ); + super(galleryService, telemetryService, uriIdentityService, logService, productService, allowedExtensionsService, userDataProfilesService); + const extensionLifecycle = this._register(instantiationService.createInstance(ExtensionsLifecycle)); + this.extensionsScanner = this._register(instantiationService.createInstance(ExtensionsScanner, extension => extensionLifecycle.postUninstall(extension))); + this.manifestCache = this._register(new ExtensionsManifestCache(userDataProfilesService, fileService, uriIdentityService, this, this.logService)); + this.extensionsDownloader = this._register(instantiationService.createInstance(ExtensionsDownloader)); + + const extensionsWatcher = this._register(new ExtensionsWatcher(this, this.extensionsScannerService, userDataProfilesService, extensionsProfileScannerService, uriIdentityService, fileService, logService)); + this._register(extensionsWatcher.onDidChangeExtensionsByAnotherSource(e => this.onDidChangeExtensionsFromAnotherSource(e))); this.watchForExtensionsNotInstalledBySystem(); } private _targetPlatformPromise: Promise | undefined; getTargetPlatform(): Promise { if (!this._targetPlatformPromise) { - this._targetPlatformPromise = computeTargetPlatform( - this.fileService, - this.logService, - ); + this._targetPlatformPromise = computeTargetPlatform(this.fileService, this.logService); } return this._targetPlatformPromise; } async zip(extension: ILocalExtension): Promise { - this.logService.trace( - "ExtensionManagementService#zip", - extension.identifier.id, - ); + this.logService.trace('ExtensionManagementService#zip', extension.identifier.id); const files = await this.collectFiles(extension); - const location = await zip( - joinPath( - this.extensionsDownloader.extensionsDownloadDir, - generateUuid(), - ).fsPath, - files, - ); + const location = await zip(joinPath(this.extensionsDownloader.extensionsDownloadDir, generateUuid()).fsPath, files); return URI.file(location); } @@ -250,161 +128,73 @@ export class ExtensionManagementService } } - getInstalled( - type?: ExtensionType, - profileLocation: URI = this.userDataProfilesService.defaultProfile - .extensionsResource, - productVersion: IProductVersion = { - version: this.productService.version, - date: this.productService.date, - }, - ): Promise { - return this.extensionsScanner.scanExtensions( - type ?? null, - profileLocation, - productVersion, - ); + getInstalled(type?: ExtensionType, profileLocation: URI = this.userDataProfilesService.defaultProfile.extensionsResource, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise { + return this.extensionsScanner.scanExtensions(type ?? null, profileLocation, productVersion); } scanAllUserInstalledExtensions(): Promise { return this.extensionsScanner.scanAllUserExtensions(false); } - scanInstalledExtensionAtLocation( - location: URI, - ): Promise { + scanInstalledExtensionAtLocation(location: URI): Promise { return this.extensionsScanner.scanUserExtensionAtLocation(location); } - async install( - vsix: URI, - options: InstallOptions = {}, - ): Promise { - this.logService.trace( - "ExtensionManagementService#install", - vsix.toString(), - ); + async install(vsix: URI, options: InstallOptions = {}): Promise { + this.logService.trace('ExtensionManagementService#install', vsix.toString()); const { location, cleanup } = await this.downloadVsix(vsix); try { const manifest = await getManifest(path.resolve(location.fsPath)); - const extensionId = getGalleryExtensionId( - manifest.publisher, - manifest.name, - ); - if ( - manifest.engines && - manifest.engines.vscode && - !isEngineValid( - manifest.engines.vscode, - this.productService.version, - this.productService.date, - ) - ) { - throw new Error( - nls.localize( - "incompatible", - "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", - extensionId, - this.productService.version, - ), - ); + const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name); + if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode, this.productService.version, this.productService.date)) { + throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", extensionId, this.productService.version)); + } + + const allowedToInstall = this.allowedExtensionsService.isAllowed({ id: extensionId, version: manifest.version, publisherDisplayName: undefined }); + if (allowedToInstall !== true) { + throw new Error(nls.localize('notAllowed', "This extension cannot be installed because {0}", allowedToInstall.value)); } - const results = await this.installExtensions([ - { manifest, extension: location, options }, - ]); - const result = results.find(({ identifier }) => - areSameExtensions(identifier, { id: extensionId }), - ); + const results = await this.installExtensions([{ manifest, extension: location, options }]); + const result = results.find(({ identifier }) => areSameExtensions(identifier, { id: extensionId })); if (result?.local) { return result.local; } if (result?.error) { throw result.error; } - throw toExtensionManagementError( - new Error( - `Unknown error while installing extension ${extensionId}`, - ), - ); + throw toExtensionManagementError(new Error(`Unknown error while installing extension ${extensionId}`)); } finally { await cleanup(); } } - async installFromLocation( - location: URI, - profileLocation: URI, - ): Promise { - this.logService.trace( - "ExtensionManagementService#installFromLocation", - location.toString(), - ); - const local = - await this.extensionsScanner.scanUserExtensionAtLocation(location); + async installFromLocation(location: URI, profileLocation: URI): Promise { + this.logService.trace('ExtensionManagementService#installFromLocation', location.toString()); + const local = await this.extensionsScanner.scanUserExtensionAtLocation(location); if (!local || !local.manifest.name || !local.manifest.version) { - throw new Error( - `Cannot find a valid extension from the location ${location.toString()}`, - ); + throw new Error(`Cannot find a valid extension from the location ${location.toString()}`); } - await this.addExtensionsToProfile( - [[local, { source: "resource" }]], - profileLocation, - ); - this.logService.info( - "Successfully installed extension", - local.identifier.id, - profileLocation.toString(), - ); + await this.addExtensionsToProfile([[local, { source: 'resource' }]], profileLocation); + this.logService.info('Successfully installed extension', local.identifier.id, profileLocation.toString()); return local; } - async installExtensionsFromProfile( - extensions: IExtensionIdentifier[], - fromProfileLocation: URI, - toProfileLocation: URI, - ): Promise { - this.logService.trace( - "ExtensionManagementService#installExtensionsFromProfile", - extensions, - fromProfileLocation.toString(), - toProfileLocation.toString(), - ); - const extensionsToInstall = ( - await this.getInstalled(ExtensionType.User, fromProfileLocation) - ).filter((e) => - extensions.some((id) => areSameExtensions(id, e.identifier)), - ); + async installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise { + this.logService.trace('ExtensionManagementService#installExtensionsFromProfile', extensions, fromProfileLocation.toString(), toProfileLocation.toString()); + const extensionsToInstall = (await this.getInstalled(ExtensionType.User, fromProfileLocation)).filter(e => extensions.some(id => areSameExtensions(id, e.identifier))); if (extensionsToInstall.length) { - const metadata = await Promise.all( - extensionsToInstall.map((e) => - this.extensionsScanner.scanMetadata(e, fromProfileLocation), - ), - ); - await this.addExtensionsToProfile( - extensionsToInstall.map((e, index) => [e, metadata[index]]), - toProfileLocation, - ); - this.logService.info( - "Successfully installed extensions", - extensionsToInstall.map((e) => e.identifier.id), - toProfileLocation.toString(), - ); + const metadata = await Promise.all(extensionsToInstall.map(e => this.extensionsScanner.scanMetadata(e, fromProfileLocation))); + await this.addExtensionsToProfile(extensionsToInstall.map((e, index) => [e, metadata[index]]), toProfileLocation); + this.logService.info('Successfully installed extensions', extensionsToInstall.map(e => e.identifier.id), toProfileLocation.toString()); } return extensionsToInstall; } - async updateMetadata( - local: ILocalExtension, - metadata: Partial, - profileLocation: URI, - ): Promise { - this.logService.trace( - "ExtensionManagementService#updateMetadata", - local.identifier.id, - ); + async updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation: URI): Promise { + this.logService.trace('ExtensionManagementService#updateMetadata', local.identifier.id); if (metadata.isPreReleaseVersion) { metadata.preRelease = true; metadata.hasPreReleaseVersion = true; @@ -419,88 +209,39 @@ export class ExtensionManagementService if (metadata.pinned === false) { metadata.pinned = undefined; } - local = await this.extensionsScanner.updateMetadata( - local, - metadata, - profileLocation, - ); + local = await this.extensionsScanner.updateMetadata(local, metadata, profileLocation); this.manifestCache.invalidate(profileLocation); this._onDidUpdateExtensionMetadata.fire({ local, profileLocation }); return local; } - async reinstallFromGallery( - extension: ILocalExtension, - ): Promise { - this.logService.trace( - "ExtensionManagementService#reinstallFromGallery", - extension.identifier.id, - ); + async reinstallFromGallery(extension: ILocalExtension): Promise { + this.logService.trace('ExtensionManagementService#reinstallFromGallery', extension.identifier.id); if (!this.galleryService.isEnabled()) { - throw new Error( - nls.localize( - "MarketPlaceDisabled", - "Marketplace is not enabled", - ), - ); + throw new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled")); } const targetPlatform = await this.getTargetPlatform(); - const [galleryExtension] = await this.galleryService.getExtensions( - [{ ...extension.identifier, preRelease: extension.preRelease }], - { targetPlatform, compatible: true }, - CancellationToken.None, - ); + const [galleryExtension] = await this.galleryService.getExtensions([{ ...extension.identifier, preRelease: extension.preRelease }], { targetPlatform, compatible: true }, CancellationToken.None); if (!galleryExtension) { - throw new Error( - nls.localize( - "Not a Marketplace extension", - "Only Marketplace Extensions can be reinstalled", - ), - ); + throw new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled")); } await this.extensionsScanner.setUninstalled(extension); try { await this.extensionsScanner.removeUninstalledExtension(extension); } catch (e) { - throw new Error( - nls.localize( - "removeError", - "Error while removing the extension: {0}. Please Quit and Start VS Code before trying again.", - toErrorMessage(e), - ), - ); + throw new Error(nls.localize('removeError', "Error while removing the extension: {0}. Please Quit and Start VS Code before trying again.", toErrorMessage(e))); } return this.installFromGallery(galleryExtension); } - protected copyExtension( - extension: ILocalExtension, - fromProfileLocation: URI, - toProfileLocation: URI, - metadata: Partial, - ): Promise { - return this.extensionsScanner.copyExtension( - extension, - fromProfileLocation, - toProfileLocation, - metadata, - ); + protected copyExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata: Partial): Promise { + return this.extensionsScanner.copyExtension(extension, fromProfileLocation, toProfileLocation, metadata); } - copyExtensions( - fromProfileLocation: URI, - toProfileLocation: URI, - ): Promise { - return this.extensionsScanner.copyExtensions( - fromProfileLocation, - toProfileLocation, - { - version: this.productService.version, - date: this.productService.date, - }, - ); + copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise { + return this.extensionsScanner.copyExtensions(fromProfileLocation, toProfileLocation, { version: this.productService.version, date: this.productService.date }); } markAsUninstalled(...extensions: IExtension[]): Promise { @@ -508,7 +249,7 @@ export class ExtensionManagementService } async cleanUp(): Promise { - this.logService.trace("ExtensionManagementService#cleanUp"); + this.logService.trace('ExtensionManagementService#cleanUp'); try { await this.extensionsScanner.cleanUp(); } catch (error) { @@ -516,32 +257,19 @@ export class ExtensionManagementService } } - async download( - extension: IGalleryExtension, - operation: InstallOperation, - donotVerifySignature: boolean, - ): Promise { - const { location } = await this.downloadExtension( - extension, - operation, - !donotVerifySignature, - ); + async download(extension: IGalleryExtension, operation: InstallOperation, donotVerifySignature: boolean): Promise { + const { location } = await this.downloadExtension(extension, operation, !donotVerifySignature); return location; } - private async downloadVsix( - vsix: URI, - ): Promise<{ location: URI; cleanup: () => Promise }> { + private async downloadVsix(vsix: URI): Promise<{ location: URI; cleanup: () => Promise }> { if (vsix.scheme === Schemas.file) { - return { location: vsix, async cleanup() {} }; + return { location: vsix, async cleanup() { } }; } - this.logService.trace("Downloading extension from", vsix.toString()); - const location = joinPath( - this.extensionsDownloader.extensionsDownloadDir, - generateUuid(), - ); + this.logService.trace('Downloading extension from', vsix.toString()); + const location = joinPath(this.extensionsDownloader.extensionsDownloadDir, generateUuid()); await this.downloadService.download(vsix, location); - this.logService.info("Downloaded extension to", location.toString()); + this.logService.info('Downloaded extension to', location.toString()); const cleanup = async () => { try { await this.fileService.del(location); @@ -556,116 +284,37 @@ export class ExtensionManagementService return this.userDataProfilesService.defaultProfile.extensionsResource; } - protected createInstallExtensionTask( - manifest: IExtensionManifest, - extension: URI | IGalleryExtension, - options: InstallExtensionTaskOptions, - ): IInstallExtensionTask { - const extensionKey = - extension instanceof URI - ? new ExtensionKey( - { - id: getGalleryExtensionId( - manifest.publisher, - manifest.name, - ), - }, - manifest.version, - ) - : ExtensionKey.create(extension); - return this.instantiationService.createInstance( - InstallExtensionInProfileTask, - extensionKey, - manifest, - extension, - options, - (operation, token) => { - if (extension instanceof URI) { - return this.extractVSIX( - extensionKey, - extension, - options, - token, - ); - } - let promise = this.extractingGalleryExtensions.get( - extensionKey.toString(), - ); - if (!promise) { - this.extractingGalleryExtensions.set( - extensionKey.toString(), - (promise = this.downloadAndExtractGalleryExtension( - extensionKey, - extension, - operation, - options, - token, - )), - ); - promise.finally(() => - this.extractingGalleryExtensions.delete( - extensionKey.toString(), - ), - ); - } - return promise; - }, - this.extensionsScanner, - ); + protected createInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallExtensionTaskOptions): IInstallExtensionTask { + const extensionKey = extension instanceof URI ? new ExtensionKey({ id: getGalleryExtensionId(manifest.publisher, manifest.name) }, manifest.version) : ExtensionKey.create(extension); + return this.instantiationService.createInstance(InstallExtensionInProfileTask, extensionKey, manifest, extension, options, (operation, token) => { + if (extension instanceof URI) { + return this.extractVSIX(extensionKey, extension, options, token); + } + let promise = this.extractingGalleryExtensions.get(extensionKey.toString()); + if (!promise) { + this.extractingGalleryExtensions.set(extensionKey.toString(), promise = this.downloadAndExtractGalleryExtension(extensionKey, extension, operation, options, token)); + promise.finally(() => this.extractingGalleryExtensions.delete(extensionKey.toString())); + } + return promise; + }, this.extensionsScanner); } - protected createUninstallExtensionTask( - extension: ILocalExtension, - options: UninstallExtensionTaskOptions, - ): IUninstallExtensionTask { - return new UninstallExtensionInProfileTask( - extension, - options, - this.extensionsProfileScannerService, - ); + protected createUninstallExtensionTask(extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask { + return new UninstallExtensionInProfileTask(extension, options, this.extensionsProfileScannerService); } - private async downloadAndExtractGalleryExtension( - extensionKey: ExtensionKey, - gallery: IGalleryExtension, - operation: InstallOperation, - options: InstallExtensionTaskOptions, - token: CancellationToken, - ): Promise { - const { verificationStatus, location } = await this.downloadExtension( - gallery, - operation, - !options.donotVerifySignature, - options.context?.[EXTENSION_INSTALL_CLIENT_TARGET_PLATFORM_CONTEXT], - ); + private async downloadAndExtractGalleryExtension(extensionKey: ExtensionKey, gallery: IGalleryExtension, operation: InstallOperation, options: InstallExtensionTaskOptions, token: CancellationToken): Promise { + const { verificationStatus, location } = await this.downloadExtension(gallery, operation, !options.donotVerifySignature, options.context?.[EXTENSION_INSTALL_CLIENT_TARGET_PLATFORM_CONTEXT]); try { + if (token.isCancellationRequested) { throw new CancellationError(); } // validate manifest const manifest = await getManifest(location.fsPath); - if ( - !new ExtensionKey(gallery.identifier, gallery.version).equals( - new ExtensionKey( - { - id: getGalleryExtensionId( - manifest.publisher, - manifest.name, - ), - }, - manifest.version, - ), - ) - ) { - throw new ExtensionManagementError( - nls.localize( - "invalidManifest", - "Cannot install '{0}' extension because of manifest mismatch with Marketplace", - gallery.identifier.id, - ), - ExtensionManagementErrorCode.Invalid, - ); + if (!new ExtensionKey(gallery.identifier, gallery.version).equals(new ExtensionKey({ id: getGalleryExtensionId(manifest.publisher, manifest.name) }, manifest.version))) { + throw new ExtensionManagementError(nls.localize('invalidManifest', "Cannot install '{0}' extension because of manifest mismatch with Marketplace", gallery.identifier.id), ExtensionManagementErrorCode.Invalid); } const local = await this.extensionsScanner.extractUserExtension( @@ -680,36 +329,23 @@ export class ExtensionManagementService isMachineScoped: options.isMachineScoped, isBuiltin: options.isBuiltin, isPreReleaseVersion: gallery.properties.isPreReleaseVersion, - hasPreReleaseVersion: - gallery.properties.isPreReleaseVersion, + hasPreReleaseVersion: gallery.properties.isPreReleaseVersion, installedTimestamp: Date.now(), - pinned: options.installGivenVersion - ? true - : !!options.pinned, + pinned: options.installGivenVersion ? true : !!options.pinned, preRelease: isBoolean(options.preRelease) ? options.preRelease - : options.installPreReleaseVersion || - gallery.properties.isPreReleaseVersion, - source: "gallery", + : options.installPreReleaseVersion || gallery.properties.isPreReleaseVersion, + source: 'gallery', }, false, - token, - ); - - if ( - verificationStatus !== - ExtensionSignatureVerificationCode.Success && - this.environmentService.isBuilt - ) { + token); + + if (verificationStatus !== ExtensionSignatureVerificationCode.Success && this.environmentService.isBuilt) { try { await this.extensionsDownloader.delete(location); } catch (e) { /* Ignore */ - this.logService.warn( - `Error while deleting the downloaded file`, - location.toString(), - getErrorMessage(e), - ); + this.logService.warn(`Error while deleting the downloaded file`, location.toString(), getErrorMessage(e)); } } @@ -719,73 +355,29 @@ export class ExtensionManagementService await this.extensionsDownloader.delete(location); } catch (e) { /* Ignore */ - this.logService.warn( - `Error while deleting the downloaded file`, - location.toString(), - getErrorMessage(e), - ); + this.logService.warn(`Error while deleting the downloaded file`, location.toString(), getErrorMessage(e)); } throw toExtensionManagementError(error); } } - private async downloadExtension( - extension: IGalleryExtension, - operation: InstallOperation, - verifySignature: boolean, - clientTargetPlatform?: TargetPlatform, - ): Promise<{ - readonly location: URI; - readonly verificationStatus: - | ExtensionSignatureVerificationCode - | undefined; - }> { + private async downloadExtension(extension: IGalleryExtension, operation: InstallOperation, verifySignature: boolean, clientTargetPlatform?: TargetPlatform): Promise<{ readonly location: URI; readonly verificationStatus: ExtensionSignatureVerificationCode | undefined }> { if (verifySignature) { - const value = this.configurationService.getValue( - "extensions.verifySignature", - ); + const value = this.configurationService.getValue('extensions.verifySignature'); verifySignature = isBoolean(value) ? value : true; } - const { location, verificationStatus } = - await this.extensionsDownloader.download( - extension, - operation, - verifySignature, - clientTargetPlatform, - ); - - if ( - verificationStatus !== ExtensionSignatureVerificationCode.Success && - verifySignature && - this.environmentService.isBuilt && - !isLinux - ) { + const { location, verificationStatus } = await this.extensionsDownloader.download(extension, operation, verifySignature, clientTargetPlatform); + + if (verificationStatus !== ExtensionSignatureVerificationCode.Success && verificationStatus !== ExtensionSignatureVerificationCode.NotSigned && verifySignature && this.environmentService.isBuilt && !isLinux) { try { await this.extensionsDownloader.delete(location); } catch (e) { /* Ignore */ - this.logService.warn( - `Error while deleting the downloaded file`, - location.toString(), - getErrorMessage(e), - ); - } - - if (!extension.isSigned) { - throw new ExtensionManagementError( - nls.localize("not signed", "Extension is not signed."), - ExtensionManagementErrorCode.PackageNotSigned, - ); + this.logService.warn(`Error while deleting the downloaded file`, location.toString(), getErrorMessage(e)); } if (!verificationStatus) { - throw new ExtensionManagementError( - nls.localize( - "signature verification not executed", - "Signature verification was not executed.", - ), - ExtensionManagementErrorCode.SignatureVerificationInternal, - ); + throw new ExtensionManagementError(nls.localize('signature verification not executed', "Signature verification was not executed."), ExtensionManagementErrorCode.SignatureVerificationInternal); } switch (verificationStatus) { @@ -799,35 +391,16 @@ export class ExtensionManagementService case ExtensionSignatureVerificationCode.CertificateRevoked: case ExtensionSignatureVerificationCode.SignatureIsNotValid: case ExtensionSignatureVerificationCode.SignatureArchiveHasTooManyEntries: - throw new ExtensionManagementError( - nls.localize( - "signature verification failed", - "Signature verification failed with '{0}' error.", - verificationStatus, - ), - ExtensionManagementErrorCode.SignatureVerificationFailed, - ); + throw new ExtensionManagementError(nls.localize('signature verification failed', "Signature verification failed with '{0}' error.", verificationStatus), ExtensionManagementErrorCode.SignatureVerificationFailed); } - throw new ExtensionManagementError( - nls.localize( - "signature verification failed", - "Signature verification failed with '{0}' error.", - verificationStatus, - ), - ExtensionManagementErrorCode.SignatureVerificationInternal, - ); + throw new ExtensionManagementError(nls.localize('signature verification failed', "Signature verification failed with '{0}' error.", verificationStatus), ExtensionManagementErrorCode.SignatureVerificationInternal); } return { location, verificationStatus }; } - private async extractVSIX( - extensionKey: ExtensionKey, - location: URI, - options: InstallExtensionTaskOptions, - token: CancellationToken, - ): Promise { + private async extractVSIX(extensionKey: ExtensionKey, location: URI, options: InstallExtensionTaskOptions, token: CancellationToken): Promise { const local = await this.extensionsScanner.extractUserExtension( extensionKey, path.resolve(location.fsPath), @@ -837,140 +410,73 @@ export class ExtensionManagementService isBuiltin: options.isBuiltin, installedTimestamp: Date.now(), pinned: options.installGivenVersion ? true : !!options.pinned, - source: "vsix", + source: 'vsix', }, isBoolean(options.keepExisting) ? !options.keepExisting : true, - token, - ); + token); return { local }; } private async collectFiles(extension: ILocalExtension): Promise { - const collectFilesFromDirectory = async ( - dir: string, - ): Promise => { + + const collectFilesFromDirectory = async (dir: string): Promise => { let entries = await pfs.Promises.readdir(dir); - entries = entries.map((e) => path.join(dir, e)); - const stats = await Promise.all( - entries.map((e) => fs.promises.stat(e)), - ); + entries = entries.map(e => path.join(dir, e)); + const stats = await Promise.all(entries.map(e => fs.promises.stat(e))); let promise: Promise = Promise.resolve([]); stats.forEach((stat, index) => { const entry = entries[index]; if (stat.isFile()) { - promise = promise.then((result) => [...result, entry]); + promise = promise.then(result => ([...result, entry])); } if (stat.isDirectory()) { - promise = promise.then((result) => - collectFilesFromDirectory(entry).then((files) => [ - ...result, - ...files, - ]), - ); + promise = promise + .then(result => collectFilesFromDirectory(entry) + .then(files => ([...result, ...files]))); } }); return promise; }; - const files = await collectFilesFromDirectory( - extension.location.fsPath, - ); - return files.map((f) => ({ - path: `extension/${path.relative(extension.location.fsPath, f)}`, - localPath: f, - })); + const files = await collectFilesFromDirectory(extension.location.fsPath); + return files.map(f => ({ path: `extension/${path.relative(extension.location.fsPath, f)}`, localPath: f })); } - private async onDidChangeExtensionsFromAnotherSource({ - added, - removed, - }: DidChangeProfileExtensionsEvent): Promise { + private async onDidChangeExtensionsFromAnotherSource({ added, removed }: DidChangeProfileExtensionsEvent): Promise { if (removed) { - const removedExtensions = - added && - this.uriIdentityService.extUri.isEqual( - removed.profileLocation, - added.profileLocation, - ) - ? removed.extensions.filter((e) => - added.extensions.every( - (identifier) => - !areSameExtensions(identifier, e), - ), - ) - : removed.extensions; + const removedExtensions = added && this.uriIdentityService.extUri.isEqual(removed.profileLocation, added.profileLocation) + ? removed.extensions.filter(e => added.extensions.every(identifier => !areSameExtensions(identifier, e))) + : removed.extensions; for (const identifier of removedExtensions) { - this.logService.info( - "Extensions removed from another source", - identifier.id, - removed.profileLocation.toString(), - ); - this._onDidUninstallExtension.fire({ - identifier, - profileLocation: removed.profileLocation, - }); + this.logService.info('Extensions removed from another source', identifier.id, removed.profileLocation.toString()); + this._onDidUninstallExtension.fire({ identifier, profileLocation: removed.profileLocation }); } } if (added) { - const extensions = await this.getInstalled( - ExtensionType.User, - added.profileLocation, - ); - const addedExtensions = extensions.filter((e) => - added.extensions.some((identifier) => - areSameExtensions(identifier, e.identifier), - ), - ); - this._onDidInstallExtensions.fire( - addedExtensions.map((local) => { - this.logService.info( - "Extensions added from another source", - local.identifier.id, - added.profileLocation.toString(), - ); - return { - identifier: local.identifier, - local, - profileLocation: added.profileLocation, - operation: InstallOperation.None, - }; - }), - ); + const extensions = await this.getInstalled(ExtensionType.User, added.profileLocation); + const addedExtensions = extensions.filter(e => added.extensions.some(identifier => areSameExtensions(identifier, e.identifier))); + this._onDidInstallExtensions.fire(addedExtensions.map(local => { + this.logService.info('Extensions added from another source', local.identifier.id, added.profileLocation.toString()); + return { identifier: local.identifier, local, profileLocation: added.profileLocation, operation: InstallOperation.None }; + })); } } private readonly knownDirectories = new ResourceSet(); private async watchForExtensionsNotInstalledBySystem(): Promise { - this._register( - this.extensionsScanner.onExtract((resource) => - this.knownDirectories.add(resource), - ), - ); - const stat = await this.fileService.resolve( - this.extensionsScannerService.userExtensionsLocation, - ); + this._register(this.extensionsScanner.onExtract(resource => this.knownDirectories.add(resource))); + const stat = await this.fileService.resolve(this.extensionsScannerService.userExtensionsLocation); for (const childStat of stat.children ?? []) { if (childStat.isDirectory) { this.knownDirectories.add(childStat.resource); } } - this._register( - this.fileService.watch( - this.extensionsScannerService.userExtensionsLocation, - ), - ); - this._register( - this.fileService.onDidFilesChange((e) => this.onDidFilesChange(e)), - ); + this._register(this.fileService.watch(this.extensionsScannerService.userExtensionsLocation)); + this._register(this.fileService.onDidFilesChange(e => this.onDidFilesChange(e))); } private async onDidFilesChange(e: FileChangesEvent): Promise { - if ( - !e.affects( - this.extensionsScannerService.userExtensionsLocation, - FileChangeType.ADDED, - ) - ) { + if (!e.affects(this.extensionsScannerService.userExtensionsLocation, FileChangeType.ADDED)) { return; } @@ -982,34 +488,17 @@ export class ExtensionManagementService } // Is not immediate child of extensions resource - if ( - !this.uriIdentityService.extUri.isEqual( - this.uriIdentityService.extUri.dirname(resource), - this.extensionsScannerService.userExtensionsLocation, - ) - ) { + if (!this.uriIdentityService.extUri.isEqual(this.uriIdentityService.extUri.dirname(resource), this.extensionsScannerService.userExtensionsLocation)) { continue; } // .obsolete file changed - if ( - this.uriIdentityService.extUri.isEqual( - resource, - this.uriIdentityService.extUri.joinPath( - this.extensionsScannerService.userExtensionsLocation, - ".obsolete", - ), - ) - ) { + if (this.uriIdentityService.extUri.isEqual(resource, this.uriIdentityService.extUri.joinPath(this.extensionsScannerService.userExtensionsLocation, '.obsolete'))) { continue; } // Ignore changes to files starting with `.` - if ( - this.uriIdentityService.extUri - .basename(resource) - .startsWith(".") - ) { + if (this.uriIdentityService.extUri.basename(resource).startsWith('.')) { continue; } @@ -1020,10 +509,7 @@ export class ExtensionManagementService // Check if this is an extension added by another source // Extension added by another source will not have installed timestamp - const extension = - await this.extensionsScanner.scanUserExtensionAtLocation( - resource, - ); + const extension = await this.extensionsScanner.scanUserExtensionAtLocation(resource); if (extension && extension.installedTimestamp === undefined) { this.knownDirectories.add(resource); added.push(extension); @@ -1031,76 +517,38 @@ export class ExtensionManagementService } if (added.length) { - await this.addExtensionsToProfile( - added.map((e) => [e, undefined]), - this.userDataProfilesService.defaultProfile.extensionsResource, - ); - this.logService.info( - "Added extensions to default profile from external source", - added.map((e) => e.identifier.id), - ); + await this.addExtensionsToProfile(added.map(e => [e, undefined]), this.userDataProfilesService.defaultProfile.extensionsResource); + this.logService.info('Added extensions to default profile from external source', added.map(e => e.identifier.id)); } } - private async addExtensionsToProfile( - extensions: [ILocalExtension, Metadata | undefined][], - profileLocation: URI, - ): Promise { - const localExtensions = extensions.map((e) => e[0]); + private async addExtensionsToProfile(extensions: [ILocalExtension, Metadata | undefined][], profileLocation: URI): Promise { + const localExtensions = extensions.map(e => e[0]); await this.setInstalled(localExtensions); - await this.extensionsProfileScannerService.addExtensionsToProfile( - extensions, - profileLocation, - ); - this._onDidInstallExtensions.fire( - localExtensions.map((local) => ({ - local, - identifier: local.identifier, - operation: InstallOperation.None, - profileLocation, - })), - ); + await this.extensionsProfileScannerService.addExtensionsToProfile(extensions, profileLocation); + this._onDidInstallExtensions.fire(localExtensions.map(local => ({ local, identifier: local.identifier, operation: InstallOperation.None, profileLocation }))); } private async setInstalled(extensions: ILocalExtension[]): Promise { - const uninstalled = - await this.extensionsScanner.getUninstalledExtensions(); + const uninstalled = await this.extensionsScanner.getUninstalledExtensions(); for (const extension of extensions) { const extensionKey = ExtensionKey.create(extension); if (!uninstalled[extensionKey.toString()]) { continue; } - this.logService.trace( - "Removing the extension from uninstalled list:", - extensionKey.id, - ); + this.logService.trace('Removing the extension from uninstalled list:', extensionKey.id); await this.extensionsScanner.setInstalled(extensionKey); - this.logService.info( - "Removed the extension from uninstalled list:", - extensionKey.id, - ); + this.logService.info('Removed the extension from uninstalled list:', extensionKey.id); } } } type UpdateMetadataErrorClassification = { - owner: "sandy081"; - comment: "Update metadata error"; - extensionId: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "extension identifier"; - }; - code?: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "error code"; - }; - isProfile?: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "Is writing into profile"; - }; + owner: 'sandy081'; + comment: 'Update metadata error'; + extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'extension identifier' }; + code?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'error code' }; + isProfile?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Is writing into profile' }; }; type UpdateMetadataErrorEvent = { extensionId: string; @@ -1109,38 +557,27 @@ type UpdateMetadataErrorEvent = { }; export class ExtensionsScanner extends Disposable { + private readonly uninstalledResource: URI; private readonly uninstalledFileLimiter: Queue; private readonly _onExtract = this._register(new Emitter()); readonly onExtract = this._onExtract.event; - private scanAllExtensionPromise = new ResourceMap< - Promise - >(); - private scanUserExtensionsPromise = new ResourceMap< - Promise - >(); + private scanAllExtensionPromise = new ResourceMap>(); + private scanUserExtensionsPromise = new ResourceMap>(); constructor( - private readonly beforeRemovingExtension: ( - e: ILocalExtension, - ) => Promise, + private readonly beforeRemovingExtension: (e: ILocalExtension) => Promise, @IFileService private readonly fileService: IFileService, - @IExtensionsScannerService - private readonly extensionsScannerService: IExtensionsScannerService, - @IExtensionsProfileScannerService - private readonly extensionsProfileScannerService: IExtensionsProfileScannerService, - @IUriIdentityService - private readonly uriIdentityService: IUriIdentityService, + @IExtensionsScannerService private readonly extensionsScannerService: IExtensionsScannerService, + @IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @ITelemetryService private readonly telemetryService: ITelemetryService, @ILogService private readonly logService: ILogService, ) { super(); - this.uninstalledResource = joinPath( - this.extensionsScannerService.userExtensionsLocation, - ".obsolete", - ); + this.uninstalledResource = joinPath(this.extensionsScannerService.userExtensionsLocation, '.obsolete'); this.uninstalledFileLimiter = new Queue(); } @@ -1150,106 +587,46 @@ export class ExtensionsScanner extends Disposable { await this.initializeMetadata(); } - async scanExtensions( - type: ExtensionType | null, - profileLocation: URI, - productVersion: IProductVersion, - ): Promise { + async scanExtensions(type: ExtensionType | null, profileLocation: URI, productVersion: IProductVersion): Promise { try { - const userScanOptions: ScanOptions = { - includeInvalid: true, - profileLocation, - productVersion, - }; + const userScanOptions: ScanOptions = { includeInvalid: true, profileLocation, productVersion }; let scannedExtensions: IScannedExtension[] = []; if (type === null || type === ExtensionType.System) { - let scanAllExtensionsPromise = - this.scanAllExtensionPromise.get(profileLocation); + let scanAllExtensionsPromise = this.scanAllExtensionPromise.get(profileLocation); if (!scanAllExtensionsPromise) { - scanAllExtensionsPromise = this.extensionsScannerService - .scanAllExtensions( - { includeInvalid: true, useCache: true }, - userScanOptions, - false, - ) - .finally(() => - this.scanAllExtensionPromise.delete( - profileLocation, - ), - ); - this.scanAllExtensionPromise.set( - profileLocation, - scanAllExtensionsPromise, - ); + scanAllExtensionsPromise = this.extensionsScannerService.scanAllExtensions({ includeInvalid: true, useCache: true }, userScanOptions, false) + .finally(() => this.scanAllExtensionPromise.delete(profileLocation)); + this.scanAllExtensionPromise.set(profileLocation, scanAllExtensionsPromise); } - scannedExtensions.push(...(await scanAllExtensionsPromise)); + scannedExtensions.push(...await scanAllExtensionsPromise); } else if (type === ExtensionType.User) { - let scanUserExtensionsPromise = - this.scanUserExtensionsPromise.get(profileLocation); + let scanUserExtensionsPromise = this.scanUserExtensionsPromise.get(profileLocation); if (!scanUserExtensionsPromise) { - scanUserExtensionsPromise = this.extensionsScannerService - .scanUserExtensions(userScanOptions) - .finally(() => - this.scanUserExtensionsPromise.delete( - profileLocation, - ), - ); - this.scanUserExtensionsPromise.set( - profileLocation, - scanUserExtensionsPromise, - ); + scanUserExtensionsPromise = this.extensionsScannerService.scanUserExtensions(userScanOptions) + .finally(() => this.scanUserExtensionsPromise.delete(profileLocation)); + this.scanUserExtensionsPromise.set(profileLocation, scanUserExtensionsPromise); } - scannedExtensions.push(...(await scanUserExtensionsPromise)); + scannedExtensions.push(...await scanUserExtensionsPromise); } - scannedExtensions = - type !== null - ? scannedExtensions.filter((r) => r.type === type) - : scannedExtensions; - return await Promise.all( - scannedExtensions.map((extension) => - this.toLocalExtension(extension), - ), - ); + scannedExtensions = type !== null ? scannedExtensions.filter(r => r.type === type) : scannedExtensions; + return await Promise.all(scannedExtensions.map(extension => this.toLocalExtension(extension))); } catch (error) { - throw toExtensionManagementError( - error, - ExtensionManagementErrorCode.Scanning, - ); + throw toExtensionManagementError(error, ExtensionManagementErrorCode.Scanning); } } - async scanAllUserExtensions( - excludeOutdated: boolean, - ): Promise { + async scanAllUserExtensions(excludeOutdated: boolean): Promise { try { - const scannedExtensions = - await this.extensionsScannerService.scanUserExtensions({ - includeAllVersions: !excludeOutdated, - includeInvalid: true, - }); - return await Promise.all( - scannedExtensions.map((extension) => - this.toLocalExtension(extension), - ), - ); + const scannedExtensions = await this.extensionsScannerService.scanUserExtensions({ includeAllVersions: !excludeOutdated, includeInvalid: true }); + return await Promise.all(scannedExtensions.map(extension => this.toLocalExtension(extension))); } catch (error) { - throw toExtensionManagementError( - error, - ExtensionManagementErrorCode.Scanning, - ); + throw toExtensionManagementError(error, ExtensionManagementErrorCode.Scanning); } } - async scanUserExtensionAtLocation( - location: URI, - ): Promise { + async scanUserExtensionAtLocation(location: URI): Promise { try { - const scannedExtension = - await this.extensionsScannerService.scanExistingExtension( - location, - ExtensionType.User, - { includeInvalid: true }, - ); + const scannedExtension = await this.extensionsScannerService.scanExistingExtension(location, ExtensionType.User, { includeInvalid: true }); if (scannedExtension) { return await this.toLocalExtension(scannedExtension); } @@ -1259,58 +636,24 @@ export class ExtensionsScanner extends Disposable { return null; } - async extractUserExtension( - extensionKey: ExtensionKey, - zipPath: string, - metadata: Metadata, - removeIfExists: boolean, - token: CancellationToken, - ): Promise { + async extractUserExtension(extensionKey: ExtensionKey, zipPath: string, metadata: Metadata, removeIfExists: boolean, token: CancellationToken): Promise { const folderName = extensionKey.toString(); - const tempLocation = URI.file( - path.join( - this.extensionsScannerService.userExtensionsLocation.fsPath, - `.${generateUuid()}`, - ), - ); - const extensionLocation = URI.file( - path.join( - this.extensionsScannerService.userExtensionsLocation.fsPath, - folderName, - ), - ); + const tempLocation = URI.file(path.join(this.extensionsScannerService.userExtensionsLocation.fsPath, `.${generateUuid()}`)); + const extensionLocation = URI.file(path.join(this.extensionsScannerService.userExtensionsLocation.fsPath, folderName)); if (await this.fileService.exists(extensionLocation)) { if (!removeIfExists) { try { - return await this.scanLocalExtension( - extensionLocation, - ExtensionType.User, - ); + return await this.scanLocalExtension(extensionLocation, ExtensionType.User); } catch (error) { - this.logService.warn( - `Error while scanning the existing extension at ${extensionLocation.path}. Deleting the existing extension and extracting it.`, - getErrorMessage(error), - ); + this.logService.warn(`Error while scanning the existing extension at ${extensionLocation.path}. Deleting the existing extension and extracting it.`, getErrorMessage(error)); } } try { - await this.deleteExtensionFromLocation( - extensionKey.id, - extensionLocation, - "removeExisting", - ); + await this.deleteExtensionFromLocation(extensionKey.id, extensionLocation, 'removeExisting'); } catch (error) { - throw new ExtensionManagementError( - nls.localize( - "errorDeleting", - "Unable to delete the existing folder '{0}' while installing the extension '{1}'. Please delete the folder manually and try again", - extensionLocation.fsPath, - extensionKey.id, - ), - ExtensionManagementErrorCode.Delete, - ); + throw new ExtensionManagementError(nls.localize('errorDeleting', "Unable to delete the existing folder '{0}' while installing the extension '{1}'. Please delete the folder manually and try again", extensionLocation.fsPath, extensionKey.id), ExtensionManagementErrorCode.Delete); } } @@ -1321,53 +664,25 @@ export class ExtensionsScanner extends Disposable { // Extract try { - this.logService.trace( - `Started extracting the extension from ${zipPath} to ${extensionLocation.fsPath}`, - ); - await extract( - zipPath, - tempLocation.fsPath, - { sourcePath: "extension", overwrite: true }, - token, - ); - this.logService.info( - `Extracted extension to ${extensionLocation}:`, - extensionKey.id, - ); + this.logService.trace(`Started extracting the extension from ${zipPath} to ${extensionLocation.fsPath}`); + await extract(zipPath, tempLocation.fsPath, { sourcePath: 'extension', overwrite: true }, token); + this.logService.info(`Extracted extension to ${extensionLocation}:`, extensionKey.id); } catch (e) { throw fromExtractError(e); } try { - metadata.size = await computeSize( - tempLocation, - this.fileService, - ); + metadata.size = await computeSize(tempLocation, this.fileService); } catch (error) { // Log & ignore - this.logService.warn( - `Error while getting the size of the extracted extension : ${tempLocation.fsPath}`, - getErrorMessage(error), - ); + this.logService.warn(`Error while getting the size of the extracted extension : ${tempLocation.fsPath}`, getErrorMessage(error)); } try { - await this.extensionsScannerService.updateMetadata( - tempLocation, - metadata, - ); + await this.extensionsScannerService.updateMetadata(tempLocation, metadata); } catch (error) { - this.telemetryService.publicLog2< - UpdateMetadataErrorEvent, - UpdateMetadataErrorClassification - >("extension:extract", { - extensionId: extensionKey.id, - code: `${toFileOperationResult(error)}`, - }); - throw toExtensionManagementError( - error, - ExtensionManagementErrorCode.UpdateMetadata, - ); + this.telemetryService.publicLog2('extension:extract', { extensionId: extensionKey.id, code: `${toFileOperationResult(error)}` }); + throw toExtensionManagementError(error, ExtensionManagementErrorCode.UpdateMetadata); } if (token.isCancellationRequested) { @@ -1376,304 +691,137 @@ export class ExtensionsScanner extends Disposable { // Rename try { - this.logService.trace( - `Started renaming the extension from ${tempLocation.fsPath} to ${extensionLocation.fsPath}`, - ); - await this.rename( - tempLocation.fsPath, - extensionLocation.fsPath, - ); - this.logService.info("Renamed to", extensionLocation.fsPath); + this.logService.trace(`Started renaming the extension from ${tempLocation.fsPath} to ${extensionLocation.fsPath}`); + await this.rename(tempLocation.fsPath, extensionLocation.fsPath); + this.logService.info('Renamed to', extensionLocation.fsPath); } catch (error) { - if (error.code === "ENOTEMPTY") { - this.logService.info( - `Rename failed because extension was installed by another source. So ignoring renaming.`, - extensionKey.id, - ); - try { - await this.fileService.del(tempLocation, { - recursive: true, - }); - } catch (e) { - /* ignore */ - } + if (error.code === 'ENOTEMPTY') { + this.logService.info(`Rename failed because extension was installed by another source. So ignoring renaming.`, extensionKey.id); + try { await this.fileService.del(tempLocation, { recursive: true }); } catch (e) { /* ignore */ } } else { - this.logService.info( - `Rename failed because of ${getErrorMessage(error)}. Deleted from extracted location`, - tempLocation, - ); + this.logService.info(`Rename failed because of ${getErrorMessage(error)}. Deleted from extracted location`, tempLocation); throw error; } } this._onExtract.fire(extensionLocation); + } catch (error) { - try { - await this.fileService.del(tempLocation, { recursive: true }); - } catch (e) { - /* ignore */ - } + try { await this.fileService.del(tempLocation, { recursive: true }); } catch (e) { /* ignore */ } throw error; } return this.scanLocalExtension(extensionLocation, ExtensionType.User); } - async scanMetadata( - local: ILocalExtension, - profileLocation?: URI, - ): Promise { + async scanMetadata(local: ILocalExtension, profileLocation?: URI): Promise { if (profileLocation) { - const extension = await this.getScannedExtension( - local, - profileLocation, - ); + const extension = await this.getScannedExtension(local, profileLocation); return extension?.metadata; } else { return this.extensionsScannerService.scanMetadata(local.location); } } - private async getScannedExtension( - local: ILocalExtension, - profileLocation: URI, - ): Promise { - const extensions = - await this.extensionsProfileScannerService.scanProfileExtensions( - profileLocation, - ); - return extensions.find((e) => - areSameExtensions(e.identifier, local.identifier), - ); + private async getScannedExtension(local: ILocalExtension, profileLocation: URI): Promise { + const extensions = await this.extensionsProfileScannerService.scanProfileExtensions(profileLocation); + return extensions.find(e => areSameExtensions(e.identifier, local.identifier)); } - async updateMetadata( - local: ILocalExtension, - metadata: Partial, - profileLocation?: URI, - ): Promise { + async updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation?: URI): Promise { try { if (profileLocation) { - await this.extensionsProfileScannerService.updateMetadata( - [[local, metadata]], - profileLocation, - ); + await this.extensionsProfileScannerService.updateMetadata([[local, metadata]], profileLocation); } else { - await this.extensionsScannerService.updateMetadata( - local.location, - metadata, - ); + await this.extensionsScannerService.updateMetadata(local.location, metadata); } } catch (error) { - this.telemetryService.publicLog2< - UpdateMetadataErrorEvent, - UpdateMetadataErrorClassification - >("extension:extract", { - extensionId: local.identifier.id, - code: `${toFileOperationResult(error)}`, - isProfile: !!profileLocation, - }); - throw toExtensionManagementError( - error, - ExtensionManagementErrorCode.UpdateMetadata, - ); + this.telemetryService.publicLog2('extension:extract', { extensionId: local.identifier.id, code: `${toFileOperationResult(error)}`, isProfile: !!profileLocation }); + throw toExtensionManagementError(error, ExtensionManagementErrorCode.UpdateMetadata); } - return this.scanLocalExtension( - local.location, - local.type, - profileLocation, - ); + return this.scanLocalExtension(local.location, local.type, profileLocation); } async getUninstalledExtensions(): Promise> { try { return await this.withUninstalledExtensions(); } catch (error) { - throw toExtensionManagementError( - error, - ExtensionManagementErrorCode.ReadUninstalled, - ); + throw toExtensionManagementError(error, ExtensionManagementErrorCode.ReadUninstalled); } } async setUninstalled(...extensions: IExtension[]): Promise { - const extensionKeys: ExtensionKey[] = extensions.map((e) => - ExtensionKey.create(e), - ); - await this.withUninstalledExtensions((uninstalled) => - extensionKeys.forEach((extensionKey) => { + const extensionKeys: ExtensionKey[] = extensions.map(e => ExtensionKey.create(e)); + await this.withUninstalledExtensions(uninstalled => + extensionKeys.forEach(extensionKey => { uninstalled[extensionKey.toString()] = true; - this.logService.info( - "Marked extension as uninstalled", - extensionKey.toString(), - ); - }), - ); + this.logService.info('Marked extension as uninstalled', extensionKey.toString()); + })); } async setInstalled(extensionKey: ExtensionKey): Promise { try { - await this.withUninstalledExtensions( - (uninstalled) => delete uninstalled[extensionKey.toString()], - ); + await this.withUninstalledExtensions(uninstalled => delete uninstalled[extensionKey.toString()]); } catch (error) { - throw toExtensionManagementError( - error, - ExtensionManagementErrorCode.UnsetUninstalled, - ); + throw toExtensionManagementError(error, ExtensionManagementErrorCode.UnsetUninstalled); } } - async removeExtension( - extension: ILocalExtension | IScannedExtension, - type: string, - ): Promise { - if ( - this.uriIdentityService.extUri.isEqualOrParent( - extension.location, - this.extensionsScannerService.userExtensionsLocation, - ) - ) { - return this.deleteExtensionFromLocation( - extension.identifier.id, - extension.location, - type, - ); + async removeExtension(extension: ILocalExtension | IScannedExtension, type: string): Promise { + if (this.uriIdentityService.extUri.isEqualOrParent(extension.location, this.extensionsScannerService.userExtensionsLocation)) { + return this.deleteExtensionFromLocation(extension.identifier.id, extension.location, type); } } - async removeUninstalledExtension( - extension: ILocalExtension | IScannedExtension, - ): Promise { - await this.removeExtension(extension, "uninstalled"); - await this.withUninstalledExtensions( - (uninstalled) => - delete uninstalled[ExtensionKey.create(extension).toString()], - ); + async removeUninstalledExtension(extension: ILocalExtension | IScannedExtension): Promise { + await this.removeExtension(extension, 'uninstalled'); + await this.withUninstalledExtensions(uninstalled => delete uninstalled[ExtensionKey.create(extension).toString()]); } - async copyExtension( - extension: ILocalExtension, - fromProfileLocation: URI, - toProfileLocation: URI, - metadata: Partial, - ): Promise { - const source = await this.getScannedExtension( - extension, - fromProfileLocation, - ); - const target = await this.getScannedExtension( - extension, - toProfileLocation, - ); + async copyExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata: Partial): Promise { + const source = await this.getScannedExtension(extension, fromProfileLocation); + const target = await this.getScannedExtension(extension, toProfileLocation); metadata = { ...source?.metadata, ...metadata }; if (target) { - if ( - this.uriIdentityService.extUri.isEqual( - target.location, - extension.location, - ) - ) { - await this.extensionsProfileScannerService.updateMetadata( - [[extension, { ...target.metadata, ...metadata }]], - toProfileLocation, - ); + if (this.uriIdentityService.extUri.isEqual(target.location, extension.location)) { + await this.extensionsProfileScannerService.updateMetadata([[extension, { ...target.metadata, ...metadata }]], toProfileLocation); } else { - const targetExtension = await this.scanLocalExtension( - target.location, - extension.type, - toProfileLocation, - ); - await this.extensionsProfileScannerService.removeExtensionFromProfile( - targetExtension, - toProfileLocation, - ); - await this.extensionsProfileScannerService.addExtensionsToProfile( - [[extension, { ...target.metadata, ...metadata }]], - toProfileLocation, - ); + const targetExtension = await this.scanLocalExtension(target.location, extension.type, toProfileLocation); + await this.extensionsProfileScannerService.removeExtensionFromProfile(targetExtension, toProfileLocation); + await this.extensionsProfileScannerService.addExtensionsToProfile([[extension, { ...target.metadata, ...metadata }]], toProfileLocation); } } else { - await this.extensionsProfileScannerService.addExtensionsToProfile( - [[extension, metadata]], - toProfileLocation, - ); + await this.extensionsProfileScannerService.addExtensionsToProfile([[extension, metadata]], toProfileLocation); } - return this.scanLocalExtension( - extension.location, - extension.type, - toProfileLocation, - ); + return this.scanLocalExtension(extension.location, extension.type, toProfileLocation); } - async copyExtensions( - fromProfileLocation: URI, - toProfileLocation: URI, - productVersion: IProductVersion, - ): Promise { - const fromExtensions = await this.scanExtensions( - ExtensionType.User, - fromProfileLocation, - productVersion, - ); - const extensions: [ILocalExtension, Metadata | undefined][] = - await Promise.all( - fromExtensions - .filter( - (e) => !e.isApplicationScoped, - ) /* remove application scoped extensions */ - .map(async (e) => [ - e, - await this.scanMetadata(e, fromProfileLocation), - ]), - ); - await this.extensionsProfileScannerService.addExtensionsToProfile( - extensions, - toProfileLocation, - ); + async copyExtensions(fromProfileLocation: URI, toProfileLocation: URI, productVersion: IProductVersion): Promise { + const fromExtensions = await this.scanExtensions(ExtensionType.User, fromProfileLocation, productVersion); + const extensions: [ILocalExtension, Metadata | undefined][] = await Promise.all(fromExtensions + .filter(e => !e.isApplicationScoped) /* remove application scoped extensions */ + .map(async e => ([e, await this.scanMetadata(e, fromProfileLocation)]))); + await this.extensionsProfileScannerService.addExtensionsToProfile(extensions, toProfileLocation); } - private async deleteExtensionFromLocation( - id: string, - location: URI, - type: string, - ): Promise { - this.logService.trace( - `Deleting ${type} extension from disk`, - id, - location.fsPath, - ); - const renamedLocation = this.uriIdentityService.extUri.joinPath( - this.uriIdentityService.extUri.dirname(location), - `${this.uriIdentityService.extUri.basename(location)}.${hash(generateUuid()).toString(16)}${DELETED_FOLDER_POSTFIX}`, - ); + private async deleteExtensionFromLocation(id: string, location: URI, type: string): Promise { + this.logService.trace(`Deleting ${type} extension from disk`, id, location.fsPath); + const renamedLocation = this.uriIdentityService.extUri.joinPath(this.uriIdentityService.extUri.dirname(location), `${this.uriIdentityService.extUri.basename(location)}.${hash(generateUuid()).toString(16)}${DELETED_FOLDER_POSTFIX}`); await this.rename(location.fsPath, renamedLocation.fsPath); await this.fileService.del(renamedLocation, { recursive: true }); - this.logService.info( - `Deleted ${type} extension from disk`, - id, - location.fsPath, - ); + this.logService.info(`Deleted ${type} extension from disk`, id, location.fsPath); } - private withUninstalledExtensions( - updateFn?: (uninstalled: IStringDictionary) => void, - ): Promise> { + private withUninstalledExtensions(updateFn?: (uninstalled: IStringDictionary) => void): Promise> { return this.uninstalledFileLimiter.queue(async () => { let raw: string | undefined; try { - const content = await this.fileService.readFile( - this.uninstalledResource, - "utf8", - ); + const content = await this.fileService.readFile(this.uninstalledResource, 'utf8'); raw = content.value.toString(); } catch (error) { - if ( - toFileOperationResult(error) !== - FileOperationResult.FILE_NOT_FOUND - ) { + if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) { throw error; } } @@ -1682,18 +830,13 @@ export class ExtensionsScanner extends Disposable { if (raw) { try { uninstalled = JSON.parse(raw); - } catch (e) { - /* ignore */ - } + } catch (e) { /* ignore */ } } if (updateFn) { updateFn(uninstalled); if (Object.keys(uninstalled).length) { - await this.fileService.writeFile( - this.uninstalledResource, - VSBuffer.fromString(JSON.stringify(uninstalled)), - ); + await this.fileService.writeFile(this.uninstalledResource, VSBuffer.fromString(JSON.stringify(uninstalled))); } else { await this.fileService.del(this.uninstalledResource); } @@ -1703,84 +846,41 @@ export class ExtensionsScanner extends Disposable { }); } - private async rename( - extractPath: string, - renamePath: string, - ): Promise { + private async rename(extractPath: string, renamePath: string): Promise { try { - await pfs.Promises.rename( - extractPath, - renamePath, - 2 * 60 * 1000 /* Retry for 2 minutes */, - ); + await pfs.Promises.rename(extractPath, renamePath, 2 * 60 * 1000 /* Retry for 2 minutes */); } catch (error) { - throw toExtensionManagementError( - error, - ExtensionManagementErrorCode.Rename, - ); + throw toExtensionManagementError(error, ExtensionManagementErrorCode.Rename); } } - async scanLocalExtension( - location: URI, - type: ExtensionType, - profileLocation?: URI, - ): Promise { + async scanLocalExtension(location: URI, type: ExtensionType, profileLocation?: URI): Promise { try { if (profileLocation) { - const scannedExtensions = - await this.extensionsScannerService.scanUserExtensions({ - profileLocation, - }); - const scannedExtension = scannedExtensions.find((e) => - this.uriIdentityService.extUri.isEqual( - e.location, - location, - ), - ); + const scannedExtensions = await this.extensionsScannerService.scanUserExtensions({ profileLocation }); + const scannedExtension = scannedExtensions.find(e => this.uriIdentityService.extUri.isEqual(e.location, location)); if (scannedExtension) { return await this.toLocalExtension(scannedExtension); } } else { - const scannedExtension = - await this.extensionsScannerService.scanExistingExtension( - location, - type, - { includeInvalid: true }, - ); + const scannedExtension = await this.extensionsScannerService.scanExistingExtension(location, type, { includeInvalid: true }); if (scannedExtension) { return await this.toLocalExtension(scannedExtension); } } - throw new ExtensionManagementError( - nls.localize( - "cannot read", - "Cannot read the extension from {0}", - location.path, - ), - ExtensionManagementErrorCode.ScanningExtension, - ); + throw new ExtensionManagementError(nls.localize('cannot read', "Cannot read the extension from {0}", location.path), ExtensionManagementErrorCode.ScanningExtension); } catch (error) { - throw toExtensionManagementError( - error, - ExtensionManagementErrorCode.ScanningExtension, - ); + throw toExtensionManagementError(error, ExtensionManagementErrorCode.ScanningExtension); } } - private async toLocalExtension( - extension: IScannedExtension, - ): Promise { + private async toLocalExtension(extension: IScannedExtension): Promise { const stat = await this.fileService.resolve(extension.location); let readmeUrl: URI | undefined; let changelogUrl: URI | undefined; if (stat.children) { - readmeUrl = stat.children.find(({ name }) => - /^readme(\.txt|\.md|)$/i.test(name), - )?.resource; - changelogUrl = stat.children.find(({ name }) => - /^changelog(\.txt|\.md|)$/i.test(name), - )?.resource; + readmeUrl = stat.children.find(({ name }) => /^readme(\.txt|\.md|)$/i.test(name))?.resource; + changelogUrl = stat.children.find(({ name }) => /^changelog(\.txt|\.md|)$/i.test(name))?.resource; } return { identifier: extension.identifier, @@ -1804,36 +904,20 @@ export class ExtensionsScanner extends Disposable { updated: !!extension.metadata?.updated, pinned: !!extension.metadata?.pinned, isWorkspaceScoped: false, - source: - extension.metadata?.source ?? - (extension.identifier.uuid ? "gallery" : "vsix"), + source: extension.metadata?.source ?? (extension.identifier.uuid ? 'gallery' : 'vsix'), size: extension.metadata?.size ?? 0, }; } private async initializeMetadata(): Promise { - const extensions = - await this.extensionsScannerService.scanUserExtensions({ - includeInvalid: true, - }); - await Promise.all( - extensions.map(async (extension) => { - // set size if not set before - if ( - !extension.metadata?.size && - extension.metadata?.source !== "resource" - ) { - const size = await computeSize( - extension.location, - this.fileService, - ); - await this.extensionsScannerService.updateMetadata( - extension.location, - { size }, - ); - } - }), - ); + const extensions = await this.extensionsScannerService.scanUserExtensions({ includeInvalid: true }); + await Promise.all(extensions.map(async extension => { + // set size if not set before + if (!extension.metadata?.size && extension.metadata?.source !== 'resource') { + const size = await computeSize(extension.location, this.fileService); + await this.extensionsScannerService.updateMetadata(extension.location, { size }); + } + })); } private async removeUninstalledExtensions(): Promise { @@ -1843,17 +927,9 @@ export class ExtensionsScanner extends Disposable { return; } - this.logService.debug( - `Removing uninstalled extensions:`, - Object.keys(uninstalled), - ); - - const extensions = - await this.extensionsScannerService.scanUserExtensions({ - includeAllVersions: true, - includeUninstalled: true, - includeInvalid: true, - }); // All user extensions + this.logService.debug(`Removing uninstalled extensions:`, Object.keys(uninstalled)); + + const extensions = await this.extensionsScannerService.scanUserExtensions({ includeAllVersions: true, includeUninstalled: true, includeInvalid: true }); // All user extensions const installed: Set = new Set(); for (const e of extensions) { if (!uninstalled[ExtensionKey.create(e).toString()]) { @@ -1863,51 +939,29 @@ export class ExtensionsScanner extends Disposable { try { // running post uninstall tasks for extensions that are not installed anymore - const byExtension = groupByExtension( - extensions, - (e) => e.identifier, - ); - await Promises.settled( - byExtension.map(async (e) => { - const latest = e.sort((a, b) => - semver.rcompare(a.manifest.version, b.manifest.version), - )[0]; - if (!installed.has(latest.identifier.id.toLowerCase())) { - await this.beforeRemovingExtension( - await this.toLocalExtension(latest), - ); - } - }), - ); + const byExtension = groupByExtension(extensions, e => e.identifier); + await Promises.settled(byExtension.map(async e => { + const latest = e.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version))[0]; + if (!installed.has(latest.identifier.id.toLowerCase())) { + await this.beforeRemovingExtension(await this.toLocalExtension(latest)); + } + })); } catch (error) { this.logService.error(error); } - const toRemove = extensions.filter( - (e) => - e.metadata /* Installed by System */ && - uninstalled[ExtensionKey.create(e).toString()], - ); - await Promise.allSettled( - toRemove.map((e) => this.removeUninstalledExtension(e)), - ); + const toRemove = extensions.filter(e => e.metadata /* Installed by System */ && uninstalled[ExtensionKey.create(e).toString()]); + await Promise.allSettled(toRemove.map(e => this.removeUninstalledExtension(e))); } private async removeTemporarilyDeletedFolders(): Promise { - this.logService.trace( - "ExtensionManagementService#removeTempDeleteFolders", - ); + this.logService.trace('ExtensionManagementService#removeTempDeleteFolders'); let stat; try { - stat = await this.fileService.resolve( - this.extensionsScannerService.userExtensionsLocation, - ); + stat = await this.fileService.resolve(this.extensionsScannerService.userExtensionsLocation); } catch (error) { - if ( - toFileOperationResult(error) !== - FileOperationResult.FILE_NOT_FOUND - ) { + if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) { this.logService.error(error); } return; @@ -1918,55 +972,32 @@ export class ExtensionsScanner extends Disposable { } try { - await Promise.allSettled( - stat.children.map(async (child) => { - if ( - !child.isDirectory || - !child.name.endsWith(DELETED_FOLDER_POSTFIX) - ) { - return; - } - this.logService.trace( - "Deleting the temporarily deleted folder", - child.resource.toString(), - ); - try { - await this.fileService.del(child.resource, { - recursive: true, - }); - this.logService.trace( - "Deleted the temporarily deleted folder", - child.resource.toString(), - ); - } catch (error) { - if ( - toFileOperationResult(error) !== - FileOperationResult.FILE_NOT_FOUND - ) { - this.logService.error(error); - } + await Promise.allSettled(stat.children.map(async child => { + if (!child.isDirectory || !child.name.endsWith(DELETED_FOLDER_POSTFIX)) { + return; + } + this.logService.trace('Deleting the temporarily deleted folder', child.resource.toString()); + try { + await this.fileService.del(child.resource, { recursive: true }); + this.logService.trace('Deleted the temporarily deleted folder', child.resource.toString()); + } catch (error) { + if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) { + this.logService.error(error); } - }), - ); - } catch (error) { - /* ignore */ - } + } + })); + } catch (error) { /* ignore */ } } + } -class InstallExtensionInProfileTask - extends AbstractExtensionTask - implements IInstallExtensionTask -{ +class InstallExtensionInProfileTask extends AbstractExtensionTask implements IInstallExtensionTask { + private _operation = InstallOperation.Install; - get operation() { - return this.options.operation ?? this._operation; - } + get operation() { return this.options.operation ?? this._operation; } private _verificationStatus: ExtensionSignatureVerificationCode | undefined; - get verificationStatus() { - return this._verificationStatus; - } + get verificationStatus() { return this._verificationStatus; } readonly identifier: IExtensionIdentifier; @@ -1975,21 +1006,13 @@ class InstallExtensionInProfileTask readonly manifest: IExtensionManifest, readonly source: IGalleryExtension | URI, readonly options: InstallExtensionTaskOptions, - private readonly extractExtensionFn: ( - operation: InstallOperation, - token: CancellationToken, - ) => Promise, + private readonly extractExtensionFn: (operation: InstallOperation, token: CancellationToken) => Promise, private readonly extensionsScanner: ExtensionsScanner, - @IUriIdentityService - private readonly uriIdentityService: IUriIdentityService, - @IExtensionGalleryService - private readonly galleryService: IExtensionGalleryService, - @IUserDataProfilesService - private readonly userDataProfilesService: IUserDataProfilesService, - @IExtensionsScannerService - private readonly extensionsScannerService: IExtensionsScannerService, - @IExtensionsProfileScannerService - private readonly extensionsProfileScannerService: IExtensionsProfileScannerService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, + @IExtensionsScannerService private readonly extensionsScannerService: IExtensionsScannerService, + @IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService, @ILogService private readonly logService: ILogService, ) { super(); @@ -1997,35 +1020,20 @@ class InstallExtensionInProfileTask } protected async doRun(token: CancellationToken): Promise { - const installed = await this.extensionsScanner.scanExtensions( - ExtensionType.User, - this.options.profileLocation, - this.options.productVersion, - ); - const existingExtension = installed.find((i) => - areSameExtensions(i.identifier, this.identifier), - ); + const installed = await this.extensionsScanner.scanExtensions(ExtensionType.User, this.options.profileLocation, this.options.productVersion); + const existingExtension = installed.find(i => areSameExtensions(i.identifier, this.identifier)); if (existingExtension) { this._operation = InstallOperation.Update; } const metadata: Metadata = { - isApplicationScoped: - this.options.isApplicationScoped || - existingExtension?.isApplicationScoped, - isMachineScoped: - this.options.isMachineScoped || - existingExtension?.isMachineScoped, + isApplicationScoped: this.options.isApplicationScoped || existingExtension?.isApplicationScoped, + isMachineScoped: this.options.isMachineScoped || existingExtension?.isMachineScoped, isBuiltin: this.options.isBuiltin || existingExtension?.isBuiltin, - isSystem: - existingExtension?.type === ExtensionType.System - ? true - : undefined, + isSystem: existingExtension?.type === ExtensionType.System ? true : undefined, installedTimestamp: Date.now(), - pinned: this.options.installGivenVersion - ? true - : (this.options.pinned ?? existingExtension?.pinned), - source: this.source instanceof URI ? "vsix" : "gallery", + pinned: this.options.installGivenVersion ? true : (this.options.pinned ?? existingExtension?.pinned), + source: this.source instanceof URI ? 'vsix' : 'gallery', }; let local: ILocalExtension | undefined; @@ -2033,51 +1041,25 @@ class InstallExtensionInProfileTask // VSIX if (this.source instanceof URI) { if (existingExtension) { - if ( - this.extensionKey.equals( - new ExtensionKey( - existingExtension.identifier, - existingExtension.manifest.version, - ), - ) - ) { + if (this.extensionKey.equals(new ExtensionKey(existingExtension.identifier, existingExtension.manifest.version))) { try { - await this.extensionsScanner.removeExtension( - existingExtension, - "existing", - ); + await this.extensionsScanner.removeExtension(existingExtension, 'existing'); } catch (e) { - throw new Error( - nls.localize( - "restartCode", - "Please restart VS Code before reinstalling {0}.", - this.manifest.displayName || this.manifest.name, - ), - ); + throw new Error(nls.localize('restartCode', "Please restart VS Code before reinstalling {0}.", this.manifest.displayName || this.manifest.name)); } } } // Remove the extension with same version if it is already uninstalled. // Installing a VSIX extension shall replace the existing extension always. - const existingWithSameVersion = await this.unsetIfUninstalled( - this.extensionKey, - ); + const existingWithSameVersion = await this.unsetIfUninstalled(this.extensionKey); if (existingWithSameVersion) { try { - await this.extensionsScanner.removeExtension( - existingWithSameVersion, - "existing", - ); + await this.extensionsScanner.removeExtension(existingWithSameVersion, 'existing'); } catch (e) { - throw new Error( - nls.localize( - "restartCode", - "Please restart VS Code before reinstalling {0}.", - this.manifest.displayName || this.manifest.name, - ), - ); + throw new Error(nls.localize('restartCode', "Please restart VS Code before reinstalling {0}.", this.manifest.displayName || this.manifest.name)); } } + } // Gallery @@ -2087,27 +1069,14 @@ class InstallExtensionInProfileTask metadata.publisherDisplayName = this.source.publisherDisplayName; metadata.targetPlatform = this.source.properties.targetPlatform; metadata.updated = !!existingExtension; - metadata.isPreReleaseVersion = - this.source.properties.isPreReleaseVersion; - metadata.hasPreReleaseVersion = - existingExtension?.hasPreReleaseVersion || - this.source.properties.isPreReleaseVersion; + metadata.isPreReleaseVersion = this.source.properties.isPreReleaseVersion; + metadata.hasPreReleaseVersion = existingExtension?.hasPreReleaseVersion || this.source.properties.isPreReleaseVersion; metadata.preRelease = isBoolean(this.options.preRelease) ? this.options.preRelease - : this.options.installPreReleaseVersion || - this.source.properties.isPreReleaseVersion || - existingExtension?.preRelease; - - if ( - existingExtension && - existingExtension.type !== ExtensionType.System && - existingExtension.manifest.version === this.source.version - ) { - return this.extensionsScanner.updateMetadata( - existingExtension, - metadata, - this.options.profileLocation, - ); + : this.options.installPreReleaseVersion || this.source.properties.isPreReleaseVersion || existingExtension?.preRelease; + + if (existingExtension && existingExtension.type !== ExtensionType.System && existingExtension.manifest.version === this.source.version) { + return this.extensionsScanner.updateMetadata(existingExtension, metadata, this.options.profileLocation); } // Unset if the extension is uninstalled and return the unset extension. @@ -2124,19 +1093,11 @@ class InstallExtensionInProfileTask this._verificationStatus = result.verificationStatus; } - if ( - this.uriIdentityService.extUri.isEqual( - this.userDataProfilesService.defaultProfile.extensionsResource, - this.options.profileLocation, - ) - ) { + if (this.uriIdentityService.extUri.isEqual(this.userDataProfilesService.defaultProfile.extensionsResource, this.options.profileLocation)) { try { await this.extensionsScannerService.initializeDefaultProfileExtensions(); } catch (error) { - throw toExtensionManagementError( - error, - ExtensionManagementErrorCode.IntializeDefaultProfile, - ); + throw toExtensionManagementError(error, ExtensionManagementErrorCode.IntializeDefaultProfile); } } @@ -2145,28 +1106,14 @@ class InstallExtensionInProfileTask } try { - await this.extensionsProfileScannerService.addExtensionsToProfile( - [[local, metadata]], - this.options.profileLocation, - !local.isValid, - ); + await this.extensionsProfileScannerService.addExtensionsToProfile([[local, metadata]], this.options.profileLocation, !local.isValid); } catch (error) { - throw toExtensionManagementError( - error, - ExtensionManagementErrorCode.AddToProfile, - ); + throw toExtensionManagementError(error, ExtensionManagementErrorCode.AddToProfile); } - const result = await this.extensionsScanner.scanLocalExtension( - local.location, - ExtensionType.User, - this.options.profileLocation, - ); + const result = await this.extensionsScanner.scanLocalExtension(local.location, ExtensionType.User, this.options.profileLocation); if (!result) { - throw new ExtensionManagementError( - "Cannot find the installed extension", - ExtensionManagementErrorCode.InstalledExtensionNotFound, - ); + throw new ExtensionManagementError('Cannot find the installed extension', ExtensionManagementErrorCode.InstalledExtensionNotFound); } if (this.source instanceof URI) { @@ -2176,72 +1123,37 @@ class InstallExtensionInProfileTask return result; } - private async unsetIfUninstalled( - extensionKey: ExtensionKey, - ): Promise { - const uninstalled = - await this.extensionsScanner.getUninstalledExtensions(); + private async unsetIfUninstalled(extensionKey: ExtensionKey): Promise { + const uninstalled = await this.extensionsScanner.getUninstalledExtensions(); if (!uninstalled[extensionKey.toString()]) { return undefined; } - this.logService.trace( - "Removing the extension from uninstalled list:", - extensionKey.id, - ); + this.logService.trace('Removing the extension from uninstalled list:', extensionKey.id); // If the same version of extension is marked as uninstalled, remove it from there and return the local. await this.extensionsScanner.setInstalled(extensionKey); - this.logService.info( - "Removed the extension from uninstalled list:", - extensionKey.id, - ); - - const userExtensions = - await this.extensionsScanner.scanAllUserExtensions(true); - return userExtensions.find((i) => - ExtensionKey.create(i).equals(extensionKey), - ); + this.logService.info('Removed the extension from uninstalled list:', extensionKey.id); + + const userExtensions = await this.extensionsScanner.scanAllUserExtensions(true); + return userExtensions.find(i => ExtensionKey.create(i).equals(extensionKey)); } - private async updateMetadata( - extension: ILocalExtension, - token: CancellationToken, - ): Promise { + private async updateMetadata(extension: ILocalExtension, token: CancellationToken): Promise { try { - let [galleryExtension] = await this.galleryService.getExtensions( - [ - { - id: extension.identifier.id, - version: extension.manifest.version, - }, - ], - token, - ); + let [galleryExtension] = await this.galleryService.getExtensions([{ id: extension.identifier.id, version: extension.manifest.version }], token); if (!galleryExtension) { - [galleryExtension] = await this.galleryService.getExtensions( - [{ id: extension.identifier.id }], - token, - ); + [galleryExtension] = await this.galleryService.getExtensions([{ id: extension.identifier.id }], token); } if (galleryExtension) { const metadata = { id: galleryExtension.identifier.uuid, publisherDisplayName: galleryExtension.publisherDisplayName, publisherId: galleryExtension.publisherId, - isPreReleaseVersion: - galleryExtension.properties.isPreReleaseVersion, - hasPreReleaseVersion: - extension.hasPreReleaseVersion || - galleryExtension.properties.isPreReleaseVersion, - preRelease: - galleryExtension.properties.isPreReleaseVersion || - this.options.installPreReleaseVersion, + isPreReleaseVersion: galleryExtension.properties.isPreReleaseVersion, + hasPreReleaseVersion: extension.hasPreReleaseVersion || galleryExtension.properties.isPreReleaseVersion, + preRelease: galleryExtension.properties.isPreReleaseVersion || this.options.installPreReleaseVersion }; - await this.extensionsScanner.updateMetadata( - extension, - metadata, - this.options.profileLocation, - ); + await this.extensionsScanner.updateMetadata(extension, metadata, this.options.profileLocation); } } catch (error) { /* Ignore Error */ @@ -2249,10 +1161,8 @@ class InstallExtensionInProfileTask } } -class UninstallExtensionInProfileTask - extends AbstractExtensionTask - implements IUninstallExtensionTask -{ +class UninstallExtensionInProfileTask extends AbstractExtensionTask implements IUninstallExtensionTask { + constructor( readonly extension: ILocalExtension, readonly options: UninstallExtensionTaskOptions, @@ -2262,9 +1172,7 @@ class UninstallExtensionInProfileTask } protected async doRun(token: CancellationToken): Promise { - await this.extensionsProfileScannerService.removeExtensionFromProfile( - this.extension, - this.options.profileLocation, - ); + await this.extensionsProfileScannerService.removeExtensionFromProfile(this.extension, this.options.profileLocation); } + } diff --git a/Source/vs/platform/extensions/common/extensionsApiProposals.ts b/Source/vs/platform/extensions/common/extensionsApiProposals.ts index 1b00fe920389a..1aec67707c70f 100644 --- a/Source/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/Source/vs/platform/extensions/common/extensionsApiProposals.ts @@ -7,539 +7,402 @@ const _allApiProposals = { activeComment: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.activeComment.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.activeComment.d.ts', }, aiRelatedInformation: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.aiRelatedInformation.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.aiRelatedInformation.d.ts', }, aiTextSearchProvider: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.aiTextSearchProvider.d.ts", - version: 2, - }, - attributableCoverage: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.attributableCoverage.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.aiTextSearchProvider.d.ts', + version: 2 }, authLearnMore: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authLearnMore.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authLearnMore.d.ts', }, authSession: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authSession.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authSession.d.ts', }, canonicalUriProvider: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.canonicalUriProvider.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.canonicalUriProvider.d.ts', }, chatEditing: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatEditing.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatEditing.d.ts', }, chatParticipantAdditions: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts', }, chatParticipantPrivate: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts", - version: 2, + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts', + version: 2 }, chatProvider: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatProvider.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatProvider.d.ts', }, chatReferenceBinaryData: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatReferenceBinaryData.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatReferenceBinaryData.d.ts', }, chatTab: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatTab.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatTab.d.ts', }, chatVariableResolver: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts', }, codeActionAI: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionAI.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionAI.d.ts', }, codeActionRanges: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionRanges.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionRanges.d.ts', }, codiconDecoration: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codiconDecoration.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codiconDecoration.d.ts', }, commentReactor: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentReactor.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentReactor.d.ts', }, commentReveal: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentReveal.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentReveal.d.ts', }, commentThreadApplicability: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentThreadApplicability.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentThreadApplicability.d.ts', }, commentingRangeHint: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentingRangeHint.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentingRangeHint.d.ts', }, commentsDraftState: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentsDraftState.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentsDraftState.d.ts', }, contribAccessibilityHelpContent: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribAccessibilityHelpContent.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribAccessibilityHelpContent.d.ts', }, contribCommentEditorActionsMenu: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentEditorActionsMenu.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentEditorActionsMenu.d.ts', }, contribCommentPeekContext: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentPeekContext.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentPeekContext.d.ts', }, contribCommentThreadAdditionalMenu: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentThreadAdditionalMenu.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentThreadAdditionalMenu.d.ts', }, contribCommentsViewThreadMenus: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentsViewThreadMenus.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentsViewThreadMenus.d.ts', }, contribDebugCreateConfiguration: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribDebugCreateConfiguration.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribDebugCreateConfiguration.d.ts', }, contribDiffEditorGutterToolBarMenus: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribDiffEditorGutterToolBarMenus.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribDiffEditorGutterToolBarMenus.d.ts', }, contribEditSessions: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribEditSessions.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribEditSessions.d.ts', }, contribEditorContentMenu: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribEditorContentMenu.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribEditorContentMenu.d.ts', }, contribLabelFormatterWorkspaceTooltip: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribLabelFormatterWorkspaceTooltip.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribLabelFormatterWorkspaceTooltip.d.ts', }, contribMenuBarHome: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMenuBarHome.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMenuBarHome.d.ts', }, contribMergeEditorMenus: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMergeEditorMenus.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMergeEditorMenus.d.ts', }, contribMultiDiffEditorMenus: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMultiDiffEditorMenus.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMultiDiffEditorMenus.d.ts', }, contribNotebookStaticPreloads: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribNotebookStaticPreloads.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribNotebookStaticPreloads.d.ts', }, contribRemoteHelp: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribRemoteHelp.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribRemoteHelp.d.ts', }, contribShareMenu: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribShareMenu.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribShareMenu.d.ts', }, contribSourceControlHistoryItemMenu: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemMenu.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemMenu.d.ts', }, contribSourceControlHistoryTitleMenu: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlHistoryTitleMenu.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlHistoryTitleMenu.d.ts', }, contribSourceControlInputBoxMenu: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlInputBoxMenu.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlInputBoxMenu.d.ts', }, contribSourceControlTitleMenu: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlTitleMenu.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlTitleMenu.d.ts', }, contribStatusBarItems: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribStatusBarItems.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribStatusBarItems.d.ts', }, contribViewContainerTitle: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewContainerTitle.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewContainerTitle.d.ts', }, contribViewsRemote: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsRemote.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsRemote.d.ts', }, contribViewsWelcome: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsWelcome.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsWelcome.d.ts', }, createFileSystemWatcher: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.createFileSystemWatcher.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.createFileSystemWatcher.d.ts', }, customEditorMove: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.customEditorMove.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.customEditorMove.d.ts', }, debugVisualization: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.debugVisualization.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.debugVisualization.d.ts', }, defaultChatParticipant: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts", - version: 2, + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts', + version: 2 }, diffCommand: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.diffCommand.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.diffCommand.d.ts', }, diffContentOptions: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.diffContentOptions.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.diffContentOptions.d.ts', }, documentFiltersExclusive: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.documentFiltersExclusive.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.documentFiltersExclusive.d.ts', }, documentPaste: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.documentPaste.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.documentPaste.d.ts', }, editSessionIdentityProvider: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editSessionIdentityProvider.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editSessionIdentityProvider.d.ts', }, editorHoverVerbosityLevel: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editorHoverVerbosityLevel.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editorHoverVerbosityLevel.d.ts', }, editorInsets: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editorInsets.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editorInsets.d.ts', }, embeddings: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.embeddings.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.embeddings.d.ts', }, extensionRuntime: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.extensionRuntime.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.extensionRuntime.d.ts', }, extensionsAny: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.extensionsAny.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.extensionsAny.d.ts', }, externalUriOpener: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.externalUriOpener.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.externalUriOpener.d.ts', }, fileComments: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fileComments.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fileComments.d.ts', }, fileSearchProvider: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fileSearchProvider.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fileSearchProvider.d.ts', }, fileSearchProvider2: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fileSearchProvider2.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fileSearchProvider2.d.ts', }, findFiles2: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findFiles2.d.ts", - version: 2, + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findFiles2.d.ts', + version: 2 }, findTextInFiles: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findTextInFiles.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findTextInFiles.d.ts', }, findTextInFiles2: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findTextInFiles2.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findTextInFiles2.d.ts', }, fsChunks: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fsChunks.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fsChunks.d.ts', }, idToken: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.idToken.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.idToken.d.ts', }, inlineCompletionsAdditions: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts', }, inlineEdit: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlineEdit.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlineEdit.d.ts', }, interactive: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.interactive.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.interactive.d.ts', }, interactiveWindow: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.interactiveWindow.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.interactiveWindow.d.ts', }, ipc: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.ipc.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.ipc.d.ts', }, languageModelSystem: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageModelSystem.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageModelSystem.d.ts', }, languageStatusText: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageStatusText.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageStatusText.d.ts', }, mappedEditsProvider: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts', }, multiDocumentHighlightProvider: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.multiDocumentHighlightProvider.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.multiDocumentHighlightProvider.d.ts', }, nativeWindowHandle: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.nativeWindowHandle.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.nativeWindowHandle.d.ts', }, newSymbolNamesProvider: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.newSymbolNamesProvider.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.newSymbolNamesProvider.d.ts', }, notebookCellExecution: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCellExecution.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCellExecution.d.ts', }, notebookCellExecutionState: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCellExecutionState.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCellExecutionState.d.ts', }, notebookControllerAffinityHidden: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookControllerAffinityHidden.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookControllerAffinityHidden.d.ts', }, notebookDeprecated: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookDeprecated.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookDeprecated.d.ts', }, notebookExecution: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookExecution.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookExecution.d.ts', }, notebookKernelSource: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookKernelSource.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookKernelSource.d.ts', }, notebookLiveShare: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookLiveShare.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookLiveShare.d.ts', }, notebookMessaging: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookMessaging.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookMessaging.d.ts', }, notebookMime: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookMime.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookMime.d.ts', }, notebookReplDocument: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookReplDocument.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookReplDocument.d.ts', }, notebookVariableProvider: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts', }, portsAttributes: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.portsAttributes.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.portsAttributes.d.ts', }, profileContentHandlers: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.profileContentHandlers.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.profileContentHandlers.d.ts', }, quickDiffProvider: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickDiffProvider.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickDiffProvider.d.ts', }, quickInputButtonLocation: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickInputButtonLocation.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickInputButtonLocation.d.ts', }, quickPickItemTooltip: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickItemTooltip.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickItemTooltip.d.ts', }, quickPickSortByLabel: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts', }, resolvers: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.resolvers.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.resolvers.d.ts', }, scmActionButton: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmActionButton.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmActionButton.d.ts', }, scmHistoryProvider: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts', }, scmMultiDiffEditor: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmMultiDiffEditor.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmMultiDiffEditor.d.ts', }, scmSelectedProvider: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts', }, scmTextDocument: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmTextDocument.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmTextDocument.d.ts', }, scmValidation: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmValidation.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmValidation.d.ts', }, shareProvider: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.shareProvider.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.shareProvider.d.ts', }, showLocal: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.showLocal.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.showLocal.d.ts', }, speech: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.speech.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.speech.d.ts', }, tabInputMultiDiff: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tabInputMultiDiff.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tabInputMultiDiff.d.ts', }, tabInputTextMerge: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tabInputTextMerge.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tabInputTextMerge.d.ts', }, taskPresentationGroup: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.taskPresentationGroup.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.taskPresentationGroup.d.ts', }, telemetry: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.telemetry.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.telemetry.d.ts', }, terminalCompletionProvider: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts', }, terminalDataWriteEvent: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDataWriteEvent.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDataWriteEvent.d.ts', }, terminalDimensions: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDimensions.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDimensions.d.ts', }, terminalExecuteCommandEvent: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalExecuteCommandEvent.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalExecuteCommandEvent.d.ts', }, terminalQuickFixProvider: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalQuickFixProvider.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalQuickFixProvider.d.ts', }, terminalSelection: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalSelection.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalSelection.d.ts', }, testObserver: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testObserver.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testObserver.d.ts', }, testRelatedCode: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testRelatedCode.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testRelatedCode.d.ts', }, textEditorDiffInformation: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textEditorDiffInformation.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textEditorDiffInformation.d.ts', }, textSearchComplete2: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchComplete2.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchComplete2.d.ts', }, textSearchProvider: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts', }, textSearchProvider2: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchProvider2.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchProvider2.d.ts', }, timeline: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.timeline.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.timeline.d.ts', }, tokenInformation: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tokenInformation.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tokenInformation.d.ts', }, treeViewActiveItem: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewActiveItem.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewActiveItem.d.ts', }, treeViewMarkdownMessage: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewMarkdownMessage.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewMarkdownMessage.d.ts', }, treeViewReveal: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewReveal.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewReveal.d.ts', }, tunnelFactory: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tunnelFactory.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tunnelFactory.d.ts', }, tunnels: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tunnels.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tunnels.d.ts', }, valueSelectionInQuickPick: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.valueSelectionInQuickPick.d.ts", + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.valueSelectionInQuickPick.d.ts', }, workspaceTrust: { - proposal: - "https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.workspaceTrust.d.ts", - }, + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.workspaceTrust.d.ts', + } }; -export const allApiProposals = Object.freeze<{ - [proposalName: string]: Readonly<{ proposal: string; version?: number }>; -}>(_allApiProposals); +export const allApiProposals = Object.freeze<{ [proposalName: string]: Readonly<{ proposal: string; version?: number }> }>(_allApiProposals); export type ApiProposalName = keyof typeof _allApiProposals; diff --git a/Source/vs/platform/request/node/requestService.ts b/Source/vs/platform/request/node/requestService.ts index 8c6555c3a7fe8..a9db3e2d6a3a9 100644 --- a/Source/vs/platform/request/node/requestService.ts +++ b/Source/vs/platform/request/node/requestService.ts @@ -2,290 +2,217 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as http from "http"; -import * as https from "https"; -import { parse as parseUrl } from "url"; -import { createGunzip } from "zlib"; - -import { Promises } from "../../../base/common/async.js"; -import { streamToBufferReadableStream } from "../../../base/common/buffer.js"; -import { CancellationToken } from "../../../base/common/cancellation.js"; -import { - CancellationError, - getErrorMessage, -} from "../../../base/common/errors.js"; -import * as streams from "../../../base/common/stream.js"; -import { isBoolean, isNumber } from "../../../base/common/types.js"; -import { - IRequestContext, - IRequestOptions, -} from "../../../base/parts/request/common/request.js"; -import { IConfigurationService } from "../../configuration/common/configuration.js"; -import { INativeEnvironmentService } from "../../environment/common/environment.js"; -import { ILogService } from "../../log/common/log.js"; -import { getResolvedShellEnv } from "../../shell/node/shellEnv.js"; -import { - AbstractRequestService, - AuthInfo, - Credentials, - IRequestService, -} from "../common/request.js"; -import { Agent, getProxyAgent } from "./proxy.js"; + +import * as http from 'http'; +import * as https from 'https'; +import { parse as parseUrl } from 'url'; +import { Promises } from '../../../base/common/async.js'; +import { streamToBufferReadableStream } from '../../../base/common/buffer.js'; +import { CancellationToken } from '../../../base/common/cancellation.js'; +import { CancellationError, getErrorMessage } from '../../../base/common/errors.js'; +import * as streams from '../../../base/common/stream.js'; +import { isBoolean, isNumber } from '../../../base/common/types.js'; +import { IRequestContext, IRequestOptions } from '../../../base/parts/request/common/request.js'; +import { IConfigurationService } from '../../configuration/common/configuration.js'; +import { INativeEnvironmentService } from '../../environment/common/environment.js'; +import { getResolvedShellEnv } from '../../shell/node/shellEnv.js'; +import { ILogService } from '../../log/common/log.js'; +import { AbstractRequestService, AuthInfo, Credentials, IRequestService } from '../common/request.js'; +import { Agent, getProxyAgent } from './proxy.js'; +import { createGunzip } from 'zlib'; interface IHTTPConfiguration { proxy?: string; proxyStrictSSL?: boolean; proxyAuthorization?: string; } + export interface IRawRequestFunction { - ( - options: http.RequestOptions, - callback?: (res: http.IncomingMessage) => void, - ): http.ClientRequest; + (options: http.RequestOptions, callback?: (res: http.IncomingMessage) => void): http.ClientRequest; } + export interface NodeRequestOptions extends IRequestOptions { agent?: Agent; strictSSL?: boolean; isChromiumNetwork?: boolean; - getRawRequest?(options: IRequestOptions): IRawRequestFunction; } + /** * This service exposes the `request` API, while using the global * or configured proxy settings. */ -export class RequestService - extends AbstractRequestService - implements IRequestService -{ +export class RequestService extends AbstractRequestService implements IRequestService { + declare readonly _serviceBrand: undefined; + private proxyUrl?: string; private strictSSL: boolean | undefined; private authorization?: string; private shellEnvErrorLogged?: boolean; constructor( - @IConfigurationService - private readonly configurationService: IConfigurationService, - @INativeEnvironmentService - private readonly environmentService: INativeEnvironmentService, - @ILogService - logService: ILogService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @INativeEnvironmentService private readonly environmentService: INativeEnvironmentService, + @ILogService logService: ILogService, ) { super(logService); this.configure(); - this._register( - configurationService.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration("http")) { - this.configure(); - } - }), - ); + this._register(configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('http')) { + this.configure(); + } + })); } + private configure() { - const config = this.configurationService.getValue< - IHTTPConfiguration | undefined - >("http"); + const config = this.configurationService.getValue('http'); + this.proxyUrl = config?.proxy; this.strictSSL = !!config?.proxyStrictSSL; this.authorization = config?.proxyAuthorization; } - async request( - options: NodeRequestOptions, - token: CancellationToken, - ): Promise { + + async request(options: NodeRequestOptions, token: CancellationToken): Promise { const { proxyUrl, strictSSL } = this; let shellEnv: typeof process.env | undefined = undefined; - try { - shellEnv = await getResolvedShellEnv( - this.configurationService, - this.logService, - this.environmentService.args, - process.env, - ); + shellEnv = await getResolvedShellEnv(this.configurationService, this.logService, this.environmentService.args, process.env); } catch (error) { if (!this.shellEnvErrorLogged) { this.shellEnvErrorLogged = true; - this.logService.error( - `resolving shell environment failed`, - getErrorMessage(error), - ); + this.logService.error(`resolving shell environment failed`, getErrorMessage(error)); } } + const env = { ...process.env, - ...shellEnv, + ...shellEnv }; + const agent = options.agent ? options.agent : await getProxyAgent(options.url || '', env, { proxyUrl, strictSSL }); - const agent = options.agent - ? options.agent - : await getProxyAgent(options.url || "", env, { - proxyUrl, - strictSSL, - }); options.agent = agent; options.strictSSL = strictSSL; if (this.authorization) { options.headers = { ...(options.headers || {}), - "Proxy-Authorization": this.authorization, + 'Proxy-Authorization': this.authorization }; } + return this.logAndRequest(options, () => nodeRequest(options, token)); } + async resolveProxy(url: string): Promise { return undefined; // currently not implemented in node } - async lookupAuthorization( - authInfo: AuthInfo, - ): Promise { + + async lookupAuthorization(authInfo: AuthInfo): Promise { return undefined; // currently not implemented in node } - async lookupKerberosAuthorization( - urlStr: string, - ): Promise { - try { - const kerberos = await import("kerberos"); + async lookupKerberosAuthorization(urlStr: string): Promise { + try { + const importKerberos = await import('kerberos'); + const kerberos = importKerberos.default || importKerberos; const url = new URL(urlStr); - - const spn = - this.configurationService.getValue( - "http.proxyKerberosServicePrincipal", - ) || - (process.platform === "win32" - ? `HTTP/${url.hostname}` - : `HTTP@${url.hostname}`); - this.logService.debug( - "RequestService#lookupKerberosAuthorization Kerberos authentication lookup", - `proxyURL:${url}`, - `spn:${spn}`, - ); - + const spn = this.configurationService.getValue('http.proxyKerberosServicePrincipal') + || (process.platform === 'win32' ? `HTTP/${url.hostname}` : `HTTP@${url.hostname}`); + this.logService.debug('RequestService#lookupKerberosAuthorization Kerberos authentication lookup', `proxyURL:${url}`, `spn:${spn}`); const client = await kerberos.initializeClient(spn); - - const response = await client.step(""); - - return "Negotiate " + response; + const response = await client.step(''); + return 'Negotiate ' + response; } catch (err) { - this.logService.debug( - "RequestService#lookupKerberosAuthorization Kerberos authentication failed", - err, - ); - + this.logService.debug('RequestService#lookupKerberosAuthorization Kerberos authentication failed', err); return undefined; } } - async loadCertificates(): Promise { - const proxyAgent = await import("@vscode/proxy-agent"); + async loadCertificates(): Promise { + const proxyAgent = await import('@vscode/proxy-agent'); return proxyAgent.loadSystemCertificates({ log: this.logService }); } } -async function getNodeRequest( - options: IRequestOptions, -): Promise { - const endpoint = parseUrl(options.url!); - const module = - endpoint.protocol === "https:" - ? await import("https") - : await import("http"); +async function getNodeRequest(options: IRequestOptions): Promise { + const endpoint = parseUrl(options.url!); + const module = endpoint.protocol === 'https:' ? await import('https') : await import('http'); return module.request; } -export async function nodeRequest( - options: NodeRequestOptions, - token: CancellationToken, -): Promise { + +export async function nodeRequest(options: NodeRequestOptions, token: CancellationToken): Promise { return Promises.withAsyncBody(async (resolve, reject) => { const endpoint = parseUrl(options.url!); - const rawRequest = options.getRawRequest ? options.getRawRequest(options) : await getNodeRequest(options); const opts: https.RequestOptions = { hostname: endpoint.hostname, - port: endpoint.port - ? parseInt(endpoint.port) - : endpoint.protocol === "https:" - ? 443 - : 80, + port: endpoint.port ? parseInt(endpoint.port) : (endpoint.protocol === 'https:' ? 443 : 80), protocol: endpoint.protocol, path: endpoint.path, - method: options.type || "GET", + method: options.type || 'GET', headers: options.headers, agent: options.agent, - rejectUnauthorized: isBoolean(options.strictSSL) - ? options.strictSSL - : true, + rejectUnauthorized: isBoolean(options.strictSSL) ? options.strictSSL : true }; if (options.user && options.password) { - opts.auth = options.user + ":" + options.password; + opts.auth = options.user + ':' + options.password; } + const req = rawRequest(opts, (res: http.IncomingMessage) => { - const followRedirects: number = isNumber(options.followRedirects) - ? options.followRedirects - : 3; - - if ( - res.statusCode && - res.statusCode >= 300 && - res.statusCode < 400 && - followRedirects > 0 && - res.headers["location"] - ) { - nodeRequest( - { - ...options, - url: res.headers["location"], - followRedirects: followRedirects - 1, - }, - token, - ).then(resolve, reject); + const followRedirects: number = isNumber(options.followRedirects) ? options.followRedirects : 3; + if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && followRedirects > 0 && res.headers['location']) { + nodeRequest({ + ...options, + url: res.headers['location'], + followRedirects: followRedirects - 1 + }, token).then(resolve, reject); } else { let stream: streams.ReadableStreamEvents = res; + // Responses from Electron net module should be treated as response // from browser, which will apply gzip filter and decompress the response // using zlib before passing the result to us. Following step can be bypassed // in this case and proceed further. // Refs https://source.chromium.org/chromium/chromium/src/+/main:net/url_request/url_request_http_job.cc;l=1266-1318 - if ( - !options.isChromiumNetwork && - res.headers["content-encoding"] === "gzip" - ) { + if (!options.isChromiumNetwork && res.headers['content-encoding'] === 'gzip') { stream = res.pipe(createGunzip()); } - resolve({ - res, - stream: streamToBufferReadableStream(stream), - } satisfies IRequestContext); + + resolve({ res, stream: streamToBufferReadableStream(stream) } satisfies IRequestContext); } }); - req.on("error", reject); + + req.on('error', reject); if (options.timeout) { req.setTimeout(options.timeout); } + // Chromium will abort the request if forbidden headers are set. // Ref https://source.chromium.org/chromium/chromium/src/+/main:services/network/public/cpp/header_util.cc;l=14-48; // for additional context. if (options.isChromiumNetwork) { - req.removeHeader("Content-Length"); + req.removeHeader('Content-Length'); } + if (options.data) { - if (typeof options.data === "string") { + if (typeof options.data === 'string') { req.write(options.data); } } + req.end(); + token.onCancellationRequested(() => { req.abort(); + reject(new CancellationError()); }); }); diff --git a/Source/vs/workbench/api/browser/mainThreadTerminalShellIntegration.ts b/Source/vs/workbench/api/browser/mainThreadTerminalShellIntegration.ts index 0b4f1b2acea3a..6181cd4c63794 100644 --- a/Source/vs/workbench/api/browser/mainThreadTerminalShellIntegration.ts +++ b/Source/vs/workbench/api/browser/mainThreadTerminalShellIntegration.ts @@ -2,189 +2,105 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from "../../../base/common/event.js"; -import { - Disposable, - toDisposable, - type IDisposable, -} from "../../../base/common/lifecycle.js"; -import { URI } from "../../../base/common/uri.js"; -import { - TerminalCapability, - type ITerminalCommand, -} from "../../../platform/terminal/common/capabilities/capabilities.js"; -import { ITerminalService } from "../../contrib/terminal/browser/terminal.js"; -import { IWorkbenchEnvironmentService } from "../../services/environment/common/environmentService.js"; -import { - extHostNamedCustomer, - type IExtHostContext, -} from "../../services/extensions/common/extHostCustomers.js"; -import { - ExtHostContext, - MainContext, - type ExtHostTerminalShellIntegrationShape, - type MainThreadTerminalShellIntegrationShape, -} from "../common/extHost.protocol.js"; -import { TerminalShellExecutionCommandLineConfidence } from "../common/extHostTypes.js"; + +import { Event } from '../../../base/common/event.js'; +import { Disposable, toDisposable, type IDisposable } from '../../../base/common/lifecycle.js'; +import { URI } from '../../../base/common/uri.js'; +import { TerminalCapability, type ITerminalCommand } from '../../../platform/terminal/common/capabilities/capabilities.js'; +import { ExtHostContext, MainContext, type ExtHostTerminalShellIntegrationShape, type MainThreadTerminalShellIntegrationShape } from '../common/extHost.protocol.js'; +import { ITerminalService } from '../../contrib/terminal/browser/terminal.js'; +import { IWorkbenchEnvironmentService } from '../../services/environment/common/environmentService.js'; +import { extHostNamedCustomer, type IExtHostContext } from '../../services/extensions/common/extHostCustomers.js'; +import { TerminalShellExecutionCommandLineConfidence } from '../common/extHostTypes.js'; @extHostNamedCustomer(MainContext.MainThreadTerminalShellIntegration) -export class MainThreadTerminalShellIntegration - extends Disposable - implements MainThreadTerminalShellIntegrationShape -{ +export class MainThreadTerminalShellIntegration extends Disposable implements MainThreadTerminalShellIntegrationShape { private readonly _proxy: ExtHostTerminalShellIntegrationShape; constructor( extHostContext: IExtHostContext, - @ITerminalService - private readonly _terminalService: ITerminalService, - @IWorkbenchEnvironmentService - workbenchEnvironmentService: IWorkbenchEnvironmentService, + @ITerminalService private readonly _terminalService: ITerminalService, + @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService ) { super(); - this._proxy = extHostContext.getProxy( - ExtHostContext.ExtHostTerminalShellIntegration, - ); + + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTerminalShellIntegration); const instanceDataListeners: Map = new Map(); - this._register( - toDisposable(() => { - for (const listener of instanceDataListeners.values()) { - listener.dispose(); - } - }), - ); + this._register(toDisposable(() => { + for (const listener of instanceDataListeners.values()) { + listener.dispose(); + } + })); + // onDidChangeTerminalShellIntegration - const onDidAddCommandDetection = this._store.add( - this._terminalService.createOnInstanceEvent((instance) => { - return Event.map( - Event.filter( - instance.capabilities.onDidAddCapabilityType, - (e) => { - return e === TerminalCapability.CommandDetection; - }, - ), - () => instance, - ); - }), - ).event; - this._store.add( - onDidAddCommandDetection((e) => - this._proxy.$shellIntegrationChange(e.instanceId), - ), - ); - // onDidStartTerminalShellExecution - const commandDetectionStartEvent = this._store.add( - this._terminalService.createOnInstanceCapabilityEvent( - TerminalCapability.CommandDetection, - (e) => e.onCommandExecuted, - ), - ); + const onDidAddCommandDetection = this._store.add(this._terminalService.createOnInstanceEvent(instance => { + return Event.map( + Event.filter(instance.capabilities.onDidAddCapabilityType, e => { + return e === TerminalCapability.CommandDetection || e === TerminalCapability.CwdDetection; + }), () => instance + ); + })).event; + this._store.add(onDidAddCommandDetection(e => this._proxy.$shellIntegrationChange(e.instanceId))); + // onDidStartTerminalShellExecution + const commandDetectionStartEvent = this._store.add(this._terminalService.createOnInstanceCapabilityEvent(TerminalCapability.CommandDetection, e => e.onCommandExecuted)); let currentCommand: ITerminalCommand | undefined; - this._store.add( - commandDetectionStartEvent.event((e) => { - // Prevent duplicate events from being sent in case command detection double fires the - // event - if (e.data === currentCommand) { - return; - } - // String paths are not exposed in the extension API - currentCommand = e.data; + this._store.add(commandDetectionStartEvent.event(e => { + // Prevent duplicate events from being sent in case command detection double fires the + // event + if (e.data === currentCommand) { + return; + } + // String paths are not exposed in the extension API + currentCommand = e.data; + const instanceId = e.instance.instanceId; + this._proxy.$shellExecutionStart(instanceId, e.data.command, convertToExtHostCommandLineConfidence(e.data), e.data.isTrusted, this._convertCwdToUri(e.data.cwd)); + + // TerminalShellExecution.createDataStream + // Debounce events to reduce the message count - when this listener is disposed the events will be flushed + instanceDataListeners.get(instanceId)?.dispose(); + instanceDataListeners.set(instanceId, Event.accumulate(e.instance.onData, 50, this._store)(events => this._proxy.$shellExecutionData(instanceId, events.join('')))); + })); - const instanceId = e.instance.instanceId; - this._proxy.$shellExecutionStart( - instanceId, - e.data.command, - convertToExtHostCommandLineConfidence(e.data), - e.data.isTrusted, - this._convertCwdToUri(e.data.cwd), - ); - // TerminalShellExecution.createDataStream - // Debounce events to reduce the message count - when this listener is disposed the events will be flushed - instanceDataListeners.get(instanceId)?.dispose(); - instanceDataListeners.set( - instanceId, - Event.accumulate( - e.instance.onData, - 50, - this._store, - )((events) => - this._proxy.$shellExecutionData( - instanceId, - events.join(""), - ), - ), - ); - }), - ); // onDidEndTerminalShellExecution - const commandDetectionEndEvent = this._store.add( - this._terminalService.createOnInstanceCapabilityEvent( - TerminalCapability.CommandDetection, - (e) => e.onCommandFinished, - ), - ); - this._store.add( - commandDetectionEndEvent.event((e) => { - currentCommand = undefined; + const commandDetectionEndEvent = this._store.add(this._terminalService.createOnInstanceCapabilityEvent(TerminalCapability.CommandDetection, e => e.onCommandFinished)); + this._store.add(commandDetectionEndEvent.event(e => { + currentCommand = undefined; + const instanceId = e.instance.instanceId; + instanceDataListeners.get(instanceId)?.dispose(); + // Send end in a microtask to ensure the data events are sent first + setTimeout(() => { + this._proxy.$shellExecutionEnd(instanceId, e.data.command, convertToExtHostCommandLineConfidence(e.data), e.data.isTrusted, e.data.exitCode); + }); + })); - const instanceId = e.instance.instanceId; - instanceDataListeners.get(instanceId)?.dispose(); - // Send end in a microtask to ensure the data events are sent first - setTimeout(() => { - this._proxy.$shellExecutionEnd( - instanceId, - e.data.command, - convertToExtHostCommandLineConfidence(e.data), - e.data.isTrusted, - e.data.exitCode, - ); - }); - }), - ); // onDidChangeTerminalShellIntegration via cwd - const cwdChangeEvent = this._store.add( - this._terminalService.createOnInstanceCapabilityEvent( - TerminalCapability.CwdDetection, - (e) => e.onDidChangeCwd, - ), - ); - this._store.add( - cwdChangeEvent.event((e) => { - this._proxy.$cwdChange( - e.instance.instanceId, - this._convertCwdToUri(e.data), - ); - }), - ); + const cwdChangeEvent = this._store.add(this._terminalService.createOnInstanceCapabilityEvent(TerminalCapability.CwdDetection, e => e.onDidChangeCwd)); + this._store.add(cwdChangeEvent.event(e => { + this._proxy.$cwdChange(e.instance.instanceId, this._convertCwdToUri(e.data)); + })); + // Clean up after dispose - this._store.add( - this._terminalService.onDidDisposeInstance((e) => - this._proxy.$closeTerminal(e.instanceId), - ), - ); + this._store.add(this._terminalService.onDidDisposeInstance(e => this._proxy.$closeTerminal(e.instanceId))); } + $executeCommand(terminalId: number, commandLine: string): void { - this._terminalService - .getInstanceFromId(terminalId) - ?.runCommand(commandLine, true); + this._terminalService.getInstanceFromId(terminalId)?.runCommand(commandLine, true); } + private _convertCwdToUri(cwd: string | undefined): URI | undefined { return cwd ? URI.file(cwd) : undefined; } } -function convertToExtHostCommandLineConfidence( - command: ITerminalCommand, -): TerminalShellExecutionCommandLineConfidence { + +function convertToExtHostCommandLineConfidence(command: ITerminalCommand): TerminalShellExecutionCommandLineConfidence { switch (command.commandLineConfidence) { - case "high": + case 'high': return TerminalShellExecutionCommandLineConfidence.High; - - case "medium": + case 'medium': return TerminalShellExecutionCommandLineConfidence.Medium; - - case "low": + case 'low': default: return TerminalShellExecutionCommandLineConfidence.Low; } diff --git a/Source/vs/workbench/api/common/extHost.api.impl.ts b/Source/vs/workbench/api/common/extHost.api.impl.ts index 43836a5079c92..6e68860da3f89 100644 --- a/Source/vs/workbench/api/common/extHost.api.impl.ts +++ b/Source/vs/workbench/api/common/extHost.api.impl.ts @@ -3,149 +3,112 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type * as vscode from "vscode"; - -import { CancellationTokenSource } from "../../../base/common/cancellation.js"; -import * as errors from "../../../base/common/errors.js"; -import { Emitter, Event } from "../../../base/common/event.js"; -import { combinedDisposable } from "../../../base/common/lifecycle.js"; -import { matchesScheme, Schemas } from "../../../base/common/network.js"; -import Severity from "../../../base/common/severity.js"; -import { URI } from "../../../base/common/uri.js"; -import { TextEditorCursorStyle } from "../../../editor/common/config/editorOptions.js"; -import * as languageConfiguration from "../../../editor/common/languages/languageConfiguration.js"; -import { - score, - targetsNotebooks, -} from "../../../editor/common/languageSelector.js"; -import { OverviewRulerLane } from "../../../editor/common/model.js"; -import { - ExtensionIdentifierSet, - IExtensionDescription, -} from "../../../platform/extensions/common/extensions.js"; -import * as files from "../../../platform/files/common/files.js"; -import { ServicesAccessor } from "../../../platform/instantiation/common/instantiation.js"; -import { - ILoggerService, - ILogService, - LogLevel, -} from "../../../platform/log/common/log.js"; -import { getRemoteName } from "../../../platform/remote/common/remoteHosts.js"; -import { TelemetryTrustedValue } from "../../../platform/telemetry/common/telemetryUtils.js"; -import { EditSessionIdentityMatch } from "../../../platform/workspace/common/editSessions.js"; -import { DebugConfigurationProviderTriggerKind } from "../../contrib/debug/common/debug.js"; -import { ExtensionDescriptionRegistry } from "../../services/extensions/common/extensionDescriptionRegistry.js"; -import { UIKind } from "../../services/extensions/common/extensionHostProtocol.js"; -import { - checkProposedApiEnabled, - isProposedApiEnabled, -} from "../../services/extensions/common/extensions.js"; -import { ProxyIdentifier } from "../../services/extensions/common/proxyIdentifier.js"; -import { - ExcludeSettingOptions, - TextSearchCompleteMessageType, - TextSearchContext2, - TextSearchMatch2, -} from "../../services/search/common/searchExtTypes.js"; -import { - CandidatePortSource, - ExtHostContext, - ExtHostLogLevelServiceShape, - MainContext, -} from "./extHost.protocol.js"; -import { ExtHostRelatedInformation } from "./extHostAiRelatedInformation.js"; -import { ExtHostApiCommands } from "./extHostApiCommands.js"; -import { IExtHostApiDeprecationService } from "./extHostApiDeprecationService.js"; -import { IExtHostAuthentication } from "./extHostAuthentication.js"; -import { ExtHostBulkEdits } from "./extHostBulkEdits.js"; -import { ExtHostChatAgents2 } from "./extHostChatAgents2.js"; -import { ExtHostChatVariables } from "./extHostChatVariables.js"; -import { ExtHostClipboard } from "./extHostClipboard.js"; -import { ExtHostEditorInsets } from "./extHostCodeInsets.js"; -import { ExtHostCodeMapper } from "./extHostCodeMapper.js"; -import { IExtHostCommands } from "./extHostCommands.js"; -import { createExtHostComments } from "./extHostComments.js"; -import { - ExtHostConfigProvider, - IExtHostConfiguration, -} from "./extHostConfiguration.js"; -import { ExtHostCustomEditors } from "./extHostCustomEditors.js"; -import { IExtHostDebugService } from "./extHostDebugService.js"; -import { IExtHostDecorations } from "./extHostDecorations.js"; -import { ExtHostDiagnostics } from "./extHostDiagnostics.js"; -import { ExtHostDialogs } from "./extHostDialogs.js"; -import { ExtHostDocumentContentProvider } from "./extHostDocumentContentProviders.js"; -import { ExtHostDocuments } from "./extHostDocuments.js"; -import { IExtHostDocumentsAndEditors } from "./extHostDocumentsAndEditors.js"; -import { ExtHostDocumentSaveParticipant } from "./extHostDocumentSaveParticipant.js"; -import { IExtHostEditorTabs } from "./extHostEditorTabs.js"; -import { ExtHostEmbeddings } from "./extHostEmbedding.js"; -import { ExtHostAiEmbeddingVector } from "./extHostEmbeddingVector.js"; -import { - Extension, - IExtHostExtensionService, -} from "./extHostExtensionService.js"; -import { ExtHostFileSystem } from "./extHostFileSystem.js"; -import { IExtHostConsumerFileSystem } from "./extHostFileSystemConsumer.js"; -import { - ExtHostFileSystemEventService, - FileSystemWatcherCreateOptions, -} from "./extHostFileSystemEventService.js"; -import { IExtHostFileSystemInfo } from "./extHostFileSystemInfo.js"; -import { IExtHostInitDataService } from "./extHostInitDataService.js"; -import { ExtHostInteractive } from "./extHostInteractive.js"; -import { ExtHostLabelService } from "./extHostLabelService.js"; -import { ExtHostLanguageFeatures } from "./extHostLanguageFeatures.js"; -import { IExtHostLanguageModels } from "./extHostLanguageModels.js"; -import { ExtHostLanguageModelTools } from "./extHostLanguageModelTools.js"; -import { ExtHostLanguages } from "./extHostLanguages.js"; -import { IExtHostLocalizationService } from "./extHostLocalizationService.js"; -import { IExtHostManagedSockets } from "./extHostManagedSockets.js"; -import { ExtHostMessageService } from "./extHostMessageService.js"; -import { ExtHostNotebookController } from "./extHostNotebook.js"; -import { ExtHostNotebookDocuments } from "./extHostNotebookDocuments.js"; -import { ExtHostNotebookDocumentSaveParticipant } from "./extHostNotebookDocumentSaveParticipant.js"; -import { ExtHostNotebookEditors } from "./extHostNotebookEditors.js"; -import { ExtHostNotebookKernels } from "./extHostNotebookKernels.js"; -import { ExtHostNotebookRenderers } from "./extHostNotebookRenderers.js"; -import { IExtHostOutputService } from "./extHostOutput.js"; -import { ExtHostProfileContentHandlers } from "./extHostProfileContentHandler.js"; -import { ExtHostProgress } from "./extHostProgress.js"; -import { ExtHostQuickDiff } from "./extHostQuickDiff.js"; -import { createExtHostQuickOpen } from "./extHostQuickOpen.js"; -import { IExtHostRpcService } from "./extHostRpcService.js"; -import { ExtHostSCM } from "./extHostSCM.js"; -import { IExtHostSearch } from "./extHostSearch.js"; -import { IExtHostSecretState } from "./extHostSecretState.js"; -import { ExtHostShare } from "./extHostShare.js"; -import { ExtHostSpeech } from "./extHostSpeech.js"; -import { ExtHostStatusBar } from "./extHostStatusBar.js"; -import { IExtHostStorage } from "./extHostStorage.js"; -import { IExtensionStoragePaths } from "./extHostStoragePaths.js"; -import { IExtHostTask } from "./extHostTask.js"; -import { - ExtHostTelemetryLogger, - IExtHostTelemetry, - isNewAppInstall, -} from "./extHostTelemetry.js"; -import { IExtHostTerminalService } from "./extHostTerminalService.js"; -import { IExtHostTerminalShellIntegration } from "./extHostTerminalShellIntegration.js"; -import { IExtHostTesting } from "./extHostTesting.js"; -import { ExtHostEditors } from "./extHostTextEditors.js"; -import { ExtHostTheming } from "./extHostTheming.js"; -import { ExtHostTimeline } from "./extHostTimeline.js"; -import { ExtHostTreeViews } from "./extHostTreeViews.js"; -import { IExtHostTunnelService } from "./extHostTunnelService.js"; -import * as typeConverters from "./extHostTypeConverters.js"; -import * as extHostTypes from "./extHostTypes.js"; -import { ExtHostUriOpeners } from "./extHostUriOpener.js"; -import { IURITransformerService } from "./extHostUriTransformerService.js"; -import { ExtHostUrls } from "./extHostUrls.js"; -import { ExtHostWebviews } from "./extHostWebview.js"; -import { ExtHostWebviewPanels } from "./extHostWebviewPanels.js"; -import { ExtHostWebviewViews } from "./extHostWebviewView.js"; -import { IExtHostWindow } from "./extHostWindow.js"; -import { IExtHostWorkspace } from "./extHostWorkspace.js"; +import { CancellationTokenSource } from '../../../base/common/cancellation.js'; +import * as errors from '../../../base/common/errors.js'; +import { Emitter, Event } from '../../../base/common/event.js'; +import { combinedDisposable } from '../../../base/common/lifecycle.js'; +import { Schemas, matchesScheme } from '../../../base/common/network.js'; +import Severity from '../../../base/common/severity.js'; +import { URI } from '../../../base/common/uri.js'; +import { TextEditorCursorStyle } from '../../../editor/common/config/editorOptions.js'; +import { score, targetsNotebooks } from '../../../editor/common/languageSelector.js'; +import * as languageConfiguration from '../../../editor/common/languages/languageConfiguration.js'; +import { OverviewRulerLane } from '../../../editor/common/model.js'; +import { ExtensionIdentifierSet, IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; +import * as files from '../../../platform/files/common/files.js'; +import { ServicesAccessor } from '../../../platform/instantiation/common/instantiation.js'; +import { ILogService, ILoggerService, LogLevel } from '../../../platform/log/common/log.js'; +import { getRemoteName } from '../../../platform/remote/common/remoteHosts.js'; +import { TelemetryTrustedValue } from '../../../platform/telemetry/common/telemetryUtils.js'; +import { EditSessionIdentityMatch } from '../../../platform/workspace/common/editSessions.js'; +import { CandidatePortSource, ExtHostContext, ExtHostLogLevelServiceShape, MainContext } from './extHost.protocol.js'; +import { ExtHostRelatedInformation } from './extHostAiRelatedInformation.js'; +import { ExtHostApiCommands } from './extHostApiCommands.js'; +import { IExtHostApiDeprecationService } from './extHostApiDeprecationService.js'; +import { IExtHostAuthentication } from './extHostAuthentication.js'; +import { ExtHostBulkEdits } from './extHostBulkEdits.js'; +import { ExtHostChatAgents2 } from './extHostChatAgents2.js'; +import { ExtHostChatVariables } from './extHostChatVariables.js'; +import { ExtHostClipboard } from './extHostClipboard.js'; +import { ExtHostEditorInsets } from './extHostCodeInsets.js'; +import { IExtHostCommands } from './extHostCommands.js'; +import { createExtHostComments } from './extHostComments.js'; +import { ExtHostConfigProvider, IExtHostConfiguration } from './extHostConfiguration.js'; +import { ExtHostCustomEditors } from './extHostCustomEditors.js'; +import { IExtHostDebugService } from './extHostDebugService.js'; +import { IExtHostDecorations } from './extHostDecorations.js'; +import { ExtHostDiagnostics } from './extHostDiagnostics.js'; +import { ExtHostDialogs } from './extHostDialogs.js'; +import { ExtHostDocumentContentProvider } from './extHostDocumentContentProviders.js'; +import { ExtHostDocumentSaveParticipant } from './extHostDocumentSaveParticipant.js'; +import { ExtHostDocuments } from './extHostDocuments.js'; +import { IExtHostDocumentsAndEditors } from './extHostDocumentsAndEditors.js'; +import { IExtHostEditorTabs } from './extHostEditorTabs.js'; +import { ExtHostEmbeddings } from './extHostEmbedding.js'; +import { ExtHostAiEmbeddingVector } from './extHostEmbeddingVector.js'; +import { Extension, IExtHostExtensionService } from './extHostExtensionService.js'; +import { ExtHostFileSystem } from './extHostFileSystem.js'; +import { IExtHostConsumerFileSystem } from './extHostFileSystemConsumer.js'; +import { ExtHostFileSystemEventService, FileSystemWatcherCreateOptions } from './extHostFileSystemEventService.js'; +import { IExtHostFileSystemInfo } from './extHostFileSystemInfo.js'; +import { IExtHostInitDataService } from './extHostInitDataService.js'; +import { ExtHostInteractive } from './extHostInteractive.js'; +import { ExtHostLabelService } from './extHostLabelService.js'; +import { ExtHostLanguageFeatures } from './extHostLanguageFeatures.js'; +import { ExtHostLanguageModelTools } from './extHostLanguageModelTools.js'; +import { IExtHostLanguageModels } from './extHostLanguageModels.js'; +import { ExtHostLanguages } from './extHostLanguages.js'; +import { IExtHostLocalizationService } from './extHostLocalizationService.js'; +import { IExtHostManagedSockets } from './extHostManagedSockets.js'; +import { ExtHostMessageService } from './extHostMessageService.js'; +import { ExtHostNotebookController } from './extHostNotebook.js'; +import { ExtHostNotebookDocumentSaveParticipant } from './extHostNotebookDocumentSaveParticipant.js'; +import { ExtHostNotebookDocuments } from './extHostNotebookDocuments.js'; +import { ExtHostNotebookEditors } from './extHostNotebookEditors.js'; +import { ExtHostNotebookKernels } from './extHostNotebookKernels.js'; +import { ExtHostNotebookRenderers } from './extHostNotebookRenderers.js'; +import { IExtHostOutputService } from './extHostOutput.js'; +import { ExtHostProfileContentHandlers } from './extHostProfileContentHandler.js'; +import { ExtHostProgress } from './extHostProgress.js'; +import { ExtHostQuickDiff } from './extHostQuickDiff.js'; +import { createExtHostQuickOpen } from './extHostQuickOpen.js'; +import { IExtHostRpcService } from './extHostRpcService.js'; +import { ExtHostSCM } from './extHostSCM.js'; +import { IExtHostSearch } from './extHostSearch.js'; +import { IExtHostSecretState } from './extHostSecretState.js'; +import { ExtHostShare } from './extHostShare.js'; +import { ExtHostSpeech } from './extHostSpeech.js'; +import { ExtHostStatusBar } from './extHostStatusBar.js'; +import { IExtHostStorage } from './extHostStorage.js'; +import { IExtensionStoragePaths } from './extHostStoragePaths.js'; +import { IExtHostTask } from './extHostTask.js'; +import { ExtHostTelemetryLogger, IExtHostTelemetry, isNewAppInstall } from './extHostTelemetry.js'; +import { IExtHostTerminalService } from './extHostTerminalService.js'; +import { IExtHostTerminalShellIntegration } from './extHostTerminalShellIntegration.js'; +import { IExtHostTesting } from './extHostTesting.js'; +import { ExtHostEditors } from './extHostTextEditors.js'; +import { ExtHostTheming } from './extHostTheming.js'; +import { ExtHostTimeline } from './extHostTimeline.js'; +import { ExtHostTreeViews } from './extHostTreeViews.js'; +import { IExtHostTunnelService } from './extHostTunnelService.js'; +import * as typeConverters from './extHostTypeConverters.js'; +import * as extHostTypes from './extHostTypes.js'; +import { ExtHostUriOpeners } from './extHostUriOpener.js'; +import { IURITransformerService } from './extHostUriTransformerService.js'; +import { ExtHostUrls } from './extHostUrls.js'; +import { ExtHostWebviews } from './extHostWebview.js'; +import { ExtHostWebviewPanels } from './extHostWebviewPanels.js'; +import { ExtHostWebviewViews } from './extHostWebviewView.js'; +import { IExtHostWindow } from './extHostWindow.js'; +import { IExtHostWorkspace } from './extHostWorkspace.js'; +import { DebugConfigurationProviderTriggerKind } from '../../contrib/debug/common/debug.js'; +import { ExtensionDescriptionRegistry } from '../../services/extensions/common/extensionDescriptionRegistry.js'; +import { UIKind } from '../../services/extensions/common/extensionHostProtocol.js'; +import { checkProposedApiEnabled, isProposedApiEnabled } from '../../services/extensions/common/extensions.js'; +import { ProxyIdentifier } from '../../services/extensions/common/proxyIdentifier.js'; +import { ExcludeSettingOptions, TextSearchCompleteMessageType, TextSearchContext2, TextSearchMatch2 } from '../../services/search/common/searchExtTypes.js'; +import type * as vscode from 'vscode'; +import { ExtHostCodeMapper } from './extHostCodeMapper.js'; export interface IExtensionRegistries { mine: ExtensionDescriptionRegistry; @@ -153,19 +116,14 @@ export interface IExtensionRegistries { } export interface IExtensionApiFactory { - ( - extension: IExtensionDescription, - extensionInfo: IExtensionRegistries, - configProvider: ExtHostConfigProvider, - ): typeof vscode; + (extension: IExtensionDescription, extensionInfo: IExtensionRegistries, configProvider: ExtHostConfigProvider): typeof vscode; } /** * This method instantiates and returns the extension API surface */ -export function createApiFactoryAndRegisterActors( - accessor: ServicesAccessor, -): IExtensionApiFactory { +export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): IExtensionApiFactory { + // services const initData = accessor.get(IExtHostInitDataService); const extHostFileSystemInfo = accessor.get(IExtHostFileSystemInfo); @@ -190,14 +148,8 @@ export function createApiFactoryAndRegisterActors( const extHostLanguageModels = accessor.get(IExtHostLanguageModels); // register addressable instances - rpcProtocol.set( - ExtHostContext.ExtHostFileSystemInfo, - extHostFileSystemInfo, - ); - rpcProtocol.set( - ExtHostContext.ExtHostLogLevelServiceShape, - (extHostLoggerService), - ); + rpcProtocol.set(ExtHostContext.ExtHostFileSystemInfo, extHostFileSystemInfo); + rpcProtocol.set(ExtHostContext.ExtHostLogLevelServiceShape, extHostLoggerService); rpcProtocol.set(ExtHostContext.ExtHostWorkspace, extHostWorkspace); rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration); rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService); @@ -207,373 +159,94 @@ export function createApiFactoryAndRegisterActors( rpcProtocol.set(ExtHostContext.ExtHostSecretState, extHostSecretState); rpcProtocol.set(ExtHostContext.ExtHostTelemetry, extHostTelemetry); rpcProtocol.set(ExtHostContext.ExtHostEditorTabs, extHostEditorTabs); - rpcProtocol.set( - ExtHostContext.ExtHostManagedSockets, - extHostManagedSockets, - ); - rpcProtocol.set( - ExtHostContext.ExtHostAuthentication, - extHostAuthentication, - ); + rpcProtocol.set(ExtHostContext.ExtHostManagedSockets, extHostManagedSockets); + rpcProtocol.set(ExtHostContext.ExtHostAuthentication, extHostAuthentication); rpcProtocol.set(ExtHostContext.ExtHostChatProvider, extHostLanguageModels); // automatically create and register addressable instances - const extHostDecorations = rpcProtocol.set( - ExtHostContext.ExtHostDecorations, - accessor.get(IExtHostDecorations), - ); - const extHostDocumentsAndEditors = rpcProtocol.set( - ExtHostContext.ExtHostDocumentsAndEditors, - accessor.get(IExtHostDocumentsAndEditors), - ); - const extHostCommands = rpcProtocol.set( - ExtHostContext.ExtHostCommands, - accessor.get(IExtHostCommands), - ); - const extHostTerminalService = rpcProtocol.set( - ExtHostContext.ExtHostTerminalService, - accessor.get(IExtHostTerminalService), - ); - const extHostTerminalShellIntegration = rpcProtocol.set( - ExtHostContext.ExtHostTerminalShellIntegration, - accessor.get(IExtHostTerminalShellIntegration), - ); - const extHostDebugService = rpcProtocol.set( - ExtHostContext.ExtHostDebugService, - accessor.get(IExtHostDebugService), - ); - const extHostSearch = rpcProtocol.set( - ExtHostContext.ExtHostSearch, - accessor.get(IExtHostSearch), - ); - const extHostTask = rpcProtocol.set( - ExtHostContext.ExtHostTask, - accessor.get(IExtHostTask), - ); - const extHostOutputService = rpcProtocol.set( - ExtHostContext.ExtHostOutputService, - accessor.get(IExtHostOutputService), - ); - const extHostLocalization = rpcProtocol.set( - ExtHostContext.ExtHostLocalization, - accessor.get(IExtHostLocalizationService), - ); + const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, accessor.get(IExtHostDecorations)); + const extHostDocumentsAndEditors = rpcProtocol.set(ExtHostContext.ExtHostDocumentsAndEditors, accessor.get(IExtHostDocumentsAndEditors)); + const extHostCommands = rpcProtocol.set(ExtHostContext.ExtHostCommands, accessor.get(IExtHostCommands)); + const extHostTerminalService = rpcProtocol.set(ExtHostContext.ExtHostTerminalService, accessor.get(IExtHostTerminalService)); + const extHostTerminalShellIntegration = rpcProtocol.set(ExtHostContext.ExtHostTerminalShellIntegration, accessor.get(IExtHostTerminalShellIntegration)); + const extHostDebugService = rpcProtocol.set(ExtHostContext.ExtHostDebugService, accessor.get(IExtHostDebugService)); + const extHostSearch = rpcProtocol.set(ExtHostContext.ExtHostSearch, accessor.get(IExtHostSearch)); + const extHostTask = rpcProtocol.set(ExtHostContext.ExtHostTask, accessor.get(IExtHostTask)); + const extHostOutputService = rpcProtocol.set(ExtHostContext.ExtHostOutputService, accessor.get(IExtHostOutputService)); + const extHostLocalization = rpcProtocol.set(ExtHostContext.ExtHostLocalization, accessor.get(IExtHostLocalizationService)); // manually create and register addressable instances - const extHostUrls = rpcProtocol.set( - ExtHostContext.ExtHostUrls, - new ExtHostUrls(rpcProtocol), - ); - const extHostDocuments = rpcProtocol.set( - ExtHostContext.ExtHostDocuments, - new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors), - ); - const extHostDocumentContentProviders = rpcProtocol.set( - ExtHostContext.ExtHostDocumentContentProviders, - new ExtHostDocumentContentProvider( - rpcProtocol, - extHostDocumentsAndEditors, - extHostLogService, - ), - ); - const extHostDocumentSaveParticipant = rpcProtocol.set( - ExtHostContext.ExtHostDocumentSaveParticipant, - new ExtHostDocumentSaveParticipant( - extHostLogService, - extHostDocuments, - rpcProtocol.getProxy(MainContext.MainThreadBulkEdits), - ), - ); - const extHostNotebook = rpcProtocol.set( - ExtHostContext.ExtHostNotebook, - new ExtHostNotebookController( - rpcProtocol, - extHostCommands, - extHostDocumentsAndEditors, - extHostDocuments, - extHostConsumerFileSystem, - extHostSearch, - extHostLogService, - ), - ); - const extHostNotebookDocuments = rpcProtocol.set( - ExtHostContext.ExtHostNotebookDocuments, - new ExtHostNotebookDocuments(extHostNotebook), - ); - const extHostNotebookEditors = rpcProtocol.set( - ExtHostContext.ExtHostNotebookEditors, - new ExtHostNotebookEditors(extHostLogService, extHostNotebook), - ); - const extHostNotebookKernels = rpcProtocol.set( - ExtHostContext.ExtHostNotebookKernels, - new ExtHostNotebookKernels( - rpcProtocol, - initData, - extHostNotebook, - extHostCommands, - extHostLogService, - ), - ); - const extHostNotebookRenderers = rpcProtocol.set( - ExtHostContext.ExtHostNotebookRenderers, - new ExtHostNotebookRenderers(rpcProtocol, extHostNotebook), - ); - const extHostNotebookDocumentSaveParticipant = rpcProtocol.set( - ExtHostContext.ExtHostNotebookDocumentSaveParticipant, - new ExtHostNotebookDocumentSaveParticipant( - extHostLogService, - extHostNotebook, - rpcProtocol.getProxy(MainContext.MainThreadBulkEdits), - ), - ); - const extHostEditors = rpcProtocol.set( - ExtHostContext.ExtHostEditors, - new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors), - ); - const extHostTreeViews = rpcProtocol.set( - ExtHostContext.ExtHostTreeViews, - new ExtHostTreeViews( - rpcProtocol.getProxy(MainContext.MainThreadTreeViews), - extHostCommands, - extHostLogService, - ), - ); - const extHostEditorInsets = rpcProtocol.set( - ExtHostContext.ExtHostEditorInsets, - new ExtHostEditorInsets( - rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), - extHostEditors, - initData.remote, - ), - ); - const extHostDiagnostics = rpcProtocol.set( - ExtHostContext.ExtHostDiagnostics, - new ExtHostDiagnostics( - rpcProtocol, - extHostLogService, - extHostFileSystemInfo, - extHostDocumentsAndEditors, - ), - ); - const extHostLanguages = rpcProtocol.set( - ExtHostContext.ExtHostLanguages, - new ExtHostLanguages( - rpcProtocol, - extHostDocuments, - extHostCommands.converter, - uriTransformer, - ), - ); - const extHostLanguageFeatures = rpcProtocol.set( - ExtHostContext.ExtHostLanguageFeatures, - new ExtHostLanguageFeatures( - rpcProtocol, - uriTransformer, - extHostDocuments, - extHostCommands, - extHostDiagnostics, - extHostLogService, - extHostApiDeprecation, - extHostTelemetry, - ), - ); - const extHostCodeMapper = rpcProtocol.set( - ExtHostContext.ExtHostCodeMapper, - new ExtHostCodeMapper(rpcProtocol), - ); - const extHostFileSystem = rpcProtocol.set( - ExtHostContext.ExtHostFileSystem, - new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures), - ); - const extHostFileSystemEvent = rpcProtocol.set( - ExtHostContext.ExtHostFileSystemEventService, - new ExtHostFileSystemEventService( - rpcProtocol, - extHostLogService, - extHostDocumentsAndEditors, - ), - ); - const extHostQuickOpen = rpcProtocol.set( - ExtHostContext.ExtHostQuickOpen, - createExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands), - ); - const extHostSCM = rpcProtocol.set( - ExtHostContext.ExtHostSCM, - new ExtHostSCM( - rpcProtocol, - extHostCommands, - extHostDocuments, - extHostLogService, - ), - ); - const extHostQuickDiff = rpcProtocol.set( - ExtHostContext.ExtHostQuickDiff, - new ExtHostQuickDiff(rpcProtocol, uriTransformer), - ); - const extHostShare = rpcProtocol.set( - ExtHostContext.ExtHostShare, - new ExtHostShare(rpcProtocol, uriTransformer), - ); - const extHostComment = rpcProtocol.set( - ExtHostContext.ExtHostComments, - createExtHostComments(rpcProtocol, extHostCommands, extHostDocuments), - ); - const extHostProgress = rpcProtocol.set( - ExtHostContext.ExtHostProgress, - new ExtHostProgress( - rpcProtocol.getProxy(MainContext.MainThreadProgress), - ), - ); - const extHostLabelService = rpcProtocol.set( - ExtHostContext.ExtHostLabelService, - new ExtHostLabelService(rpcProtocol), - ); - const extHostTheming = rpcProtocol.set( - ExtHostContext.ExtHostTheming, - new ExtHostTheming(rpcProtocol), - ); - const extHostTimeline = rpcProtocol.set( - ExtHostContext.ExtHostTimeline, - new ExtHostTimeline(rpcProtocol, extHostCommands), - ); - const extHostWebviews = rpcProtocol.set( - ExtHostContext.ExtHostWebviews, - new ExtHostWebviews( - rpcProtocol, - initData.remote, - extHostWorkspace, - extHostLogService, - extHostApiDeprecation, - ), - ); - const extHostWebviewPanels = rpcProtocol.set( - ExtHostContext.ExtHostWebviewPanels, - new ExtHostWebviewPanels( - rpcProtocol, - extHostWebviews, - extHostWorkspace, - ), - ); - const extHostCustomEditors = rpcProtocol.set( - ExtHostContext.ExtHostCustomEditors, - new ExtHostCustomEditors( - rpcProtocol, - extHostDocuments, - extensionStoragePaths, - extHostWebviews, - extHostWebviewPanels, - ), - ); - const extHostWebviewViews = rpcProtocol.set( - ExtHostContext.ExtHostWebviewViews, - new ExtHostWebviewViews(rpcProtocol, extHostWebviews), - ); - const extHostTesting = rpcProtocol.set( - ExtHostContext.ExtHostTesting, - accessor.get(IExtHostTesting), - ); - const extHostUriOpeners = rpcProtocol.set( - ExtHostContext.ExtHostUriOpeners, - new ExtHostUriOpeners(rpcProtocol), - ); - const extHostProfileContentHandlers = rpcProtocol.set( - ExtHostContext.ExtHostProfileContentHandlers, - new ExtHostProfileContentHandlers(rpcProtocol), - ); - rpcProtocol.set( - ExtHostContext.ExtHostInteractive, - new ExtHostInteractive( - rpcProtocol, - extHostNotebook, - extHostDocumentsAndEditors, - extHostCommands, - extHostLogService, - ), - ); - const extHostChatAgents2 = rpcProtocol.set( - ExtHostContext.ExtHostChatAgents2, - new ExtHostChatAgents2( - rpcProtocol, - extHostLogService, - extHostCommands, - extHostDocuments, - extHostLanguageModels, - ), - ); - const extHostChatVariables = rpcProtocol.set( - ExtHostContext.ExtHostChatVariables, - new ExtHostChatVariables(rpcProtocol), - ); - const extHostLanguageModelTools = rpcProtocol.set( - ExtHostContext.ExtHostLanguageModelTools, - new ExtHostLanguageModelTools(rpcProtocol), - ); - const extHostAiRelatedInformation = rpcProtocol.set( - ExtHostContext.ExtHostAiRelatedInformation, - new ExtHostRelatedInformation(rpcProtocol), - ); - const extHostAiEmbeddingVector = rpcProtocol.set( - ExtHostContext.ExtHostAiEmbeddingVector, - new ExtHostAiEmbeddingVector(rpcProtocol), - ); - const extHostStatusBar = rpcProtocol.set( - ExtHostContext.ExtHostStatusBar, - new ExtHostStatusBar(rpcProtocol, extHostCommands.converter), - ); - const extHostSpeech = rpcProtocol.set( - ExtHostContext.ExtHostSpeech, - new ExtHostSpeech(rpcProtocol), - ); - const extHostEmbeddings = rpcProtocol.set( - ExtHostContext.ExtHostEmbeddings, - new ExtHostEmbeddings(rpcProtocol), - ); + const extHostUrls = rpcProtocol.set(ExtHostContext.ExtHostUrls, new ExtHostUrls(rpcProtocol)); + const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors)); + const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService)); + const extHostDocumentSaveParticipant = rpcProtocol.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(extHostLogService, extHostDocuments, rpcProtocol.getProxy(MainContext.MainThreadBulkEdits))); + const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, extHostDocuments, extHostConsumerFileSystem, extHostSearch, extHostLogService)); + const extHostNotebookDocuments = rpcProtocol.set(ExtHostContext.ExtHostNotebookDocuments, new ExtHostNotebookDocuments(extHostNotebook)); + const extHostNotebookEditors = rpcProtocol.set(ExtHostContext.ExtHostNotebookEditors, new ExtHostNotebookEditors(extHostLogService, extHostNotebook)); + const extHostNotebookKernels = rpcProtocol.set(ExtHostContext.ExtHostNotebookKernels, new ExtHostNotebookKernels(rpcProtocol, initData, extHostNotebook, extHostCommands, extHostLogService)); + const extHostNotebookRenderers = rpcProtocol.set(ExtHostContext.ExtHostNotebookRenderers, new ExtHostNotebookRenderers(rpcProtocol, extHostNotebook)); + const extHostNotebookDocumentSaveParticipant = rpcProtocol.set(ExtHostContext.ExtHostNotebookDocumentSaveParticipant, new ExtHostNotebookDocumentSaveParticipant(extHostLogService, extHostNotebook, rpcProtocol.getProxy(MainContext.MainThreadBulkEdits))); + const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors)); + const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService)); + const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors, initData.remote)); + const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol, extHostLogService, extHostFileSystemInfo, extHostDocumentsAndEditors)); + const extHostLanguages = rpcProtocol.set(ExtHostContext.ExtHostLanguages, new ExtHostLanguages(rpcProtocol, extHostDocuments, extHostCommands.converter, uriTransformer)); + const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, uriTransformer, extHostDocuments, extHostCommands, extHostDiagnostics, extHostLogService, extHostApiDeprecation, extHostTelemetry)); + const extHostCodeMapper = rpcProtocol.set(ExtHostContext.ExtHostCodeMapper, new ExtHostCodeMapper(rpcProtocol)); + const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures)); + const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpcProtocol, extHostLogService, extHostDocumentsAndEditors)); + const extHostQuickOpen = rpcProtocol.set(ExtHostContext.ExtHostQuickOpen, createExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands)); + const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, extHostDocuments, extHostLogService)); + const extHostQuickDiff = rpcProtocol.set(ExtHostContext.ExtHostQuickDiff, new ExtHostQuickDiff(rpcProtocol, uriTransformer)); + const extHostShare = rpcProtocol.set(ExtHostContext.ExtHostShare, new ExtHostShare(rpcProtocol, uriTransformer)); + const extHostComment = rpcProtocol.set(ExtHostContext.ExtHostComments, createExtHostComments(rpcProtocol, extHostCommands, extHostDocuments)); + const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress))); + const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHostLabelService, new ExtHostLabelService(rpcProtocol)); + const extHostTheming = rpcProtocol.set(ExtHostContext.ExtHostTheming, new ExtHostTheming(rpcProtocol)); + const extHostTimeline = rpcProtocol.set(ExtHostContext.ExtHostTimeline, new ExtHostTimeline(rpcProtocol, extHostCommands)); + const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, initData.remote, extHostWorkspace, extHostLogService, extHostApiDeprecation)); + const extHostWebviewPanels = rpcProtocol.set(ExtHostContext.ExtHostWebviewPanels, new ExtHostWebviewPanels(rpcProtocol, extHostWebviews, extHostWorkspace)); + const extHostCustomEditors = rpcProtocol.set(ExtHostContext.ExtHostCustomEditors, new ExtHostCustomEditors(rpcProtocol, extHostDocuments, extensionStoragePaths, extHostWebviews, extHostWebviewPanels)); + const extHostWebviewViews = rpcProtocol.set(ExtHostContext.ExtHostWebviewViews, new ExtHostWebviewViews(rpcProtocol, extHostWebviews)); + const extHostTesting = rpcProtocol.set(ExtHostContext.ExtHostTesting, accessor.get(IExtHostTesting)); + const extHostUriOpeners = rpcProtocol.set(ExtHostContext.ExtHostUriOpeners, new ExtHostUriOpeners(rpcProtocol)); + const extHostProfileContentHandlers = rpcProtocol.set(ExtHostContext.ExtHostProfileContentHandlers, new ExtHostProfileContentHandlers(rpcProtocol)); + rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors, extHostCommands, extHostLogService)); + const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostLogService, extHostCommands, extHostDocuments, extHostLanguageModels)); + const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol)); + const extHostLanguageModelTools = rpcProtocol.set(ExtHostContext.ExtHostLanguageModelTools, new ExtHostLanguageModelTools(rpcProtocol)); + const extHostAiRelatedInformation = rpcProtocol.set(ExtHostContext.ExtHostAiRelatedInformation, new ExtHostRelatedInformation(rpcProtocol)); + const extHostAiEmbeddingVector = rpcProtocol.set(ExtHostContext.ExtHostAiEmbeddingVector, new ExtHostAiEmbeddingVector(rpcProtocol)); + const extHostStatusBar = rpcProtocol.set(ExtHostContext.ExtHostStatusBar, new ExtHostStatusBar(rpcProtocol, extHostCommands.converter)); + const extHostSpeech = rpcProtocol.set(ExtHostContext.ExtHostSpeech, new ExtHostSpeech(rpcProtocol)); + const extHostEmbeddings = rpcProtocol.set(ExtHostContext.ExtHostEmbeddings, new ExtHostEmbeddings(rpcProtocol)); // Check that no named customers are missing const expected = Object.values>(ExtHostContext); rpcProtocol.assertRegistered(expected); // Other instances - const extHostBulkEdits = new ExtHostBulkEdits( - rpcProtocol, - extHostDocumentsAndEditors, - ); + const extHostBulkEdits = new ExtHostBulkEdits(rpcProtocol, extHostDocumentsAndEditors); const extHostClipboard = new ExtHostClipboard(rpcProtocol); - const extHostMessageService = new ExtHostMessageService( - rpcProtocol, - extHostLogService, - ); + const extHostMessageService = new ExtHostMessageService(rpcProtocol, extHostLogService); const extHostDialogs = new ExtHostDialogs(rpcProtocol); // Register API-ish commands ExtHostApiCommands.register(extHostCommands); - return function ( - extension: IExtensionDescription, - extensionInfo: IExtensionRegistries, - configProvider: ExtHostConfigProvider, - ): typeof vscode { + return function (extension: IExtensionDescription, extensionInfo: IExtensionRegistries, configProvider: ExtHostConfigProvider): typeof vscode { + // Wraps an event with error handling and telemetry so that we know what extension fails // handling events. This will prevent us from reporting this as "our" error-telemetry and // allows for better blaming - function _asExtensionEvent( - actual: vscode.Event, - ): vscode.Event { + function _asExtensionEvent(actual: vscode.Event): vscode.Event { return (listener, thisArgs, disposables) => { - const handle = actual((e) => { + const handle = actual(e => { try { listener.call(thisArgs, e); } catch (err) { - errors.onUnexpectedExternalError( - new Error( - `[ExtensionListenerError] Extension '${extension.identifier.value}' FAILED to handle event: ${err.toString()}`, - { cause: err }, - ), - ); - extHostTelemetry.onExtensionError( - extension.identifier, - err, - ); + errors.onUnexpectedExternalError(new Error(`[ExtensionListenerError] Extension '${extension.identifier.value}' FAILED to handle event: ${err.toString()}`, { cause: err })); + extHostTelemetry.onExtensionError(extension.identifier, err); } }); disposables?.push(handle); @@ -581,6 +254,7 @@ export function createApiFactoryAndRegisterActors( }; } + // Check document selectors for being overly generic. Technically this isn't a problem but // in practice many extensions say they support `fooLang` but need fs-access to do so. Those // extension should specify then the `file`-scheme, e.g. `{ scheme: 'fooLang', language: 'fooLang' }` @@ -590,29 +264,22 @@ export function createApiFactoryAndRegisterActors( let done = !extension.isUnderDevelopment; function informOnce() { if (!done) { - extHostLogService.info( - `Extension '${extension.identifier.value}' uses a document selector without scheme. Learn more about this: https://go.microsoft.com/fwlink/?linkid=872305`, - ); + extHostLogService.info(`Extension '${extension.identifier.value}' uses a document selector without scheme. Learn more about this: https://go.microsoft.com/fwlink/?linkid=872305`); done = true; } } - return function perform( - selector: vscode.DocumentSelector, - ): vscode.DocumentSelector { + return function perform(selector: vscode.DocumentSelector): vscode.DocumentSelector { if (Array.isArray(selector)) { selector.forEach(perform); - } else if (typeof selector === "string") { + } else if (typeof selector === 'string') { informOnce(); } else { const filter = selector as vscode.DocumentFilter; // TODO: microsoft/TypeScript#42768 - if (typeof filter.scheme === "undefined") { + if (typeof filter.scheme === 'undefined') { informOnce(); } - if (typeof filter.exclusive === "boolean") { - checkProposedApiEnabled( - extension, - "documentFiltersExclusive", - ); + if (typeof filter.exclusive === 'boolean') { + checkProposedApiEnabled(extension, 'documentFiltersExclusive'); } } return selector; @@ -620,240 +287,112 @@ export function createApiFactoryAndRegisterActors( })(); const authentication: typeof vscode.authentication = { - getSession( - providerId: string, - scopes: readonly string[], - options?: vscode.AuthenticationGetSessionOptions, - ) { - if ( - typeof options?.forceNewSession === "object" && - options.forceNewSession.learnMore - ) { - checkProposedApiEnabled(extension, "authLearnMore"); + getSession(providerId: string, scopes: readonly string[], options?: vscode.AuthenticationGetSessionOptions) { + if (typeof options?.forceNewSession === 'object' && options.forceNewSession.learnMore) { + checkProposedApiEnabled(extension, 'authLearnMore'); } - return extHostAuthentication.getSession( - extension, - providerId, - scopes, - options as any, - ); + return extHostAuthentication.getSession(extension, providerId, scopes, options as any); }, getAccounts(providerId: string) { return extHostAuthentication.getAccounts(providerId); }, // TODO: remove this after GHPR and Codespaces move off of it async hasSession(providerId: string, scopes: readonly string[]) { - checkProposedApiEnabled(extension, "authSession"); - return !!(await extHostAuthentication.getSession( - extension, - providerId, - scopes, - { silent: true } as any, - )); + checkProposedApiEnabled(extension, 'authSession'); + return !!(await extHostAuthentication.getSession(extension, providerId, scopes, { silent: true } as any)); }, get onDidChangeSessions(): vscode.Event { - return _asExtensionEvent( - extHostAuthentication.getExtensionScopedSessionsEvent( - extension.identifier.value, - ), - ); - }, - registerAuthenticationProvider( - id: string, - label: string, - provider: vscode.AuthenticationProvider, - options?: vscode.AuthenticationProviderOptions, - ): vscode.Disposable { - return extHostAuthentication.registerAuthenticationProvider( - id, - label, - provider, - options, - ); + return _asExtensionEvent(extHostAuthentication.getExtensionScopedSessionsEvent(extension.identifier.value)); }, + registerAuthenticationProvider(id: string, label: string, provider: vscode.AuthenticationProvider, options?: vscode.AuthenticationProviderOptions): vscode.Disposable { + return extHostAuthentication.registerAuthenticationProvider(id, label, provider, options); + } }; // namespace: commands const commands: typeof vscode.commands = { - registerCommand( - id: string, - command: (...args: any[]) => T | Thenable, - thisArgs?: any, - ): vscode.Disposable { - return extHostCommands.registerCommand( - true, - id, - command, - thisArgs, - undefined, - extension, - ); - }, - registerTextEditorCommand( - id: string, - callback: ( - textEditor: vscode.TextEditor, - edit: vscode.TextEditorEdit, - ...args: any[] - ) => void, - thisArg?: any, - ): vscode.Disposable { - return extHostCommands.registerCommand( - true, - id, - (...args: any[]): any => { - const activeTextEditor = - extHostEditors.getActiveTextEditor(); - if (!activeTextEditor) { - extHostLogService.warn( - "Cannot execute " + - id + - " because there is no active text editor.", - ); - return undefined; - } + registerCommand(id: string, command: (...args: any[]) => T | Thenable, thisArgs?: any): vscode.Disposable { + return extHostCommands.registerCommand(true, id, command, thisArgs, undefined, extension); + }, + registerTextEditorCommand(id: string, callback: (textEditor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args: any[]) => void, thisArg?: any): vscode.Disposable { + return extHostCommands.registerCommand(true, id, (...args: any[]): any => { + const activeTextEditor = extHostEditors.getActiveTextEditor(); + if (!activeTextEditor) { + extHostLogService.warn('Cannot execute ' + id + ' because there is no active text editor.'); + return undefined; + } - return activeTextEditor - .edit((edit: vscode.TextEditorEdit) => { - callback.apply(thisArg, [ - activeTextEditor, - edit, - ...args, - ]); - }) - .then( - (result) => { - if (!result) { - extHostLogService.warn( - "Edits from command " + - id + - " were not applied.", - ); - } - }, - (err) => { - extHostLogService.warn( - "An error occurred while running command " + - id, - err, - ); - }, - ); - }, - undefined, - undefined, - extension, - ); - }, - registerDiffInformationCommand: ( - id: string, - callback: (diff: vscode.LineChange[], ...args: any[]) => any, - thisArg?: any, - ): vscode.Disposable => { - checkProposedApiEnabled(extension, "diffCommand"); - return extHostCommands.registerCommand( - true, - id, - async (...args: any[]): Promise => { - const activeTextEditor = - extHostDocumentsAndEditors.activeEditor(true); - if (!activeTextEditor) { - extHostLogService.warn( - "Cannot execute " + - id + - " because there is no active text editor.", - ); - return undefined; + return activeTextEditor.edit((edit: vscode.TextEditorEdit) => { + callback.apply(thisArg, [activeTextEditor, edit, ...args]); + + }).then((result) => { + if (!result) { + extHostLogService.warn('Edits from command ' + id + ' were not applied.'); } + }, (err) => { + extHostLogService.warn('An error occurred while running command ' + id, err); + }); + }, undefined, undefined, extension); + }, + registerDiffInformationCommand: (id: string, callback: (diff: vscode.LineChange[], ...args: any[]) => any, thisArg?: any): vscode.Disposable => { + checkProposedApiEnabled(extension, 'diffCommand'); + return extHostCommands.registerCommand(true, id, async (...args: any[]): Promise => { + const activeTextEditor = extHostDocumentsAndEditors.activeEditor(true); + if (!activeTextEditor) { + extHostLogService.warn('Cannot execute ' + id + ' because there is no active text editor.'); + return undefined; + } - const diff = await extHostEditors.getDiffInformation( - activeTextEditor.id, - ); - callback.apply(thisArg, [diff, ...args]); - }, - undefined, - undefined, - extension, - ); + const diff = await extHostEditors.getDiffInformation(activeTextEditor.id); + callback.apply(thisArg, [diff, ...args]); + }, undefined, undefined, extension); }, executeCommand(id: string, ...args: any[]): Thenable { return extHostCommands.executeCommand(id, ...args); }, getCommands(filterInternal: boolean = false): Thenable { return extHostCommands.getCommands(filterInternal); - }, + } }; // namespace: env const env: typeof vscode.env = { - get machineId() { - return initData.telemetryInfo.machineId; - }, - get sessionId() { - return initData.telemetryInfo.sessionId; - }, - get language() { - return initData.environment.appLanguage; - }, - get appName() { - return initData.environment.appName; - }, - get appRoot() { - return initData.environment.appRoot?.fsPath ?? ""; - }, - get appHost() { - return initData.environment.appHost; - }, - get uriScheme() { - return initData.environment.appUriScheme; - }, - get clipboard(): vscode.Clipboard { - return extHostClipboard.value; - }, + get machineId() { return initData.telemetryInfo.machineId; }, + get sessionId() { return initData.telemetryInfo.sessionId; }, + get language() { return initData.environment.appLanguage; }, + get appName() { return initData.environment.appName; }, + get appRoot() { return initData.environment.appRoot?.fsPath ?? ''; }, + get appHost() { return initData.environment.appHost; }, + get uriScheme() { return initData.environment.appUriScheme; }, + get clipboard(): vscode.Clipboard { return extHostClipboard.value; }, get shell() { return extHostTerminalService.getDefaultShell(false); }, get onDidChangeShell() { - return _asExtensionEvent( - extHostTerminalService.onDidChangeShell, - ); + return _asExtensionEvent(extHostTerminalService.onDidChangeShell); }, get isTelemetryEnabled() { return extHostTelemetry.getTelemetryConfiguration(); }, get onDidChangeTelemetryEnabled(): vscode.Event { - return _asExtensionEvent( - extHostTelemetry.onDidChangeTelemetryEnabled, - ); + return _asExtensionEvent(extHostTelemetry.onDidChangeTelemetryEnabled); }, get telemetryConfiguration(): vscode.TelemetryConfiguration { - checkProposedApiEnabled(extension, "telemetry"); + checkProposedApiEnabled(extension, 'telemetry'); return extHostTelemetry.getTelemetryDetails(); }, get onDidChangeTelemetryConfiguration(): vscode.Event { - checkProposedApiEnabled(extension, "telemetry"); - return _asExtensionEvent( - extHostTelemetry.onDidChangeTelemetryConfiguration, - ); + checkProposedApiEnabled(extension, 'telemetry'); + return _asExtensionEvent(extHostTelemetry.onDidChangeTelemetryConfiguration); }, get isNewAppInstall() { return isNewAppInstall(initData.telemetryInfo.firstSessionDate); }, - createTelemetryLogger( - sender: vscode.TelemetrySender, - options?: vscode.TelemetryLoggerOptions, - ): vscode.TelemetryLogger { + createTelemetryLogger(sender: vscode.TelemetrySender, options?: vscode.TelemetryLoggerOptions): vscode.TelemetryLogger { ExtHostTelemetryLogger.validateSender(sender); - return extHostTelemetry.instantiateLogger( - extension, - sender, - options, - ); + return extHostTelemetry.instantiateLogger(extension, sender, options); }, - openExternal( - uri: URI, - options?: { allowContributedOpeners?: boolean | string }, - ) { + openExternal(uri: URI, options?: { allowContributedOpeners?: boolean | string }) { return extHostWindow.openUri(uri, { allowTunneling: !!initData.remote.authority, allowContributedOpeners: options?.allowContributedOpeners, @@ -865,14 +404,9 @@ export function createApiFactoryAndRegisterActors( } try { - return await extHostWindow.asExternalUri(uri, { - allowTunneling: !!initData.remote.authority, - }); + return await extHostWindow.asExternalUri(uri, { allowTunneling: !!initData.remote.authority }); } catch (err) { - if ( - matchesScheme(uri, Schemas.http) || - matchesScheme(uri, Schemas.https) - ) { + if (matchesScheme(uri, Schemas.http) || matchesScheme(uri, Schemas.https)) { return uri; } @@ -883,7 +417,7 @@ export function createApiFactoryAndRegisterActors( return getRemoteName(initData.remote.authority); }, get remoteAuthority() { - checkProposedApiEnabled(extension, "resolvers"); + checkProposedApiEnabled(extension, 'resolvers'); return initData.remote.authority; }, get uiKind() { @@ -896,13 +430,13 @@ export function createApiFactoryAndRegisterActors( return _asExtensionEvent(extHostLogService.onDidChangeLogLevel); }, get appQuality(): string | undefined { - checkProposedApiEnabled(extension, "resolvers"); + checkProposedApiEnabled(extension, 'resolvers'); return initData.quality; }, get appCommit(): string | undefined { - checkProposedApiEnabled(extension, "resolvers"); + checkProposedApiEnabled(extension, 'resolvers'); return initData.commit; - }, + } }; if (!initData.environment.extensionTestsLocationURI) { // allow to patch env-function when running tests @@ -911,38 +445,27 @@ export function createApiFactoryAndRegisterActors( // namespace: tests const tests: typeof vscode.tests = { - createTestController( - provider, - label, - refreshHandler?: ( - token: vscode.CancellationToken, - ) => Thenable | void, - ) { - return extHostTesting.createTestController( - extension, - provider, - label, - refreshHandler, - ); + createTestController(provider, label, refreshHandler?: (token: vscode.CancellationToken) => Thenable | void) { + return extHostTesting.createTestController(extension, provider, label, refreshHandler); }, createTestObserver() { - checkProposedApiEnabled(extension, "testObserver"); + checkProposedApiEnabled(extension, 'testObserver'); return extHostTesting.createTestObserver(); }, runTests(provider) { - checkProposedApiEnabled(extension, "testObserver"); + checkProposedApiEnabled(extension, 'testObserver'); return extHostTesting.runTests(provider); }, registerTestFollowupProvider(provider) { - checkProposedApiEnabled(extension, "testObserver"); + checkProposedApiEnabled(extension, 'testObserver'); return extHostTesting.registerTestFollowupProvider(provider); }, get onDidChangeTestResults() { - checkProposedApiEnabled(extension, "testObserver"); + checkProposedApiEnabled(extension, 'testObserver'); return _asExtensionEvent(extHostTesting.onResultsChanged); }, get testResults() { - checkProposedApiEnabled(extension, "testObserver"); + checkProposedApiEnabled(extension, 'testObserver'); return extHostTesting.results; }, }; @@ -953,35 +476,18 @@ export function createApiFactoryAndRegisterActors( : extHostTypes.ExtensionKind.UI; const extensions: typeof vscode.extensions = { - getExtension( - extensionId: string, - includeFromDifferentExtensionHosts?: boolean, - ): vscode.Extension | undefined { - if (!isProposedApiEnabled(extension, "extensionsAny")) { + getExtension(extensionId: string, includeFromDifferentExtensionHosts?: boolean): vscode.Extension | undefined { + if (!isProposedApiEnabled(extension, 'extensionsAny')) { includeFromDifferentExtensionHosts = false; } - const mine = - extensionInfo.mine.getExtensionDescription(extensionId); + const mine = extensionInfo.mine.getExtensionDescription(extensionId); if (mine) { - return new Extension( - extensionService, - extension.identifier, - mine, - extensionKind, - false, - ); + return new Extension(extensionService, extension.identifier, mine, extensionKind, false); } if (includeFromDifferentExtensionHosts) { - const foreign = - extensionInfo.all.getExtensionDescription(extensionId); + const foreign = extensionInfo.all.getExtensionDescription(extensionId); if (foreign) { - return new Extension( - extensionService, - extension.identifier, - foreign, - extensionKind /* TODO@alexdima THIS IS WRONG */, - true, - ); + return new Extension(extensionService, extension.identifier, foreign, extensionKind /* TODO@alexdima THIS IS WRONG */, true); } } return undefined; @@ -989,69 +495,35 @@ export function createApiFactoryAndRegisterActors( get all(): vscode.Extension[] { const result: vscode.Extension[] = []; for (const desc of extensionInfo.mine.getAllExtensionDescriptions()) { - result.push( - new Extension( - extensionService, - extension.identifier, - desc, - extensionKind, - false, - ), - ); + result.push(new Extension(extensionService, extension.identifier, desc, extensionKind, false)); } return result; }, get allAcrossExtensionHosts(): vscode.Extension[] { - checkProposedApiEnabled(extension, "extensionsAny"); - const local = new ExtensionIdentifierSet( - extensionInfo.mine - .getAllExtensionDescriptions() - .map((desc) => desc.identifier), - ); + checkProposedApiEnabled(extension, 'extensionsAny'); + const local = new ExtensionIdentifierSet(extensionInfo.mine.getAllExtensionDescriptions().map(desc => desc.identifier)); const result: vscode.Extension[] = []; for (const desc of extensionInfo.all.getAllExtensionDescriptions()) { - const isFromDifferentExtensionHost = !local.has( - desc.identifier, - ); - result.push( - new Extension( - extensionService, - extension.identifier, - desc, - extensionKind /* TODO@alexdima THIS IS WRONG */, - isFromDifferentExtensionHost, - ), - ); + const isFromDifferentExtensionHost = !local.has(desc.identifier); + result.push(new Extension(extensionService, extension.identifier, desc, extensionKind /* TODO@alexdima THIS IS WRONG */, isFromDifferentExtensionHost)); } return result; }, get onDidChange() { - if (isProposedApiEnabled(extension, "extensionsAny")) { - return _asExtensionEvent( - Event.any( - extensionInfo.mine.onDidChange, - extensionInfo.all.onDidChange, - ), - ); + if (isProposedApiEnabled(extension, 'extensionsAny')) { + return _asExtensionEvent(Event.any(extensionInfo.mine.onDidChange, extensionInfo.all.onDidChange)); } return _asExtensionEvent(extensionInfo.mine.onDidChange); - }, + } }; // namespace: languages const languages: typeof vscode.languages = { - createDiagnosticCollection( - name?: string, - ): vscode.DiagnosticCollection { - return extHostDiagnostics.createDiagnosticCollection( - extension.identifier, - name, - ); + createDiagnosticCollection(name?: string): vscode.DiagnosticCollection { + return extHostDiagnostics.createDiagnosticCollection(extension.identifier, name); }, get onDidChangeDiagnostics() { - return _asExtensionEvent( - extHostDiagnostics.onDidChangeDiagnostics, - ); + return _asExtensionEvent(extHostDiagnostics.onDidChangeDiagnostics); }, getDiagnostics: (resource?: vscode.Uri) => { return extHostDiagnostics.getDiagnostics(resource); @@ -1059,471 +531,147 @@ export function createApiFactoryAndRegisterActors( getLanguages(): Thenable { return extHostLanguages.getLanguages(); }, - setTextDocumentLanguage( - document: vscode.TextDocument, - languageId: string, - ): Thenable { - return extHostLanguages.changeLanguage( - document.uri, - languageId, - ); + setTextDocumentLanguage(document: vscode.TextDocument, languageId: string): Thenable { + return extHostLanguages.changeLanguage(document.uri, languageId); }, - match( - selector: vscode.DocumentSelector, - document: vscode.TextDocument, - ): number { - const interalSelector = - typeConverters.LanguageSelector.from(selector); + match(selector: vscode.DocumentSelector, document: vscode.TextDocument): number { + const interalSelector = typeConverters.LanguageSelector.from(selector); let notebook: vscode.NotebookDocument | undefined; if (targetsNotebooks(interalSelector)) { - notebook = extHostNotebook.notebookDocuments.find((value) => - value.apiNotebook - .getCells() - .find((c) => c.document === document), - )?.apiNotebook; + notebook = extHostNotebook.notebookDocuments.find(value => value.apiNotebook.getCells().find(c => c.document === document))?.apiNotebook; } - return score( - interalSelector, - document.uri, - document.languageId, - true, - notebook?.uri, - notebook?.notebookType, - ); + return score(interalSelector, document.uri, document.languageId, true, notebook?.uri, notebook?.notebookType); }, - registerCodeActionsProvider( - selector: vscode.DocumentSelector, - provider: vscode.CodeActionProvider, - metadata?: vscode.CodeActionProviderMetadata, - ): vscode.Disposable { - return extHostLanguageFeatures.registerCodeActionProvider( - extension, - checkSelector(selector), - provider, - metadata, - ); + registerCodeActionsProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider, metadata?: vscode.CodeActionProviderMetadata): vscode.Disposable { + return extHostLanguageFeatures.registerCodeActionProvider(extension, checkSelector(selector), provider, metadata); }, - registerDocumentPasteEditProvider( - selector: vscode.DocumentSelector, - provider: vscode.DocumentPasteEditProvider, - metadata: vscode.DocumentPasteProviderMetadata, - ): vscode.Disposable { - checkProposedApiEnabled(extension, "documentPaste"); - return extHostLanguageFeatures.registerDocumentPasteEditProvider( - extension, - checkSelector(selector), - provider, - metadata, - ); + registerDocumentPasteEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentPasteEditProvider, metadata: vscode.DocumentPasteProviderMetadata): vscode.Disposable { + checkProposedApiEnabled(extension, 'documentPaste'); + return extHostLanguageFeatures.registerDocumentPasteEditProvider(extension, checkSelector(selector), provider, metadata); }, - registerCodeLensProvider( - selector: vscode.DocumentSelector, - provider: vscode.CodeLensProvider, - ): vscode.Disposable { - return extHostLanguageFeatures.registerCodeLensProvider( - extension, - checkSelector(selector), - provider, - ); + registerCodeLensProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable { + return extHostLanguageFeatures.registerCodeLensProvider(extension, checkSelector(selector), provider); }, - registerDefinitionProvider( - selector: vscode.DocumentSelector, - provider: vscode.DefinitionProvider, - ): vscode.Disposable { - return extHostLanguageFeatures.registerDefinitionProvider( - extension, - checkSelector(selector), - provider, - ); + registerDefinitionProvider(selector: vscode.DocumentSelector, provider: vscode.DefinitionProvider): vscode.Disposable { + return extHostLanguageFeatures.registerDefinitionProvider(extension, checkSelector(selector), provider); }, - registerDeclarationProvider( - selector: vscode.DocumentSelector, - provider: vscode.DeclarationProvider, - ): vscode.Disposable { - return extHostLanguageFeatures.registerDeclarationProvider( - extension, - checkSelector(selector), - provider, - ); + registerDeclarationProvider(selector: vscode.DocumentSelector, provider: vscode.DeclarationProvider): vscode.Disposable { + return extHostLanguageFeatures.registerDeclarationProvider(extension, checkSelector(selector), provider); }, - registerImplementationProvider( - selector: vscode.DocumentSelector, - provider: vscode.ImplementationProvider, - ): vscode.Disposable { - return extHostLanguageFeatures.registerImplementationProvider( - extension, - checkSelector(selector), - provider, - ); + registerImplementationProvider(selector: vscode.DocumentSelector, provider: vscode.ImplementationProvider): vscode.Disposable { + return extHostLanguageFeatures.registerImplementationProvider(extension, checkSelector(selector), provider); }, - registerTypeDefinitionProvider( - selector: vscode.DocumentSelector, - provider: vscode.TypeDefinitionProvider, - ): vscode.Disposable { - return extHostLanguageFeatures.registerTypeDefinitionProvider( - extension, - checkSelector(selector), - provider, - ); + registerTypeDefinitionProvider(selector: vscode.DocumentSelector, provider: vscode.TypeDefinitionProvider): vscode.Disposable { + return extHostLanguageFeatures.registerTypeDefinitionProvider(extension, checkSelector(selector), provider); }, - registerHoverProvider( - selector: vscode.DocumentSelector, - provider: vscode.HoverProvider, - ): vscode.Disposable { - return extHostLanguageFeatures.registerHoverProvider( - extension, - checkSelector(selector), - provider, - extension.identifier, - ); + registerHoverProvider(selector: vscode.DocumentSelector, provider: vscode.HoverProvider): vscode.Disposable { + return extHostLanguageFeatures.registerHoverProvider(extension, checkSelector(selector), provider, extension.identifier); }, - registerEvaluatableExpressionProvider( - selector: vscode.DocumentSelector, - provider: vscode.EvaluatableExpressionProvider, - ): vscode.Disposable { - return extHostLanguageFeatures.registerEvaluatableExpressionProvider( - extension, - checkSelector(selector), - provider, - extension.identifier, - ); + registerEvaluatableExpressionProvider(selector: vscode.DocumentSelector, provider: vscode.EvaluatableExpressionProvider): vscode.Disposable { + return extHostLanguageFeatures.registerEvaluatableExpressionProvider(extension, checkSelector(selector), provider, extension.identifier); }, - registerInlineValuesProvider( - selector: vscode.DocumentSelector, - provider: vscode.InlineValuesProvider, - ): vscode.Disposable { - return extHostLanguageFeatures.registerInlineValuesProvider( - extension, - checkSelector(selector), - provider, - extension.identifier, - ); + registerInlineValuesProvider(selector: vscode.DocumentSelector, provider: vscode.InlineValuesProvider): vscode.Disposable { + return extHostLanguageFeatures.registerInlineValuesProvider(extension, checkSelector(selector), provider, extension.identifier); }, - registerDocumentHighlightProvider( - selector: vscode.DocumentSelector, - provider: vscode.DocumentHighlightProvider, - ): vscode.Disposable { - return extHostLanguageFeatures.registerDocumentHighlightProvider( - extension, - checkSelector(selector), - provider, - ); + registerDocumentHighlightProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable { + return extHostLanguageFeatures.registerDocumentHighlightProvider(extension, checkSelector(selector), provider); }, - registerMultiDocumentHighlightProvider( - selector: vscode.DocumentSelector, - provider: vscode.MultiDocumentHighlightProvider, - ): vscode.Disposable { - return extHostLanguageFeatures.registerMultiDocumentHighlightProvider( - extension, - checkSelector(selector), - provider, - ); + registerMultiDocumentHighlightProvider(selector: vscode.DocumentSelector, provider: vscode.MultiDocumentHighlightProvider): vscode.Disposable { + return extHostLanguageFeatures.registerMultiDocumentHighlightProvider(extension, checkSelector(selector), provider); }, - registerLinkedEditingRangeProvider( - selector: vscode.DocumentSelector, - provider: vscode.LinkedEditingRangeProvider, - ): vscode.Disposable { - return extHostLanguageFeatures.registerLinkedEditingRangeProvider( - extension, - checkSelector(selector), - provider, - ); + registerLinkedEditingRangeProvider(selector: vscode.DocumentSelector, provider: vscode.LinkedEditingRangeProvider): vscode.Disposable { + return extHostLanguageFeatures.registerLinkedEditingRangeProvider(extension, checkSelector(selector), provider); }, - registerReferenceProvider( - selector: vscode.DocumentSelector, - provider: vscode.ReferenceProvider, - ): vscode.Disposable { - return extHostLanguageFeatures.registerReferenceProvider( - extension, - checkSelector(selector), - provider, - ); + registerReferenceProvider(selector: vscode.DocumentSelector, provider: vscode.ReferenceProvider): vscode.Disposable { + return extHostLanguageFeatures.registerReferenceProvider(extension, checkSelector(selector), provider); }, - registerRenameProvider( - selector: vscode.DocumentSelector, - provider: vscode.RenameProvider, - ): vscode.Disposable { - return extHostLanguageFeatures.registerRenameProvider( - extension, - checkSelector(selector), - provider, - ); + registerRenameProvider(selector: vscode.DocumentSelector, provider: vscode.RenameProvider): vscode.Disposable { + return extHostLanguageFeatures.registerRenameProvider(extension, checkSelector(selector), provider); }, - registerNewSymbolNamesProvider( - selector: vscode.DocumentSelector, - provider: vscode.NewSymbolNamesProvider, - ): vscode.Disposable { - checkProposedApiEnabled(extension, "newSymbolNamesProvider"); - return extHostLanguageFeatures.registerNewSymbolNamesProvider( - extension, - checkSelector(selector), - provider, - ); + registerNewSymbolNamesProvider(selector: vscode.DocumentSelector, provider: vscode.NewSymbolNamesProvider): vscode.Disposable { + checkProposedApiEnabled(extension, 'newSymbolNamesProvider'); + return extHostLanguageFeatures.registerNewSymbolNamesProvider(extension, checkSelector(selector), provider); }, - registerDocumentSymbolProvider( - selector: vscode.DocumentSelector, - provider: vscode.DocumentSymbolProvider, - metadata?: vscode.DocumentSymbolProviderMetadata, - ): vscode.Disposable { - return extHostLanguageFeatures.registerDocumentSymbolProvider( - extension, - checkSelector(selector), - provider, - metadata, - ); + registerDocumentSymbolProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentSymbolProvider, metadata?: vscode.DocumentSymbolProviderMetadata): vscode.Disposable { + return extHostLanguageFeatures.registerDocumentSymbolProvider(extension, checkSelector(selector), provider, metadata); }, - registerWorkspaceSymbolProvider( - provider: vscode.WorkspaceSymbolProvider, - ): vscode.Disposable { - return extHostLanguageFeatures.registerWorkspaceSymbolProvider( - extension, - provider, - ); + registerWorkspaceSymbolProvider(provider: vscode.WorkspaceSymbolProvider): vscode.Disposable { + return extHostLanguageFeatures.registerWorkspaceSymbolProvider(extension, provider); }, - registerDocumentFormattingEditProvider( - selector: vscode.DocumentSelector, - provider: vscode.DocumentFormattingEditProvider, - ): vscode.Disposable { - return extHostLanguageFeatures.registerDocumentFormattingEditProvider( - extension, - checkSelector(selector), - provider, - ); + registerDocumentFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentFormattingEditProvider): vscode.Disposable { + return extHostLanguageFeatures.registerDocumentFormattingEditProvider(extension, checkSelector(selector), provider); }, - registerDocumentRangeFormattingEditProvider( - selector: vscode.DocumentSelector, - provider: vscode.DocumentRangeFormattingEditProvider, - ): vscode.Disposable { - return extHostLanguageFeatures.registerDocumentRangeFormattingEditProvider( - extension, - checkSelector(selector), - provider, - ); + registerDocumentRangeFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentRangeFormattingEditProvider): vscode.Disposable { + return extHostLanguageFeatures.registerDocumentRangeFormattingEditProvider(extension, checkSelector(selector), provider); }, - registerOnTypeFormattingEditProvider( - selector: vscode.DocumentSelector, - provider: vscode.OnTypeFormattingEditProvider, - firstTriggerCharacter: string, - ...moreTriggerCharacters: string[] - ): vscode.Disposable { - return extHostLanguageFeatures.registerOnTypeFormattingEditProvider( - extension, - checkSelector(selector), - provider, - [firstTriggerCharacter].concat(moreTriggerCharacters), - ); + registerOnTypeFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.OnTypeFormattingEditProvider, firstTriggerCharacter: string, ...moreTriggerCharacters: string[]): vscode.Disposable { + return extHostLanguageFeatures.registerOnTypeFormattingEditProvider(extension, checkSelector(selector), provider, [firstTriggerCharacter].concat(moreTriggerCharacters)); }, - registerDocumentSemanticTokensProvider( - selector: vscode.DocumentSelector, - provider: vscode.DocumentSemanticTokensProvider, - legend: vscode.SemanticTokensLegend, - ): vscode.Disposable { - return extHostLanguageFeatures.registerDocumentSemanticTokensProvider( - extension, - checkSelector(selector), - provider, - legend, - ); + registerDocumentSemanticTokensProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentSemanticTokensProvider, legend: vscode.SemanticTokensLegend): vscode.Disposable { + return extHostLanguageFeatures.registerDocumentSemanticTokensProvider(extension, checkSelector(selector), provider, legend); }, - registerDocumentRangeSemanticTokensProvider( - selector: vscode.DocumentSelector, - provider: vscode.DocumentRangeSemanticTokensProvider, - legend: vscode.SemanticTokensLegend, - ): vscode.Disposable { - return extHostLanguageFeatures.registerDocumentRangeSemanticTokensProvider( - extension, - checkSelector(selector), - provider, - legend, - ); + registerDocumentRangeSemanticTokensProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentRangeSemanticTokensProvider, legend: vscode.SemanticTokensLegend): vscode.Disposable { + return extHostLanguageFeatures.registerDocumentRangeSemanticTokensProvider(extension, checkSelector(selector), provider, legend); }, - registerSignatureHelpProvider( - selector: vscode.DocumentSelector, - provider: vscode.SignatureHelpProvider, - firstItem?: string | vscode.SignatureHelpProviderMetadata, - ...remaining: string[] - ): vscode.Disposable { - if (typeof firstItem === "object") { - return extHostLanguageFeatures.registerSignatureHelpProvider( - extension, - checkSelector(selector), - provider, - firstItem, - ); + registerSignatureHelpProvider(selector: vscode.DocumentSelector, provider: vscode.SignatureHelpProvider, firstItem?: string | vscode.SignatureHelpProviderMetadata, ...remaining: string[]): vscode.Disposable { + if (typeof firstItem === 'object') { + return extHostLanguageFeatures.registerSignatureHelpProvider(extension, checkSelector(selector), provider, firstItem); } - return extHostLanguageFeatures.registerSignatureHelpProvider( - extension, - checkSelector(selector), - provider, - typeof firstItem === "undefined" - ? [] - : [firstItem, ...remaining], - ); + return extHostLanguageFeatures.registerSignatureHelpProvider(extension, checkSelector(selector), provider, typeof firstItem === 'undefined' ? [] : [firstItem, ...remaining]); }, - registerCompletionItemProvider( - selector: vscode.DocumentSelector, - provider: vscode.CompletionItemProvider, - ...triggerCharacters: string[] - ): vscode.Disposable { - return extHostLanguageFeatures.registerCompletionItemProvider( - extension, - checkSelector(selector), - provider, - triggerCharacters, - ); + registerCompletionItemProvider(selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, ...triggerCharacters: string[]): vscode.Disposable { + return extHostLanguageFeatures.registerCompletionItemProvider(extension, checkSelector(selector), provider, triggerCharacters); }, - registerInlineCompletionItemProvider( - selector: vscode.DocumentSelector, - provider: vscode.InlineCompletionItemProvider, - metadata?: vscode.InlineCompletionItemProviderMetadata, - ): vscode.Disposable { + registerInlineCompletionItemProvider(selector: vscode.DocumentSelector, provider: vscode.InlineCompletionItemProvider, metadata?: vscode.InlineCompletionItemProviderMetadata): vscode.Disposable { if (provider.handleDidShowCompletionItem) { - checkProposedApiEnabled( - extension, - "inlineCompletionsAdditions", - ); + checkProposedApiEnabled(extension, 'inlineCompletionsAdditions'); } if (provider.handleDidPartiallyAcceptCompletionItem) { - checkProposedApiEnabled( - extension, - "inlineCompletionsAdditions", - ); + checkProposedApiEnabled(extension, 'inlineCompletionsAdditions'); } if (metadata) { - checkProposedApiEnabled( - extension, - "inlineCompletionsAdditions", - ); + checkProposedApiEnabled(extension, 'inlineCompletionsAdditions'); } - return extHostLanguageFeatures.registerInlineCompletionsProvider( - extension, - checkSelector(selector), - provider, - metadata, - ); + return extHostLanguageFeatures.registerInlineCompletionsProvider(extension, checkSelector(selector), provider, metadata); }, - registerInlineEditProvider( - selector: vscode.DocumentSelector, - provider: vscode.InlineEditProvider, - ): vscode.Disposable { - checkProposedApiEnabled(extension, "inlineEdit"); - return extHostLanguageFeatures.registerInlineEditProvider( - extension, - checkSelector(selector), - provider, - ); + registerInlineEditProvider(selector: vscode.DocumentSelector, provider: vscode.InlineEditProvider): vscode.Disposable { + checkProposedApiEnabled(extension, 'inlineEdit'); + return extHostLanguageFeatures.registerInlineEditProvider(extension, checkSelector(selector), provider); }, - registerDocumentLinkProvider( - selector: vscode.DocumentSelector, - provider: vscode.DocumentLinkProvider, - ): vscode.Disposable { - return extHostLanguageFeatures.registerDocumentLinkProvider( - extension, - checkSelector(selector), - provider, - ); + registerDocumentLinkProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentLinkProvider): vscode.Disposable { + return extHostLanguageFeatures.registerDocumentLinkProvider(extension, checkSelector(selector), provider); }, - registerColorProvider( - selector: vscode.DocumentSelector, - provider: vscode.DocumentColorProvider, - ): vscode.Disposable { - return extHostLanguageFeatures.registerColorProvider( - extension, - checkSelector(selector), - provider, - ); + registerColorProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentColorProvider): vscode.Disposable { + return extHostLanguageFeatures.registerColorProvider(extension, checkSelector(selector), provider); }, - registerFoldingRangeProvider( - selector: vscode.DocumentSelector, - provider: vscode.FoldingRangeProvider, - ): vscode.Disposable { - return extHostLanguageFeatures.registerFoldingRangeProvider( - extension, - checkSelector(selector), - provider, - ); + registerFoldingRangeProvider(selector: vscode.DocumentSelector, provider: vscode.FoldingRangeProvider): vscode.Disposable { + return extHostLanguageFeatures.registerFoldingRangeProvider(extension, checkSelector(selector), provider); }, - registerSelectionRangeProvider( - selector: vscode.DocumentSelector, - provider: vscode.SelectionRangeProvider, - ): vscode.Disposable { - return extHostLanguageFeatures.registerSelectionRangeProvider( - extension, - selector, - provider, - ); + registerSelectionRangeProvider(selector: vscode.DocumentSelector, provider: vscode.SelectionRangeProvider): vscode.Disposable { + return extHostLanguageFeatures.registerSelectionRangeProvider(extension, selector, provider); }, - registerCallHierarchyProvider( - selector: vscode.DocumentSelector, - provider: vscode.CallHierarchyProvider, - ): vscode.Disposable { - return extHostLanguageFeatures.registerCallHierarchyProvider( - extension, - selector, - provider, - ); + registerCallHierarchyProvider(selector: vscode.DocumentSelector, provider: vscode.CallHierarchyProvider): vscode.Disposable { + return extHostLanguageFeatures.registerCallHierarchyProvider(extension, selector, provider); }, - registerTypeHierarchyProvider( - selector: vscode.DocumentSelector, - provider: vscode.TypeHierarchyProvider, - ): vscode.Disposable { - return extHostLanguageFeatures.registerTypeHierarchyProvider( - extension, - selector, - provider, - ); + registerTypeHierarchyProvider(selector: vscode.DocumentSelector, provider: vscode.TypeHierarchyProvider): vscode.Disposable { + return extHostLanguageFeatures.registerTypeHierarchyProvider(extension, selector, provider); }, - setLanguageConfiguration: ( - language: string, - configuration: vscode.LanguageConfiguration, - ): vscode.Disposable => { - return extHostLanguageFeatures.setLanguageConfiguration( - extension, - language, - configuration, - ); + setLanguageConfiguration: (language: string, configuration: vscode.LanguageConfiguration): vscode.Disposable => { + return extHostLanguageFeatures.setLanguageConfiguration(extension, language, configuration); }, - getTokenInformationAtPosition( - doc: vscode.TextDocument, - pos: vscode.Position, - ) { - checkProposedApiEnabled(extension, "tokenInformation"); + getTokenInformationAtPosition(doc: vscode.TextDocument, pos: vscode.Position) { + checkProposedApiEnabled(extension, 'tokenInformation'); return extHostLanguages.tokenAtPosition(doc, pos); }, - registerInlayHintsProvider( - selector: vscode.DocumentSelector, - provider: vscode.InlayHintsProvider, - ): vscode.Disposable { - return extHostLanguageFeatures.registerInlayHintsProvider( - extension, - selector, - provider, - ); - }, - createLanguageStatusItem( - id: string, - selector: vscode.DocumentSelector, - ): vscode.LanguageStatusItem { - return extHostLanguages.createLanguageStatusItem( - extension, - id, - selector, - ); + registerInlayHintsProvider(selector: vscode.DocumentSelector, provider: vscode.InlayHintsProvider): vscode.Disposable { + return extHostLanguageFeatures.registerInlayHintsProvider(extension, selector, provider); }, - registerDocumentDropEditProvider( - selector: vscode.DocumentSelector, - provider: vscode.DocumentDropEditProvider, - metadata?: vscode.DocumentDropEditProviderMetadata, - ): vscode.Disposable { - return extHostLanguageFeatures.registerDocumentOnDropEditProvider( - extension, - selector, - provider, - isProposedApiEnabled(extension, "documentPaste") - ? metadata - : undefined, - ); + createLanguageStatusItem(id: string, selector: vscode.DocumentSelector): vscode.LanguageStatusItem { + return extHostLanguages.createLanguageStatusItem(extension, id, selector); }, + registerDocumentDropEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentDropEditProvider, metadata?: vscode.DocumentDropEditProviderMetadata): vscode.Disposable { + return extHostLanguageFeatures.registerDocumentOnDropEditProvider(extension, selector, provider, isProposedApiEnabled(extension, 'documentPaste') ? metadata : undefined); + } }; // namespace: window @@ -1540,233 +688,96 @@ export function createApiFactoryAndRegisterActors( get terminals() { return extHostTerminalService.terminals; }, - async showTextDocument( - documentOrUri: vscode.TextDocument | vscode.Uri, - columnOrOptions?: - | vscode.ViewColumn - | vscode.TextDocumentShowOptions, - preserveFocus?: boolean, - ): Promise { - if ( - URI.isUri(documentOrUri) && - documentOrUri.scheme === Schemas.vscodeRemote && - !documentOrUri.authority - ) { - extHostApiDeprecation.report( - "workspace.showTextDocument", - extension, - `A URI of 'vscode-remote' scheme requires an authority.`, - ); + async showTextDocument(documentOrUri: vscode.TextDocument | vscode.Uri, columnOrOptions?: vscode.ViewColumn | vscode.TextDocumentShowOptions, preserveFocus?: boolean): Promise { + if (URI.isUri(documentOrUri) && documentOrUri.scheme === Schemas.vscodeRemote && !documentOrUri.authority) { + extHostApiDeprecation.report('workspace.showTextDocument', extension, `A URI of 'vscode-remote' scheme requires an authority.`); } const document = await (URI.isUri(documentOrUri) ? Promise.resolve(workspace.openTextDocument(documentOrUri)) : Promise.resolve(documentOrUri)); - return extHostEditors.showTextDocument( - document, - columnOrOptions, - preserveFocus, - ); + return extHostEditors.showTextDocument(document, columnOrOptions, preserveFocus); }, - createTextEditorDecorationType( - options: vscode.DecorationRenderOptions, - ): vscode.TextEditorDecorationType { - return extHostEditors.createTextEditorDecorationType( - extension, - options, - ); + createTextEditorDecorationType(options: vscode.DecorationRenderOptions): vscode.TextEditorDecorationType { + return extHostEditors.createTextEditorDecorationType(extension, options); }, onDidChangeActiveTextEditor(listener, thisArg?, disposables?) { - return _asExtensionEvent( - extHostEditors.onDidChangeActiveTextEditor, - )(listener, thisArg, disposables); + return _asExtensionEvent(extHostEditors.onDidChangeActiveTextEditor)(listener, thisArg, disposables); }, onDidChangeVisibleTextEditors(listener, thisArg, disposables) { - return _asExtensionEvent( - extHostEditors.onDidChangeVisibleTextEditors, - )(listener, thisArg, disposables); - }, - onDidChangeTextEditorSelection( - listener: (e: vscode.TextEditorSelectionChangeEvent) => any, - thisArgs?: any, - disposables?: extHostTypes.Disposable[], - ) { - return _asExtensionEvent( - extHostEditors.onDidChangeTextEditorSelection, - )(listener, thisArgs, disposables); - }, - onDidChangeTextEditorOptions( - listener: (e: vscode.TextEditorOptionsChangeEvent) => any, - thisArgs?: any, - disposables?: extHostTypes.Disposable[], - ) { - return _asExtensionEvent( - extHostEditors.onDidChangeTextEditorOptions, - )(listener, thisArgs, disposables); - }, - onDidChangeTextEditorVisibleRanges( - listener: (e: vscode.TextEditorVisibleRangesChangeEvent) => any, - thisArgs?: any, - disposables?: extHostTypes.Disposable[], - ) { - return _asExtensionEvent( - extHostEditors.onDidChangeTextEditorVisibleRanges, - )(listener, thisArgs, disposables); + return _asExtensionEvent(extHostEditors.onDidChangeVisibleTextEditors)(listener, thisArg, disposables); + }, + onDidChangeTextEditorSelection(listener: (e: vscode.TextEditorSelectionChangeEvent) => any, thisArgs?: any, disposables?: extHostTypes.Disposable[]) { + return _asExtensionEvent(extHostEditors.onDidChangeTextEditorSelection)(listener, thisArgs, disposables); + }, + onDidChangeTextEditorOptions(listener: (e: vscode.TextEditorOptionsChangeEvent) => any, thisArgs?: any, disposables?: extHostTypes.Disposable[]) { + return _asExtensionEvent(extHostEditors.onDidChangeTextEditorOptions)(listener, thisArgs, disposables); + }, + onDidChangeTextEditorVisibleRanges(listener: (e: vscode.TextEditorVisibleRangesChangeEvent) => any, thisArgs?: any, disposables?: extHostTypes.Disposable[]) { + return _asExtensionEvent(extHostEditors.onDidChangeTextEditorVisibleRanges)(listener, thisArgs, disposables); }, onDidChangeTextEditorViewColumn(listener, thisArg?, disposables?) { - return _asExtensionEvent( - extHostEditors.onDidChangeTextEditorViewColumn, - )(listener, thisArg, disposables); - }, - onDidChangeTextEditorDiffInformation( - listener, - thisArg?, - disposables?, - ) { - checkProposedApiEnabled(extension, "textEditorDiffInformation"); - return _asExtensionEvent( - extHostEditors.onDidChangeTextEditorDiffInformation, - )(listener, thisArg, disposables); + return _asExtensionEvent(extHostEditors.onDidChangeTextEditorViewColumn)(listener, thisArg, disposables); + }, + onDidChangeTextEditorDiffInformation(listener, thisArg?, disposables?) { + checkProposedApiEnabled(extension, 'textEditorDiffInformation'); + return _asExtensionEvent(extHostEditors.onDidChangeTextEditorDiffInformation)(listener, thisArg, disposables); }, onDidCloseTerminal(listener, thisArg?, disposables?) { - return _asExtensionEvent( - extHostTerminalService.onDidCloseTerminal, - )(listener, thisArg, disposables); + return _asExtensionEvent(extHostTerminalService.onDidCloseTerminal)(listener, thisArg, disposables); }, onDidOpenTerminal(listener, thisArg?, disposables?) { - return _asExtensionEvent( - extHostTerminalService.onDidOpenTerminal, - )(listener, thisArg, disposables); + return _asExtensionEvent(extHostTerminalService.onDidOpenTerminal)(listener, thisArg, disposables); }, onDidChangeActiveTerminal(listener, thisArg?, disposables?) { - return _asExtensionEvent( - extHostTerminalService.onDidChangeActiveTerminal, - )(listener, thisArg, disposables); + return _asExtensionEvent(extHostTerminalService.onDidChangeActiveTerminal)(listener, thisArg, disposables); }, onDidChangeTerminalDimensions(listener, thisArg?, disposables?) { - checkProposedApiEnabled(extension, "terminalDimensions"); - return _asExtensionEvent( - extHostTerminalService.onDidChangeTerminalDimensions, - )(listener, thisArg, disposables); + checkProposedApiEnabled(extension, 'terminalDimensions'); + return _asExtensionEvent(extHostTerminalService.onDidChangeTerminalDimensions)(listener, thisArg, disposables); }, onDidChangeTerminalState(listener, thisArg?, disposables?) { - return _asExtensionEvent( - extHostTerminalService.onDidChangeTerminalState, - )(listener, thisArg, disposables); + return _asExtensionEvent(extHostTerminalService.onDidChangeTerminalState)(listener, thisArg, disposables); }, onDidWriteTerminalData(listener, thisArg?, disposables?) { - checkProposedApiEnabled(extension, "terminalDataWriteEvent"); - return _asExtensionEvent( - extHostTerminalService.onDidWriteTerminalData, - )(listener, thisArg, disposables); + checkProposedApiEnabled(extension, 'terminalDataWriteEvent'); + return _asExtensionEvent(extHostTerminalService.onDidWriteTerminalData)(listener, thisArg, disposables); }, onDidExecuteTerminalCommand(listener, thisArg?, disposables?) { - checkProposedApiEnabled( - extension, - "terminalExecuteCommandEvent", - ); - return _asExtensionEvent( - extHostTerminalService.onDidExecuteTerminalCommand, - )(listener, thisArg, disposables); - }, - onDidChangeTerminalShellIntegration( - listener, - thisArg?, - disposables?, - ) { - return _asExtensionEvent( - extHostTerminalShellIntegration.onDidChangeTerminalShellIntegration, - )(listener, thisArg, disposables); + checkProposedApiEnabled(extension, 'terminalExecuteCommandEvent'); + return _asExtensionEvent(extHostTerminalService.onDidExecuteTerminalCommand)(listener, thisArg, disposables); + }, + onDidChangeTerminalShellIntegration(listener, thisArg?, disposables?) { + return _asExtensionEvent(extHostTerminalShellIntegration.onDidChangeTerminalShellIntegration)(listener, thisArg, disposables); }, onDidStartTerminalShellExecution(listener, thisArg?, disposables?) { - return _asExtensionEvent( - extHostTerminalShellIntegration.onDidStartTerminalShellExecution, - )(listener, thisArg, disposables); + return _asExtensionEvent(extHostTerminalShellIntegration.onDidStartTerminalShellExecution)(listener, thisArg, disposables); }, onDidEndTerminalShellExecution(listener, thisArg?, disposables?) { - return _asExtensionEvent( - extHostTerminalShellIntegration.onDidEndTerminalShellExecution, - )(listener, thisArg, disposables); + return _asExtensionEvent(extHostTerminalShellIntegration.onDidEndTerminalShellExecution)(listener, thisArg, disposables); }, get state() { return extHostWindow.getState(); }, onDidChangeWindowState(listener, thisArg?, disposables?) { - return _asExtensionEvent(extHostWindow.onDidChangeWindowState)( - listener, - thisArg, - disposables, - ); + return _asExtensionEvent(extHostWindow.onDidChangeWindowState)(listener, thisArg, disposables); }, - showInformationMessage( - message: string, - ...rest: Array< - vscode.MessageOptions | string | vscode.MessageItem - > - ) { - return >( - extHostMessageService.showMessage( - extension, - Severity.Info, - message, - rest[0], - >rest.slice(1), - ) - ); + showInformationMessage(message: string, ...rest: Array) { + return >extHostMessageService.showMessage(extension, Severity.Info, message, rest[0], >rest.slice(1)); }, - showWarningMessage( - message: string, - ...rest: Array< - vscode.MessageOptions | string | vscode.MessageItem - > - ) { - return >( - extHostMessageService.showMessage( - extension, - Severity.Warning, - message, - rest[0], - >rest.slice(1), - ) - ); + showWarningMessage(message: string, ...rest: Array) { + return >extHostMessageService.showMessage(extension, Severity.Warning, message, rest[0], >rest.slice(1)); }, - showErrorMessage( - message: string, - ...rest: Array< - vscode.MessageOptions | string | vscode.MessageItem - > - ) { - return >( - extHostMessageService.showMessage( - extension, - Severity.Error, - message, - rest[0], - >rest.slice(1), - ) - ); + showErrorMessage(message: string, ...rest: Array) { + return >extHostMessageService.showMessage(extension, Severity.Error, message, rest[0], >rest.slice(1)); }, - showQuickPick( - items: any, - options?: vscode.QuickPickOptions, - token?: vscode.CancellationToken, - ): any { - return extHostQuickOpen.showQuickPick( - extension, - items, - options, - token, - ); + showQuickPick(items: any, options?: vscode.QuickPickOptions, token?: vscode.CancellationToken): any { + return extHostQuickOpen.showQuickPick(extension, items, options, token); }, - showWorkspaceFolderPick( - options?: vscode.WorkspaceFolderPickOptions, - ) { + showWorkspaceFolderPick(options?: vscode.WorkspaceFolderPickOptions) { return extHostQuickOpen.showWorkspaceFolderPick(options); }, - showInputBox( - options?: vscode.InputBoxOptions, - token?: vscode.CancellationToken, - ) { + showInputBox(options?: vscode.InputBoxOptions, token?: vscode.CancellationToken) { return extHostQuickOpen.showInput(options, token); }, showOpenDialog(options) { @@ -1775,16 +786,12 @@ export function createApiFactoryAndRegisterActors( showSaveDialog(options) { return extHostDialogs.showSaveDialog(options); }, - createStatusBarItem( - alignmentOrId?: vscode.StatusBarAlignment | string, - priorityOrAlignment?: number | vscode.StatusBarAlignment, - priorityArg?: number, - ): vscode.StatusBarItem { + createStatusBarItem(alignmentOrId?: vscode.StatusBarAlignment | string, priorityOrAlignment?: number | vscode.StatusBarAlignment, priorityArg?: number): vscode.StatusBarItem { let id: string | undefined; let alignment: number | undefined; let priority: number | undefined; - if (typeof alignmentOrId === "string") { + if (typeof alignmentOrId === 'string') { id = alignmentOrId; alignment = priorityOrAlignment; priority = priorityArg; @@ -1793,223 +800,72 @@ export function createApiFactoryAndRegisterActors( priority = priorityOrAlignment; } - return extHostStatusBar.createStatusBarEntry( - extension, - id, - alignment, - priority, - ); + return extHostStatusBar.createStatusBarEntry(extension, id, alignment, priority); }, - setStatusBarMessage( - text: string, - timeoutOrThenable?: number | Thenable, - ): vscode.Disposable { - return extHostStatusBar.setStatusBarMessage( - text, - timeoutOrThenable, - ); + setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable): vscode.Disposable { + return extHostStatusBar.setStatusBarMessage(text, timeoutOrThenable); }, - withScmProgress( - task: (progress: vscode.Progress) => Thenable, - ) { - extHostApiDeprecation.report( - "window.withScmProgress", - extension, - `Use 'withProgress' instead.`, - ); + withScmProgress(task: (progress: vscode.Progress) => Thenable) { + extHostApiDeprecation.report('window.withScmProgress', extension, + `Use 'withProgress' instead.`); - return extHostProgress.withProgress( - extension, - { location: extHostTypes.ProgressLocation.SourceControl }, - (progress, token) => - task({ - report(n: number) { - /*noop*/ - }, - }), - ); + return extHostProgress.withProgress(extension, { location: extHostTypes.ProgressLocation.SourceControl }, (progress, token) => task({ report(n: number) { /*noop*/ } })); }, - withProgress( - options: vscode.ProgressOptions, - task: ( - progress: vscode.Progress<{ - message?: string; - worked?: number; - }>, - token: vscode.CancellationToken, - ) => Thenable, - ) { + withProgress(options: vscode.ProgressOptions, task: (progress: vscode.Progress<{ message?: string; worked?: number }>, token: vscode.CancellationToken) => Thenable) { return extHostProgress.withProgress(extension, options, task); }, - createOutputChannel( - name: string, - options: string | { log: true } | undefined, - ): any { - return extHostOutputService.createOutputChannel( - name, - options, - extension, - ); + createOutputChannel(name: string, options: string | { log: true } | undefined): any { + return extHostOutputService.createOutputChannel(name, options, extension); }, - createWebviewPanel( - viewType: string, - title: string, - showOptions: - | vscode.ViewColumn - | { - viewColumn: vscode.ViewColumn; - preserveFocus?: boolean; - }, - options?: vscode.WebviewPanelOptions & vscode.WebviewOptions, - ): vscode.WebviewPanel { - return extHostWebviewPanels.createWebviewPanel( - extension, - viewType, - title, - showOptions, - options, - ); + createWebviewPanel(viewType: string, title: string, showOptions: vscode.ViewColumn | { viewColumn: vscode.ViewColumn; preserveFocus?: boolean }, options?: vscode.WebviewPanelOptions & vscode.WebviewOptions): vscode.WebviewPanel { + return extHostWebviewPanels.createWebviewPanel(extension, viewType, title, showOptions, options); }, - createWebviewTextEditorInset( - editor: vscode.TextEditor, - line: number, - height: number, - options?: vscode.WebviewOptions, - ): vscode.WebviewEditorInset { - checkProposedApiEnabled(extension, "editorInsets"); - return extHostEditorInsets.createWebviewEditorInset( - editor, - line, - height, - options, - extension, - ); + createWebviewTextEditorInset(editor: vscode.TextEditor, line: number, height: number, options?: vscode.WebviewOptions): vscode.WebviewEditorInset { + checkProposedApiEnabled(extension, 'editorInsets'); + return extHostEditorInsets.createWebviewEditorInset(editor, line, height, options, extension); }, - createTerminal( - nameOrOptions?: - | vscode.TerminalOptions - | vscode.ExtensionTerminalOptions - | string, - shellPath?: string, - shellArgs?: readonly string[] | string, - ): vscode.Terminal { - if (typeof nameOrOptions === "object") { - if ("pty" in nameOrOptions) { - return extHostTerminalService.createExtensionTerminal( - nameOrOptions, - ); + createTerminal(nameOrOptions?: vscode.TerminalOptions | vscode.ExtensionTerminalOptions | string, shellPath?: string, shellArgs?: readonly string[] | string): vscode.Terminal { + if (typeof nameOrOptions === 'object') { + if ('pty' in nameOrOptions) { + return extHostTerminalService.createExtensionTerminal(nameOrOptions); } - return extHostTerminalService.createTerminalFromOptions( - nameOrOptions, - ); + return extHostTerminalService.createTerminalFromOptions(nameOrOptions); } - return extHostTerminalService.createTerminal( - nameOrOptions, - shellPath, - shellArgs, - ); + return extHostTerminalService.createTerminal(nameOrOptions, shellPath, shellArgs); }, - registerTerminalLinkProvider( - provider: vscode.TerminalLinkProvider, - ): vscode.Disposable { + registerTerminalLinkProvider(provider: vscode.TerminalLinkProvider): vscode.Disposable { return extHostTerminalService.registerLinkProvider(provider); }, - registerTerminalProfileProvider( - id: string, - provider: vscode.TerminalProfileProvider, - ): vscode.Disposable { - return extHostTerminalService.registerProfileProvider( - extension, - id, - provider, - ); + registerTerminalProfileProvider(id: string, provider: vscode.TerminalProfileProvider): vscode.Disposable { + return extHostTerminalService.registerProfileProvider(extension, id, provider); }, - registerTerminalCompletionProvider( - provider: vscode.TerminalCompletionProvider, - ...triggerCharacters: string[] - ): vscode.Disposable { - checkProposedApiEnabled( - extension, - "terminalCompletionProvider", - ); - return extHostTerminalService.registerTerminalCompletionProvider( - extension, - provider, - ...triggerCharacters, - ); + registerTerminalCompletionProvider(provider: vscode.TerminalCompletionProvider, ...triggerCharacters: string[]): vscode.Disposable { + checkProposedApiEnabled(extension, 'terminalCompletionProvider'); + return extHostTerminalService.registerTerminalCompletionProvider(extension, provider, ...triggerCharacters); }, - registerTerminalQuickFixProvider( - id: string, - provider: vscode.TerminalQuickFixProvider, - ): vscode.Disposable { - checkProposedApiEnabled(extension, "terminalQuickFixProvider"); - return extHostTerminalService.registerTerminalQuickFixProvider( - id, - extension.identifier.value, - provider, - ); + registerTerminalQuickFixProvider(id: string, provider: vscode.TerminalQuickFixProvider): vscode.Disposable { + checkProposedApiEnabled(extension, 'terminalQuickFixProvider'); + return extHostTerminalService.registerTerminalQuickFixProvider(id, extension.identifier.value, provider); }, - registerTreeDataProvider( - viewId: string, - treeDataProvider: vscode.TreeDataProvider, - ): vscode.Disposable { - return extHostTreeViews.registerTreeDataProvider( - viewId, - treeDataProvider, - extension, - ); + registerTreeDataProvider(viewId: string, treeDataProvider: vscode.TreeDataProvider): vscode.Disposable { + return extHostTreeViews.registerTreeDataProvider(viewId, treeDataProvider, extension); }, - createTreeView( - viewId: string, - options: { treeDataProvider: vscode.TreeDataProvider }, - ): vscode.TreeView { - return extHostTreeViews.createTreeView( - viewId, - options, - extension, - ); + createTreeView(viewId: string, options: { treeDataProvider: vscode.TreeDataProvider }): vscode.TreeView { + return extHostTreeViews.createTreeView(viewId, options, extension); }, - registerWebviewPanelSerializer: ( - viewType: string, - serializer: vscode.WebviewPanelSerializer, - ) => { - return extHostWebviewPanels.registerWebviewPanelSerializer( - extension, - viewType, - serializer, - ); + registerWebviewPanelSerializer: (viewType: string, serializer: vscode.WebviewPanelSerializer) => { + return extHostWebviewPanels.registerWebviewPanelSerializer(extension, viewType, serializer); }, - registerCustomEditorProvider: ( - viewType: string, - provider: - | vscode.CustomTextEditorProvider - | vscode.CustomReadonlyEditorProvider, - options: { - webviewOptions?: vscode.WebviewPanelOptions; - supportsMultipleEditorsPerDocument?: boolean; - } = {}, - ) => { - return extHostCustomEditors.registerCustomEditorProvider( - extension, - viewType, - provider, - options, - ); + registerCustomEditorProvider: (viewType: string, provider: vscode.CustomTextEditorProvider | vscode.CustomReadonlyEditorProvider, options: { webviewOptions?: vscode.WebviewPanelOptions; supportsMultipleEditorsPerDocument?: boolean } = {}) => { + return extHostCustomEditors.registerCustomEditorProvider(extension, viewType, provider, options); }, - registerFileDecorationProvider( - provider: vscode.FileDecorationProvider, - ) { - return extHostDecorations.registerFileDecorationProvider( - provider, - extension, - ); + registerFileDecorationProvider(provider: vscode.FileDecorationProvider) { + return extHostDecorations.registerFileDecorationProvider(provider, extension); }, registerUriHandler(handler: vscode.UriHandler) { return extHostUrls.registerUriHandler(extension, handler); }, - createQuickPick< - T extends vscode.QuickPickItem, - >(): vscode.QuickPick { + createQuickPick(): vscode.QuickPick { return extHostQuickOpen.createQuickPick(extension); }, createInputBox(): vscode.InputBox { @@ -2019,134 +875,72 @@ export function createApiFactoryAndRegisterActors( return extHostTheming.activeColorTheme; }, onDidChangeActiveColorTheme(listener, thisArg?, disposables?) { - return _asExtensionEvent( - extHostTheming.onDidChangeActiveColorTheme, - )(listener, thisArg, disposables); - }, - registerWebviewViewProvider( - viewId: string, - provider: vscode.WebviewViewProvider, - options?: { - webviewOptions?: { - retainContextWhenHidden?: boolean; - }; - }, - ) { - return extHostWebviewViews.registerWebviewViewProvider( - extension, - viewId, - provider, - options?.webviewOptions, - ); + return _asExtensionEvent(extHostTheming.onDidChangeActiveColorTheme)(listener, thisArg, disposables); + }, + registerWebviewViewProvider(viewId: string, provider: vscode.WebviewViewProvider, options?: { + webviewOptions?: { + retainContextWhenHidden?: boolean; + }; + }) { + return extHostWebviewViews.registerWebviewViewProvider(extension, viewId, provider, options?.webviewOptions); }, get activeNotebookEditor(): vscode.NotebookEditor | undefined { return extHostNotebook.activeNotebookEditor; }, onDidChangeActiveNotebookEditor(listener, thisArgs?, disposables?) { - return _asExtensionEvent( - extHostNotebook.onDidChangeActiveNotebookEditor, - )(listener, thisArgs, disposables); + return _asExtensionEvent(extHostNotebook.onDidChangeActiveNotebookEditor)(listener, thisArgs, disposables); }, get visibleNotebookEditors() { return extHostNotebook.visibleNotebookEditors; }, get onDidChangeVisibleNotebookEditors() { - return _asExtensionEvent( - extHostNotebook.onDidChangeVisibleNotebookEditors, - ); + return _asExtensionEvent(extHostNotebook.onDidChangeVisibleNotebookEditors); }, - onDidChangeNotebookEditorSelection( - listener, - thisArgs?, - disposables?, - ) { - return _asExtensionEvent( - extHostNotebookEditors.onDidChangeNotebookEditorSelection, - )(listener, thisArgs, disposables); - }, - onDidChangeNotebookEditorVisibleRanges( - listener, - thisArgs?, - disposables?, - ) { - return _asExtensionEvent( - extHostNotebookEditors.onDidChangeNotebookEditorVisibleRanges, - )(listener, thisArgs, disposables); + onDidChangeNotebookEditorSelection(listener, thisArgs?, disposables?) { + return _asExtensionEvent(extHostNotebookEditors.onDidChangeNotebookEditorSelection)(listener, thisArgs, disposables); + }, + onDidChangeNotebookEditorVisibleRanges(listener, thisArgs?, disposables?) { + return _asExtensionEvent(extHostNotebookEditors.onDidChangeNotebookEditorVisibleRanges)(listener, thisArgs, disposables); }, showNotebookDocument(document, options?) { return extHostNotebook.showNotebookDocument(document, options); }, - registerExternalUriOpener( - id: string, - opener: vscode.ExternalUriOpener, - metadata: vscode.ExternalUriOpenerMetadata, - ) { - checkProposedApiEnabled(extension, "externalUriOpener"); - return extHostUriOpeners.registerExternalUriOpener( - extension.identifier, - id, - opener, - metadata, - ); + registerExternalUriOpener(id: string, opener: vscode.ExternalUriOpener, metadata: vscode.ExternalUriOpenerMetadata) { + checkProposedApiEnabled(extension, 'externalUriOpener'); + return extHostUriOpeners.registerExternalUriOpener(extension.identifier, id, opener, metadata); }, - registerProfileContentHandler( - id: string, - handler: vscode.ProfileContentHandler, - ) { - checkProposedApiEnabled(extension, "profileContentHandlers"); - return extHostProfileContentHandlers.registerProfileContentHandler( - extension, - id, - handler, - ); + registerProfileContentHandler(id: string, handler: vscode.ProfileContentHandler) { + checkProposedApiEnabled(extension, 'profileContentHandlers'); + return extHostProfileContentHandlers.registerProfileContentHandler(extension, id, handler); }, - registerQuickDiffProvider( - selector: vscode.DocumentSelector, - quickDiffProvider: vscode.QuickDiffProvider, - label: string, - rootUri?: vscode.Uri, - ): vscode.Disposable { - checkProposedApiEnabled(extension, "quickDiffProvider"); - return extHostQuickDiff.registerQuickDiffProvider( - checkSelector(selector), - quickDiffProvider, - label, - rootUri, - ); + registerQuickDiffProvider(selector: vscode.DocumentSelector, quickDiffProvider: vscode.QuickDiffProvider, label: string, rootUri?: vscode.Uri): vscode.Disposable { + checkProposedApiEnabled(extension, 'quickDiffProvider'); + return extHostQuickDiff.registerQuickDiffProvider(checkSelector(selector), quickDiffProvider, label, rootUri); }, get tabGroups(): vscode.TabGroups { return extHostEditorTabs.tabGroups; }, - registerShareProvider( - selector: vscode.DocumentSelector, - provider: vscode.ShareProvider, - ): vscode.Disposable { - checkProposedApiEnabled(extension, "shareProvider"); - return extHostShare.registerShareProvider( - checkSelector(selector), - provider, - ); + registerShareProvider(selector: vscode.DocumentSelector, provider: vscode.ShareProvider): vscode.Disposable { + checkProposedApiEnabled(extension, 'shareProvider'); + return extHostShare.registerShareProvider(checkSelector(selector), provider); }, get nativeHandle(): Uint8Array | undefined { - checkProposedApiEnabled(extension, "nativeWindowHandle"); + checkProposedApiEnabled(extension, 'nativeWindowHandle'); return extHostWindow.nativeHandle; - }, + } }; // namespace: workspace const workspace: typeof vscode.workspace = { get rootPath() { - extHostApiDeprecation.report( - "workspace.rootPath", - extension, - `Please use 'workspace.workspaceFolders' instead. More details: https://aka.ms/vscode-eliminating-rootpath`, - ); + extHostApiDeprecation.report('workspace.rootPath', extension, + `Please use 'workspace.workspaceFolders' instead. More details: https://aka.ms/vscode-eliminating-rootpath`); return extHostWorkspace.getPath(); }, set rootPath(value) { - throw new errors.ReadonlyError("rootPath"); + throw new errors.ReadonlyError('rootPath'); }, getWorkspaceFolder(resource) { return extHostWorkspace.getWorkspaceFolder(resource); @@ -2158,112 +952,51 @@ export function createApiFactoryAndRegisterActors( return extHostWorkspace.name; }, set name(value) { - throw new errors.ReadonlyError("name"); + throw new errors.ReadonlyError('name'); }, get workspaceFile() { return extHostWorkspace.workspaceFile; }, set workspaceFile(value) { - throw new errors.ReadonlyError("workspaceFile"); - }, - updateWorkspaceFolders: ( - index, - deleteCount, - ...workspaceFoldersToAdd - ) => { - return extHostWorkspace.updateWorkspaceFolders( - extension, - index, - deleteCount || 0, - ...workspaceFoldersToAdd, - ); + throw new errors.ReadonlyError('workspaceFile'); }, - onDidChangeWorkspaceFolders: function ( - listener, - thisArgs?, - disposables?, - ) { - return _asExtensionEvent(extHostWorkspace.onDidChangeWorkspace)( - listener, - thisArgs, - disposables, - ); + updateWorkspaceFolders: (index, deleteCount, ...workspaceFoldersToAdd) => { + return extHostWorkspace.updateWorkspaceFolders(extension, index, deleteCount || 0, ...workspaceFoldersToAdd); + }, + onDidChangeWorkspaceFolders: function (listener, thisArgs?, disposables?) { + return _asExtensionEvent(extHostWorkspace.onDidChangeWorkspace)(listener, thisArgs, disposables); }, asRelativePath: (pathOrUri, includeWorkspace?) => { - return extHostWorkspace.getRelativePath( - pathOrUri, - includeWorkspace, - ); + return extHostWorkspace.getRelativePath(pathOrUri, includeWorkspace); }, findFiles: (include, exclude, maxResults?, token?) => { // Note, undefined/null have different meanings on "exclude" - return extHostWorkspace.findFiles( - include, - exclude, - maxResults, - extension.identifier, - token, - ); + return extHostWorkspace.findFiles(include, exclude, maxResults, extension.identifier, token); }, - findFiles2: ( - filePattern: vscode.GlobPattern[], - options?: vscode.FindFiles2Options, - token?: vscode.CancellationToken, - ): Thenable => { - checkProposedApiEnabled(extension, "findFiles2"); - return extHostWorkspace.findFiles2( - filePattern, - options, - extension.identifier, - token, - ); + findFiles2: (filePattern: vscode.GlobPattern[], options?: vscode.FindFiles2Options, token?: vscode.CancellationToken): Thenable => { + checkProposedApiEnabled(extension, 'findFiles2'); + return extHostWorkspace.findFiles2(filePattern, options, extension.identifier, token); }, - findTextInFiles: ( - query: vscode.TextSearchQuery, - optionsOrCallback: - | vscode.FindTextInFilesOptions - | ((result: vscode.TextSearchResult) => void), - callbackOrToken?: - | vscode.CancellationToken - | ((result: vscode.TextSearchResult) => void), - token?: vscode.CancellationToken, - ) => { - checkProposedApiEnabled(extension, "findTextInFiles"); + findTextInFiles: (query: vscode.TextSearchQuery, optionsOrCallback: vscode.FindTextInFilesOptions | ((result: vscode.TextSearchResult) => void), callbackOrToken?: vscode.CancellationToken | ((result: vscode.TextSearchResult) => void), token?: vscode.CancellationToken) => { + checkProposedApiEnabled(extension, 'findTextInFiles'); let options: vscode.FindTextInFilesOptions; let callback: (result: vscode.TextSearchResult) => void; - if (typeof optionsOrCallback === "object") { + if (typeof optionsOrCallback === 'object') { options = optionsOrCallback; - callback = callbackOrToken as ( - result: vscode.TextSearchResult, - ) => void; + callback = callbackOrToken as (result: vscode.TextSearchResult) => void; } else { options = {}; callback = optionsOrCallback; token = callbackOrToken as vscode.CancellationToken; } - return extHostWorkspace.findTextInFiles( - query, - options || {}, - callback, - extension.identifier, - token, - ); + return extHostWorkspace.findTextInFiles(query, options || {}, callback, extension.identifier, token); }, - findTextInFiles2: ( - query: vscode.TextSearchQuery2, - options?: vscode.FindTextInFilesOptions2, - token?: vscode.CancellationToken, - ): vscode.FindTextInFilesResponse => { - checkProposedApiEnabled(extension, "findTextInFiles2"); - checkProposedApiEnabled(extension, "textSearchProvider2"); - return extHostWorkspace.findTextInFiles2( - query, - options, - extension.identifier, - token, - ); + findTextInFiles2: (query: vscode.TextSearchQuery2, options?: vscode.FindTextInFilesOptions2, token?: vscode.CancellationToken): vscode.FindTextInFilesResponse => { + checkProposedApiEnabled(extension, 'findTextInFiles2'); + checkProposedApiEnabled(extension, 'textSearchProvider2'); + return extHostWorkspace.findTextInFiles2(query, options, extension.identifier, token); }, save: (uri) => { return extHostWorkspace.save(uri); @@ -2274,541 +1007,259 @@ export function createApiFactoryAndRegisterActors( saveAll: (includeUntitled?) => { return extHostWorkspace.saveAll(includeUntitled); }, - applyEdit( - edit: vscode.WorkspaceEdit, - metadata?: vscode.WorkspaceEditMetadata, - ): Thenable { - return extHostBulkEdits.applyWorkspaceEdit( - edit, - extension, - metadata, - ); + applyEdit(edit: vscode.WorkspaceEdit, metadata?: vscode.WorkspaceEditMetadata): Thenable { + return extHostBulkEdits.applyWorkspaceEdit(edit, extension, metadata); }, - createFileSystemWatcher: ( - pattern, - optionsOrIgnoreCreate, - ignoreChange?, - ignoreDelete?, - ): vscode.FileSystemWatcher => { - let options: FileSystemWatcherCreateOptions | undefined = - undefined; + createFileSystemWatcher: (pattern, optionsOrIgnoreCreate, ignoreChange?, ignoreDelete?): vscode.FileSystemWatcher => { + let options: FileSystemWatcherCreateOptions | undefined = undefined; - if ( - optionsOrIgnoreCreate && - typeof optionsOrIgnoreCreate !== "boolean" - ) { - checkProposedApiEnabled( - extension, - "createFileSystemWatcher", - ); + if (optionsOrIgnoreCreate && typeof optionsOrIgnoreCreate !== 'boolean') { + checkProposedApiEnabled(extension, 'createFileSystemWatcher'); options = { ...optionsOrIgnoreCreate, - correlate: true, + correlate: true }; } else { options = { ignoreCreateEvents: Boolean(optionsOrIgnoreCreate), ignoreChangeEvents: Boolean(ignoreChange), ignoreDeleteEvents: Boolean(ignoreDelete), - correlate: false, + correlate: false }; } - return extHostFileSystemEvent.createFileSystemWatcher( - extHostWorkspace, - configProvider, - extension, - pattern, - options, - ); + return extHostFileSystemEvent.createFileSystemWatcher(extHostWorkspace, configProvider, extension, pattern, options); }, get textDocuments() { - return extHostDocuments - .getAllDocumentData() - .map((data) => data.document); + return extHostDocuments.getAllDocumentData().map(data => data.document); }, set textDocuments(value) { - throw new errors.ReadonlyError("textDocuments"); - }, - openTextDocument( - uriOrFileNameOrOptions?: - | vscode.Uri - | string - | { language?: string; content?: string }, - ) { + throw new errors.ReadonlyError('textDocuments'); + }, + openTextDocument(uriOrFileNameOrOptions?: vscode.Uri | string | { language?: string; content?: string }) { let uriPromise: Thenable; - const options = uriOrFileNameOrOptions as { - language?: string; - content?: string; - }; - if (typeof uriOrFileNameOrOptions === "string") { - uriPromise = Promise.resolve( - URI.file(uriOrFileNameOrOptions), - ); + const options = uriOrFileNameOrOptions as { language?: string; content?: string }; + if (typeof uriOrFileNameOrOptions === 'string') { + uriPromise = Promise.resolve(URI.file(uriOrFileNameOrOptions)); } else if (URI.isUri(uriOrFileNameOrOptions)) { uriPromise = Promise.resolve(uriOrFileNameOrOptions); - } else if (!options || typeof options === "object") { + } else if (!options || typeof options === 'object') { uriPromise = extHostDocuments.createDocumentData(options); } else { - throw new Error( - "illegal argument - uriOrFileNameOrOptions", - ); + throw new Error('illegal argument - uriOrFileNameOrOptions'); } - return uriPromise.then((uri) => { - extHostLogService.trace( - `openTextDocument from ${extension.identifier}`, - ); + return uriPromise.then(uri => { + extHostLogService.trace(`openTextDocument from ${extension.identifier}`); if (uri.scheme === Schemas.vscodeRemote && !uri.authority) { - extHostApiDeprecation.report( - "workspace.openTextDocument", - extension, - `A URI of 'vscode-remote' scheme requires an authority.`, - ); + extHostApiDeprecation.report('workspace.openTextDocument', extension, `A URI of 'vscode-remote' scheme requires an authority.`); } - return extHostDocuments - .ensureDocumentData(uri) - .then((documentData) => { - return documentData.document; - }); + return extHostDocuments.ensureDocumentData(uri).then(documentData => { + return documentData.document; + }); }); }, onDidOpenTextDocument: (listener, thisArgs?, disposables?) => { - return _asExtensionEvent(extHostDocuments.onDidAddDocument)( - listener, - thisArgs, - disposables, - ); + return _asExtensionEvent(extHostDocuments.onDidAddDocument)(listener, thisArgs, disposables); }, onDidCloseTextDocument: (listener, thisArgs?, disposables?) => { - return _asExtensionEvent(extHostDocuments.onDidRemoveDocument)( - listener, - thisArgs, - disposables, - ); + return _asExtensionEvent(extHostDocuments.onDidRemoveDocument)(listener, thisArgs, disposables); }, onDidChangeTextDocument: (listener, thisArgs?, disposables?) => { - return _asExtensionEvent(extHostDocuments.onDidChangeDocument)( - listener, - thisArgs, - disposables, - ); + return _asExtensionEvent(extHostDocuments.onDidChangeDocument)(listener, thisArgs, disposables); }, onDidSaveTextDocument: (listener, thisArgs?, disposables?) => { - return _asExtensionEvent(extHostDocuments.onDidSaveDocument)( - listener, - thisArgs, - disposables, - ); + return _asExtensionEvent(extHostDocuments.onDidSaveDocument)(listener, thisArgs, disposables); }, onWillSaveTextDocument: (listener, thisArgs?, disposables?) => { - return _asExtensionEvent( - extHostDocumentSaveParticipant.getOnWillSaveTextDocumentEvent( - extension, - ), - )(listener, thisArgs, disposables); + return _asExtensionEvent(extHostDocumentSaveParticipant.getOnWillSaveTextDocumentEvent(extension))(listener, thisArgs, disposables); }, get notebookDocuments(): vscode.NotebookDocument[] { - return extHostNotebook.notebookDocuments.map( - (d) => d.apiNotebook, - ); + return extHostNotebook.notebookDocuments.map(d => d.apiNotebook); }, - async openNotebookDocument( - uriOrType?: URI | string, - content?: vscode.NotebookData, - ) { + async openNotebookDocument(uriOrType?: URI | string, content?: vscode.NotebookData) { let uri: URI; if (URI.isUri(uriOrType)) { uri = uriOrType; await extHostNotebook.openNotebookDocument(uriOrType); - } else if (typeof uriOrType === "string") { - uri = URI.revive( - await extHostNotebook.createNotebookDocument({ - viewType: uriOrType, - content, - }), - ); + } else if (typeof uriOrType === 'string') { + uri = URI.revive(await extHostNotebook.createNotebookDocument({ viewType: uriOrType, content })); } else { - throw new Error("Invalid arguments"); + throw new Error('Invalid arguments'); } return extHostNotebook.getNotebookDocument(uri).apiNotebook; }, onDidSaveNotebookDocument(listener, thisArg, disposables) { - return _asExtensionEvent( - extHostNotebookDocuments.onDidSaveNotebookDocument, - )(listener, thisArg, disposables); + return _asExtensionEvent(extHostNotebookDocuments.onDidSaveNotebookDocument)(listener, thisArg, disposables); }, onDidChangeNotebookDocument(listener, thisArg, disposables) { - return _asExtensionEvent( - extHostNotebookDocuments.onDidChangeNotebookDocument, - )(listener, thisArg, disposables); + return _asExtensionEvent(extHostNotebookDocuments.onDidChangeNotebookDocument)(listener, thisArg, disposables); }, onWillSaveNotebookDocument(listener, thisArg, disposables) { - return _asExtensionEvent( - extHostNotebookDocumentSaveParticipant.getOnWillSaveNotebookDocumentEvent( - extension, - ), - )(listener, thisArg, disposables); + return _asExtensionEvent(extHostNotebookDocumentSaveParticipant.getOnWillSaveNotebookDocumentEvent(extension))(listener, thisArg, disposables); }, get onDidOpenNotebookDocument() { - return _asExtensionEvent( - extHostNotebook.onDidOpenNotebookDocument, - ); + return _asExtensionEvent(extHostNotebook.onDidOpenNotebookDocument); }, get onDidCloseNotebookDocument() { - return _asExtensionEvent( - extHostNotebook.onDidCloseNotebookDocument, - ); + return _asExtensionEvent(extHostNotebook.onDidCloseNotebookDocument); }, - registerNotebookSerializer( - viewType: string, - serializer: vscode.NotebookSerializer, - options?: vscode.NotebookDocumentContentOptions, - registration?: vscode.NotebookRegistrationData, - ) { - return extHostNotebook.registerNotebookSerializer( - extension, - viewType, - serializer, - options, - isProposedApiEnabled(extension, "notebookLiveShare") - ? registration - : undefined, - ); + registerNotebookSerializer(viewType: string, serializer: vscode.NotebookSerializer, options?: vscode.NotebookDocumentContentOptions, registration?: vscode.NotebookRegistrationData) { + return extHostNotebook.registerNotebookSerializer(extension, viewType, serializer, options, isProposedApiEnabled(extension, 'notebookLiveShare') ? registration : undefined); }, - onDidChangeConfiguration: ( - listener: (_: any) => any, - thisArgs?: any, - disposables?: extHostTypes.Disposable[], - ) => { - return _asExtensionEvent( - configProvider.onDidChangeConfiguration, - )(listener, thisArgs, disposables); - }, - getConfiguration( - section?: string, - scope?: vscode.ConfigurationScope | null, - ): vscode.WorkspaceConfiguration { + onDidChangeConfiguration: (listener: (_: any) => any, thisArgs?: any, disposables?: extHostTypes.Disposable[]) => { + return _asExtensionEvent(configProvider.onDidChangeConfiguration)(listener, thisArgs, disposables); + }, + getConfiguration(section?: string, scope?: vscode.ConfigurationScope | null): vscode.WorkspaceConfiguration { scope = arguments.length === 1 ? undefined : scope; - return configProvider.getConfiguration( - section, - scope, - extension, - ); + return configProvider.getConfiguration(section, scope, extension); }, - registerTextDocumentContentProvider( - scheme: string, - provider: vscode.TextDocumentContentProvider, - ) { - return extHostDocumentContentProviders.registerTextDocumentContentProvider( - scheme, - provider, - ); + registerTextDocumentContentProvider(scheme: string, provider: vscode.TextDocumentContentProvider) { + return extHostDocumentContentProviders.registerTextDocumentContentProvider(scheme, provider); }, - registerTaskProvider: ( - type: string, - provider: vscode.TaskProvider, - ) => { - extHostApiDeprecation.report( - "window.registerTaskProvider", - extension, - `Use the corresponding function on the 'tasks' namespace instead`, - ); + registerTaskProvider: (type: string, provider: vscode.TaskProvider) => { + extHostApiDeprecation.report('window.registerTaskProvider', extension, + `Use the corresponding function on the 'tasks' namespace instead`); - return extHostTask.registerTaskProvider( - extension, - type, - provider, - ); + return extHostTask.registerTaskProvider(extension, type, provider); }, registerFileSystemProvider(scheme, provider, options) { return combinedDisposable( - extHostFileSystem.registerFileSystemProvider( - extension, - scheme, - provider, - options, - ), - extHostConsumerFileSystem.addFileSystemProvider( - scheme, - provider, - options, - ), + extHostFileSystem.registerFileSystemProvider(extension, scheme, provider, options), + extHostConsumerFileSystem.addFileSystemProvider(scheme, provider, options) ); }, get fs() { return extHostConsumerFileSystem.value; }, - registerFileSearchProvider: ( - scheme: string, - provider: vscode.FileSearchProvider, - ) => { - checkProposedApiEnabled(extension, "fileSearchProvider"); - return extHostSearch.registerFileSearchProviderOld( - scheme, - provider, - ); + registerFileSearchProvider: (scheme: string, provider: vscode.FileSearchProvider) => { + checkProposedApiEnabled(extension, 'fileSearchProvider'); + return extHostSearch.registerFileSearchProviderOld(scheme, provider); }, - registerTextSearchProvider: ( - scheme: string, - provider: vscode.TextSearchProvider, - ) => { - checkProposedApiEnabled(extension, "textSearchProvider"); - return extHostSearch.registerTextSearchProviderOld( - scheme, - provider, - ); + registerTextSearchProvider: (scheme: string, provider: vscode.TextSearchProvider) => { + checkProposedApiEnabled(extension, 'textSearchProvider'); + return extHostSearch.registerTextSearchProviderOld(scheme, provider); }, - registerAITextSearchProvider: ( - scheme: string, - provider: vscode.AITextSearchProvider, - ) => { + registerAITextSearchProvider: (scheme: string, provider: vscode.AITextSearchProvider) => { // there are some dependencies on textSearchProvider, so we need to check for both - checkProposedApiEnabled(extension, "aiTextSearchProvider"); - checkProposedApiEnabled(extension, "textSearchProvider2"); - return extHostSearch.registerAITextSearchProvider( - scheme, - provider, - ); + checkProposedApiEnabled(extension, 'aiTextSearchProvider'); + checkProposedApiEnabled(extension, 'textSearchProvider2'); + return extHostSearch.registerAITextSearchProvider(scheme, provider); }, - registerFileSearchProvider2: ( - scheme: string, - provider: vscode.FileSearchProvider2, - ) => { - checkProposedApiEnabled(extension, "fileSearchProvider2"); - return extHostSearch.registerFileSearchProvider( - scheme, - provider, - ); + registerFileSearchProvider2: (scheme: string, provider: vscode.FileSearchProvider2) => { + checkProposedApiEnabled(extension, 'fileSearchProvider2'); + return extHostSearch.registerFileSearchProvider(scheme, provider); }, - registerTextSearchProvider2: ( - scheme: string, - provider: vscode.TextSearchProvider2, - ) => { - checkProposedApiEnabled(extension, "textSearchProvider2"); - return extHostSearch.registerTextSearchProvider( - scheme, - provider, - ); + registerTextSearchProvider2: (scheme: string, provider: vscode.TextSearchProvider2) => { + checkProposedApiEnabled(extension, 'textSearchProvider2'); + return extHostSearch.registerTextSearchProvider(scheme, provider); }, - registerRemoteAuthorityResolver: ( - authorityPrefix: string, - resolver: vscode.RemoteAuthorityResolver, - ) => { - checkProposedApiEnabled(extension, "resolvers"); - return extensionService.registerRemoteAuthorityResolver( - authorityPrefix, - resolver, - ); + registerRemoteAuthorityResolver: (authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver) => { + checkProposedApiEnabled(extension, 'resolvers'); + return extensionService.registerRemoteAuthorityResolver(authorityPrefix, resolver); }, - registerResourceLabelFormatter: ( - formatter: vscode.ResourceLabelFormatter, - ) => { - checkProposedApiEnabled(extension, "resolvers"); - return extHostLabelService.$registerResourceLabelFormatter( - formatter, - ); + registerResourceLabelFormatter: (formatter: vscode.ResourceLabelFormatter) => { + checkProposedApiEnabled(extension, 'resolvers'); + return extHostLabelService.$registerResourceLabelFormatter(formatter); }, getRemoteExecServer: (authority: string) => { - checkProposedApiEnabled(extension, "resolvers"); + checkProposedApiEnabled(extension, 'resolvers'); return extensionService.getRemoteExecServer(authority); }, onDidCreateFiles: (listener, thisArg, disposables) => { - return _asExtensionEvent( - extHostFileSystemEvent.onDidCreateFile, - )(listener, thisArg, disposables); + return _asExtensionEvent(extHostFileSystemEvent.onDidCreateFile)(listener, thisArg, disposables); }, onDidDeleteFiles: (listener, thisArg, disposables) => { - return _asExtensionEvent( - extHostFileSystemEvent.onDidDeleteFile, - )(listener, thisArg, disposables); + return _asExtensionEvent(extHostFileSystemEvent.onDidDeleteFile)(listener, thisArg, disposables); }, onDidRenameFiles: (listener, thisArg, disposables) => { - return _asExtensionEvent( - extHostFileSystemEvent.onDidRenameFile, - )(listener, thisArg, disposables); - }, - onWillCreateFiles: ( - listener: (e: vscode.FileWillCreateEvent) => any, - thisArg?: any, - disposables?: vscode.Disposable[], - ) => { - return _asExtensionEvent( - extHostFileSystemEvent.getOnWillCreateFileEvent(extension), - )(listener, thisArg, disposables); - }, - onWillDeleteFiles: ( - listener: (e: vscode.FileWillDeleteEvent) => any, - thisArg?: any, - disposables?: vscode.Disposable[], - ) => { - return _asExtensionEvent( - extHostFileSystemEvent.getOnWillDeleteFileEvent(extension), - )(listener, thisArg, disposables); - }, - onWillRenameFiles: ( - listener: (e: vscode.FileWillRenameEvent) => any, - thisArg?: any, - disposables?: vscode.Disposable[], - ) => { - return _asExtensionEvent( - extHostFileSystemEvent.getOnWillRenameFileEvent(extension), - )(listener, thisArg, disposables); + return _asExtensionEvent(extHostFileSystemEvent.onDidRenameFile)(listener, thisArg, disposables); + }, + onWillCreateFiles: (listener: (e: vscode.FileWillCreateEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { + return _asExtensionEvent(extHostFileSystemEvent.getOnWillCreateFileEvent(extension))(listener, thisArg, disposables); + }, + onWillDeleteFiles: (listener: (e: vscode.FileWillDeleteEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { + return _asExtensionEvent(extHostFileSystemEvent.getOnWillDeleteFileEvent(extension))(listener, thisArg, disposables); + }, + onWillRenameFiles: (listener: (e: vscode.FileWillRenameEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { + return _asExtensionEvent(extHostFileSystemEvent.getOnWillRenameFileEvent(extension))(listener, thisArg, disposables); }, openTunnel: (forward: vscode.TunnelOptions) => { - checkProposedApiEnabled(extension, "tunnels"); - return extHostTunnelService - .openTunnel(extension, forward) - .then((value) => { - if (!value) { - throw new Error("cannot open tunnel"); - } - return value; - }); + checkProposedApiEnabled(extension, 'tunnels'); + return extHostTunnelService.openTunnel(extension, forward).then(value => { + if (!value) { + throw new Error('cannot open tunnel'); + } + return value; + }); }, get tunnels() { - checkProposedApiEnabled(extension, "tunnels"); + checkProposedApiEnabled(extension, 'tunnels'); return extHostTunnelService.getTunnels(); }, onDidChangeTunnels: (listener, thisArg?, disposables?) => { - checkProposedApiEnabled(extension, "tunnels"); - return _asExtensionEvent( - extHostTunnelService.onDidChangeTunnels, - )(listener, thisArg, disposables); - }, - registerPortAttributesProvider: ( - portSelector: vscode.PortAttributesSelector, - provider: vscode.PortAttributesProvider, - ) => { - checkProposedApiEnabled(extension, "portsAttributes"); - return extHostTunnelService.registerPortsAttributesProvider( - portSelector, - provider, - ); + checkProposedApiEnabled(extension, 'tunnels'); + return _asExtensionEvent(extHostTunnelService.onDidChangeTunnels)(listener, thisArg, disposables); }, - registerTunnelProvider: ( - tunnelProvider: vscode.TunnelProvider, - information: vscode.TunnelInformation, - ) => { - checkProposedApiEnabled(extension, "tunnelFactory"); - return extHostTunnelService.registerTunnelProvider( - tunnelProvider, - information, - ); + registerPortAttributesProvider: (portSelector: vscode.PortAttributesSelector, provider: vscode.PortAttributesProvider) => { + checkProposedApiEnabled(extension, 'portsAttributes'); + return extHostTunnelService.registerPortsAttributesProvider(portSelector, provider); }, - registerTimelineProvider: ( - scheme: string | string[], - provider: vscode.TimelineProvider, - ) => { - checkProposedApiEnabled(extension, "timeline"); - return extHostTimeline.registerTimelineProvider( - scheme, - provider, - extension.identifier, - extHostCommands.converter, - ); + registerTunnelProvider: (tunnelProvider: vscode.TunnelProvider, information: vscode.TunnelInformation) => { + checkProposedApiEnabled(extension, 'tunnelFactory'); + return extHostTunnelService.registerTunnelProvider(tunnelProvider, information); + }, + registerTimelineProvider: (scheme: string | string[], provider: vscode.TimelineProvider) => { + checkProposedApiEnabled(extension, 'timeline'); + return extHostTimeline.registerTimelineProvider(scheme, provider, extension.identifier, extHostCommands.converter); }, get isTrusted() { return extHostWorkspace.trusted; }, - requestWorkspaceTrust: ( - options?: vscode.WorkspaceTrustRequestOptions, - ) => { - checkProposedApiEnabled(extension, "workspaceTrust"); + requestWorkspaceTrust: (options?: vscode.WorkspaceTrustRequestOptions) => { + checkProposedApiEnabled(extension, 'workspaceTrust'); return extHostWorkspace.requestWorkspaceTrust(options); }, onDidGrantWorkspaceTrust: (listener, thisArgs?, disposables?) => { - return _asExtensionEvent( - extHostWorkspace.onDidGrantWorkspaceTrust, - )(listener, thisArgs, disposables); - }, - registerEditSessionIdentityProvider: ( - scheme: string, - provider: vscode.EditSessionIdentityProvider, - ) => { - checkProposedApiEnabled( - extension, - "editSessionIdentityProvider", - ); - return extHostWorkspace.registerEditSessionIdentityProvider( - scheme, - provider, - ); + return _asExtensionEvent(extHostWorkspace.onDidGrantWorkspaceTrust)(listener, thisArgs, disposables); }, - onWillCreateEditSessionIdentity: ( - listener, - thisArgs?, - disposables?, - ) => { - checkProposedApiEnabled( - extension, - "editSessionIdentityProvider", - ); - return _asExtensionEvent( - extHostWorkspace.getOnWillCreateEditSessionIdentityEvent( - extension, - ), - )(listener, thisArgs, disposables); - }, - registerCanonicalUriProvider: ( - scheme: string, - provider: vscode.CanonicalUriProvider, - ) => { - checkProposedApiEnabled(extension, "canonicalUriProvider"); - return extHostWorkspace.registerCanonicalUriProvider( - scheme, - provider, - ); + registerEditSessionIdentityProvider: (scheme: string, provider: vscode.EditSessionIdentityProvider) => { + checkProposedApiEnabled(extension, 'editSessionIdentityProvider'); + return extHostWorkspace.registerEditSessionIdentityProvider(scheme, provider); }, - getCanonicalUri: ( - uri: vscode.Uri, - options: vscode.CanonicalUriRequestOptions, - token: vscode.CancellationToken, - ) => { - checkProposedApiEnabled(extension, "canonicalUriProvider"); - return extHostWorkspace.provideCanonicalUri( - uri, - options, - token, - ); + onWillCreateEditSessionIdentity: (listener, thisArgs?, disposables?) => { + checkProposedApiEnabled(extension, 'editSessionIdentityProvider'); + return _asExtensionEvent(extHostWorkspace.getOnWillCreateEditSessionIdentityEvent(extension))(listener, thisArgs, disposables); }, + registerCanonicalUriProvider: (scheme: string, provider: vscode.CanonicalUriProvider) => { + checkProposedApiEnabled(extension, 'canonicalUriProvider'); + return extHostWorkspace.registerCanonicalUriProvider(scheme, provider); + }, + getCanonicalUri: (uri: vscode.Uri, options: vscode.CanonicalUriRequestOptions, token: vscode.CancellationToken) => { + checkProposedApiEnabled(extension, 'canonicalUriProvider'); + return extHostWorkspace.provideCanonicalUri(uri, options, token); + } }; // namespace: scm const scm: typeof vscode.scm = { get inputBox() { - extHostApiDeprecation.report( - "scm.inputBox", - extension, - `Use 'SourceControl.inputBox' instead`, - ); + extHostApiDeprecation.report('scm.inputBox', extension, + `Use 'SourceControl.inputBox' instead`); return extHostSCM.getLastInputBox(extension)!; // Strict null override - Deprecated api }, - createSourceControl( - id: string, - label: string, - rootUri?: vscode.Uri, - ) { - return extHostSCM.createSourceControl( - extension, - id, - label, - rootUri, - ); - }, + createSourceControl(id: string, label: string, rootUri?: vscode.Uri) { + return extHostSCM.createSourceControl(extension, id, label, rootUri); + } }; // namespace: comments const comments: typeof vscode.comments = { createCommentController(id: string, label: string) { - return extHostComment.createCommentController( - extension, - id, - label, - ); - }, + return extHostComment.createCommentController(extension, id, label); + } }; // namespace: debug @@ -2826,109 +1277,45 @@ export function createApiFactoryAndRegisterActors( return extHostDebugService.activeStackItem; }, registerDebugVisualizationProvider(id, provider) { - checkProposedApiEnabled(extension, "debugVisualization"); - return extHostDebugService.registerDebugVisualizationProvider( - extension, - id, - provider, - ); + checkProposedApiEnabled(extension, 'debugVisualization'); + return extHostDebugService.registerDebugVisualizationProvider(extension, id, provider); }, registerDebugVisualizationTreeProvider(id, provider) { - checkProposedApiEnabled(extension, "debugVisualization"); - return extHostDebugService.registerDebugVisualizationTree( - extension, - id, - provider, - ); + checkProposedApiEnabled(extension, 'debugVisualization'); + return extHostDebugService.registerDebugVisualizationTree(extension, id, provider); }, onDidStartDebugSession(listener, thisArg?, disposables?) { - return _asExtensionEvent( - extHostDebugService.onDidStartDebugSession, - )(listener, thisArg, disposables); + return _asExtensionEvent(extHostDebugService.onDidStartDebugSession)(listener, thisArg, disposables); }, onDidTerminateDebugSession(listener, thisArg?, disposables?) { - return _asExtensionEvent( - extHostDebugService.onDidTerminateDebugSession, - )(listener, thisArg, disposables); + return _asExtensionEvent(extHostDebugService.onDidTerminateDebugSession)(listener, thisArg, disposables); }, onDidChangeActiveDebugSession(listener, thisArg?, disposables?) { - return _asExtensionEvent( - extHostDebugService.onDidChangeActiveDebugSession, - )(listener, thisArg, disposables); - }, - onDidReceiveDebugSessionCustomEvent( - listener, - thisArg?, - disposables?, - ) { - return _asExtensionEvent( - extHostDebugService.onDidReceiveDebugSessionCustomEvent, - )(listener, thisArg, disposables); + return _asExtensionEvent(extHostDebugService.onDidChangeActiveDebugSession)(listener, thisArg, disposables); + }, + onDidReceiveDebugSessionCustomEvent(listener, thisArg?, disposables?) { + return _asExtensionEvent(extHostDebugService.onDidReceiveDebugSessionCustomEvent)(listener, thisArg, disposables); }, onDidChangeBreakpoints(listener, thisArgs?, disposables?) { - return _asExtensionEvent( - extHostDebugService.onDidChangeBreakpoints, - )(listener, thisArgs, disposables); + return _asExtensionEvent(extHostDebugService.onDidChangeBreakpoints)(listener, thisArgs, disposables); }, onDidChangeActiveStackItem(listener, thisArg?, disposables?) { - return _asExtensionEvent( - extHostDebugService.onDidChangeActiveStackItem, - )(listener, thisArg, disposables); - }, - registerDebugConfigurationProvider( - debugType: string, - provider: vscode.DebugConfigurationProvider, - triggerKind?: vscode.DebugConfigurationProviderTriggerKind, - ) { - return extHostDebugService.registerDebugConfigurationProvider( - debugType, - provider, - triggerKind || - DebugConfigurationProviderTriggerKind.Initial, - ); + return _asExtensionEvent(extHostDebugService.onDidChangeActiveStackItem)(listener, thisArg, disposables); }, - registerDebugAdapterDescriptorFactory( - debugType: string, - factory: vscode.DebugAdapterDescriptorFactory, - ) { - return extHostDebugService.registerDebugAdapterDescriptorFactory( - extension, - debugType, - factory, - ); + registerDebugConfigurationProvider(debugType: string, provider: vscode.DebugConfigurationProvider, triggerKind?: vscode.DebugConfigurationProviderTriggerKind) { + return extHostDebugService.registerDebugConfigurationProvider(debugType, provider, triggerKind || DebugConfigurationProviderTriggerKind.Initial); }, - registerDebugAdapterTrackerFactory( - debugType: string, - factory: vscode.DebugAdapterTrackerFactory, - ) { - return extHostDebugService.registerDebugAdapterTrackerFactory( - debugType, - factory, - ); + registerDebugAdapterDescriptorFactory(debugType: string, factory: vscode.DebugAdapterDescriptorFactory) { + return extHostDebugService.registerDebugAdapterDescriptorFactory(extension, debugType, factory); }, - startDebugging( - folder: vscode.WorkspaceFolder | undefined, - nameOrConfig: string | vscode.DebugConfiguration, - parentSessionOrOptions?: - | vscode.DebugSession - | vscode.DebugSessionOptions, - ) { - if ( - !parentSessionOrOptions || - (typeof parentSessionOrOptions === "object" && - "configuration" in parentSessionOrOptions) - ) { - return extHostDebugService.startDebugging( - folder, - nameOrConfig, - { parentSession: parentSessionOrOptions }, - ); + registerDebugAdapterTrackerFactory(debugType: string, factory: vscode.DebugAdapterTrackerFactory) { + return extHostDebugService.registerDebugAdapterTrackerFactory(debugType, factory); + }, + startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, parentSessionOrOptions?: vscode.DebugSession | vscode.DebugSessionOptions) { + if (!parentSessionOrOptions || (typeof parentSessionOrOptions === 'object' && 'configuration' in parentSessionOrOptions)) { + return extHostDebugService.startDebugging(folder, nameOrConfig, { parentSession: parentSessionOrOptions }); } - return extHostDebugService.startDebugging( - folder, - nameOrConfig, - parentSessionOrOptions || {}, - ); + return extHostDebugService.startDebugging(folder, nameOrConfig, parentSessionOrOptions || {}); }, stopDebugging(session?: vscode.DebugSession) { return extHostDebugService.stopDebugging(session); @@ -2939,457 +1326,200 @@ export function createApiFactoryAndRegisterActors( removeBreakpoints(breakpoints: readonly vscode.Breakpoint[]) { return extHostDebugService.removeBreakpoints(breakpoints); }, - asDebugSourceUri( - source: vscode.DebugProtocolSource, - session?: vscode.DebugSession, - ): vscode.Uri { + asDebugSourceUri(source: vscode.DebugProtocolSource, session?: vscode.DebugSession): vscode.Uri { return extHostDebugService.asDebugSourceUri(source, session); - }, + } }; const tasks: typeof vscode.tasks = { - registerTaskProvider: ( - type: string, - provider: vscode.TaskProvider, - ) => { - return extHostTask.registerTaskProvider( - extension, - type, - provider, - ); + registerTaskProvider: (type: string, provider: vscode.TaskProvider) => { + return extHostTask.registerTaskProvider(extension, type, provider); }, - fetchTasks: ( - filter?: vscode.TaskFilter, - ): Thenable => { + fetchTasks: (filter?: vscode.TaskFilter): Thenable => { return extHostTask.fetchTasks(filter); }, - executeTask: ( - task: vscode.Task, - ): Thenable => { + executeTask: (task: vscode.Task): Thenable => { return extHostTask.executeTask(extension, task); }, get taskExecutions(): vscode.TaskExecution[] { return extHostTask.taskExecutions; }, onDidStartTask: (listeners, thisArgs?, disposables?) => { - return _asExtensionEvent(extHostTask.onDidStartTask)( - listeners, - thisArgs, - disposables, - ); + return _asExtensionEvent(extHostTask.onDidStartTask)(listeners, thisArgs, disposables); }, onDidEndTask: (listeners, thisArgs?, disposables?) => { - return _asExtensionEvent(extHostTask.onDidEndTask)( - listeners, - thisArgs, - disposables, - ); + return _asExtensionEvent(extHostTask.onDidEndTask)(listeners, thisArgs, disposables); }, onDidStartTaskProcess: (listeners, thisArgs?, disposables?) => { - return _asExtensionEvent(extHostTask.onDidStartTaskProcess)( - listeners, - thisArgs, - disposables, - ); + return _asExtensionEvent(extHostTask.onDidStartTaskProcess)(listeners, thisArgs, disposables); }, onDidEndTaskProcess: (listeners, thisArgs?, disposables?) => { - return _asExtensionEvent(extHostTask.onDidEndTaskProcess)( - listeners, - thisArgs, - disposables, - ); - }, + return _asExtensionEvent(extHostTask.onDidEndTaskProcess)(listeners, thisArgs, disposables); + } }; // namespace: notebook const notebooks: typeof vscode.notebooks = { - createNotebookController( - id: string, - notebookType: string, - label: string, - handler?, - rendererScripts?: vscode.NotebookRendererScript[], - ) { - return extHostNotebookKernels.createNotebookController( - extension, - id, - notebookType, - label, - handler, - isProposedApiEnabled(extension, "notebookMessaging") - ? rendererScripts - : undefined, - ); + createNotebookController(id: string, notebookType: string, label: string, handler?, rendererScripts?: vscode.NotebookRendererScript[]) { + return extHostNotebookKernels.createNotebookController(extension, id, notebookType, label, handler, isProposedApiEnabled(extension, 'notebookMessaging') ? rendererScripts : undefined); }, - registerNotebookCellStatusBarItemProvider: ( - notebookType: string, - provider: vscode.NotebookCellStatusBarItemProvider, - ) => { - return extHostNotebook.registerNotebookCellStatusBarItemProvider( - extension, - notebookType, - provider, - ); + registerNotebookCellStatusBarItemProvider: (notebookType: string, provider: vscode.NotebookCellStatusBarItemProvider) => { + return extHostNotebook.registerNotebookCellStatusBarItemProvider(extension, notebookType, provider); }, createRendererMessaging(rendererId) { - return extHostNotebookRenderers.createRendererMessaging( - extension, - rendererId, - ); + return extHostNotebookRenderers.createRendererMessaging(extension, rendererId); }, createNotebookControllerDetectionTask(notebookType: string) { - checkProposedApiEnabled(extension, "notebookKernelSource"); - return extHostNotebookKernels.createNotebookControllerDetectionTask( - extension, - notebookType, - ); - }, - registerKernelSourceActionProvider( - notebookType: string, - provider: vscode.NotebookKernelSourceActionProvider, - ) { - checkProposedApiEnabled(extension, "notebookKernelSource"); - return extHostNotebookKernels.registerKernelSourceActionProvider( - extension, - notebookType, - provider, - ); + checkProposedApiEnabled(extension, 'notebookKernelSource'); + return extHostNotebookKernels.createNotebookControllerDetectionTask(extension, notebookType); }, - onDidChangeNotebookCellExecutionState( - listener, - thisArgs?, - disposables?, - ) { - checkProposedApiEnabled( - extension, - "notebookCellExecutionState", - ); - return _asExtensionEvent( - extHostNotebookKernels.onDidChangeNotebookCellExecutionState, - )(listener, thisArgs, disposables); + registerKernelSourceActionProvider(notebookType: string, provider: vscode.NotebookKernelSourceActionProvider) { + checkProposedApiEnabled(extension, 'notebookKernelSource'); + return extHostNotebookKernels.registerKernelSourceActionProvider(extension, notebookType, provider); }, + onDidChangeNotebookCellExecutionState(listener, thisArgs?, disposables?) { + checkProposedApiEnabled(extension, 'notebookCellExecutionState'); + return _asExtensionEvent(extHostNotebookKernels.onDidChangeNotebookCellExecutionState)(listener, thisArgs, disposables); + } }; // namespace: l10n const l10n: typeof vscode.l10n = { - t( - ...params: - | [ - message: string, - ...args: Array, - ] - | [message: string, args: Record] - | [ - { - message: string; - args?: - | Array - | Record; - comment: string | string[]; - }, - ] - ): string { - if (typeof params[0] === "string") { + t(...params: [message: string, ...args: Array] | [message: string, args: Record] | [{ message: string; args?: Array | Record; comment: string | string[] }]): string { + if (typeof params[0] === 'string') { const key = params.shift() as string; // We have either rest args which are Array or an array with a single Record. // This ensures we get a Record which will be formatted correctly. - const argsFormatted = - !params || typeof params[0] !== "object" - ? params - : params[0]; - return extHostLocalization.getMessage( - extension.identifier.value, - { - message: key, - args: argsFormatted as - | Record - | undefined, - }, - ); + const argsFormatted = !params || typeof params[0] !== 'object' ? params : params[0]; + return extHostLocalization.getMessage(extension.identifier.value, { message: key, args: argsFormatted as Record | undefined }); } - return extHostLocalization.getMessage( - extension.identifier.value, - params[0], - ); + return extHostLocalization.getMessage(extension.identifier.value, params[0]); }, get bundle() { - return extHostLocalization.getBundle( - extension.identifier.value, - ); + return extHostLocalization.getBundle(extension.identifier.value); }, get uri() { - return extHostLocalization.getBundleUri( - extension.identifier.value, - ); - }, + return extHostLocalization.getBundleUri(extension.identifier.value); + } }; // namespace: interactive const interactive: typeof vscode.interactive = { transferActiveChat(toWorkspace: vscode.Uri) { - checkProposedApiEnabled(extension, "interactive"); + checkProposedApiEnabled(extension, 'interactive'); return extHostChatAgents2.transferActiveChat(toWorkspace); - }, + } }; // namespace: ai const ai: typeof vscode.ai = { - getRelatedInformation( - query: string, - types: vscode.RelatedInformationType[], - ): Thenable { - checkProposedApiEnabled(extension, "aiRelatedInformation"); - return extHostAiRelatedInformation.getRelatedInformation( - extension, - query, - types, - ); - }, - registerRelatedInformationProvider( - type: vscode.RelatedInformationType, - provider: vscode.RelatedInformationProvider, - ) { - checkProposedApiEnabled(extension, "aiRelatedInformation"); - return extHostAiRelatedInformation.registerRelatedInformationProvider( - extension, - type, - provider, - ); + getRelatedInformation(query: string, types: vscode.RelatedInformationType[]): Thenable { + checkProposedApiEnabled(extension, 'aiRelatedInformation'); + return extHostAiRelatedInformation.getRelatedInformation(extension, query, types); }, - registerEmbeddingVectorProvider( - model: string, - provider: vscode.EmbeddingVectorProvider, - ) { - checkProposedApiEnabled(extension, "aiRelatedInformation"); - return extHostAiEmbeddingVector.registerEmbeddingVectorProvider( - extension, - model, - provider, - ); + registerRelatedInformationProvider(type: vscode.RelatedInformationType, provider: vscode.RelatedInformationProvider) { + checkProposedApiEnabled(extension, 'aiRelatedInformation'); + return extHostAiRelatedInformation.registerRelatedInformationProvider(extension, type, provider); }, + registerEmbeddingVectorProvider(model: string, provider: vscode.EmbeddingVectorProvider) { + checkProposedApiEnabled(extension, 'aiRelatedInformation'); + return extHostAiEmbeddingVector.registerEmbeddingVectorProvider(extension, model, provider); + } }; // namespace: chat const chat: typeof vscode.chat = { - registerChatResponseProvider( - id: string, - provider: vscode.ChatResponseProvider, - metadata: vscode.ChatResponseProviderMetadata, - ) { - checkProposedApiEnabled(extension, "chatProvider"); - return extHostLanguageModels.registerLanguageModel( - extension, - id, - provider, - metadata, - ); + registerChatResponseProvider(id: string, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata) { + checkProposedApiEnabled(extension, 'chatProvider'); + return extHostLanguageModels.registerLanguageModel(extension, id, provider, metadata); }, - registerChatVariableResolver( - id: string, - name: string, - userDescription: string, - modelDescription: string | undefined, - isSlow: boolean | undefined, - resolver: vscode.ChatVariableResolver, - fullName?: string, - icon?: vscode.ThemeIcon, - ) { - checkProposedApiEnabled(extension, "chatVariableResolver"); - return extHostChatVariables.registerVariableResolver( - extension, - id, - name, - userDescription, - modelDescription, - isSlow, - resolver, - fullName, - icon?.id, - ); + registerChatVariableResolver(id: string, name: string, userDescription: string, modelDescription: string | undefined, isSlow: boolean | undefined, resolver: vscode.ChatVariableResolver, fullName?: string, icon?: vscode.ThemeIcon) { + checkProposedApiEnabled(extension, 'chatVariableResolver'); + return extHostChatVariables.registerVariableResolver(extension, id, name, userDescription, modelDescription, isSlow, resolver, fullName, icon?.id); }, - registerMappedEditsProvider( - selector: vscode.DocumentSelector, - provider: vscode.MappedEditsProvider, - ) { - checkProposedApiEnabled(extension, "mappedEditsProvider"); - return extHostLanguageFeatures.registerMappedEditsProvider( - extension, - selector, - provider, - ); + registerMappedEditsProvider(selector: vscode.DocumentSelector, provider: vscode.MappedEditsProvider) { + checkProposedApiEnabled(extension, 'mappedEditsProvider'); + return extHostLanguageFeatures.registerMappedEditsProvider(extension, selector, provider); }, - registerMappedEditsProvider2( - provider: vscode.MappedEditsProvider2, - ) { - checkProposedApiEnabled(extension, "mappedEditsProvider"); - return extHostCodeMapper.registerMappedEditsProvider( - extension, - provider, - ); + registerMappedEditsProvider2(provider: vscode.MappedEditsProvider2) { + checkProposedApiEnabled(extension, 'mappedEditsProvider'); + return extHostCodeMapper.registerMappedEditsProvider(extension, provider); }, - createChatParticipant( - id: string, - handler: vscode.ChatExtendedRequestHandler, - ) { - return extHostChatAgents2.createChatAgent( - extension, - id, - handler, - ); + createChatParticipant(id: string, handler: vscode.ChatExtendedRequestHandler) { + return extHostChatAgents2.createChatAgent(extension, id, handler); }, - createDynamicChatParticipant( - id: string, - dynamicProps: vscode.DynamicChatParticipantProps, - handler: vscode.ChatExtendedRequestHandler, - ): vscode.ChatParticipant { - checkProposedApiEnabled(extension, "chatParticipantPrivate"); - return extHostChatAgents2.createDynamicChatAgent( - extension, - id, - dynamicProps, - handler, - ); + createDynamicChatParticipant(id: string, dynamicProps: vscode.DynamicChatParticipantProps, handler: vscode.ChatExtendedRequestHandler): vscode.ChatParticipant { + checkProposedApiEnabled(extension, 'chatParticipantPrivate'); + return extHostChatAgents2.createDynamicChatAgent(extension, id, dynamicProps, handler); }, - registerChatParticipantDetectionProvider( - provider: vscode.ChatParticipantDetectionProvider, - ) { - checkProposedApiEnabled(extension, "chatParticipantAdditions"); - return extHostChatAgents2.registerChatParticipantDetectionProvider( - extension, - provider, - ); - }, - registerRelatedFilesProvider( - provider: vscode.ChatRelatedFilesProvider, - metadata: vscode.ChatRelatedFilesProviderMetadata, - ) { - checkProposedApiEnabled(extension, "chatEditing"); - return extHostChatAgents2.registerRelatedFilesProvider( - extension, - provider, - metadata, - ); + registerChatParticipantDetectionProvider(provider: vscode.ChatParticipantDetectionProvider) { + checkProposedApiEnabled(extension, 'chatParticipantAdditions'); + return extHostChatAgents2.registerChatParticipantDetectionProvider(extension, provider); }, + registerRelatedFilesProvider(provider: vscode.ChatRelatedFilesProvider, metadata: vscode.ChatRelatedFilesProviderMetadata) { + checkProposedApiEnabled(extension, 'chatEditing'); + return extHostChatAgents2.registerRelatedFilesProvider(extension, provider, metadata); + } }; // namespace: lm const lm: typeof vscode.lm = { selectChatModels: (selector) => { - return extHostLanguageModels.selectLanguageModels( - extension, - selector ?? {}, - ); + return extHostLanguageModels.selectLanguageModels(extension, selector ?? {}); }, onDidChangeChatModels: (listener, thisArgs?, disposables?) => { - return extHostLanguageModels.onDidChangeProviders( - listener, - thisArgs, - disposables, - ); + return extHostLanguageModels.onDidChangeProviders(listener, thisArgs, disposables); }, registerChatModelProvider: (id, provider, metadata) => { - checkProposedApiEnabled(extension, "chatProvider"); - return extHostLanguageModels.registerLanguageModel( - extension, - id, - provider, - metadata, - ); + checkProposedApiEnabled(extension, 'chatProvider'); + return extHostLanguageModels.registerLanguageModel(extension, id, provider, metadata); }, // --- embeddings get embeddingModels() { - checkProposedApiEnabled(extension, "embeddings"); + checkProposedApiEnabled(extension, 'embeddings'); return extHostEmbeddings.embeddingsModels; }, onDidChangeEmbeddingModels: (listener, thisArgs?, disposables?) => { - checkProposedApiEnabled(extension, "embeddings"); - return extHostEmbeddings.onDidChange( - listener, - thisArgs, - disposables, - ); + checkProposedApiEnabled(extension, 'embeddings'); + return extHostEmbeddings.onDidChange(listener, thisArgs, disposables); }, registerEmbeddingsProvider(embeddingsModel, provider) { - checkProposedApiEnabled(extension, "embeddings"); - return extHostEmbeddings.registerEmbeddingsProvider( - extension, - embeddingsModel, - provider, - ); + checkProposedApiEnabled(extension, 'embeddings'); + return extHostEmbeddings.registerEmbeddingsProvider(extension, embeddingsModel, provider); }, - async computeEmbeddings( - embeddingsModel, - input, - token?, - ): Promise { - checkProposedApiEnabled(extension, "embeddings"); - if (typeof input === "string") { - return extHostEmbeddings.computeEmbeddings( - embeddingsModel, - input, - token, - ); + async computeEmbeddings(embeddingsModel, input, token?): Promise { + checkProposedApiEnabled(extension, 'embeddings'); + if (typeof input === 'string') { + return extHostEmbeddings.computeEmbeddings(embeddingsModel, input, token); } else { - return extHostEmbeddings.computeEmbeddings( - embeddingsModel, - input, - token, - ); + return extHostEmbeddings.computeEmbeddings(embeddingsModel, input, token); } }, registerTool(name: string, tool: vscode.LanguageModelTool) { - return extHostLanguageModelTools.registerTool( - extension, - name, - tool, - ); + return extHostLanguageModelTools.registerTool(extension, name, tool); }, - invokeTool( - name: string, - parameters: vscode.LanguageModelToolInvocationOptions, - token?: vscode.CancellationToken, - ) { - return extHostLanguageModelTools.invokeTool( - name, - parameters, - token, - ); + invokeTool(name: string, parameters: vscode.LanguageModelToolInvocationOptions, token?: vscode.CancellationToken) { + return extHostLanguageModelTools.invokeTool(name, parameters, token); }, get tools() { return extHostLanguageModelTools.tools; }, fileIsIgnored(uri: vscode.Uri, token: vscode.CancellationToken) { - return extHostLanguageModels.fileIsIgnored( - extension, - uri, - token, - ); - }, - registerIgnoredFileProvider( - provider: vscode.LanguageModelIgnoredFileProvider, - ) { - return extHostLanguageModels.registerIgnoredFileProvider( - extension, - provider, - ); + return extHostLanguageModels.fileIsIgnored(extension, uri, token); }, + registerIgnoredFileProvider(provider: vscode.LanguageModelIgnoredFileProvider) { + return extHostLanguageModels.registerIgnoredFileProvider(extension, provider); + } }; // namespace: speech const speech: typeof vscode.speech = { - registerSpeechProvider( - id: string, - provider: vscode.SpeechProvider, - ) { - checkProposedApiEnabled(extension, "speech"); - return extHostSpeech.registerProvider( - extension.identifier, - id, - provider, - ); - }, + registerSpeechProvider(id: string, provider: vscode.SpeechProvider) { + checkProposedApiEnabled(extension, 'speech'); + return extHostSpeech.registerProvider(extension.identifier, id, provider); + } }; // eslint-disable-next-line local/code-no-dangerous-type-assertions @@ -3437,8 +1567,7 @@ export function createApiFactoryAndRegisterActors( ColorThemeKind: extHostTypes.ColorThemeKind, CommentMode: extHostTypes.CommentMode, CommentState: extHostTypes.CommentState, - CommentThreadCollapsibleState: - extHostTypes.CommentThreadCollapsibleState, + CommentThreadCollapsibleState: extHostTypes.CommentThreadCollapsibleState, CommentThreadState: extHostTypes.CommentThreadState, CommentThreadApplicability: extHostTypes.CommentThreadApplicability, CommentThreadFocus: extHostTypes.CommentThreadFocus, @@ -3450,19 +1579,15 @@ export function createApiFactoryAndRegisterActors( ConfigurationTarget: extHostTypes.ConfigurationTarget, CustomExecution: extHostTypes.CustomExecution, DebugAdapterExecutable: extHostTypes.DebugAdapterExecutable, - DebugAdapterInlineImplementation: - extHostTypes.DebugAdapterInlineImplementation, - DebugAdapterNamedPipeServer: - extHostTypes.DebugAdapterNamedPipeServer, + DebugAdapterInlineImplementation: extHostTypes.DebugAdapterInlineImplementation, + DebugAdapterNamedPipeServer: extHostTypes.DebugAdapterNamedPipeServer, DebugAdapterServer: extHostTypes.DebugAdapterServer, - DebugConfigurationProviderTriggerKind: - DebugConfigurationProviderTriggerKind, + DebugConfigurationProviderTriggerKind: DebugConfigurationProviderTriggerKind, DebugConsoleMode: extHostTypes.DebugConsoleMode, DebugVisualization: extHostTypes.DebugVisualization, DecorationRangeBehavior: extHostTypes.DecorationRangeBehavior, Diagnostic: extHostTypes.Diagnostic, - DiagnosticRelatedInformation: - extHostTypes.DiagnosticRelatedInformation, + DiagnosticRelatedInformation: extHostTypes.DiagnosticRelatedInformation, DiagnosticSeverity: extHostTypes.DiagnosticSeverity, DiagnosticTag: extHostTypes.DiagnosticTag, Disposable: extHostTypes.Disposable, @@ -3472,15 +1597,12 @@ export function createApiFactoryAndRegisterActors( DocumentLink: extHostTypes.DocumentLink, DocumentSymbol: extHostTypes.DocumentSymbol, EndOfLine: extHostTypes.EndOfLine, - EnvironmentVariableMutatorType: - extHostTypes.EnvironmentVariableMutatorType, + EnvironmentVariableMutatorType: extHostTypes.EnvironmentVariableMutatorType, EvaluatableExpression: extHostTypes.EvaluatableExpression, InlineValueText: extHostTypes.InlineValueText, InlineValueVariableLookup: extHostTypes.InlineValueVariableLookup, - InlineValueEvaluatableExpression: - extHostTypes.InlineValueEvaluatableExpression, - InlineCompletionTriggerKind: - extHostTypes.InlineCompletionTriggerKind, + InlineValueEvaluatableExpression: extHostTypes.InlineValueEvaluatableExpression, + InlineCompletionTriggerKind: extHostTypes.InlineCompletionTriggerKind, EventEmitter: Emitter, ExtensionKind: extHostTypes.ExtensionKind, ExtensionMode: extHostTypes.ExtensionMode, @@ -3537,14 +1659,12 @@ export function createApiFactoryAndRegisterActors( TaskRevealKind: extHostTypes.TaskRevealKind, TaskScope: extHostTypes.TaskScope, TerminalLink: extHostTypes.TerminalLink, - TerminalQuickFixTerminalCommand: - extHostTypes.TerminalQuickFixCommand, + TerminalQuickFixTerminalCommand: extHostTypes.TerminalQuickFixCommand, TerminalQuickFixOpener: extHostTypes.TerminalQuickFixOpener, TerminalLocation: extHostTypes.TerminalLocation, TerminalProfile: extHostTypes.TerminalProfile, TerminalExitReason: extHostTypes.TerminalExitReason, - TerminalShellExecutionCommandLineConfidence: - extHostTypes.TerminalShellExecutionCommandLineConfidence, + TerminalShellExecutionCommandLineConfidence: extHostTypes.TerminalShellExecutionCommandLineConfidence, TerminalCompletionItem: extHostTypes.TerminalCompletionItem, TerminalCompletionItemKind: extHostTypes.TerminalCompletionItemKind, TerminalCompletionList: extHostTypes.TerminalCompletionList, @@ -3555,8 +1675,7 @@ export function createApiFactoryAndRegisterActors( TextEditorChangeKind: extHostTypes.TextEditorChangeKind, TextEditorLineNumbersStyle: extHostTypes.TextEditorLineNumbersStyle, TextEditorRevealType: extHostTypes.TextEditorRevealType, - TextEditorSelectionChangeKind: - extHostTypes.TextEditorSelectionChangeKind, + TextEditorSelectionChangeKind: extHostTypes.TextEditorSelectionChangeKind, SyntaxTokenType: extHostTypes.SyntaxTokenType, TextDocumentChangeReason: extHostTypes.TextDocumentChangeReason, ThemeColor: extHostTypes.ThemeColor, @@ -3572,18 +1691,15 @@ export function createApiFactoryAndRegisterActors( // proposed api types DocumentPasteTriggerKind: extHostTypes.DocumentPasteTriggerKind, DocumentDropEdit: extHostTypes.DocumentDropEdit, - DocumentDropOrPasteEditKind: - extHostTypes.DocumentDropOrPasteEditKind, + DocumentDropOrPasteEditKind: extHostTypes.DocumentDropOrPasteEditKind, DocumentPasteEdit: extHostTypes.DocumentPasteEdit, InlayHint: extHostTypes.InlayHint, InlayHintLabelPart: extHostTypes.InlayHintLabelPart, InlayHintKind: extHostTypes.InlayHintKind, - RemoteAuthorityResolverError: - extHostTypes.RemoteAuthorityResolverError, + RemoteAuthorityResolverError: extHostTypes.RemoteAuthorityResolverError, ResolvedAuthority: extHostTypes.ResolvedAuthority, ManagedResolvedAuthority: extHostTypes.ManagedResolvedAuthority, - SourceControlInputBoxValidationType: - extHostTypes.SourceControlInputBoxValidationType, + SourceControlInputBoxValidationType: extHostTypes.SourceControlInputBoxValidationType, ExtensionRuntime: extHostTypes.ExtensionRuntime, TimelineItem: extHostTypes.TimelineItem, NotebookRange: extHostTypes.NotebookRange, @@ -3592,20 +1708,17 @@ export function createApiFactoryAndRegisterActors( NotebookCellData: extHostTypes.NotebookCellData, NotebookData: extHostTypes.NotebookData, NotebookRendererScript: extHostTypes.NotebookRendererScript, - NotebookCellStatusBarAlignment: - extHostTypes.NotebookCellStatusBarAlignment, + NotebookCellStatusBarAlignment: extHostTypes.NotebookCellStatusBarAlignment, NotebookEditorRevealType: extHostTypes.NotebookEditorRevealType, NotebookCellOutput: extHostTypes.NotebookCellOutput, NotebookCellOutputItem: extHostTypes.NotebookCellOutputItem, CellErrorStackFrame: extHostTypes.CellErrorStackFrame, NotebookCellStatusBarItem: extHostTypes.NotebookCellStatusBarItem, NotebookControllerAffinity: extHostTypes.NotebookControllerAffinity, - NotebookControllerAffinity2: - extHostTypes.NotebookControllerAffinity2, + NotebookControllerAffinity2: extHostTypes.NotebookControllerAffinity2, NotebookEdit: extHostTypes.NotebookEdit, NotebookKernelSourceAction: extHostTypes.NotebookKernelSourceAction, - NotebookVariablesRequestKind: - extHostTypes.NotebookVariablesRequestKind, + NotebookVariablesRequestKind: extHostTypes.NotebookVariablesRequestKind, PortAttributes: extHostTypes.PortAttributes, LinkedEditingRanges: extHostTypes.LinkedEditingRanges, TestResultState: extHostTypes.TestResultState, @@ -3619,7 +1732,6 @@ export function createApiFactoryAndRegisterActors( DataTransferItem: extHostTypes.DataTransferItem, TestCoverageCount: extHostTypes.TestCoverageCount, FileCoverage: extHostTypes.FileCoverage, - FileCoverage2: extHostTypes.FileCoverage, StatementCoverage: extHostTypes.StatementCoverage, BranchCoverage: extHostTypes.BranchCoverage, DeclarationCoverage: extHostTypes.DeclarationCoverage, @@ -3641,13 +1753,10 @@ export function createApiFactoryAndRegisterActors( TelemetryTrustedValue: TelemetryTrustedValue, LogLevel: LogLevel, EditSessionIdentityMatch: EditSessionIdentityMatch, - InteractiveSessionVoteDirection: - extHostTypes.InteractiveSessionVoteDirection, + InteractiveSessionVoteDirection: extHostTypes.InteractiveSessionVoteDirection, ChatCopyKind: extHostTypes.ChatCopyKind, - ChatEditingSessionActionOutcome: - extHostTypes.ChatEditingSessionActionOutcome, - InteractiveEditorResponseFeedbackKind: - extHostTypes.InteractiveEditorResponseFeedbackKind, + ChatEditingSessionActionOutcome: extHostTypes.ChatEditingSessionActionOutcome, + InteractiveEditorResponseFeedbackKind: extHostTypes.InteractiveEditorResponseFeedbackKind, DebugStackFrame: extHostTypes.DebugStackFrame, DebugThread: extHostTypes.DebugThread, RelatedInformationType: extHostTypes.RelatedInformationType, @@ -3662,34 +1771,25 @@ export function createApiFactoryAndRegisterActors( ChatResponseProgressPart2: extHostTypes.ChatResponseProgressPart2, ChatResponseReferencePart: extHostTypes.ChatResponseReferencePart, ChatResponseReferencePart2: extHostTypes.ChatResponseReferencePart, - ChatResponseCodeCitationPart: - extHostTypes.ChatResponseCodeCitationPart, - ChatResponseCodeblockUriPart: - extHostTypes.ChatResponseCodeblockUriPart, + ChatResponseCodeCitationPart: extHostTypes.ChatResponseCodeCitationPart, + ChatResponseCodeblockUriPart: extHostTypes.ChatResponseCodeblockUriPart, ChatResponseWarningPart: extHostTypes.ChatResponseWarningPart, ChatResponseTextEditPart: extHostTypes.ChatResponseTextEditPart, - ChatResponseMarkdownWithVulnerabilitiesPart: - extHostTypes.ChatResponseMarkdownWithVulnerabilitiesPart, - ChatResponseCommandButtonPart: - extHostTypes.ChatResponseCommandButtonPart, - ChatResponseDetectedParticipantPart: - extHostTypes.ChatResponseDetectedParticipantPart, - ChatResponseConfirmationPart: - extHostTypes.ChatResponseConfirmationPart, + ChatResponseMarkdownWithVulnerabilitiesPart: extHostTypes.ChatResponseMarkdownWithVulnerabilitiesPart, + ChatResponseCommandButtonPart: extHostTypes.ChatResponseCommandButtonPart, + ChatResponseDetectedParticipantPart: extHostTypes.ChatResponseDetectedParticipantPart, + ChatResponseConfirmationPart: extHostTypes.ChatResponseConfirmationPart, ChatResponseMovePart: extHostTypes.ChatResponseMovePart, - ChatResponseReferencePartStatusKind: - extHostTypes.ChatResponseReferencePartStatusKind, + ChatResponseReferencePartStatusKind: extHostTypes.ChatResponseReferencePartStatusKind, ChatRequestTurn: extHostTypes.ChatRequestTurn, ChatResponseTurn: extHostTypes.ChatResponseTurn, ChatLocation: extHostTypes.ChatLocation, ChatRequestEditorData: extHostTypes.ChatRequestEditorData, ChatRequestNotebookData: extHostTypes.ChatRequestNotebookData, ChatReferenceBinaryData: extHostTypes.ChatReferenceBinaryData, - LanguageModelChatMessageRole: - extHostTypes.LanguageModelChatMessageRole, + LanguageModelChatMessageRole: extHostTypes.LanguageModelChatMessageRole, LanguageModelChatMessage: extHostTypes.LanguageModelChatMessage, - LanguageModelToolResultPart: - extHostTypes.LanguageModelToolResultPart, + LanguageModelToolResultPart: extHostTypes.LanguageModelToolResultPart, LanguageModelTextPart: extHostTypes.LanguageModelTextPart, LanguageModelToolCallPart: extHostTypes.LanguageModelToolCallPart, LanguageModelError: extHostTypes.LanguageModelError, diff --git a/Source/vs/workbench/api/common/extHostApiCommands.ts b/Source/vs/workbench/api/common/extHostApiCommands.ts index bf2db893a3698..8167b0ca3fa99 100644 --- a/Source/vs/workbench/api/common/extHostApiCommands.ts +++ b/Source/vs/workbench/api/common/extHostApiCommands.ts @@ -2,463 +2,204 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type * as vscode from "vscode"; -import { isFalsyOrEmpty } from "../../../base/common/arrays.js"; -import { VSBuffer } from "../../../base/common/buffer.js"; -import { matchesSomeScheme, Schemas } from "../../../base/common/network.js"; -import { URI } from "../../../base/common/uri.js"; -import { IPosition } from "../../../editor/common/core/position.js"; -import { IRange } from "../../../editor/common/core/range.js"; -import { ISelection } from "../../../editor/common/core/selection.js"; -import * as languages from "../../../editor/common/languages.js"; -import { decodeSemanticTokensDto } from "../../../editor/common/services/semanticTokensDto.js"; -import { validateWhenClauses } from "../../../platform/contextkey/common/contextkey.js"; -import { ITextEditorOptions } from "../../../platform/editor/common/editor.js"; -import { - TransientCellMetadata, - TransientDocumentMetadata, -} from "../../contrib/notebook/common/notebookCommon.js"; -import * as search from "../../contrib/search/common/search.js"; -import { - ICallHierarchyItemDto, - IIncomingCallDto, - IInlineValueContextDto, - IOutgoingCallDto, - IRawColorInfo, - ITypeHierarchyItemDto, - IWorkspaceEditDto, -} from "./extHost.protocol.js"; -import { - ApiCommand, - ApiCommandArgument, - ApiCommandResult, - ExtHostCommands, -} from "./extHostCommands.js"; -import { CustomCodeAction } from "./extHostLanguageFeatures.js"; -import * as typeConverters from "./extHostTypeConverters.js"; -import * as types from "./extHostTypes.js"; +import { isFalsyOrEmpty } from '../../../base/common/arrays.js'; +import { VSBuffer } from '../../../base/common/buffer.js'; +import { Schemas, matchesSomeScheme } from '../../../base/common/network.js'; +import { URI } from '../../../base/common/uri.js'; +import { IPosition } from '../../../editor/common/core/position.js'; +import { IRange } from '../../../editor/common/core/range.js'; +import { ISelection } from '../../../editor/common/core/selection.js'; +import * as languages from '../../../editor/common/languages.js'; +import { decodeSemanticTokensDto } from '../../../editor/common/services/semanticTokensDto.js'; +import { validateWhenClauses } from '../../../platform/contextkey/common/contextkey.js'; +import { ITextEditorOptions } from '../../../platform/editor/common/editor.js'; +import { ICallHierarchyItemDto, IIncomingCallDto, IInlineValueContextDto, IOutgoingCallDto, IRawColorInfo, ITypeHierarchyItemDto, IWorkspaceEditDto } from './extHost.protocol.js'; +import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from './extHostCommands.js'; +import { CustomCodeAction } from './extHostLanguageFeatures.js'; +import * as typeConverters from './extHostTypeConverters.js'; +import * as types from './extHostTypes.js'; +import { TransientCellMetadata, TransientDocumentMetadata } from '../../contrib/notebook/common/notebookCommon.js'; +import * as search from '../../contrib/search/common/search.js'; +import type * as vscode from 'vscode'; //#region --- NEW world + const newCommands: ApiCommand[] = [ // -- document highlights new ApiCommand( - "vscode.executeDocumentHighlights", - "_executeDocumentHighlights", - "Execute document highlight provider.", + 'vscode.executeDocumentHighlights', '_executeDocumentHighlights', 'Execute document highlight provider.', [ApiCommandArgument.Uri, ApiCommandArgument.Position], - new ApiCommandResult< - languages.DocumentHighlight[], - types.DocumentHighlight[] | undefined - >( - "A promise that resolves to an array of DocumentHighlight-instances.", - tryMapWith(typeConverters.DocumentHighlight.to), - ), + new ApiCommandResult('A promise that resolves to an array of DocumentHighlight-instances.', tryMapWith(typeConverters.DocumentHighlight.to)) ), // -- document symbols new ApiCommand( - "vscode.executeDocumentSymbolProvider", - "_executeDocumentSymbolProvider", - "Execute document symbol provider.", + 'vscode.executeDocumentSymbolProvider', '_executeDocumentSymbolProvider', 'Execute document symbol provider.', [ApiCommandArgument.Uri], - new ApiCommandResult< - languages.DocumentSymbol[], - vscode.SymbolInformation[] | undefined - >( - "A promise that resolves to an array of SymbolInformation and DocumentSymbol instances.", - (value, apiArgs) => { - if (isFalsyOrEmpty(value)) { - return undefined; - } - class MergedInfo - extends types.SymbolInformation - implements vscode.DocumentSymbol - { - static to(symbol: languages.DocumentSymbol): MergedInfo { - const res = new MergedInfo( - symbol.name, - typeConverters.SymbolKind.to(symbol.kind), - symbol.containerName || "", - new types.Location( - apiArgs[0], - typeConverters.Range.to(symbol.range), - ), - ); - res.detail = symbol.detail; - res.range = res.location.range; - res.selectionRange = typeConverters.Range.to( - symbol.selectionRange, - ); - res.children = symbol.children - ? symbol.children.map(MergedInfo.to) - : []; + new ApiCommandResult('A promise that resolves to an array of SymbolInformation and DocumentSymbol instances.', (value, apiArgs) => { - return res; - } - detail!: string; - range!: vscode.Range; - selectionRange!: vscode.Range; - children!: vscode.DocumentSymbol[]; - override containerName!: string; + if (isFalsyOrEmpty(value)) { + return undefined; + } + class MergedInfo extends types.SymbolInformation implements vscode.DocumentSymbol { + static to(symbol: languages.DocumentSymbol): MergedInfo { + const res = new MergedInfo( + symbol.name, + typeConverters.SymbolKind.to(symbol.kind), + symbol.containerName || '', + new types.Location(apiArgs[0], typeConverters.Range.to(symbol.range)) + ); + res.detail = symbol.detail; + res.range = res.location.range; + res.selectionRange = typeConverters.Range.to(symbol.selectionRange); + res.children = symbol.children ? symbol.children.map(MergedInfo.to) : []; + return res; } - return value.map(MergedInfo.to); - }, - ), + + detail!: string; + range!: vscode.Range; + selectionRange!: vscode.Range; + children!: vscode.DocumentSymbol[]; + override containerName!: string; + } + return value.map(MergedInfo.to); + + }) ), // -- formatting new ApiCommand( - "vscode.executeFormatDocumentProvider", - "_executeFormatDocumentProvider", - "Execute document format provider.", - [ - ApiCommandArgument.Uri, - new ApiCommandArgument( - "options", - "Formatting options", - (_) => true, - (v) => v, - ), - ], - new ApiCommandResult< - languages.TextEdit[], - types.TextEdit[] | undefined - >( - "A promise that resolves to an array of TextEdits.", - tryMapWith(typeConverters.TextEdit.to), - ), + 'vscode.executeFormatDocumentProvider', '_executeFormatDocumentProvider', 'Execute document format provider.', + [ApiCommandArgument.Uri, new ApiCommandArgument('options', 'Formatting options', _ => true, v => v)], + new ApiCommandResult('A promise that resolves to an array of TextEdits.', tryMapWith(typeConverters.TextEdit.to)) ), new ApiCommand( - "vscode.executeFormatRangeProvider", - "_executeFormatRangeProvider", - "Execute range format provider.", - [ - ApiCommandArgument.Uri, - ApiCommandArgument.Range, - new ApiCommandArgument( - "options", - "Formatting options", - (_) => true, - (v) => v, - ), - ], - new ApiCommandResult< - languages.TextEdit[], - types.TextEdit[] | undefined - >( - "A promise that resolves to an array of TextEdits.", - tryMapWith(typeConverters.TextEdit.to), - ), + 'vscode.executeFormatRangeProvider', '_executeFormatRangeProvider', 'Execute range format provider.', + [ApiCommandArgument.Uri, ApiCommandArgument.Range, new ApiCommandArgument('options', 'Formatting options', _ => true, v => v)], + new ApiCommandResult('A promise that resolves to an array of TextEdits.', tryMapWith(typeConverters.TextEdit.to)) ), new ApiCommand( - "vscode.executeFormatOnTypeProvider", - "_executeFormatOnTypeProvider", - "Execute format on type provider.", - [ - ApiCommandArgument.Uri, - ApiCommandArgument.Position, - new ApiCommandArgument( - "ch", - "Trigger character", - (v) => typeof v === "string", - (v) => v, - ), - new ApiCommandArgument( - "options", - "Formatting options", - (_) => true, - (v) => v, - ), - ], - new ApiCommandResult< - languages.TextEdit[], - types.TextEdit[] | undefined - >( - "A promise that resolves to an array of TextEdits.", - tryMapWith(typeConverters.TextEdit.to), - ), + 'vscode.executeFormatOnTypeProvider', '_executeFormatOnTypeProvider', 'Execute format on type provider.', + [ApiCommandArgument.Uri, ApiCommandArgument.Position, new ApiCommandArgument('ch', 'Trigger character', v => typeof v === 'string', v => v), new ApiCommandArgument('options', 'Formatting options', _ => true, v => v)], + new ApiCommandResult('A promise that resolves to an array of TextEdits.', tryMapWith(typeConverters.TextEdit.to)) ), // -- go to symbol (definition, type definition, declaration, impl, references) new ApiCommand( - "vscode.executeDefinitionProvider", - "_executeDefinitionProvider", - "Execute all definition providers.", + 'vscode.executeDefinitionProvider', '_executeDefinitionProvider', 'Execute all definition providers.', [ApiCommandArgument.Uri, ApiCommandArgument.Position], - new ApiCommandResult< - (languages.Location | languages.LocationLink)[], - (types.Location | vscode.LocationLink)[] | undefined - >( - "A promise that resolves to an array of Location or LocationLink instances.", - mapLocationOrLocationLink, - ), + new ApiCommandResult<(languages.Location | languages.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location or LocationLink instances.', mapLocationOrLocationLink) ), new ApiCommand( - "vscode.experimental.executeDefinitionProvider_recursive", - "_executeDefinitionProvider_recursive", - "Execute all definition providers.", + 'vscode.experimental.executeDefinitionProvider_recursive', '_executeDefinitionProvider_recursive', 'Execute all definition providers.', [ApiCommandArgument.Uri, ApiCommandArgument.Position], - new ApiCommandResult< - (languages.Location | languages.LocationLink)[], - (types.Location | vscode.LocationLink)[] | undefined - >( - "A promise that resolves to an array of Location or LocationLink instances.", - mapLocationOrLocationLink, - ), + new ApiCommandResult<(languages.Location | languages.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location or LocationLink instances.', mapLocationOrLocationLink) ), new ApiCommand( - "vscode.executeTypeDefinitionProvider", - "_executeTypeDefinitionProvider", - "Execute all type definition providers.", + 'vscode.executeTypeDefinitionProvider', '_executeTypeDefinitionProvider', 'Execute all type definition providers.', [ApiCommandArgument.Uri, ApiCommandArgument.Position], - new ApiCommandResult< - (languages.Location | languages.LocationLink)[], - (types.Location | vscode.LocationLink)[] | undefined - >( - "A promise that resolves to an array of Location or LocationLink instances.", - mapLocationOrLocationLink, - ), + new ApiCommandResult<(languages.Location | languages.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location or LocationLink instances.', mapLocationOrLocationLink) ), new ApiCommand( - "vscode.experimental.executeTypeDefinitionProvider_recursive", - "_executeTypeDefinitionProvider_recursive", - "Execute all type definition providers.", + 'vscode.experimental.executeTypeDefinitionProvider_recursive', '_executeTypeDefinitionProvider_recursive', 'Execute all type definition providers.', [ApiCommandArgument.Uri, ApiCommandArgument.Position], - new ApiCommandResult< - (languages.Location | languages.LocationLink)[], - (types.Location | vscode.LocationLink)[] | undefined - >( - "A promise that resolves to an array of Location or LocationLink instances.", - mapLocationOrLocationLink, - ), + new ApiCommandResult<(languages.Location | languages.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location or LocationLink instances.', mapLocationOrLocationLink) ), new ApiCommand( - "vscode.executeDeclarationProvider", - "_executeDeclarationProvider", - "Execute all declaration providers.", + 'vscode.executeDeclarationProvider', '_executeDeclarationProvider', 'Execute all declaration providers.', [ApiCommandArgument.Uri, ApiCommandArgument.Position], - new ApiCommandResult< - (languages.Location | languages.LocationLink)[], - (types.Location | vscode.LocationLink)[] | undefined - >( - "A promise that resolves to an array of Location or LocationLink instances.", - mapLocationOrLocationLink, - ), + new ApiCommandResult<(languages.Location | languages.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location or LocationLink instances.', mapLocationOrLocationLink) ), new ApiCommand( - "vscode.experimental.executeDeclarationProvider_recursive", - "_executeDeclarationProvider_recursive", - "Execute all declaration providers.", + 'vscode.experimental.executeDeclarationProvider_recursive', '_executeDeclarationProvider_recursive', 'Execute all declaration providers.', [ApiCommandArgument.Uri, ApiCommandArgument.Position], - new ApiCommandResult< - (languages.Location | languages.LocationLink)[], - (types.Location | vscode.LocationLink)[] | undefined - >( - "A promise that resolves to an array of Location or LocationLink instances.", - mapLocationOrLocationLink, - ), + new ApiCommandResult<(languages.Location | languages.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location or LocationLink instances.', mapLocationOrLocationLink) ), new ApiCommand( - "vscode.executeImplementationProvider", - "_executeImplementationProvider", - "Execute all implementation providers.", + 'vscode.executeImplementationProvider', '_executeImplementationProvider', 'Execute all implementation providers.', [ApiCommandArgument.Uri, ApiCommandArgument.Position], - new ApiCommandResult< - (languages.Location | languages.LocationLink)[], - (types.Location | vscode.LocationLink)[] | undefined - >( - "A promise that resolves to an array of Location or LocationLink instances.", - mapLocationOrLocationLink, - ), + new ApiCommandResult<(languages.Location | languages.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location or LocationLink instances.', mapLocationOrLocationLink) ), new ApiCommand( - "vscode.experimental.executeImplementationProvider_recursive", - "_executeImplementationProvider_recursive", - "Execute all implementation providers.", + 'vscode.experimental.executeImplementationProvider_recursive', '_executeImplementationProvider_recursive', 'Execute all implementation providers.', [ApiCommandArgument.Uri, ApiCommandArgument.Position], - new ApiCommandResult< - (languages.Location | languages.LocationLink)[], - (types.Location | vscode.LocationLink)[] | undefined - >( - "A promise that resolves to an array of Location or LocationLink instances.", - mapLocationOrLocationLink, - ), + new ApiCommandResult<(languages.Location | languages.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location or LocationLink instances.', mapLocationOrLocationLink) ), new ApiCommand( - "vscode.executeReferenceProvider", - "_executeReferenceProvider", - "Execute all reference providers.", + 'vscode.executeReferenceProvider', '_executeReferenceProvider', 'Execute all reference providers.', [ApiCommandArgument.Uri, ApiCommandArgument.Position], - new ApiCommandResult< - languages.Location[], - types.Location[] | undefined - >( - "A promise that resolves to an array of Location-instances.", - tryMapWith(typeConverters.location.to), - ), + new ApiCommandResult('A promise that resolves to an array of Location-instances.', tryMapWith(typeConverters.location.to)) ), new ApiCommand( - "vscode.experimental.executeReferenceProvider", - "_executeReferenceProvider_recursive", - "Execute all reference providers.", + 'vscode.experimental.executeReferenceProvider', '_executeReferenceProvider_recursive', 'Execute all reference providers.', [ApiCommandArgument.Uri, ApiCommandArgument.Position], - new ApiCommandResult< - languages.Location[], - types.Location[] | undefined - >( - "A promise that resolves to an array of Location-instances.", - tryMapWith(typeConverters.location.to), - ), + new ApiCommandResult('A promise that resolves to an array of Location-instances.', tryMapWith(typeConverters.location.to)) ), // -- hover new ApiCommand( - "vscode.executeHoverProvider", - "_executeHoverProvider", - "Execute all hover providers.", + 'vscode.executeHoverProvider', '_executeHoverProvider', 'Execute all hover providers.', [ApiCommandArgument.Uri, ApiCommandArgument.Position], - new ApiCommandResult( - "A promise that resolves to an array of Hover-instances.", - tryMapWith(typeConverters.Hover.to), - ), + new ApiCommandResult('A promise that resolves to an array of Hover-instances.', tryMapWith(typeConverters.Hover.to)) ), new ApiCommand( - "vscode.experimental.executeHoverProvider_recursive", - "_executeHoverProvider_recursive", - "Execute all hover providers.", + 'vscode.experimental.executeHoverProvider_recursive', '_executeHoverProvider_recursive', 'Execute all hover providers.', [ApiCommandArgument.Uri, ApiCommandArgument.Position], - new ApiCommandResult( - "A promise that resolves to an array of Hover-instances.", - tryMapWith(typeConverters.Hover.to), - ), + new ApiCommandResult('A promise that resolves to an array of Hover-instances.', tryMapWith(typeConverters.Hover.to)) ), // -- selection range new ApiCommand( - "vscode.executeSelectionRangeProvider", - "_executeSelectionRangeProvider", - "Execute selection range provider.", - [ - ApiCommandArgument.Uri, - new ApiCommandArgument( - "position", - "A position in a text document", - (v) => - Array.isArray(v) && - v.every((v) => types.Position.isPosition(v)), - (v) => v.map(typeConverters.Position.from), - ), - ], - new ApiCommandResult( - "A promise that resolves to an array of ranges.", - (result) => { - return result.map((ranges) => { - let node: types.SelectionRange | undefined; - - for (const range of ranges.reverse()) { - node = new types.SelectionRange( - typeConverters.Range.to(range), - node, - ); - } - return node!; - }); - }, - ), + 'vscode.executeSelectionRangeProvider', '_executeSelectionRangeProvider', 'Execute selection range provider.', + [ApiCommandArgument.Uri, new ApiCommandArgument('position', 'A position in a text document', v => Array.isArray(v) && v.every(v => types.Position.isPosition(v)), v => v.map(typeConverters.Position.from))], + new ApiCommandResult('A promise that resolves to an array of ranges.', result => { + return result.map(ranges => { + let node: types.SelectionRange | undefined; + for (const range of ranges.reverse()) { + node = new types.SelectionRange(typeConverters.Range.to(range), node); + } + return node!; + }); + }) ), // -- symbol search new ApiCommand( - "vscode.executeWorkspaceSymbolProvider", - "_executeWorkspaceSymbolProvider", - "Execute all workspace symbol providers.", - [ApiCommandArgument.String.with("query", "Search string")], - new ApiCommandResult< - search.IWorkspaceSymbol[], - types.SymbolInformation[] - >( - "A promise that resolves to an array of SymbolInformation-instances.", - (value) => { - return value.map(typeConverters.WorkspaceSymbol.to); - }, - ), + 'vscode.executeWorkspaceSymbolProvider', '_executeWorkspaceSymbolProvider', 'Execute all workspace symbol providers.', + [ApiCommandArgument.String.with('query', 'Search string')], + new ApiCommandResult('A promise that resolves to an array of SymbolInformation-instances.', value => { + return value.map(typeConverters.WorkspaceSymbol.to); + }) ), // --- call hierarchy new ApiCommand( - "vscode.prepareCallHierarchy", - "_executePrepareCallHierarchy", - "Prepare call hierarchy at a position inside a document", + 'vscode.prepareCallHierarchy', '_executePrepareCallHierarchy', 'Prepare call hierarchy at a position inside a document', [ApiCommandArgument.Uri, ApiCommandArgument.Position], - new ApiCommandResult< - ICallHierarchyItemDto[], - types.CallHierarchyItem[] - >( - "A promise that resolves to an array of CallHierarchyItem-instances", - (v) => v.map(typeConverters.CallHierarchyItem.to), - ), + new ApiCommandResult('A promise that resolves to an array of CallHierarchyItem-instances', v => v.map(typeConverters.CallHierarchyItem.to)) ), new ApiCommand( - "vscode.provideIncomingCalls", - "_executeProvideIncomingCalls", - "Compute incoming calls for an item", + 'vscode.provideIncomingCalls', '_executeProvideIncomingCalls', 'Compute incoming calls for an item', [ApiCommandArgument.CallHierarchyItem], - new ApiCommandResult< - IIncomingCallDto[], - types.CallHierarchyIncomingCall[] - >( - "A promise that resolves to an array of CallHierarchyIncomingCall-instances", - (v) => v.map(typeConverters.CallHierarchyIncomingCall.to), - ), + new ApiCommandResult('A promise that resolves to an array of CallHierarchyIncomingCall-instances', v => v.map(typeConverters.CallHierarchyIncomingCall.to)) ), new ApiCommand( - "vscode.provideOutgoingCalls", - "_executeProvideOutgoingCalls", - "Compute outgoing calls for an item", + 'vscode.provideOutgoingCalls', '_executeProvideOutgoingCalls', 'Compute outgoing calls for an item', [ApiCommandArgument.CallHierarchyItem], - new ApiCommandResult< - IOutgoingCallDto[], - types.CallHierarchyOutgoingCall[] - >( - "A promise that resolves to an array of CallHierarchyOutgoingCall-instances", - (v) => v.map(typeConverters.CallHierarchyOutgoingCall.to), - ), + new ApiCommandResult('A promise that resolves to an array of CallHierarchyOutgoingCall-instances', v => v.map(typeConverters.CallHierarchyOutgoingCall.to)) ), // --- rename new ApiCommand( - "vscode.prepareRename", - "_executePrepareRename", - "Execute the prepareRename of rename provider.", + 'vscode.prepareRename', '_executePrepareRename', 'Execute the prepareRename of rename provider.', [ApiCommandArgument.Uri, ApiCommandArgument.Position], - new ApiCommandResult< - languages.RenameLocation, - | { - range: types.Range; - placeholder: string; - } - | undefined - >( - "A promise that resolves to a range and placeholder text.", - (value) => { - if (!value) { - return undefined; - } - return { - range: typeConverters.Range.to(value.range), - placeholder: value.text, - }; - }, - ), + new ApiCommandResult('A promise that resolves to a range and placeholder text.', value => { + if (!value) { + return undefined; + } + return { + range: typeConverters.Range.to(value.range), + placeholder: value.text + }; + }) ), new ApiCommand( - "vscode.executeDocumentRenameProvider", - "_executeDocumentRenameProvider", - "Execute rename provider.", - [ - ApiCommandArgument.Uri, - ApiCommandArgument.Position, - ApiCommandArgument.String.with("newName", "The new symbol name"), - ], - new ApiCommandResult< - IWorkspaceEditDto & { - rejectReason?: string; - }, - types.WorkspaceEdit | undefined - >("A promise that resolves to a WorkspaceEdit.", (value) => { + 'vscode.executeDocumentRenameProvider', '_executeDocumentRenameProvider', 'Execute rename provider.', + [ApiCommandArgument.Uri, ApiCommandArgument.Position, ApiCommandArgument.String.with('newName', 'The new symbol name')], + new ApiCommandResult('A promise that resolves to a WorkspaceEdit.', value => { if (!value) { return undefined; } @@ -466,747 +207,373 @@ const newCommands: ApiCommand[] = [ throw new Error(value.rejectReason); } return typeConverters.WorkspaceEdit.to(value); - }), + }) ), // --- links new ApiCommand( - "vscode.executeLinkProvider", - "_executeLinkProvider", - "Execute document link provider.", - [ - ApiCommandArgument.Uri, - ApiCommandArgument.Number.with( - "linkResolveCount", - "Number of links that should be resolved, only when links are unresolved.", - ).optional(), - ], - new ApiCommandResult( - "A promise that resolves to an array of DocumentLink-instances.", - (value) => value.map(typeConverters.DocumentLink.to), - ), + 'vscode.executeLinkProvider', '_executeLinkProvider', 'Execute document link provider.', + [ApiCommandArgument.Uri, ApiCommandArgument.Number.with('linkResolveCount', 'Number of links that should be resolved, only when links are unresolved.').optional()], + new ApiCommandResult('A promise that resolves to an array of DocumentLink-instances.', value => value.map(typeConverters.DocumentLink.to)) ), // --- semantic tokens new ApiCommand( - "vscode.provideDocumentSemanticTokensLegend", - "_provideDocumentSemanticTokensLegend", - "Provide semantic tokens legend for a document", + 'vscode.provideDocumentSemanticTokensLegend', '_provideDocumentSemanticTokensLegend', 'Provide semantic tokens legend for a document', [ApiCommandArgument.Uri], - new ApiCommandResult< - languages.SemanticTokensLegend, - types.SemanticTokensLegend | undefined - >("A promise that resolves to SemanticTokensLegend.", (value) => { + new ApiCommandResult('A promise that resolves to SemanticTokensLegend.', value => { if (!value) { return undefined; } - return new types.SemanticTokensLegend( - value.tokenTypes, - value.tokenModifiers, - ); - }), + return new types.SemanticTokensLegend(value.tokenTypes, value.tokenModifiers); + }) ), new ApiCommand( - "vscode.provideDocumentSemanticTokens", - "_provideDocumentSemanticTokens", - "Provide semantic tokens for a document", + 'vscode.provideDocumentSemanticTokens', '_provideDocumentSemanticTokens', 'Provide semantic tokens for a document', [ApiCommandArgument.Uri], - new ApiCommandResult( - "A promise that resolves to SemanticTokens.", - (value) => { - if (!value) { - return undefined; - } - const semanticTokensDto = decodeSemanticTokensDto(value); - - if (semanticTokensDto.type !== "full") { - // only accepting full semantic tokens from provideDocumentSemanticTokens - return undefined; - } - return new types.SemanticTokens( - semanticTokensDto.data, - undefined, - ); - }, - ), + new ApiCommandResult('A promise that resolves to SemanticTokens.', value => { + if (!value) { + return undefined; + } + const semanticTokensDto = decodeSemanticTokensDto(value); + if (semanticTokensDto.type !== 'full') { + // only accepting full semantic tokens from provideDocumentSemanticTokens + return undefined; + } + return new types.SemanticTokens(semanticTokensDto.data, undefined); + }) ), new ApiCommand( - "vscode.provideDocumentRangeSemanticTokensLegend", - "_provideDocumentRangeSemanticTokensLegend", - "Provide semantic tokens legend for a document range", + 'vscode.provideDocumentRangeSemanticTokensLegend', '_provideDocumentRangeSemanticTokensLegend', 'Provide semantic tokens legend for a document range', [ApiCommandArgument.Uri, ApiCommandArgument.Range.optional()], - new ApiCommandResult< - languages.SemanticTokensLegend, - types.SemanticTokensLegend | undefined - >("A promise that resolves to SemanticTokensLegend.", (value) => { + new ApiCommandResult('A promise that resolves to SemanticTokensLegend.', value => { if (!value) { return undefined; } - return new types.SemanticTokensLegend( - value.tokenTypes, - value.tokenModifiers, - ); - }), + return new types.SemanticTokensLegend(value.tokenTypes, value.tokenModifiers); + }) ), new ApiCommand( - "vscode.provideDocumentRangeSemanticTokens", - "_provideDocumentRangeSemanticTokens", - "Provide semantic tokens for a document range", + 'vscode.provideDocumentRangeSemanticTokens', '_provideDocumentRangeSemanticTokens', 'Provide semantic tokens for a document range', [ApiCommandArgument.Uri, ApiCommandArgument.Range], - new ApiCommandResult( - "A promise that resolves to SemanticTokens.", - (value) => { - if (!value) { - return undefined; - } - const semanticTokensDto = decodeSemanticTokensDto(value); - - if (semanticTokensDto.type !== "full") { - // only accepting full semantic tokens from provideDocumentRangeSemanticTokens - return undefined; - } - return new types.SemanticTokens( - semanticTokensDto.data, - undefined, - ); - }, - ), + new ApiCommandResult('A promise that resolves to SemanticTokens.', value => { + if (!value) { + return undefined; + } + const semanticTokensDto = decodeSemanticTokensDto(value); + if (semanticTokensDto.type !== 'full') { + // only accepting full semantic tokens from provideDocumentRangeSemanticTokens + return undefined; + } + return new types.SemanticTokens(semanticTokensDto.data, undefined); + }) ), // --- completions new ApiCommand( - "vscode.executeCompletionItemProvider", - "_executeCompletionItemProvider", - "Execute completion item provider.", + 'vscode.executeCompletionItemProvider', '_executeCompletionItemProvider', 'Execute completion item provider.', [ ApiCommandArgument.Uri, ApiCommandArgument.Position, - ApiCommandArgument.String.with( - "triggerCharacter", - "Trigger completion when the user types the character, like `,` or `(`", - ).optional(), - ApiCommandArgument.Number.with( - "itemResolveCount", - "Number of completions to resolve (too large numbers slow down completions)", - ).optional(), + ApiCommandArgument.String.with('triggerCharacter', 'Trigger completion when the user types the character, like `,` or `(`').optional(), + ApiCommandArgument.Number.with('itemResolveCount', 'Number of completions to resolve (too large numbers slow down completions)').optional() ], - new ApiCommandResult( - "A promise that resolves to a CompletionList-instance.", - (value, _args, converter) => { - if (!value) { - return new types.CompletionList([]); - } - const items = value.suggestions.map((suggestion) => - typeConverters.CompletionItem.to(suggestion, converter), - ); - - return new types.CompletionList(items, value.incomplete); - }, - ), + new ApiCommandResult('A promise that resolves to a CompletionList-instance.', (value, _args, converter) => { + if (!value) { + return new types.CompletionList([]); + } + const items = value.suggestions.map(suggestion => typeConverters.CompletionItem.to(suggestion, converter)); + return new types.CompletionList(items, value.incomplete); + }) ), // --- signature help new ApiCommand( - "vscode.executeSignatureHelpProvider", - "_executeSignatureHelpProvider", - "Execute signature help provider.", - [ - ApiCommandArgument.Uri, - ApiCommandArgument.Position, - ApiCommandArgument.String.with( - "triggerCharacter", - "Trigger signature help when the user types the character, like `,` or `(`", - ).optional(), - ], - new ApiCommandResult< - languages.SignatureHelp, - vscode.SignatureHelp | undefined - >("A promise that resolves to SignatureHelp.", (value) => { + 'vscode.executeSignatureHelpProvider', '_executeSignatureHelpProvider', 'Execute signature help provider.', + [ApiCommandArgument.Uri, ApiCommandArgument.Position, ApiCommandArgument.String.with('triggerCharacter', 'Trigger signature help when the user types the character, like `,` or `(`').optional()], + new ApiCommandResult('A promise that resolves to SignatureHelp.', value => { if (value) { return typeConverters.SignatureHelp.to(value); } return undefined; - }), + }) ), // --- code lens new ApiCommand( - "vscode.executeCodeLensProvider", - "_executeCodeLensProvider", - "Execute code lens provider.", - [ - ApiCommandArgument.Uri, - ApiCommandArgument.Number.with( - "itemResolveCount", - "Number of lenses that should be resolved and returned. Will only return resolved lenses, will impact performance)", - ).optional(), - ], - new ApiCommandResult< - languages.CodeLens[], - vscode.CodeLens[] | undefined - >( - "A promise that resolves to an array of CodeLens-instances.", - (value, _args, converter) => { - return tryMapWith( - (item) => { - return new types.CodeLens( - typeConverters.Range.to(item.range), - item.command && - converter.fromInternal(item.command), - ); - }, - )(value); - }, - ), + 'vscode.executeCodeLensProvider', '_executeCodeLensProvider', 'Execute code lens provider.', + [ApiCommandArgument.Uri, ApiCommandArgument.Number.with('itemResolveCount', 'Number of lenses that should be resolved and returned. Will only return resolved lenses, will impact performance)').optional()], + new ApiCommandResult('A promise that resolves to an array of CodeLens-instances.', (value, _args, converter) => { + return tryMapWith(item => { + return new types.CodeLens(typeConverters.Range.to(item.range), item.command && converter.fromInternal(item.command)); + })(value); + }) ), // --- code actions new ApiCommand( - "vscode.executeCodeActionProvider", - "_executeCodeActionProvider", - "Execute code action provider.", + 'vscode.executeCodeActionProvider', '_executeCodeActionProvider', 'Execute code action provider.', [ ApiCommandArgument.Uri, - new ApiCommandArgument( - "rangeOrSelection", - "Range in a text document. Some refactoring provider requires Selection object.", - (v) => types.Range.isRange(v), - (v) => - types.Selection.isSelection(v) - ? typeConverters.Selection.from(v) - : typeConverters.Range.from(v), - ), - ApiCommandArgument.String.with( - "kind", - "Code action kind to return code actions for", - ).optional(), - ApiCommandArgument.Number.with( - "itemResolveCount", - "Number of code actions to resolve (too large numbers slow down code actions)", - ).optional(), + new ApiCommandArgument('rangeOrSelection', 'Range in a text document. Some refactoring provider requires Selection object.', v => types.Range.isRange(v), v => types.Selection.isSelection(v) ? typeConverters.Selection.from(v) : typeConverters.Range.from(v)), + ApiCommandArgument.String.with('kind', 'Code action kind to return code actions for').optional(), + ApiCommandArgument.Number.with('itemResolveCount', 'Number of code actions to resolve (too large numbers slow down code actions)').optional() ], - new ApiCommandResult< - CustomCodeAction[], - (vscode.CodeAction | vscode.Command | undefined)[] | undefined - >( - "A promise that resolves to an array of Command-instances.", - (value, _args, converter) => { - return tryMapWith< - CustomCodeAction, - vscode.CodeAction | vscode.Command | undefined - >((codeAction) => { - if (codeAction._isSynthetic) { - if (!codeAction.command) { - throw new Error( - "Synthetic code actions must have a command", - ); - } - return converter.fromInternal(codeAction.command); - } else { - const ret = new types.CodeAction( - codeAction.title, - codeAction.kind - ? new types.CodeActionKind(codeAction.kind) - : undefined, - ); - - if (codeAction.edit) { - ret.edit = typeConverters.WorkspaceEdit.to( - codeAction.edit, - ); - } - if (codeAction.command) { - ret.command = converter.fromInternal( - codeAction.command, - ); - } - ret.isPreferred = codeAction.isPreferred; - - return ret; + new ApiCommandResult('A promise that resolves to an array of Command-instances.', (value, _args, converter) => { + return tryMapWith((codeAction) => { + if (codeAction._isSynthetic) { + if (!codeAction.command) { + throw new Error('Synthetic code actions must have a command'); + } + return converter.fromInternal(codeAction.command); + } else { + const ret = new types.CodeAction( + codeAction.title, + codeAction.kind ? new types.CodeActionKind(codeAction.kind) : undefined + ); + if (codeAction.edit) { + ret.edit = typeConverters.WorkspaceEdit.to(codeAction.edit); + } + if (codeAction.command) { + ret.command = converter.fromInternal(codeAction.command); } - })(value); - }, - ), + ret.isPreferred = codeAction.isPreferred; + return ret; + } + })(value); + }) ), // --- colors new ApiCommand( - "vscode.executeDocumentColorProvider", - "_executeDocumentColorProvider", - "Execute document color provider.", + 'vscode.executeDocumentColorProvider', '_executeDocumentColorProvider', 'Execute document color provider.', [ApiCommandArgument.Uri], - new ApiCommandResult( - "A promise that resolves to an array of ColorInformation objects.", - (result) => { - if (result) { - return result.map( - (ci) => - new types.ColorInformation( - typeConverters.Range.to(ci.range), - typeConverters.Color.to(ci.color), - ), - ); - } - return []; - }, - ), + new ApiCommandResult('A promise that resolves to an array of ColorInformation objects.', result => { + if (result) { + return result.map(ci => new types.ColorInformation(typeConverters.Range.to(ci.range), typeConverters.Color.to(ci.color))); + } + return []; + }) ), new ApiCommand( - "vscode.executeColorPresentationProvider", - "_executeColorPresentationProvider", - "Execute color presentation provider.", + 'vscode.executeColorPresentationProvider', '_executeColorPresentationProvider', 'Execute color presentation provider.', [ - new ApiCommandArgument< - types.Color, - [number, number, number, number] - >( - "color", - "The color to show and insert", - (v) => v instanceof types.Color, - typeConverters.Color.from, - ), - new ApiCommandArgument< - { - uri: URI; - range: types.Range; - }, - { - uri: URI; - range: IRange; - } - >( - "context", - "Context object with uri and range", - (_v) => true, - (v) => ({ - uri: v.uri, - range: typeConverters.Range.from(v.range), - }), - ), + new ApiCommandArgument('color', 'The color to show and insert', v => v instanceof types.Color, typeConverters.Color.from), + new ApiCommandArgument<{ uri: URI; range: types.Range }, { uri: URI; range: IRange }>('context', 'Context object with uri and range', _v => true, v => ({ uri: v.uri, range: typeConverters.Range.from(v.range) })), ], - new ApiCommandResult< - languages.IColorPresentation[], - types.ColorPresentation[] - >( - "A promise that resolves to an array of ColorPresentation objects.", - (result) => { - if (result) { - return result.map(typeConverters.ColorPresentation.to); - } - return []; - }, - ), + new ApiCommandResult('A promise that resolves to an array of ColorPresentation objects.', result => { + if (result) { + return result.map(typeConverters.ColorPresentation.to); + } + return []; + }) ), // --- inline hints new ApiCommand( - "vscode.executeInlayHintProvider", - "_executeInlayHintProvider", - "Execute inlay hints provider", + 'vscode.executeInlayHintProvider', '_executeInlayHintProvider', 'Execute inlay hints provider', [ApiCommandArgument.Uri, ApiCommandArgument.Range], - new ApiCommandResult( - "A promise that resolves to an array of Inlay objects", - (result, args, converter) => { - return result.map( - typeConverters.InlayHint.to.bind(undefined, converter), - ); - }, - ), + new ApiCommandResult('A promise that resolves to an array of Inlay objects', (result, args, converter) => { + return result.map(typeConverters.InlayHint.to.bind(undefined, converter)); + }) ), // --- folding new ApiCommand( - "vscode.executeFoldingRangeProvider", - "_executeFoldingRangeProvider", - "Execute folding range provider", + 'vscode.executeFoldingRangeProvider', '_executeFoldingRangeProvider', 'Execute folding range provider', [ApiCommandArgument.Uri], - new ApiCommandResult< - languages.FoldingRange[] | undefined, - vscode.FoldingRange[] | undefined - >( - "A promise that resolves to an array of FoldingRange objects", - (result, args) => { - if (result) { - return result.map(typeConverters.FoldingRange.to); - } - return undefined; - }, - ), + new ApiCommandResult('A promise that resolves to an array of FoldingRange objects', (result, args) => { + if (result) { + return result.map(typeConverters.FoldingRange.to); + } + return undefined; + }) ), + // --- notebooks new ApiCommand( - "vscode.resolveNotebookContentProviders", - "_resolveNotebookContentProvider", - "Resolve Notebook Content Providers", + 'vscode.resolveNotebookContentProviders', '_resolveNotebookContentProvider', 'Resolve Notebook Content Providers', [ // new ApiCommandArgument('viewType', '', v => typeof v === 'string', v => v), // new ApiCommandArgument('displayName', '', v => typeof v === 'string', v => v), // new ApiCommandArgument('options', '', v => typeof v === 'object', v => v), ], - new ApiCommandResult< - { - viewType: string; - displayName: string; + new ApiCommandResult<{ + viewType: string; + displayName: string; + options: { transientOutputs: boolean; transientCellMetadata: TransientCellMetadata; transientDocumentMetadata: TransientDocumentMetadata }; + filenamePattern: (vscode.GlobPattern | { include: vscode.GlobPattern; exclude: vscode.GlobPattern })[]; + }[], { + viewType: string; + displayName: string; + filenamePattern: (vscode.GlobPattern | { include: vscode.GlobPattern; exclude: vscode.GlobPattern })[]; + options: vscode.NotebookDocumentContentOptions; + }[] | undefined>('A promise that resolves to an array of NotebookContentProvider static info objects.', tryMapWith(item => { + return { + viewType: item.viewType, + displayName: item.displayName, options: { - transientOutputs: boolean; - transientCellMetadata: TransientCellMetadata; - transientDocumentMetadata: TransientDocumentMetadata; - }; - filenamePattern: ( - | vscode.GlobPattern - | { - include: vscode.GlobPattern; - exclude: vscode.GlobPattern; - } - )[]; - }[], - | { - viewType: string; - displayName: string; - filenamePattern: ( - | vscode.GlobPattern - | { - include: vscode.GlobPattern; - exclude: vscode.GlobPattern; - } - )[]; - options: vscode.NotebookDocumentContentOptions; - }[] - | undefined - >( - "A promise that resolves to an array of NotebookContentProvider static info objects.", - tryMapWith((item) => { - return { - viewType: item.viewType, - displayName: item.displayName, - options: { - transientOutputs: item.options.transientOutputs, - transientCellMetadata: - item.options.transientCellMetadata, - transientDocumentMetadata: - item.options.transientDocumentMetadata, - }, - filenamePattern: item.filenamePattern.map((pattern) => - typeConverters.NotebookExclusiveDocumentPattern.to( - pattern, - ), - ), - }; - }), - ), + transientOutputs: item.options.transientOutputs, + transientCellMetadata: item.options.transientCellMetadata, + transientDocumentMetadata: item.options.transientDocumentMetadata + }, + filenamePattern: item.filenamePattern.map(pattern => typeConverters.NotebookExclusiveDocumentPattern.to(pattern)) + }; + })) ), // --- debug support new ApiCommand( - "vscode.executeInlineValueProvider", - "_executeInlineValueProvider", - "Execute inline value provider", + 'vscode.executeInlineValueProvider', '_executeInlineValueProvider', 'Execute inline value provider', [ ApiCommandArgument.Uri, ApiCommandArgument.Range, - new ApiCommandArgument< - types.InlineValueContext, - IInlineValueContextDto - >( - "context", - "An InlineValueContext", - (v) => - v && - typeof v.frameId === "number" && - v.stoppedLocation instanceof types.Range, - (v) => typeConverters.InlineValueContext.from(v), - ), + new ApiCommandArgument('context', 'An InlineValueContext', v => v && typeof v.frameId === 'number' && v.stoppedLocation instanceof types.Range, v => typeConverters.InlineValueContext.from(v)) ], - new ApiCommandResult( - "A promise that resolves to an array of InlineValue objects", - (result) => { - return result.map(typeConverters.InlineValue.to); - }, - ), + new ApiCommandResult('A promise that resolves to an array of InlineValue objects', result => { + return result.map(typeConverters.InlineValue.to); + }) ), // --- open'ish commands new ApiCommand( - "vscode.open", - "_workbench.open", - "Opens the provided resource in the editor. Can be a text or binary file, or an http(s) URL. If you need more control over the options for opening a text file, use vscode.window.showTextDocument instead.", + 'vscode.open', '_workbench.open', 'Opens the provided resource in the editor. Can be a text or binary file, or an http(s) URL. If you need more control over the options for opening a text file, use vscode.window.showTextDocument instead.', [ - new ApiCommandArgument( - "uriOrString", - "Uri-instance or string (only http/https)", - (v) => - URI.isUri(v) || - (typeof v === "string" && - matchesSomeScheme(v, Schemas.http, Schemas.https)), - (v) => v, - ), - new ApiCommandArgument< - | vscode.ViewColumn - | typeConverters.TextEditorOpenOptions - | undefined, - [vscode.ViewColumn?, ITextEditorOptions?] | undefined - >( - "columnOrOptions", - "Either the column in which to open or editor options, see vscode.TextDocumentShowOptions", - (v) => - v === undefined || - typeof v === "number" || - typeof v === "object", - (v) => - !v - ? v - : typeof v === "number" - ? [typeConverters.ViewColumn.from(v), undefined] - : [ - typeConverters.ViewColumn.from( - v.viewColumn, - ), - typeConverters.TextEditorOpenOptions.from( - v, - ), - ], + new ApiCommandArgument('uriOrString', 'Uri-instance or string (only http/https)', v => URI.isUri(v) || (typeof v === 'string' && matchesSomeScheme(v, Schemas.http, Schemas.https)), v => v), + new ApiCommandArgument('columnOrOptions', 'Either the column in which to open or editor options, see vscode.TextDocumentShowOptions', + v => v === undefined || typeof v === 'number' || typeof v === 'object', + v => !v ? v : typeof v === 'number' ? [typeConverters.ViewColumn.from(v), undefined] : [typeConverters.ViewColumn.from(v.viewColumn), typeConverters.TextEditorOpenOptions.from(v)] ).optional(), - ApiCommandArgument.String.with("label", "").optional(), + ApiCommandArgument.String.with('label', '').optional() ], - ApiCommandResult.Void, + ApiCommandResult.Void ), new ApiCommand( - "vscode.openWith", - "_workbench.openWith", - "Opens the provided resource with a specific editor.", + 'vscode.openWith', '_workbench.openWith', 'Opens the provided resource with a specific editor.', [ - ApiCommandArgument.Uri.with("resource", "Resource to open"), - ApiCommandArgument.String.with( - "viewId", - "Custom editor view id. This should be the viewType string for custom editors or the notebookType string for notebooks. Use 'default' to use VS Code's default text editor", - ), - new ApiCommandArgument< - | vscode.ViewColumn - | typeConverters.TextEditorOpenOptions - | undefined, - [vscode.ViewColumn?, ITextEditorOptions?] | undefined - >( - "columnOrOptions", - "Either the column in which to open or editor options, see vscode.TextDocumentShowOptions", - (v) => - v === undefined || - typeof v === "number" || - typeof v === "object", - (v) => - !v - ? v - : typeof v === "number" - ? [typeConverters.ViewColumn.from(v), undefined] - : [ - typeConverters.ViewColumn.from( - v.viewColumn, - ), - typeConverters.TextEditorOpenOptions.from( - v, - ), - ], - ).optional(), + ApiCommandArgument.Uri.with('resource', 'Resource to open'), + ApiCommandArgument.String.with('viewId', 'Custom editor view id. This should be the viewType string for custom editors or the notebookType string for notebooks. Use \'default\' to use VS Code\'s default text editor'), + new ApiCommandArgument('columnOrOptions', 'Either the column in which to open or editor options, see vscode.TextDocumentShowOptions', + v => v === undefined || typeof v === 'number' || typeof v === 'object', + v => !v ? v : typeof v === 'number' ? [typeConverters.ViewColumn.from(v), undefined] : [typeConverters.ViewColumn.from(v.viewColumn), typeConverters.TextEditorOpenOptions.from(v)], + ).optional() ], - ApiCommandResult.Void, + ApiCommandResult.Void ), new ApiCommand( - "vscode.diff", - "_workbench.diff", - "Opens the provided resources in the diff editor to compare their contents.", + 'vscode.diff', '_workbench.diff', 'Opens the provided resources in the diff editor to compare their contents.', [ - ApiCommandArgument.Uri.with( - "left", - "Left-hand side resource of the diff editor", - ), - ApiCommandArgument.Uri.with( - "right", - "Right-hand side resource of the diff editor", - ), - ApiCommandArgument.String.with( - "title", - "Human readable title for the diff editor", - ).optional(), - new ApiCommandArgument< - typeConverters.TextEditorOpenOptions | undefined, - [number?, ITextEditorOptions?] | undefined - >( - "columnOrOptions", - "Either the column in which to open or editor options, see vscode.TextDocumentShowOptions", - (v) => v === undefined || typeof v === "object", - (v) => - v && [ - typeConverters.ViewColumn.from(v.viewColumn), - typeConverters.TextEditorOpenOptions.from(v), - ], + ApiCommandArgument.Uri.with('left', 'Left-hand side resource of the diff editor'), + ApiCommandArgument.Uri.with('right', 'Right-hand side resource of the diff editor'), + ApiCommandArgument.String.with('title', 'Human readable title for the diff editor').optional(), + new ApiCommandArgument('columnOrOptions', 'Either the column in which to open or editor options, see vscode.TextDocumentShowOptions', + v => v === undefined || typeof v === 'object', + v => v && [typeConverters.ViewColumn.from(v.viewColumn), typeConverters.TextEditorOpenOptions.from(v)] ).optional(), ], - ApiCommandResult.Void, + ApiCommandResult.Void ), new ApiCommand( - "vscode.changes", - "_workbench.changes", - "Opens a list of resources in the changes editor to compare their contents.", + 'vscode.changes', '_workbench.changes', 'Opens a list of resources in the changes editor to compare their contents.', [ - ApiCommandArgument.String.with( - "title", - "Human readable title for the changes editor", - ), - new ApiCommandArgument<[URI, URI?, URI?][]>( - "resourceList", - "List of resources to compare", - (resources) => { + ApiCommandArgument.String.with('title', 'Human readable title for the changes editor'), + new ApiCommandArgument<[URI, URI?, URI?][]>('resourceList', 'List of resources to compare', + resources => { for (const resource of resources) { if (resource.length !== 3) { return false; } - const [label, left, right] = resource; - if ( - !URI.isUri(label) || - (!URI.isUri(left) && - left !== undefined && - left !== null) || - (!URI.isUri(right) && - right !== undefined && - right !== null) - ) { + const [label, left, right] = resource; + if (!URI.isUri(label) || + (!URI.isUri(left) && left !== undefined && left !== null) || + (!URI.isUri(right) && right !== undefined && right !== null)) { return false; } } + return true; }, - (v) => v, - ), + v => v) ], - ApiCommandResult.Void, + ApiCommandResult.Void ), // --- type hierarchy new ApiCommand( - "vscode.prepareTypeHierarchy", - "_executePrepareTypeHierarchy", - "Prepare type hierarchy at a position inside a document", + 'vscode.prepareTypeHierarchy', '_executePrepareTypeHierarchy', 'Prepare type hierarchy at a position inside a document', [ApiCommandArgument.Uri, ApiCommandArgument.Position], - new ApiCommandResult< - ITypeHierarchyItemDto[], - types.TypeHierarchyItem[] - >( - "A promise that resolves to an array of TypeHierarchyItem-instances", - (v) => v.map(typeConverters.TypeHierarchyItem.to), - ), + new ApiCommandResult('A promise that resolves to an array of TypeHierarchyItem-instances', v => v.map(typeConverters.TypeHierarchyItem.to)) ), new ApiCommand( - "vscode.provideSupertypes", - "_executeProvideSupertypes", - "Compute supertypes for an item", + 'vscode.provideSupertypes', '_executeProvideSupertypes', 'Compute supertypes for an item', [ApiCommandArgument.TypeHierarchyItem], - new ApiCommandResult< - ITypeHierarchyItemDto[], - types.TypeHierarchyItem[] - >( - "A promise that resolves to an array of TypeHierarchyItem-instances", - (v) => v.map(typeConverters.TypeHierarchyItem.to), - ), + new ApiCommandResult('A promise that resolves to an array of TypeHierarchyItem-instances', v => v.map(typeConverters.TypeHierarchyItem.to)) ), new ApiCommand( - "vscode.provideSubtypes", - "_executeProvideSubtypes", - "Compute subtypes for an item", + 'vscode.provideSubtypes', '_executeProvideSubtypes', 'Compute subtypes for an item', [ApiCommandArgument.TypeHierarchyItem], - new ApiCommandResult< - ITypeHierarchyItemDto[], - types.TypeHierarchyItem[] - >( - "A promise that resolves to an array of TypeHierarchyItem-instances", - (v) => v.map(typeConverters.TypeHierarchyItem.to), - ), + new ApiCommandResult('A promise that resolves to an array of TypeHierarchyItem-instances', v => v.map(typeConverters.TypeHierarchyItem.to)) ), // --- testing new ApiCommand( - "vscode.revealTestInExplorer", - "_revealTestInExplorer", - "Reveals a test instance in the explorer", + 'vscode.revealTestInExplorer', '_revealTestInExplorer', 'Reveals a test instance in the explorer', [ApiCommandArgument.TestItem], - ApiCommandResult.Void, + ApiCommandResult.Void + ), + new ApiCommand( + 'vscode.startContinuousTestRun', 'testing.startContinuousRunFromExtension', 'Starts running the given tests with continuous run mode.', + [ApiCommandArgument.TestProfile, ApiCommandArgument.Arr(ApiCommandArgument.TestItem)], + ApiCommandResult.Void + ), + new ApiCommand( + 'vscode.stopContinuousTestRun', 'testing.stopContinuousRunFromExtension', 'Stops running the given tests with continuous run mode.', + [ApiCommandArgument.Arr(ApiCommandArgument.TestItem)], + ApiCommandResult.Void ), // --- continue edit session new ApiCommand( - "vscode.experimental.editSession.continue", - "_workbench.editSessions.actions.continueEditSession", - "Continue the current edit session in a different workspace", - [ - ApiCommandArgument.Uri.with( - "workspaceUri", - "The target workspace to continue the current edit session in", - ), - ], - ApiCommandResult.Void, + 'vscode.experimental.editSession.continue', '_workbench.editSessions.actions.continueEditSession', 'Continue the current edit session in a different workspace', + [ApiCommandArgument.Uri.with('workspaceUri', 'The target workspace to continue the current edit session in')], + ApiCommandResult.Void ), // --- context keys new ApiCommand( - "setContext", - "_setContext", - "Set a custom context key value that can be used in when clauses.", + 'setContext', '_setContext', 'Set a custom context key value that can be used in when clauses.', [ - ApiCommandArgument.String.with("name", "The context key name"), - new ApiCommandArgument( - "value", - "The context key value", - () => true, - (v) => v, - ), + ApiCommandArgument.String.with('name', 'The context key name'), + new ApiCommandArgument('value', 'The context key value', () => true, v => v), ], - ApiCommandResult.Void, + ApiCommandResult.Void ), // --- mapped edits new ApiCommand( - "vscode.executeMappedEditsProvider", - "_executeMappedEditsProvider", - "Execute Mapped Edits Provider", + 'vscode.executeMappedEditsProvider', '_executeMappedEditsProvider', 'Execute Mapped Edits Provider', [ ApiCommandArgument.Uri, ApiCommandArgument.StringArray, new ApiCommandArgument( - "MappedEditsContext", - "Mapped Edits Context", + 'MappedEditsContext', + 'Mapped Edits Context', (v: unknown) => typeConverters.MappedEditsContext.is(v), - (v: vscode.MappedEditsContext) => - typeConverters.MappedEditsContext.from(v), - ), + (v: vscode.MappedEditsContext) => typeConverters.MappedEditsContext.from(v) + ) ], - new ApiCommandResult< - IWorkspaceEditDto | null, - vscode.WorkspaceEdit | null - >("A promise that resolves to a workspace edit or null", (value) => { - return value ? typeConverters.WorkspaceEdit.to(value) : null; - }), + new ApiCommandResult( + 'A promise that resolves to a workspace edit or null', + (value) => { + return value ? typeConverters.WorkspaceEdit.to(value) : null; + }) ), // --- inline chat new ApiCommand( - "vscode.editorChat.start", - "inlineChat.start", - "Invoke a new editor chat session", - [ - new ApiCommandArgument< - InlineChatEditorApiArg | undefined, - InlineChatRunOptions | undefined - >( - "Run arguments", - "", - (_v) => true, - (v) => { - if (!v) { - return undefined; - } - return { - initialRange: v.initialRange - ? typeConverters.Range.from(v.initialRange) - : undefined, - initialSelection: types.Selection.isSelection( - v.initialSelection, - ) - ? typeConverters.Selection.from(v.initialSelection) - : undefined, - message: v.message, - autoSend: v.autoSend, - position: v.position - ? typeConverters.Position.from(v.position) - : undefined, - }; - }, - ), - ], - ApiCommandResult.Void, - ), + 'vscode.editorChat.start', 'inlineChat.start', 'Invoke a new editor chat session', + [new ApiCommandArgument('Run arguments', '', _v => true, v => { + + if (!v) { + return undefined; + } + + return { + initialRange: v.initialRange ? typeConverters.Range.from(v.initialRange) : undefined, + initialSelection: types.Selection.isSelection(v.initialSelection) ? typeConverters.Selection.from(v.initialSelection) : undefined, + message: v.message, + autoSend: v.autoSend, + position: v.position ? typeConverters.Position.from(v.position) : undefined, + }; + })], + ApiCommandResult.Void + ) ]; + type InlineChatEditorApiArg = { initialRange?: vscode.Range; initialSelection?: vscode.Selection; @@ -1214,6 +581,7 @@ type InlineChatEditorApiArg = { autoSend?: boolean; position?: vscode.Position; }; + type InlineChatRunOptions = { initialRange?: IRange; initialSelection?: ISelection; @@ -1221,23 +589,26 @@ type InlineChatRunOptions = { autoSend?: boolean; position?: IPosition; }; + //#endregion + + //#region OLD world + export class ExtHostApiCommands { + static register(commands: ExtHostCommands) { + newCommands.forEach(commands.registerApiCommand, commands); + this._registerValidateWhenClausesCommand(commands); } - private static _registerValidateWhenClausesCommand( - commands: ExtHostCommands, - ) { - commands.registerCommand( - false, - "_validateWhenClauses", - validateWhenClauses, - ); + + private static _registerValidateWhenClausesCommand(commands: ExtHostCommands) { + commands.registerCommand(false, '_validateWhenClauses', validateWhenClauses); } } + function tryMapWith(f: (x: T) => R) { return (value: T[]) => { if (Array.isArray(value)) { @@ -1246,14 +617,12 @@ function tryMapWith(f: (x: T) => R) { return undefined; }; } -function mapLocationOrLocationLink( - values: (languages.Location | languages.LocationLink)[], -): (types.Location | vscode.LocationLink)[] | undefined { + +function mapLocationOrLocationLink(values: (languages.Location | languages.LocationLink)[]): (types.Location | vscode.LocationLink)[] | undefined { if (!Array.isArray(values)) { return undefined; } const result: (types.Location | vscode.LocationLink)[] = []; - for (const item of values) { if (languages.isLocationLink(item)) { result.push(typeConverters.DefinitionLink.to(item)); diff --git a/Source/vs/workbench/api/common/extHostCommands.ts b/Source/vs/workbench/api/common/extHostCommands.ts index 4c83ea6868dd0..b1ff98172011f 100644 --- a/Source/vs/workbench/api/common/extHostCommands.ts +++ b/Source/vs/workbench/api/common/extHostCommands.ts @@ -2,48 +2,36 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + /* eslint-disable local/code-no-native-private */ -import type * as vscode from "vscode"; - -import { isNonEmptyArray } from "../../../base/common/arrays.js"; -import { VSBuffer } from "../../../base/common/buffer.js"; -import { toErrorMessage } from "../../../base/common/errorMessage.js"; -import { - DisposableStore, - toDisposable, -} from "../../../base/common/lifecycle.js"; -import { revive } from "../../../base/common/marshalling.js"; -import { cloneAndChange } from "../../../base/common/objects.js"; -import { StopWatch } from "../../../base/common/stopwatch.js"; -import { validateConstraint } from "../../../base/common/types.js"; -import { URI } from "../../../base/common/uri.js"; -import { generateUuid } from "../../../base/common/uuid.js"; -import { IPosition, Position } from "../../../editor/common/core/position.js"; -import { IRange, Range } from "../../../editor/common/core/range.js"; -import { ISelection } from "../../../editor/common/core/selection.js"; -import * as languages from "../../../editor/common/languages.js"; -import { ICommandMetadata } from "../../../platform/commands/common/commands.js"; -import { - ExtensionIdentifier, - IExtensionDescription, -} from "../../../platform/extensions/common/extensions.js"; -import { createDecorator } from "../../../platform/instantiation/common/instantiation.js"; -import { ILogService } from "../../../platform/log/common/log.js"; -import { TelemetryTrustedValue } from "../../../platform/telemetry/common/telemetryUtils.js"; -import { SerializableObjectWithBuffers } from "../../services/extensions/common/proxyIdentifier.js"; -import { - ExtHostCommandsShape, - ICommandDto, - ICommandMetadataDto, - MainContext, - MainThreadCommandsShape, - MainThreadTelemetryShape, -} from "./extHost.protocol.js"; -import { IExtHostRpcService } from "./extHostRpcService.js"; -import { IExtHostTelemetry } from "./extHostTelemetry.js"; -import { TestItemImpl } from "./extHostTestItem.js"; -import * as extHostTypeConverter from "./extHostTypeConverters.js"; -import * as extHostTypes from "./extHostTypes.js"; + +import { validateConstraint } from '../../../base/common/types.js'; +import { ICommandMetadata } from '../../../platform/commands/common/commands.js'; +import * as extHostTypes from './extHostTypes.js'; +import * as extHostTypeConverter from './extHostTypeConverters.js'; +import { cloneAndChange } from '../../../base/common/objects.js'; +import { MainContext, MainThreadCommandsShape, ExtHostCommandsShape, ICommandDto, ICommandMetadataDto, MainThreadTelemetryShape } from './extHost.protocol.js'; +import { isNonEmptyArray } from '../../../base/common/arrays.js'; +import * as languages from '../../../editor/common/languages.js'; +import type * as vscode from 'vscode'; +import { ILogService } from '../../../platform/log/common/log.js'; +import { revive } from '../../../base/common/marshalling.js'; +import { IRange, Range } from '../../../editor/common/core/range.js'; +import { IPosition, Position } from '../../../editor/common/core/position.js'; +import { URI } from '../../../base/common/uri.js'; +import { DisposableStore, toDisposable } from '../../../base/common/lifecycle.js'; +import { createDecorator } from '../../../platform/instantiation/common/instantiation.js'; +import { IExtHostRpcService } from './extHostRpcService.js'; +import { ISelection } from '../../../editor/common/core/selection.js'; +import { TestItemImpl } from './extHostTestItem.js'; +import { VSBuffer } from '../../../base/common/buffer.js'; +import { SerializableObjectWithBuffers } from '../../services/extensions/common/proxyIdentifier.js'; +import { toErrorMessage } from '../../../base/common/errorMessage.js'; +import { StopWatch } from '../../../base/common/stopwatch.js'; +import { ExtensionIdentifier, IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; +import { TelemetryTrustedValue } from '../../../platform/telemetry/common/telemetryUtils.js'; +import { IExtHostTelemetry } from './extHostTelemetry.js'; +import { generateUuid } from '../../../base/common/uuid.js'; interface CommandHandler { callback: Function; @@ -51,30 +39,31 @@ interface CommandHandler { metadata?: ICommandMetadata; extension?: IExtensionDescription; } + export interface ArgumentProcessor { - processArgument( - arg: any, - extensionId: ExtensionIdentifier | undefined, - ): any; + processArgument(arg: any, extensionId: ExtensionIdentifier | undefined): any; } + export class ExtHostCommands implements ExtHostCommandsShape { + readonly _serviceBrand: undefined; + #proxy: MainThreadCommandsShape; + private readonly _commands = new Map(); private readonly _apiCommands = new Map(); #telemetry: MainThreadTelemetryShape; + private readonly _logService: ILogService; readonly #extHostTelemetry: IExtHostTelemetry; private readonly _argumentProcessors: ArgumentProcessor[]; + readonly converter: CommandsConverter; constructor( - @IExtHostRpcService - extHostRpc: IExtHostRpcService, - @ILogService - logService: ILogService, - @IExtHostTelemetry - extHostTelemetry: IExtHostTelemetry, + @IExtHostRpcService extHostRpc: IExtHostRpcService, + @ILogService logService: ILogService, + @IExtHostTelemetry extHostTelemetry: IExtHostTelemetry ) { this.#proxy = extHostRpc.getProxy(MainContext.MainThreadCommands); this._logService = logService; @@ -82,24 +71,22 @@ export class ExtHostCommands implements ExtHostCommandsShape { this.#telemetry = extHostRpc.getProxy(MainContext.MainThreadTelemetry); this.converter = new CommandsConverter( this, - (id) => { + id => { // API commands that have no return type (void) can be // converted to their internal command and don't need // any indirection commands const candidate = this._apiCommands.get(id); - return candidate?.result === ApiCommandResult.Void - ? candidate - : undefined; + ? candidate : undefined; }, - logService, + logService ); this._argumentProcessors = [ { processArgument(a) { // URI, Regex return revive(a); - }, + } }, { processArgument(arg) { @@ -111,10 +98,7 @@ export class ExtHostCommands implements ExtHostCommandsShape { if (Position.isIPosition(obj)) { return extHostTypeConverter.Position.to(obj); } - if ( - Range.isIRange((obj as languages.Location).range) && - URI.isUri((obj as languages.Location).uri) - ) { + if (Range.isIRange((obj as languages.Location).range) && URI.isUri((obj as languages.Location).uri)) { return extHostTypeConverter.location.to(obj); } if (obj instanceof VSBuffer) { @@ -124,45 +108,35 @@ export class ExtHostCommands implements ExtHostCommandsShape { return obj; } }); - }, - }, + } + } ]; } + registerArgumentProcessor(processor: ArgumentProcessor): void { this._argumentProcessors.push(processor); } + registerApiCommand(apiCommand: ApiCommand): extHostTypes.Disposable { - const registration = this.registerCommand( - false, - apiCommand.id, - async (...apiArgs) => { - const internalArgs = apiCommand.args.map((arg, i) => { - if (!arg.validate(apiArgs[i])) { - throw new Error( - `Invalid argument '${arg.name}' when running '${apiCommand.id}', received: ${typeof apiArgs[i] === "object" ? JSON.stringify(apiArgs[i], null, "\t") : apiArgs[i]} `, - ); - } - return arg.convert(apiArgs[i]); - }); - - const internalResult = await this.executeCommand( - apiCommand.internalId, - ...internalArgs, - ); - - return apiCommand.result.convert( - internalResult, - apiArgs, - this.converter, - ); - }, - undefined, - { - description: apiCommand.description, - args: apiCommand.args, - returns: apiCommand.result.description, - }, - ); + + + const registration = this.registerCommand(false, apiCommand.id, async (...apiArgs) => { + + const internalArgs = apiCommand.args.map((arg, i) => { + if (!arg.validate(apiArgs[i])) { + throw new Error(`Invalid argument '${arg.name}' when running '${apiCommand.id}', received: ${typeof apiArgs[i] === 'object' ? JSON.stringify(apiArgs[i], null, '\t') : apiArgs[i]} `); + } + return arg.convert(apiArgs[i]); + }); + + const internalResult = await this.executeCommand(apiCommand.internalId, ...internalArgs); + return apiCommand.result.convert(internalResult, apiArgs, this.converter); + }, undefined, { + description: apiCommand.description, + args: apiCommand.args, + returns: apiCommand.result.description + }); + this._apiCommands.set(apiCommand.id, apiCommand); return new extHostTypes.Disposable(() => { @@ -170,27 +144,23 @@ export class ExtHostCommands implements ExtHostCommandsShape { this._apiCommands.delete(apiCommand.id); }); } - registerCommand( - global: boolean, - id: string, - callback: (...args: any[]) => T | Thenable, - thisArg?: any, - metadata?: ICommandMetadata, - extension?: IExtensionDescription, - ): extHostTypes.Disposable { - this._logService.trace("ExtHostCommands#registerCommand", id); + + registerCommand(global: boolean, id: string, callback: (...args: any[]) => T | Thenable, thisArg?: any, metadata?: ICommandMetadata, extension?: IExtensionDescription): extHostTypes.Disposable { + this._logService.trace('ExtHostCommands#registerCommand', id); if (!id.trim().length) { - throw new Error("invalid id"); + throw new Error('invalid id'); } + if (this._commands.has(id)) { throw new Error(`command '${id}' already exists`); } - this._commands.set(id, { callback, thisArg, metadata, extension }); + this._commands.set(id, { callback, thisArg, metadata, extension }); if (global) { this.#proxy.$registerCommand(id); } + return new extHostTypes.Disposable(() => { if (this._commands.delete(id)) { if (global) { @@ -199,28 +169,25 @@ export class ExtHostCommands implements ExtHostCommandsShape { } }); } - executeCommand(id: string, ...args: any[]): Promise { - this._logService.trace("ExtHostCommands#executeCommand", id); + executeCommand(id: string, ...args: any[]): Promise { + this._logService.trace('ExtHostCommands#executeCommand', id); return this._doExecuteCommand(id, args, true); } - private async _doExecuteCommand( - id: string, - args: any[], - retry: boolean, - ): Promise { + + private async _doExecuteCommand(id: string, args: any[], retry: boolean): Promise { + if (this._commands.has(id)) { // - We stay inside the extension host and support // to pass any kind of parameters around. // - We still emit the corresponding activation event // BUT we don't await that event this.#proxy.$fireCommandActivationEvent(id); - return this._executeContributedCommand(id, args, false); + } else { // automagically convert some argument types let hasBuffers = false; - const toArgs = cloneAndChange(args, function (value) { if (value instanceof extHostTypes.Position) { return extHostTypeConverter.Position.from(value); @@ -232,15 +199,12 @@ export class ExtHostCommands implements ExtHostCommandsShape { return extHostTypeConverter.NotebookRange.from(value); } else if (value instanceof ArrayBuffer) { hasBuffers = true; - return VSBuffer.wrap(new Uint8Array(value)); } else if (value instanceof Uint8Array) { hasBuffers = true; - return VSBuffer.wrap(value); } else if (value instanceof VSBuffer) { hasBuffers = true; - return value; } if (!Array.isArray(value)) { @@ -249,23 +213,13 @@ export class ExtHostCommands implements ExtHostCommandsShape { }); try { - const result = await this.#proxy.$executeCommand( - id, - hasBuffers - ? new SerializableObjectWithBuffers(toArgs) - : toArgs, - retry, - ); - + const result = await this.#proxy.$executeCommand(id, hasBuffers ? new SerializableObjectWithBuffers(toArgs) : toArgs, retry); return revive(result); } catch (e) { // Rerun the command when it wasn't known, had arguments, and when retry // is enabled. We do this because the command might be registered inside // the extension host now and can therefore accept the arguments as-is. - if ( - e instanceof Error && - e.message === "$executeCommand:retry" - ) { + if (e instanceof Error && e.message === '$executeCommand:retry') { return this._doExecuteCommand(id, args, false); } else { throw e; @@ -273,31 +227,24 @@ export class ExtHostCommands implements ExtHostCommandsShape { } } } - private async _executeContributedCommand( - id: string, - args: any[], - annotateError: boolean, - ): Promise { - const command = this._commands.get(id); + private async _executeContributedCommand(id: string, args: any[], annotateError: boolean): Promise { + const command = this._commands.get(id); if (!command) { - throw new Error("Unknown command"); + throw new Error('Unknown command'); } const { callback, thisArg, metadata } = command; - if (metadata?.args) { for (let i = 0; i < metadata.args.length; i++) { try { validateConstraint(args[i], metadata.args[i].constraint); } catch (err) { - throw new Error( - `Running the contributed command: '${id}' failed. Illegal argument '${metadata.args[i].name}' - ${metadata.args[i].description}`, - ); + throw new Error(`Running the contributed command: '${id}' failed. Illegal argument '${metadata.args[i].name}' - ${metadata.args[i].description}`); } } } - const stopWatch = StopWatch.create(); + const stopWatch = StopWatch.create(); try { return await callback.apply(thisArg, args); } catch (err) { @@ -305,7 +252,6 @@ export class ExtHostCommands implements ExtHostCommandsShape { // command and in that case it is better to blame the correct command if (id === this.converter.delegatingCommandId) { const actual = this.converter.getActualCommand(...args); - if (actual) { id = actual.command; } @@ -315,35 +261,26 @@ export class ExtHostCommands implements ExtHostCommandsShape { if (!annotateError) { throw err; } + if (command.extension?.identifier) { - const reported = this.#extHostTelemetry.onExtensionError( - command.extension.identifier, - err, - ); - this._logService.trace( - "forwarded error to extension?", - reported, - command.extension?.identifier, - ); + const reported = this.#extHostTelemetry.onExtensionError(command.extension.identifier, err); + this._logService.trace('forwarded error to extension?', reported, command.extension?.identifier); } - throw new (class CommandError extends Error { - readonly id = id; - readonly source = - command!.extension?.displayName ?? command!.extension?.name; + throw new class CommandError extends Error { + readonly id = id; + readonly source = command!.extension?.displayName ?? command!.extension?.name; constructor() { super(toErrorMessage(err)); } - })(); - } finally { + }; + } + finally { this._reportTelemetry(command, id, stopWatch.elapsed()); } } - private _reportTelemetry( - command: CommandHandler, - id: string, - duration: number, - ) { + + private _reportTelemetry(command: CommandHandler, id: string, duration: number) { if (!command.extension) { return; } @@ -353,80 +290,46 @@ export class ExtHostCommands implements ExtHostCommandsShape { duration: number; }; type ExtensionActionTelemetryMeta = { - extensionId: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "The id of the extension handling the command, informing which extensions provide most-used functionality."; - }; - id: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "The id of the command, to understand which specific extension features are most popular."; - }; - duration: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "The duration of the command execution, to detect performance issues"; - }; - owner: "digitarald"; - comment: "Used to gain insight on the most popular commands used from extensions"; + extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The id of the extension handling the command, informing which extensions provide most-used functionality.' }; + id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The id of the command, to understand which specific extension features are most popular.' }; + duration: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The duration of the command execution, to detect performance issues' }; + owner: 'digitarald'; + comment: 'Used to gain insight on the most popular commands used from extensions'; }; - this.#telemetry.$publicLog2< - ExtensionActionTelemetry, - ExtensionActionTelemetryMeta - >("Extension:ActionExecuted", { + this.#telemetry.$publicLog2('Extension:ActionExecuted', { extensionId: command.extension.identifier.value, id: new TelemetryTrustedValue(id), duration: duration, }); } + $executeContributedCommand(id: string, ...args: any[]): Promise { - this._logService.trace( - "ExtHostCommands#$executeContributedCommand", - id, - ); + this._logService.trace('ExtHostCommands#$executeContributedCommand', id); const cmdHandler = this._commands.get(id); - if (!cmdHandler) { - return Promise.reject( - new Error(`Contributed command '${id}' does not exist.`), - ); + return Promise.reject(new Error(`Contributed command '${id}' does not exist.`)); } else { - args = args.map((arg) => - this._argumentProcessors.reduce( - (r, p) => - p.processArgument(r, cmdHandler.extension?.identifier), - arg, - ), - ); - + args = args.map(arg => this._argumentProcessors.reduce((r, p) => p.processArgument(r, cmdHandler.extension?.identifier), arg)); return this._executeContributedCommand(id, args, true); } } + getCommands(filterUnderscoreCommands: boolean = false): Promise { - this._logService.trace( - "ExtHostCommands#getCommands", - filterUnderscoreCommands, - ); + this._logService.trace('ExtHostCommands#getCommands', filterUnderscoreCommands); - return this.#proxy.$getCommands().then((result) => { + return this.#proxy.$getCommands().then(result => { if (filterUnderscoreCommands) { - result = result.filter((command) => command[0] !== "_"); + result = result.filter(command => command[0] !== '_'); } return result; }); } - $getContributedCommandMetadata(): Promise<{ - [id: string]: string | ICommandMetadataDto; - }> { - const result: { - [id: string]: string | ICommandMetadata; - } = Object.create(null); + $getContributedCommandMetadata(): Promise<{ [id: string]: string | ICommandMetadataDto }> { + const result: { [id: string]: string | ICommandMetadata } = Object.create(null); for (const [id, command] of this._commands) { const { metadata } = command; - if (metadata) { result[id] = metadata; } @@ -434,50 +337,38 @@ export class ExtHostCommands implements ExtHostCommandsShape { return Promise.resolve(result); } } -export interface IExtHostCommands extends ExtHostCommands {} -export const IExtHostCommands = - createDecorator("IExtHostCommands"); -export class CommandsConverter - implements extHostTypeConverter.Command.ICommandsConverter -{ + +export interface IExtHostCommands extends ExtHostCommands { } +export const IExtHostCommands = createDecorator('IExtHostCommands'); + +export class CommandsConverter implements extHostTypeConverter.Command.ICommandsConverter { + readonly delegatingCommandId: string = `__vsc${generateUuid()}`; private readonly _cache = new Map(); private _cachIdPool = 0; + // --- conversion between internal and api commands constructor( private readonly _commands: ExtHostCommands, - private readonly _lookupApiCommand: ( - id: string, - ) => ApiCommand | undefined, - private readonly _logService: ILogService, + private readonly _lookupApiCommand: (id: string) => ApiCommand | undefined, + private readonly _logService: ILogService ) { - this._commands.registerCommand( - true, - this.delegatingCommandId, - this._executeConvertedCommand, - this, - ); + this._commands.registerCommand(true, this.delegatingCommandId, this._executeConvertedCommand, this); } - toInternal( - command: vscode.Command, - disposables: DisposableStore, - ): ICommandDto; - toInternal( - command: vscode.Command | undefined, - disposables: DisposableStore, - ): ICommandDto | undefined; - toInternal( - command: vscode.Command | undefined, - disposables: DisposableStore, - ): ICommandDto | undefined { + + toInternal(command: vscode.Command, disposables: DisposableStore): ICommandDto; + toInternal(command: vscode.Command | undefined, disposables: DisposableStore): ICommandDto | undefined; + toInternal(command: vscode.Command | undefined, disposables: DisposableStore): ICommandDto | undefined { + if (!command) { return undefined; } + const result: ICommandDto = { $ident: undefined, id: command.command, title: command.title, - tooltip: command.tooltip, + tooltip: command.tooltip }; if (!command.command) { @@ -485,196 +376,128 @@ export class CommandsConverter // argument or API-command dance since this command won't run anyways return result; } - const apiCommand = this._lookupApiCommand(command.command); + const apiCommand = this._lookupApiCommand(command.command); if (apiCommand) { // API command with return-value can be converted inplace result.id = apiCommand.internalId; - result.arguments = apiCommand.args.map((arg, i) => - arg.convert(command.arguments && command.arguments[i]), - ); + result.arguments = apiCommand.args.map((arg, i) => arg.convert(command.arguments && command.arguments[i])); + + } else if (isNonEmptyArray(command.arguments)) { // we have a contributed command with arguments. that // means we don't want to send the arguments around + const id = `${command.command} /${++this._cachIdPool}`; this._cache.set(id, command); - disposables.add( - toDisposable(() => { - this._cache.delete(id); - this._logService.trace("CommandsConverter#DISPOSE", id); - }), - ); + disposables.add(toDisposable(() => { + this._cache.delete(id); + this._logService.trace('CommandsConverter#DISPOSE', id); + })); result.$ident = id; + result.id = this.delegatingCommandId; result.arguments = [id]; - this._logService.trace( - "CommandsConverter#CREATE", - command.command, - id, - ); + + this._logService.trace('CommandsConverter#CREATE', command.command, id); } + return result; } + fromInternal(command: ICommandDto): vscode.Command | undefined { - if (typeof command.$ident === "string") { + + if (typeof command.$ident === 'string') { return this._cache.get(command.$ident); + } else { return { command: command.id, title: command.title, - arguments: command.arguments, + arguments: command.arguments }; } } + + getActualCommand(...args: any[]): vscode.Command | undefined { return this._cache.get(args[0]); } + private _executeConvertedCommand(...args: any[]): Promise { const actualCmd = this.getActualCommand(...args); - this._logService.trace( - "CommandsConverter#EXECUTE", - args[0], - actualCmd ? actualCmd.command : "MISSING", - ); + this._logService.trace('CommandsConverter#EXECUTE', args[0], actualCmd ? actualCmd.command : 'MISSING'); if (!actualCmd) { - return Promise.reject( - `Actual command not found, wanted to execute ${args[0]}`, - ); + return Promise.reject(`Actual command not found, wanted to execute ${args[0]}`); } - return this._commands.executeCommand( - actualCmd.command, - ...(actualCmd.arguments || []), - ); + return this._commands.executeCommand(actualCmd.command, ...(actualCmd.arguments || [])); } + } + + export class ApiCommandArgument { - static readonly Uri = new ApiCommandArgument( - "uri", - "Uri of a text document", - (v) => URI.isUri(v), - (v) => v, - ); - static readonly Position = new ApiCommandArgument< - extHostTypes.Position, - IPosition - >( - "position", - "A position in a text document", - (v) => extHostTypes.Position.isPosition(v), - extHostTypeConverter.Position.from, - ); - static readonly Range = new ApiCommandArgument( - "range", - "A range in a text document", - (v) => extHostTypes.Range.isRange(v), - extHostTypeConverter.Range.from, - ); - static readonly Selection = new ApiCommandArgument< - extHostTypes.Selection, - ISelection - >( - "selection", - "A selection in a text document", - (v) => extHostTypes.Selection.isSelection(v), - extHostTypeConverter.Selection.from, - ); - static readonly Number = new ApiCommandArgument( - "number", - "", - (v) => typeof v === "number", - (v) => v, - ); - static readonly String = new ApiCommandArgument( - "string", - "", - (v) => typeof v === "string", - (v) => v, - ); - static readonly StringArray = ApiCommandArgument.Arr( - ApiCommandArgument.String, - ); + + static readonly Uri = new ApiCommandArgument('uri', 'Uri of a text document', v => URI.isUri(v), v => v); + static readonly Position = new ApiCommandArgument('position', 'A position in a text document', v => extHostTypes.Position.isPosition(v), extHostTypeConverter.Position.from); + static readonly Range = new ApiCommandArgument('range', 'A range in a text document', v => extHostTypes.Range.isRange(v), extHostTypeConverter.Range.from); + static readonly Selection = new ApiCommandArgument('selection', 'A selection in a text document', v => extHostTypes.Selection.isSelection(v), extHostTypeConverter.Selection.from); + static readonly Number = new ApiCommandArgument('number', '', v => typeof v === 'number', v => v); + static readonly String = new ApiCommandArgument('string', '', v => typeof v === 'string', v => v); + static readonly StringArray = ApiCommandArgument.Arr(ApiCommandArgument.String); + static Arr(element: ApiCommandArgument) { return new ApiCommandArgument( `${element.name}_array`, `Array of ${element.name}, ${element.description}`, - (v: unknown) => - Array.isArray(v) && v.every((e) => element.validate(e)), - (v: T[]) => v.map((e) => element.convert(e)), + (v: unknown) => Array.isArray(v) && v.every(e => element.validate(e)), + (v: T[]) => v.map(e => element.convert(e)) ); } - static readonly CallHierarchyItem = new ApiCommandArgument( - "item", - "A call hierarchy item", - (v) => v instanceof extHostTypes.CallHierarchyItem, - extHostTypeConverter.CallHierarchyItem.from, - ); - static readonly TypeHierarchyItem = new ApiCommandArgument( - "item", - "A type hierarchy item", - (v) => v instanceof extHostTypes.TypeHierarchyItem, - extHostTypeConverter.TypeHierarchyItem.from, - ); - static readonly TestItem = new ApiCommandArgument( - "testItem", - "A VS Code TestItem", - (v) => v instanceof TestItemImpl, - extHostTypeConverter.TestItem.from, - ); + + static readonly CallHierarchyItem = new ApiCommandArgument('item', 'A call hierarchy item', v => v instanceof extHostTypes.CallHierarchyItem, extHostTypeConverter.CallHierarchyItem.from); + static readonly TypeHierarchyItem = new ApiCommandArgument('item', 'A type hierarchy item', v => v instanceof extHostTypes.TypeHierarchyItem, extHostTypeConverter.TypeHierarchyItem.from); + static readonly TestItem = new ApiCommandArgument('testItem', 'A VS Code TestItem', v => v instanceof TestItemImpl, extHostTypeConverter.TestItem.from); + static readonly TestProfile = new ApiCommandArgument('testProfile', 'A VS Code test profile', v => v instanceof extHostTypes.TestRunProfileBase, extHostTypeConverter.TestRunProfile.from); constructor( readonly name: string, readonly description: string, readonly validate: (v: V) => boolean, - readonly convert: (v: V) => O, - ) {} + readonly convert: (v: V) => O + ) { } + optional(): ApiCommandArgument { return new ApiCommandArgument( - this.name, - `(optional) ${this.description}`, - (value) => - value === undefined || value === null || this.validate(value), - (value) => - value === undefined - ? undefined - : value === null - ? null - : this.convert(value), + this.name, `(optional) ${this.description}`, + value => value === undefined || value === null || this.validate(value), + value => value === undefined ? undefined : value === null ? null : this.convert(value) ); } - with( - name: string | undefined, - description: string | undefined, - ): ApiCommandArgument { - return new ApiCommandArgument( - name ?? this.name, - description ?? this.description, - this.validate, - this.convert, - ); + + with(name: string | undefined, description: string | undefined): ApiCommandArgument { + return new ApiCommandArgument(name ?? this.name, description ?? this.description, this.validate, this.convert); } } + export class ApiCommandResult { - static readonly Void = new ApiCommandResult( - "no result", - (v) => v, - ); + + static readonly Void = new ApiCommandResult('no result', v => v); constructor( readonly description: string, - readonly convert: ( - v: V, - apiArgs: any[], - cmdConverter: CommandsConverter, - ) => O, - ) {} + readonly convert: (v: V, apiArgs: any[], cmdConverter: CommandsConverter) => O + ) { } } + export class ApiCommand { + constructor( readonly id: string, readonly internalId: string, readonly description: string, readonly args: ApiCommandArgument[], - readonly result: ApiCommandResult, - ) {} + readonly result: ApiCommandResult + ) { } } diff --git a/Source/vs/workbench/api/common/extHostTestItem.ts b/Source/vs/workbench/api/common/extHostTestItem.ts index 1b4fcce1ee855..c45beb1c9c7f7 100644 --- a/Source/vs/workbench/api/common/extHostTestItem.ts +++ b/Source/vs/workbench/api/common/extHostTestItem.ts @@ -2,47 +2,24 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type * as vscode from "vscode"; -import { URI } from "../../../base/common/uri.js"; -import * as editorRange from "../../../editor/common/core/range.js"; -import { - TestId, - TestIdPathParts, -} from "../../contrib/testing/common/testId.js"; -import { - createTestItemChildren, - ExtHostTestItemEvent, - ITestChildrenLike, - ITestItemApi, - ITestItemChildren, - TestItemCollection, - TestItemEventOp, -} from "../../contrib/testing/common/testItemCollection.js"; -import { - denamespaceTestTag, - ITestItem, - ITestItemContext, -} from "../../contrib/testing/common/testTypes.js"; -import { ExtHostDocumentsAndEditors } from "./extHostDocumentsAndEditors.js"; -import { - createPrivateApiFor, - getPrivateApiFor, - IExtHostTestItemApi, -} from "./extHostTestingPrivateApi.js"; -import * as Convert from "./extHostTypeConverters.js"; +import type * as vscode from 'vscode'; +import { URI } from '../../../base/common/uri.js'; +import * as editorRange from '../../../editor/common/core/range.js'; +import { TestId, TestIdPathParts } from '../../contrib/testing/common/testId.js'; +import { createTestItemChildren, ExtHostTestItemEvent, ITestChildrenLike, ITestItemApi, ITestItemChildren, TestItemCollection, TestItemEventOp } from '../../contrib/testing/common/testItemCollection.js'; +import { denamespaceTestTag, ITestItem, ITestItemContext } from '../../contrib/testing/common/testTypes.js'; +import { ExtHostDocumentsAndEditors } from './extHostDocumentsAndEditors.js'; +import { createPrivateApiFor, getPrivateApiFor, IExtHostTestItemApi } from './extHostTestingPrivateApi.js'; +import * as Convert from './extHostTypeConverters.js'; const testItemPropAccessor = ( api: IExtHostTestItemApi, defaultValue: vscode.TestItem[K], equals: (a: vscode.TestItem[K], b: vscode.TestItem[K]) => boolean, - toUpdate: ( - newValue: vscode.TestItem[K], - oldValue: vscode.TestItem[K], - ) => ExtHostTestItemEvent, + toUpdate: (newValue: vscode.TestItem[K], oldValue: vscode.TestItem[K]) => ExtHostTestItemEvent, ) => { let value = defaultValue; - return { enumerable: true, configurable: false, @@ -58,33 +35,15 @@ const testItemPropAccessor = ( }, }; }; -type WritableProps = Pick< - vscode.TestItem, - | "range" - | "label" - | "description" - | "sortText" - | "canResolveChildren" - | "busy" - | "error" - | "tags" ->; + +type WritableProps = Pick; const strictEqualComparator = (a: T, b: T) => a === b; -const propComparators: { - [K in keyof Required]: ( - a: vscode.TestItem[K], - b: vscode.TestItem[K], - ) => boolean; -} = { +const propComparators: { [K in keyof Required]: (a: vscode.TestItem[K], b: vscode.TestItem[K]) => boolean } = { range: (a, b) => { - if (a === b) { - return true; - } - if (!a || !b) { - return false; - } + if (a === b) { return true; } + if (!a || !b) { return false; } return a.isEqual(b); }, label: strictEqualComparator, @@ -97,32 +56,22 @@ const propComparators: { if (a.length !== b.length) { return false; } - if (a.some((t1) => !b.find((t2) => t1.id === t2.id))) { + + if (a.some(t1 => !b.find(t2 => t1.id === t2.id))) { return false; } + return true; }, }; -const evSetProps = - ( - fn: (newValue: T) => Partial, - ): ((newValue: T) => ExtHostTestItemEvent) => - (v) => ({ op: TestItemEventOp.SetProp, update: fn(v) }); +const evSetProps = (fn: (newValue: T) => Partial): (newValue: T) => ExtHostTestItemEvent => + v => ({ op: TestItemEventOp.SetProp, update: fn(v) }); -const makePropDescriptors = ( - api: IExtHostTestItemApi, - label: string, -): { - [K in keyof Required]: PropertyDescriptor; -} => ({ +const makePropDescriptors = (api: IExtHostTestItemApi, label: string): { [K in keyof Required]: PropertyDescriptor } => ({ range: (() => { let value: vscode.Range | undefined; - - const updateProps = evSetProps((r) => ({ - range: editorRange.Range.lift(Convert.Range.from(r)), - })); - + const updateProps = evSetProps(r => ({ range: editorRange.Range.lift(Convert.Range.from(r)) })); return { enumerable: true, configurable: false, @@ -131,7 +80,6 @@ const makePropDescriptors = ( }, set(newValue: vscode.Range | undefined) { api.listener?.({ op: TestItemEventOp.DocumentSynced }); - if (!propComparators.range(value, newValue)) { value = newValue; api.listener?.(updateProps(newValue)); @@ -139,93 +87,49 @@ const makePropDescriptors = ( }, }; })(), - label: testItemPropAccessor<"label">( - api, - label, - propComparators.label, - evSetProps((label) => ({ label })), - ), - description: testItemPropAccessor<"description">( - api, - undefined, - propComparators.description, - evSetProps((description) => ({ description })), - ), - sortText: testItemPropAccessor<"sortText">( - api, - undefined, - propComparators.sortText, - evSetProps((sortText) => ({ sortText })), - ), - canResolveChildren: testItemPropAccessor<"canResolveChildren">( - api, - false, - propComparators.canResolveChildren, - (state) => ({ - op: TestItemEventOp.UpdateCanResolveChildren, - state, - }), - ), - busy: testItemPropAccessor<"busy">( - api, - false, - propComparators.busy, - evSetProps((busy) => ({ busy })), - ), - error: testItemPropAccessor<"error">( - api, - undefined, - propComparators.error, - evSetProps((error) => ({ - error: Convert.MarkdownString.fromStrict(error) || null, - })), - ), - tags: testItemPropAccessor<"tags">( - api, - [], - propComparators.tags, - (current, previous) => ({ - op: TestItemEventOp.SetTags, - new: current.map(Convert.TestTag.from), - old: previous.map(Convert.TestTag.from), - }), - ), + label: testItemPropAccessor<'label'>(api, label, propComparators.label, evSetProps(label => ({ label }))), + description: testItemPropAccessor<'description'>(api, undefined, propComparators.description, evSetProps(description => ({ description }))), + sortText: testItemPropAccessor<'sortText'>(api, undefined, propComparators.sortText, evSetProps(sortText => ({ sortText }))), + canResolveChildren: testItemPropAccessor<'canResolveChildren'>(api, false, propComparators.canResolveChildren, state => ({ + op: TestItemEventOp.UpdateCanResolveChildren, + state, + })), + busy: testItemPropAccessor<'busy'>(api, false, propComparators.busy, evSetProps(busy => ({ busy }))), + error: testItemPropAccessor<'error'>(api, undefined, propComparators.error, evSetProps(error => ({ error: Convert.MarkdownString.fromStrict(error) || null }))), + tags: testItemPropAccessor<'tags'>(api, [], propComparators.tags, (current, previous) => ({ + op: TestItemEventOp.SetTags, + new: current.map(Convert.TestTag.from), + old: previous.map(Convert.TestTag.from), + })), }); const toItemFromPlain = (item: ITestItem.Serialized): TestItemImpl => { const testId = TestId.fromString(item.extId); - - const testItem = new TestItemImpl( - testId.controllerId, - testId.localId, - item.label, - URI.revive(item.uri) || undefined, - ); + const testItem = new TestItemImpl(testId.controllerId, testId.localId, item.label, URI.revive(item.uri) || undefined); testItem.range = Convert.Range.to(item.range || undefined); testItem.description = item.description || undefined; testItem.sortText = item.sortText || undefined; - testItem.tags = item.tags.map((t) => - Convert.TestTag.to({ id: denamespaceTestTag(t).tagId }), - ); - + testItem.tags = item.tags.map(t => Convert.TestTag.to({ id: denamespaceTestTag(t).tagId })); return testItem; }; + export const toItemFromContext = (context: ITestItemContext): TestItemImpl => { let node: TestItemImpl | undefined; - for (const test of context.tests) { const next = toItemFromPlain(test.item); - getPrivateApiFor(next).parent = node; node = next; } + return node!; }; + export class TestItemImpl implements vscode.TestItem { public readonly id!: string; public readonly uri!: vscode.Uri | undefined; public readonly children!: ITestItemChildren; public readonly parent!: TestItemImpl | undefined; + public range!: vscode.Range | undefined; public description!: string | undefined; public sortText!: string | undefined; @@ -234,20 +138,15 @@ export class TestItemImpl implements vscode.TestItem { public busy!: boolean; public canResolveChildren!: boolean; public tags!: readonly vscode.TestTag[]; + /** * Note that data is deprecated and here for back-compat only */ - constructor( - controllerId: string, - id: string, - label: string, - uri: vscode.Uri | undefined, - ) { + constructor(controllerId: string, id: string, label: string, uri: vscode.Uri | undefined) { if (id.includes(TestIdPathParts.Delimiter)) { - throw new Error( - `Test IDs may not include the ${JSON.stringify(id)} symbol`, - ); + throw new Error(`Test IDs may not include the ${JSON.stringify(id)} symbol`); } + const api = createPrivateApiFor(this, controllerId); Object.defineProperties(this, { id: { @@ -263,17 +162,11 @@ export class TestItemImpl implements vscode.TestItem { parent: { enumerable: false, get() { - return api.parent instanceof TestItemRootImpl - ? undefined - : api.parent; + return api.parent instanceof TestItemRootImpl ? undefined : api.parent; }, }, children: { - value: createTestItemChildren( - api, - getPrivateApiFor, - TestItemImpl, - ), + value: createTestItemChildren(api, getPrivateApiFor, TestItemImpl), enumerable: true, writable: false, }, @@ -281,6 +174,7 @@ export class TestItemImpl implements vscode.TestItem { }); } } + export class TestItemRootImpl extends TestItemImpl { public readonly _isRoot = true; @@ -288,21 +182,14 @@ export class TestItemRootImpl extends TestItemImpl { super(controllerId, controllerId, label, undefined); } } + export class ExtHostTestItemCollection extends TestItemCollection { - constructor( - controllerId: string, - controllerLabel: string, - editors: ExtHostDocumentsAndEditors, - ) { + constructor(controllerId: string, controllerLabel: string, editors: ExtHostDocumentsAndEditors) { super({ controllerId, - getDocumentVersion: (uri) => - uri && editors.getDocument(uri)?.version, - getApiFor: getPrivateApiFor as ( - impl: TestItemImpl, - ) => ITestItemApi, - getChildren: (item) => - item.children as ITestChildrenLike, + getDocumentVersion: uri => uri && editors.getDocument(uri)?.version, + getApiFor: getPrivateApiFor as (impl: TestItemImpl) => ITestItemApi, + getChildren: (item) => item.children as ITestChildrenLike, root: new TestItemRootImpl(controllerId, controllerLabel), toITestItem: Convert.TestItem.from, }); diff --git a/Source/vs/workbench/api/common/extHostTesting.ts b/Source/vs/workbench/api/common/extHostTesting.ts index 0bf27e739976f..d3f4934ee487a 100644 --- a/Source/vs/workbench/api/common/extHostTesting.ts +++ b/Source/vs/workbench/api/common/extHostTesting.ts @@ -5,77 +5,34 @@ /* eslint-disable local/code-no-native-private */ import type * as vscode from "vscode"; -import { RunOnceScheduler } from "../../../base/common/async.js"; -import { VSBuffer } from "../../../base/common/buffer.js"; -import { - CancellationToken, - CancellationTokenSource, -} from "../../../base/common/cancellation.js"; -import { Emitter, Event } from "../../../base/common/event.js"; -import { createSingleCallFunction } from "../../../base/common/functional.js"; -import { hash } from "../../../base/common/hash.js"; -import { - Disposable, - DisposableStore, - toDisposable, -} from "../../../base/common/lifecycle.js"; -import { MarshalledId } from "../../../base/common/marshallingIds.js"; -import { isDefined } from "../../../base/common/types.js"; -import { URI, UriComponents } from "../../../base/common/uri.js"; -import { generateUuid } from "../../../base/common/uuid.js"; -import { IPosition } from "../../../editor/common/core/position.js"; -import { IExtensionDescription } from "../../../platform/extensions/common/extensions.js"; -import { createDecorator } from "../../../platform/instantiation/common/instantiation.js"; -import { ILogService } from "../../../platform/log/common/log.js"; -import { TestCommandId } from "../../contrib/testing/common/constants.js"; -import { TestId, TestPosition } from "../../contrib/testing/common/testId.js"; -import { InvalidTestItemError } from "../../contrib/testing/common/testItemCollection.js"; -import { - AbstractIncrementalTestCollection, - CoverageDetails, - ICallProfileRunHandler, - IncrementalChangeCollector, - IncrementalTestCollectionItem, - InternalTestItem, - ISerializedTestResults, - isStartControllerTests, - IStartControllerTests, - IStartControllerTestsResult, - ITestErrorMessage, - ITestItem, - ITestItemContext, - ITestMessageMenuArgs, - ITestRunProfile, - TestControllerCapability, - TestMessageFollowupRequest, - TestMessageFollowupResponse, - TestResultState, - TestRunProfileBitset, - TestsDiff, - TestsDiffOp, -} from "../../contrib/testing/common/testTypes.js"; -import { checkProposedApiEnabled } from "../../services/extensions/common/extensions.js"; -import { - ExtHostTestingShape, - ILocationDto, - MainContext, - MainThreadTestingShape, -} from "./extHost.protocol.js"; -import { IExtHostCommands } from "./extHostCommands.js"; -import { IExtHostDocumentsAndEditors } from "./extHostDocumentsAndEditors.js"; -import { IExtHostRpcService } from "./extHostRpcService.js"; -import { - ExtHostTestItemCollection, - TestItemImpl, - TestItemRootImpl, - toItemFromContext, -} from "./extHostTestItem.js"; -import * as Convert from "./extHostTypeConverters.js"; -import { - FileCoverage, - TestRunProfileKind, - TestRunRequest, -} from "./extHostTypes.js"; +import type * as vscode from 'vscode'; +import { RunOnceScheduler } from '../../../base/common/async.js'; +import { VSBuffer } from '../../../base/common/buffer.js'; +import { CancellationToken, CancellationTokenSource } from '../../../base/common/cancellation.js'; +import { Emitter, Event } from '../../../base/common/event.js'; +import { createSingleCallFunction } from '../../../base/common/functional.js'; +import { hash } from '../../../base/common/hash.js'; +import { Disposable, DisposableStore, toDisposable } from '../../../base/common/lifecycle.js'; +import { MarshalledId } from '../../../base/common/marshallingIds.js'; +import { isDefined } from '../../../base/common/types.js'; +import { URI, UriComponents } from '../../../base/common/uri.js'; +import { generateUuid } from '../../../base/common/uuid.js'; +import { IPosition } from '../../../editor/common/core/position.js'; +import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; +import { createDecorator } from '../../../platform/instantiation/common/instantiation.js'; +import { ILogService } from '../../../platform/log/common/log.js'; +import { TestCommandId } from '../../contrib/testing/common/constants.js'; +import { TestId, TestPosition } from '../../contrib/testing/common/testId.js'; +import { InvalidTestItemError } from '../../contrib/testing/common/testItemCollection.js'; +import { AbstractIncrementalTestCollection, CoverageDetails, ICallProfileRunHandler, ISerializedTestResults, IStartControllerTests, IStartControllerTestsResult, ITestErrorMessage, ITestItem, ITestItemContext, ITestMessageMenuArgs, ITestRunProfile, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, TestControllerCapability, TestMessageFollowupRequest, TestMessageFollowupResponse, TestResultState, TestsDiff, TestsDiffOp, isStartControllerTests } from '../../contrib/testing/common/testTypes.js'; +import { checkProposedApiEnabled } from '../../services/extensions/common/extensions.js'; +import { ExtHostTestingShape, ILocationDto, MainContext, MainThreadTestingShape } from './extHost.protocol.js'; +import { IExtHostCommands } from './extHostCommands.js'; +import { IExtHostDocumentsAndEditors } from './extHostDocumentsAndEditors.js'; +import { IExtHostRpcService } from './extHostRpcService.js'; +import { ExtHostTestItemCollection, TestItemImpl, TestItemRootImpl, toItemFromContext } from './extHostTestItem.js'; +import * as Convert from './extHostTypeConverters.js'; +import { FileCoverage, TestRunProfileBase, TestRunRequest } from './extHostTypes.js'; interface ControllerInfo { controller: vscode.TestController; @@ -408,26 +365,17 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape { if (!controller) { throw new Error("Controller not found"); } - await this.proxy.$runTests( - { - preserveFocus: req.preserveFocus ?? true, - group: profileGroupToBitset[profile.kind], - targets: [ - { - testIds: req.include?.map((t) => - TestId.fromExtHostTestItem( - t, - controller.collection.root.id, - ).toString(), - ) ?? [controller.collection.root.id], - profileId: profile.profileId, - controllerId: profile.controllerId, - }, - ], - exclude: req.exclude?.map((t) => t.id), - }, - token, - ); + + await this.proxy.$runTests({ + preserveFocus: req.preserveFocus ?? true, + group: Convert.TestRunProfileKind.from(profile.kind), + targets: [{ + testIds: req.include?.map(t => TestId.fromExtHostTestItem(t, controller.collection.root.id).toString()) ?? [controller.collection.root.id], + profileId: profile.profileId, + controllerId: profile.controllerId, + }], + exclude: req.exclude?.map(t => t.id), + }, token); } /** * Implements vscode.test.registerTestFollowupProvider @@ -989,7 +937,7 @@ class TestRunTracker extends Disposable { if (index === -1) { return []; // ?? } - testItem = report.fromTests[index]; + testItem = report.includesTests[index]; } const details = testItem ? this.profile?.loadDetailedCoverageForTest?.( @@ -1071,13 +1019,9 @@ class TestRunTracker extends Disposable { const fromTests = coverage instanceof FileCoverage ? coverage.fromTests : []; - if (fromTests.length) { - checkProposedApiEnabled( - this.extension, - "attributableCoverage", - ); - - for (const test of fromTests) { + const includesTests = coverage instanceof FileCoverage ? coverage.includesTests : []; + if (includesTests.length) { + for (const test of includesTests) { this.ensureTestIsKnown(test); } } @@ -1088,17 +1032,8 @@ class TestRunTracker extends Disposable { // it's been reported if it's rehomed under a different parent. Record its // ID at the time when the coverage report is generated so we can reference // it later if needeed. - this.publishedCoverage.set(id, { - report: coverage, - extIds: fromTests.map((t) => - TestId.fromExtHostTestItem(t, ctrlId).toString(), - ), - }); - this.proxy.$appendCoverage( - runId, - taskId, - Convert.TestCoverage.fromFile(ctrlId, id, coverage), - ); + this.publishedCoverage.set(id, { report: coverage, extIds: includesTests.map(t => TestId.fromExtHostTestItem(t, ctrlId).toString()) }); + this.proxy.$appendCoverage(runId, taskId, Convert.TestCoverage.fromFile(ctrlId, id, coverage)); }, //#region state mutation enqueued: guardTestMutation((test) => { @@ -1355,17 +1290,8 @@ export class TestRunCoordinator { this.proxy.$startedExtensionTestRun({ controllerId, continuous: !!request.continuous, - profile: profile && { - group: profileGroupToBitset[profile.kind], - id: profile.profileId, - }, - exclude: - request.exclude?.map((t) => - TestId.fromExtHostTestItem( - t, - collection.root.id, - ).toString(), - ) ?? [], + profile: profile && { group: Convert.TestRunProfileKind.from(profile.kind), id: profile.profileId }, + exclude: request.exclude?.map(t => TestId.fromExtHostTestItem(t, collection.root.id).toString()) ?? [], id: dto.id, include: request.include?.map((t) => TestId.fromExtHostTestItem(t, collection.root.id).toString(), @@ -1641,7 +1567,8 @@ const updateProfile = ( proxy.$updateTestRunConfig(impl.controllerId, impl.profileId, update); } }; -export class TestRunProfileImpl implements vscode.TestRunProfile { + +export class TestRunProfileImpl extends TestRunProfileBase implements vscode.TestRunProfile { readonly #proxy: MainThreadTestingShape; readonly #activeProfiles: Set; readonly #onDidChangeDefaultProfiles: Event; @@ -1721,29 +1648,24 @@ export class TestRunProfileImpl implements vscode.TestRunProfile { profiles: Map, activeProfiles: Set, onDidChangeActiveProfiles: Event, - public readonly controllerId: string, - public readonly profileId: number, + controllerId: string, + profileId: number, private _label: string, - public readonly kind: vscode.TestRunProfileKind, - public runHandler: ( - request: vscode.TestRunRequest, - token: vscode.CancellationToken, - ) => Thenable | void, + kind: vscode.TestRunProfileKind, + public runHandler: (request: vscode.TestRunRequest, token: vscode.CancellationToken) => Thenable | void, _isDefault = false, public _tag: vscode.TestTag | undefined = undefined, private _supportsContinuousRun = false, ) { + super(controllerId, profileId, kind); + this.#proxy = proxy; this.#profiles = profiles; this.#activeProfiles = activeProfiles; this.#onDidChangeDefaultProfiles = onDidChangeActiveProfiles; profiles.set(profileId, this); - const groupBitset = profileGroupToBitset[kind]; - - if (typeof groupBitset !== "number") { - throw new Error(`Unknown TestRunProfile.group ${kind}`); - } + const groupBitset = Convert.TestRunProfileKind.from(kind); if (_isDefault) { activeProfiles.add(profileId); } @@ -1776,17 +1698,8 @@ export class TestRunProfileImpl implements vscode.TestRunProfile { this.#initialPublish = undefined; } } -const profileGroupToBitset: { - [K in TestRunProfileKind]: TestRunProfileBitset; -} = { - [TestRunProfileKind.Coverage]: TestRunProfileBitset.Coverage, - [TestRunProfileKind.Debug]: TestRunProfileBitset.Debug, - [TestRunProfileKind.Run]: TestRunProfileBitset.Run, -}; -function findTestInResultSnapshot( - extId: TestId, - snapshot: readonly Readonly[], -) { + +function findTestInResultSnapshot(extId: TestId, snapshot: readonly Readonly[]) { for (let i = 0; i < extId.path.length; i++) { const item = snapshot.find((s) => s.id === extId.path[i]); diff --git a/Source/vs/workbench/api/common/extHostTypeConverters.ts b/Source/vs/workbench/api/common/extHostTypeConverters.ts index 1294f58cd6389..b7f3d7e8b5eca 100644 --- a/Source/vs/workbench/api/common/extHostTypeConverters.ts +++ b/Source/vs/workbench/api/common/extHostTypeConverters.ts @@ -3,157 +3,67 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type * as vscode from "vscode"; - -import { - asArray, - coalesce, - isNonEmptyArray, -} from "../../../base/common/arrays.js"; -import { encodeBase64, VSBuffer } from "../../../base/common/buffer.js"; -import { - IDataTransferFile, - IDataTransferItem, - UriList, -} from "../../../base/common/dataTransfer.js"; -import { createSingleCallFunction } from "../../../base/common/functional.js"; -import * as htmlContent from "../../../base/common/htmlContent.js"; -import { DisposableStore } from "../../../base/common/lifecycle.js"; -import { ResourceMap, ResourceSet } from "../../../base/common/map.js"; -import * as marked from "../../../base/common/marked/marked.js"; -import { parse, revive } from "../../../base/common/marshalling.js"; -import { MarshalledId } from "../../../base/common/marshallingIds.js"; -import { Mimes } from "../../../base/common/mime.js"; -import { cloneAndChange } from "../../../base/common/objects.js"; -import { - IPrefixTreeNode, - WellDefinedPrefixTree, -} from "../../../base/common/prefixTree.js"; -import { basename } from "../../../base/common/resources.js"; -import { ThemeIcon } from "../../../base/common/themables.js"; -import { - isDefined, - isEmptyObject, - isNumber, - isString, - isUndefinedOrNull, -} from "../../../base/common/types.js"; -import { - isUriComponents, - URI, - UriComponents, -} from "../../../base/common/uri.js"; -import { IURITransformer } from "../../../base/common/uriIpc.js"; -import { RenderLineNumbersType } from "../../../editor/common/config/editorOptions.js"; -import { IPosition } from "../../../editor/common/core/position.js"; -import * as editorRange from "../../../editor/common/core/range.js"; -import { ISelection } from "../../../editor/common/core/selection.js"; -import { - IContentDecorationRenderOptions, - IDecorationOptions, - IDecorationRenderOptions, - IThemeDecorationRenderOptions, -} from "../../../editor/common/editorCommon.js"; -import * as encodedTokenAttributes from "../../../editor/common/encodedTokenAttributes.js"; -import * as languages from "../../../editor/common/languages.js"; -import * as languageSelector from "../../../editor/common/languageSelector.js"; -import { - EndOfLineSequence, - TrackedRangeStickiness, -} from "../../../editor/common/model.js"; -import { ITextEditorOptions } from "../../../platform/editor/common/editor.js"; -import { IExtensionDescription } from "../../../platform/extensions/common/extensions.js"; -import { - IMarkerData, - IRelatedInformation, - MarkerSeverity, - MarkerTag, -} from "../../../platform/markers/common/markers.js"; -import { ProgressLocation as MainProgressLocation } from "../../../platform/progress/common/progress.js"; -import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from "../../common/editor.js"; -import { IViewBadge } from "../../common/views.js"; -import { - ChatAgentLocation, - IChatAgentRequest, - IChatAgentResult, -} from "../../contrib/chat/common/chatAgents.js"; -import { IChatRequestDraft } from "../../contrib/chat/common/chatEditingService.js"; -import { IChatRequestVariableEntry } from "../../contrib/chat/common/chatModel.js"; -import { - IChatAgentDetection, - IChatAgentMarkdownContentWithVulnerability, - IChatCodeCitation, - IChatCommandButton, - IChatConfirmation, - IChatContentInlineReference, - IChatContentReference, - IChatFollowup, - IChatMarkdownContent, - IChatMoveMessage, - IChatProgressMessage, - IChatResponseCodeblockUriPart, - IChatTaskDto, - IChatTaskResult, - IChatTextEdit, - IChatTreeData, - IChatUserActionEvent, - IChatWarningMessage, -} from "../../contrib/chat/common/chatService.js"; -import * as chatProvider from "../../contrib/chat/common/languageModels.js"; -import { - IChatResponsePromptTsxPart, - IChatResponseTextPart, -} from "../../contrib/chat/common/languageModels.js"; -import { - IToolData, - IToolResult, -} from "../../contrib/chat/common/languageModelToolsService.js"; -import { - DebugTreeItemCollapsibleState, - IDebugVisualizationTreeItem, -} from "../../contrib/debug/common/debug.js"; -import * as notebooks from "../../contrib/notebook/common/notebookCommon.js"; -import { ICellRange } from "../../contrib/notebook/common/notebookRange.js"; -import * as search from "../../contrib/search/common/search.js"; -import { TestId } from "../../contrib/testing/common/testId.js"; -import { - CoverageDetails, - denamespaceTestTag, - DetailType, - ICoverageCount, - IFileCoverage, - ISerializedTestResults, - ITestErrorMessage, - ITestItem, - ITestTag, - namespaceTestTag, - TestMessageType, - TestResultItem, -} from "../../contrib/testing/common/testTypes.js"; -import { EditorGroupColumn } from "../../services/editor/common/editorGroupColumn.js"; -import { - ACTIVE_GROUP, - SIDE_GROUP, -} from "../../services/editor/common/editorService.js"; -import { Dto } from "../../services/extensions/common/proxyIdentifier.js"; -import * as extHostProtocol from "./extHost.protocol.js"; -import { CommandsConverter } from "./extHostCommands.js"; -import { getPrivateApiFor } from "./extHostTestingPrivateApi.js"; -import * as types from "./extHostTypes.js"; -import { - LanguageModelPromptTsxPart, - LanguageModelTextPart, -} from "./extHostTypes.js"; +import type * as vscode from 'vscode'; +import { asArray, coalesce, isNonEmptyArray } from '../../../base/common/arrays.js'; +import { VSBuffer, encodeBase64 } from '../../../base/common/buffer.js'; +import { IDataTransferFile, IDataTransferItem, UriList } from '../../../base/common/dataTransfer.js'; +import { createSingleCallFunction } from '../../../base/common/functional.js'; +import * as htmlContent from '../../../base/common/htmlContent.js'; +import { DisposableStore } from '../../../base/common/lifecycle.js'; +import { ResourceMap, ResourceSet } from '../../../base/common/map.js'; +import * as marked from '../../../base/common/marked/marked.js'; +import { parse, revive } from '../../../base/common/marshalling.js'; +import { Mimes } from '../../../base/common/mime.js'; +import { cloneAndChange } from '../../../base/common/objects.js'; +import { IPrefixTreeNode, WellDefinedPrefixTree } from '../../../base/common/prefixTree.js'; +import { basename } from '../../../base/common/resources.js'; +import { ThemeIcon } from '../../../base/common/themables.js'; +import { isDefined, isEmptyObject, isNumber, isString, isUndefinedOrNull } from '../../../base/common/types.js'; +import { URI, UriComponents, isUriComponents } from '../../../base/common/uri.js'; +import { IURITransformer } from '../../../base/common/uriIpc.js'; +import { RenderLineNumbersType } from '../../../editor/common/config/editorOptions.js'; +import { IPosition } from '../../../editor/common/core/position.js'; +import * as editorRange from '../../../editor/common/core/range.js'; +import { ISelection } from '../../../editor/common/core/selection.js'; +import { IContentDecorationRenderOptions, IDecorationOptions, IDecorationRenderOptions, IThemeDecorationRenderOptions } from '../../../editor/common/editorCommon.js'; +import * as encodedTokenAttributes from '../../../editor/common/encodedTokenAttributes.js'; +import * as languageSelector from '../../../editor/common/languageSelector.js'; +import * as languages from '../../../editor/common/languages.js'; +import { EndOfLineSequence, TrackedRangeStickiness } from '../../../editor/common/model.js'; +import { ITextEditorOptions } from '../../../platform/editor/common/editor.js'; +import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; +import { IMarkerData, IRelatedInformation, MarkerSeverity, MarkerTag } from '../../../platform/markers/common/markers.js'; +import { ProgressLocation as MainProgressLocation } from '../../../platform/progress/common/progress.js'; +import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from '../../common/editor.js'; +import { IViewBadge } from '../../common/views.js'; +import { ChatAgentLocation, IChatAgentRequest, IChatAgentResult } from '../../contrib/chat/common/chatAgents.js'; +import { IChatRequestVariableEntry } from '../../contrib/chat/common/chatModel.js'; +import { IChatAgentDetection, IChatAgentMarkdownContentWithVulnerability, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatMoveMessage, IChatProgressMessage, IChatResponseCodeblockUriPart, IChatTaskDto, IChatTaskResult, IChatTextEdit, IChatTreeData, IChatUserActionEvent, IChatWarningMessage } from '../../contrib/chat/common/chatService.js'; +import { IToolData, IToolResult } from '../../contrib/chat/common/languageModelToolsService.js'; +import * as chatProvider from '../../contrib/chat/common/languageModels.js'; +import { DebugTreeItemCollapsibleState, IDebugVisualizationTreeItem } from '../../contrib/debug/common/debug.js'; +import * as notebooks from '../../contrib/notebook/common/notebookCommon.js'; +import { ICellRange } from '../../contrib/notebook/common/notebookRange.js'; +import * as search from '../../contrib/search/common/search.js'; +import { TestId } from '../../contrib/testing/common/testId.js'; +import { CoverageDetails, DetailType, ICoverageCount, IFileCoverage, ISerializedTestResults, ITestErrorMessage, ITestItem, ITestRunProfileReference, ITestTag, TestMessageType, TestResultItem, TestRunProfileBitset, denamespaceTestTag, namespaceTestTag } from '../../contrib/testing/common/testTypes.js'; +import { EditorGroupColumn } from '../../services/editor/common/editorGroupColumn.js'; +import { ACTIVE_GROUP, SIDE_GROUP } from '../../services/editor/common/editorService.js'; +import { Dto } from '../../services/extensions/common/proxyIdentifier.js'; +import * as extHostProtocol from './extHost.protocol.js'; +import { CommandsConverter } from './extHostCommands.js'; +import { getPrivateApiFor } from './extHostTestingPrivateApi.js'; +import * as types from './extHostTypes.js'; +import { IChatResponseTextPart, IChatResponsePromptTsxPart } from '../../contrib/chat/common/languageModels.js'; +import { LanguageModelTextPart, LanguageModelPromptTsxPart } from './extHostTypes.js'; +import { MarshalledId } from '../../../base/common/marshallingIds.js'; +import { IChatRequestDraft } from '../../contrib/chat/common/chatEditingService.js'; export namespace Command { + export interface ICommandsConverter { - fromInternal( - command: extHostProtocol.ICommandDto, - ): vscode.Command | undefined; - toInternal( - command: vscode.Command | undefined, - disposables: DisposableStore, - ): extHostProtocol.ICommandDto | undefined; + fromInternal(command: extHostProtocol.ICommandDto): vscode.Command | undefined; + toInternal(command: vscode.Command | undefined, disposables: DisposableStore): extHostProtocol.ICommandDto | undefined; } } @@ -172,121 +82,75 @@ export interface SelectionLike extends RangeLike { active: PositionLike; } export namespace Selection { - export function to(selection: ISelection): types.Selection { - const { - selectionStartLineNumber, - selectionStartColumn, - positionLineNumber, - positionColumn, - } = selection; - - const start = new types.Position( - selectionStartLineNumber - 1, - selectionStartColumn - 1, - ); - - const end = new types.Position( - positionLineNumber - 1, - positionColumn - 1, - ); + export function to(selection: ISelection): types.Selection { + const { selectionStartLineNumber, selectionStartColumn, positionLineNumber, positionColumn } = selection; + const start = new types.Position(selectionStartLineNumber - 1, selectionStartColumn - 1); + const end = new types.Position(positionLineNumber - 1, positionColumn - 1); return new types.Selection(start, end); } export function from(selection: SelectionLike): ISelection { const { anchor, active } = selection; - return { selectionStartLineNumber: anchor.line + 1, selectionStartColumn: anchor.character + 1, positionLineNumber: active.line + 1, - positionColumn: active.character + 1, + positionColumn: active.character + 1 }; } } export namespace Range { - export function from(range: undefined): undefined; + export function from(range: undefined): undefined; export function from(range: RangeLike): editorRange.IRange; - - export function from( - range: RangeLike | undefined, - ): editorRange.IRange | undefined; - - export function from( - range: RangeLike | undefined, - ): editorRange.IRange | undefined { + export function from(range: RangeLike | undefined): editorRange.IRange | undefined; + export function from(range: RangeLike | undefined): editorRange.IRange | undefined { if (!range) { return undefined; } const { start, end } = range; - return { startLineNumber: start.line + 1, startColumn: start.character + 1, endLineNumber: end.line + 1, - endColumn: end.character + 1, + endColumn: end.character + 1 }; } export function to(range: undefined): types.Range; - export function to(range: editorRange.IRange): types.Range; - - export function to( - range: editorRange.IRange | undefined, - ): types.Range | undefined; - - export function to( - range: editorRange.IRange | undefined, - ): types.Range | undefined { + export function to(range: editorRange.IRange | undefined): types.Range | undefined; + export function to(range: editorRange.IRange | undefined): types.Range | undefined { if (!range) { return undefined; } - const { startLineNumber, startColumn, endLineNumber, endColumn } = - range; - - return new types.Range( - startLineNumber - 1, - startColumn - 1, - endLineNumber - 1, - endColumn - 1, - ); + const { startLineNumber, startColumn, endLineNumber, endColumn } = range; + return new types.Range(startLineNumber - 1, startColumn - 1, endLineNumber - 1, endColumn - 1); } } export namespace Location { + export function from(location: vscode.Location): Dto { return { uri: location.uri, - range: Range.from(location.range), + range: Range.from(location.range) }; } export function to(location: Dto): vscode.Location { - return new types.Location( - URI.revive(location.uri), - Range.to(location.range), - ); + return new types.Location(URI.revive(location.uri), Range.to(location.range)); } } export namespace TokenType { - export function to( - type: encodedTokenAttributes.StandardTokenType, - ): types.StandardTokenType { + export function to(type: encodedTokenAttributes.StandardTokenType): types.StandardTokenType { switch (type) { - case encodedTokenAttributes.StandardTokenType.Comment: - return types.StandardTokenType.Comment; - - case encodedTokenAttributes.StandardTokenType.Other: - return types.StandardTokenType.Other; - - case encodedTokenAttributes.StandardTokenType.RegEx: - return types.StandardTokenType.RegEx; - - case encodedTokenAttributes.StandardTokenType.String: - return types.StandardTokenType.String; + case encodedTokenAttributes.StandardTokenType.Comment: return types.StandardTokenType.Comment; + case encodedTokenAttributes.StandardTokenType.Other: return types.StandardTokenType.Other; + case encodedTokenAttributes.StandardTokenType.RegEx: return types.StandardTokenType.RegEx; + case encodedTokenAttributes.StandardTokenType.String: return types.StandardTokenType.String; } } } @@ -295,35 +159,19 @@ export namespace Position { export function to(position: IPosition): types.Position { return new types.Position(position.lineNumber - 1, position.column - 1); } - export function from( - position: types.Position | vscode.Position, - ): IPosition { - return { - lineNumber: position.line + 1, - column: position.character + 1, - }; + export function from(position: types.Position | vscode.Position): IPosition { + return { lineNumber: position.line + 1, column: position.character + 1 }; } } export namespace DocumentSelector { - export function from( - value: vscode.DocumentSelector, - uriTransformer?: IURITransformer, - extension?: IExtensionDescription, - ): extHostProtocol.IDocumentFilterDto[] { - return coalesce( - asArray(value).map((sel) => - _doTransformDocumentSelector(sel, uriTransformer, extension), - ), - ); + + export function from(value: vscode.DocumentSelector, uriTransformer?: IURITransformer, extension?: IExtensionDescription): extHostProtocol.IDocumentFilterDto[] { + return coalesce(asArray(value).map(sel => _doTransformDocumentSelector(sel, uriTransformer, extension))); } - function _doTransformDocumentSelector( - selector: string | vscode.DocumentFilter, - uriTransformer: IURITransformer | undefined, - extension: IExtensionDescription | undefined, - ): extHostProtocol.IDocumentFilterDto | undefined { - if (typeof selector === "string") { + function _doTransformDocumentSelector(selector: string | vscode.DocumentFilter, uriTransformer: IURITransformer | undefined, extension: IExtensionDescription | undefined): extHostProtocol.IDocumentFilterDto | undefined { + if (typeof selector === 'string') { return { $serialized: true, language: selector, @@ -339,18 +187,15 @@ export namespace DocumentSelector { pattern: GlobPattern.from(selector.pattern) ?? undefined, exclusive: selector.exclusive, notebookType: selector.notebookType, - isBuiltin: extension?.isBuiltin, + isBuiltin: extension?.isBuiltin }; } return undefined; } - function _transformScheme( - scheme: string | undefined, - uriTransformer: IURITransformer | undefined, - ): string | undefined { - if (uriTransformer && typeof scheme === "string") { + function _transformScheme(scheme: string | undefined, uriTransformer: IURITransformer | undefined): string | undefined { + if (uriTransformer && typeof scheme === 'string') { return uriTransformer.transformOutgoingScheme(scheme); } return scheme; @@ -362,7 +207,6 @@ export namespace DiagnosticTag { switch (value) { case types.DiagnosticTag.Unnecessary: return MarkerTag.Unnecessary; - case types.DiagnosticTag.Deprecated: return MarkerTag.Deprecated; } @@ -372,10 +216,8 @@ export namespace DiagnosticTag { switch (value) { case MarkerTag.Unnecessary: return types.DiagnosticTag.Unnecessary; - case MarkerTag.Deprecated: return types.DiagnosticTag.Deprecated; - default: return undefined; } @@ -403,63 +245,43 @@ export namespace Diagnostic { source: value.source, code, severity: DiagnosticSeverity.from(value.severity), - relatedInformation: - value.relatedInformation && - value.relatedInformation.map(DiagnosticRelatedInformation.from), - tags: Array.isArray(value.tags) - ? coalesce(value.tags.map(DiagnosticTag.from)) - : undefined, + relatedInformation: value.relatedInformation && value.relatedInformation.map(DiagnosticRelatedInformation.from), + tags: Array.isArray(value.tags) ? coalesce(value.tags.map(DiagnosticTag.from)) : undefined, }; } export function to(value: IMarkerData): vscode.Diagnostic { - const res = new types.Diagnostic( - Range.to(value), - value.message, - DiagnosticSeverity.to(value.severity), - ); + const res = new types.Diagnostic(Range.to(value), value.message, DiagnosticSeverity.to(value.severity)); res.source = value.source; res.code = isString(value.code) ? value.code : value.code?.value; - res.relatedInformation = - value.relatedInformation && - value.relatedInformation.map(DiagnosticRelatedInformation.to); + res.relatedInformation = value.relatedInformation && value.relatedInformation.map(DiagnosticRelatedInformation.to); res.tags = value.tags && coalesce(value.tags.map(DiagnosticTag.to)); - return res; } } export namespace DiagnosticRelatedInformation { - export function from( - value: vscode.DiagnosticRelatedInformation, - ): IRelatedInformation { + export function from(value: vscode.DiagnosticRelatedInformation): IRelatedInformation { return { ...Range.from(value.location.range), message: value.message, - resource: value.location.uri, + resource: value.location.uri }; } - export function to( - value: IRelatedInformation, - ): types.DiagnosticRelatedInformation { - return new types.DiagnosticRelatedInformation( - new types.Location(value.resource, Range.to(value)), - value.message, - ); + export function to(value: IRelatedInformation): types.DiagnosticRelatedInformation { + return new types.DiagnosticRelatedInformation(new types.Location(value.resource, Range.to(value)), value.message); } } export namespace DiagnosticSeverity { + export function from(value: number): MarkerSeverity { switch (value) { case types.DiagnosticSeverity.Error: return MarkerSeverity.Error; - case types.DiagnosticSeverity.Warning: return MarkerSeverity.Warning; - case types.DiagnosticSeverity.Information: return MarkerSeverity.Info; - case types.DiagnosticSeverity.Hint: return MarkerSeverity.Hint; } @@ -470,16 +292,12 @@ export namespace DiagnosticSeverity { switch (value) { case MarkerSeverity.Info: return types.DiagnosticSeverity.Information; - case MarkerSeverity.Warning: return types.DiagnosticSeverity.Warning; - case MarkerSeverity.Error: return types.DiagnosticSeverity.Error; - case MarkerSeverity.Hint: return types.DiagnosticSeverity.Hint; - default: return types.DiagnosticSeverity.Error; } @@ -488,7 +306,7 @@ export namespace DiagnosticSeverity { export namespace ViewColumn { export function from(column?: vscode.ViewColumn): EditorGroupColumn { - if (typeof column === "number" && column >= types.ViewColumn.One) { + if (typeof column === 'number' && column >= types.ViewColumn.One) { return column - 1; // adjust zero index (ViewColumn.ONE => 0) } @@ -500,7 +318,7 @@ export namespace ViewColumn { } export function to(position: EditorGroupColumn): vscode.ViewColumn { - if (typeof position === "number" && position >= 0) { + if (typeof position === 'number' && position >= 0) { return position + 1; // adjust to index (ViewColumn.ONE => 1) } @@ -508,15 +326,11 @@ export namespace ViewColumn { } } -function isDecorationOptions( - something: any, -): something is vscode.DecorationOptions { - return typeof something.range !== "undefined"; +function isDecorationOptions(something: any): something is vscode.DecorationOptions { + return (typeof something.range !== 'undefined'); } -export function isDecorationOptionsArr( - something: vscode.Range[] | vscode.DecorationOptions[], -): something is vscode.DecorationOptions[] { +export function isDecorationOptionsArr(something: vscode.Range[] | vscode.DecorationOptions[]): something is vscode.DecorationOptions[] { if (something.length === 0) { return true; } @@ -524,9 +338,8 @@ export function isDecorationOptionsArr( } export namespace MarkdownString { - export function fromMany( - markup: (vscode.MarkdownString | vscode.MarkedString)[], - ): htmlContent.IMarkdownString[] { + + export function fromMany(markup: (vscode.MarkdownString | vscode.MarkedString)[]): htmlContent.IMarkdownString[] { return markup.map(MarkdownString.from); } @@ -536,34 +349,22 @@ export namespace MarkdownString { } function isCodeblock(thing: any): thing is Codeblock { - return ( - thing && - typeof thing === "object" && - typeof (thing).language === "string" && - typeof (thing).value === "string" - ); + return thing && typeof thing === 'object' + && typeof (thing).language === 'string' + && typeof (thing).value === 'string'; } - export function from( - markup: vscode.MarkdownString | vscode.MarkedString, - ): htmlContent.IMarkdownString { + export function from(markup: vscode.MarkdownString | vscode.MarkedString): htmlContent.IMarkdownString { let res: htmlContent.IMarkdownString; - if (isCodeblock(markup)) { const { language, value } = markup; - res = { value: "```" + language + "\n" + value + "\n```\n" }; + res = { value: '```' + language + '\n' + value + '\n```\n' }; } else if (types.MarkdownString.isMarkdownString(markup)) { - res = { - value: markup.value, - isTrusted: markup.isTrusted, - supportThemeIcons: markup.supportThemeIcons, - supportHtml: markup.supportHtml, - baseUri: markup.baseUri, - }; - } else if (typeof markup === "string") { + res = { value: markup.value, isTrusted: markup.isTrusted, supportThemeIcons: markup.supportThemeIcons, supportHtml: markup.supportHtml, baseUri: markup.baseUri }; + } else if (typeof markup === 'string') { res = { value: markup }; } else { - res = { value: "" }; + res = { value: '' }; } // extract uris into a separate object @@ -578,14 +379,14 @@ export namespace MarkdownString { } catch (e) { // ignore } - return ""; + return ''; }; - marked.marked.walkTokens(marked.marked.lexer(res.value), (token) => { - if (token.type === "link") { + marked.marked.walkTokens(marked.marked.lexer(res.value), token => { + if (token.type === 'link') { collectUri({ href: token.href }); - } else if (token.type === "image") { - if (typeof token.href === "string") { + } else if (token.type === 'image') { + if (typeof token.href === 'string') { collectUri(htmlContent.parseHrefAndDimensions(token.href)); } } @@ -594,15 +395,11 @@ export namespace MarkdownString { return res; } - function _uriMassage( - part: string, - bucket: { [n: string]: UriComponents }, - ): string { + function _uriMassage(part: string, bucket: { [n: string]: UriComponents }): string { if (!part) { return part; } let data: any; - try { data = parse(part); } catch (e) { @@ -612,12 +409,11 @@ export namespace MarkdownString { return part; } let changed = false; - data = cloneAndChange(data, (value) => { + data = cloneAndChange(data, value => { if (URI.isUri(value)) { const key = `__uri_${Math.random().toString(16).slice(2, 8)}`; bucket[key] = value; changed = true; - return key; } else { return undefined; @@ -631,59 +427,47 @@ export namespace MarkdownString { return JSON.stringify(data); } - export function to( - value: htmlContent.IMarkdownString, - ): vscode.MarkdownString { - const result = new types.MarkdownString( - value.value, - value.supportThemeIcons, - ); + export function to(value: htmlContent.IMarkdownString): vscode.MarkdownString { + const result = new types.MarkdownString(value.value, value.supportThemeIcons); result.isTrusted = value.isTrusted; result.supportHtml = value.supportHtml; result.baseUri = value.baseUri ? URI.from(value.baseUri) : undefined; - return result; } - export function fromStrict( - value: string | vscode.MarkdownString | undefined | null, - ): undefined | string | htmlContent.IMarkdownString { + export function fromStrict(value: string | vscode.MarkdownString | undefined | null): undefined | string | htmlContent.IMarkdownString { if (!value) { return undefined; } - return typeof value === "string" ? value : MarkdownString.from(value); + return typeof value === 'string' ? value : MarkdownString.from(value); } } -export function fromRangeOrRangeWithMessage( - ranges: vscode.Range[] | vscode.DecorationOptions[], -): IDecorationOptions[] { +export function fromRangeOrRangeWithMessage(ranges: vscode.Range[] | vscode.DecorationOptions[]): IDecorationOptions[] { if (isDecorationOptionsArr(ranges)) { return ranges.map((r): IDecorationOptions => { return { range: Range.from(r.range), hoverMessage: Array.isArray(r.hoverMessage) ? MarkdownString.fromMany(r.hoverMessage) - : r.hoverMessage - ? MarkdownString.from(r.hoverMessage) - : undefined, - renderOptions: /* URI vs Uri */ r.renderOptions, + : (r.hoverMessage ? MarkdownString.from(r.hoverMessage) : undefined), + renderOptions: /* URI vs Uri */r.renderOptions }; }); } else { return ranges.map((r): IDecorationOptions => { return { - range: Range.from(r), + range: Range.from(r) }; }); } } export function pathOrURIToURI(value: string | URI): URI { - if (typeof value === "undefined") { + if (typeof value === 'undefined') { return value; } - if (typeof value === "string") { + if (typeof value === 'string') { return URI.file(value); } else { return value; @@ -691,17 +475,13 @@ export function pathOrURIToURI(value: string | URI): URI { } export namespace ThemableDecorationAttachmentRenderOptions { - export function from( - options: vscode.ThemableDecorationAttachmentRenderOptions, - ): IContentDecorationRenderOptions { - if (typeof options === "undefined") { + export function from(options: vscode.ThemableDecorationAttachmentRenderOptions): IContentDecorationRenderOptions { + if (typeof options === 'undefined') { return options; } return { contentText: options.contentText, - contentIconPath: options.contentIconPath - ? pathOrURIToURI(options.contentIconPath) - : undefined, + contentIconPath: options.contentIconPath ? pathOrURIToURI(options.contentIconPath) : undefined, border: options.border, borderColor: options.borderColor, fontStyle: options.fontStyle, @@ -717,10 +497,8 @@ export namespace ThemableDecorationAttachmentRenderOptions { } export namespace ThemableDecorationRenderOptions { - export function from( - options: vscode.ThemableDecorationRenderOptions, - ): IThemeDecorationRenderOptions { - if (typeof options === "undefined") { + export function from(options: vscode.ThemableDecorationRenderOptions): IThemeDecorationRenderOptions { + if (typeof options === 'undefined') { return options; } return { @@ -742,40 +520,27 @@ export namespace ThemableDecorationRenderOptions { color: options.color, opacity: options.opacity, letterSpacing: options.letterSpacing, - gutterIconPath: options.gutterIconPath - ? pathOrURIToURI(options.gutterIconPath) - : undefined, + gutterIconPath: options.gutterIconPath ? pathOrURIToURI(options.gutterIconPath) : undefined, gutterIconSize: options.gutterIconSize, - overviewRulerColor: ( - options.overviewRulerColor - ), - before: options.before - ? ThemableDecorationAttachmentRenderOptions.from(options.before) - : undefined, - after: options.after - ? ThemableDecorationAttachmentRenderOptions.from(options.after) - : undefined, + overviewRulerColor: options.overviewRulerColor, + before: options.before ? ThemableDecorationAttachmentRenderOptions.from(options.before) : undefined, + after: options.after ? ThemableDecorationAttachmentRenderOptions.from(options.after) : undefined, }; } } export namespace DecorationRangeBehavior { - export function from( - value: types.DecorationRangeBehavior, - ): TrackedRangeStickiness { - if (typeof value === "undefined") { + export function from(value: types.DecorationRangeBehavior): TrackedRangeStickiness { + if (typeof value === 'undefined') { return value; } switch (value) { case types.DecorationRangeBehavior.OpenOpen: return TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges; - case types.DecorationRangeBehavior.ClosedClosed: return TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges; - case types.DecorationRangeBehavior.OpenClosed: return TrackedRangeStickiness.GrowsOnlyWhenTypingBefore; - case types.DecorationRangeBehavior.ClosedOpen: return TrackedRangeStickiness.GrowsOnlyWhenTypingAfter; } @@ -783,21 +548,13 @@ export namespace DecorationRangeBehavior { } export namespace DecorationRenderOptions { - export function from( - options: vscode.DecorationRenderOptions, - ): IDecorationRenderOptions { + export function from(options: vscode.DecorationRenderOptions): IDecorationRenderOptions { return { isWholeLine: options.isWholeLine, - rangeBehavior: options.rangeBehavior - ? DecorationRangeBehavior.from(options.rangeBehavior) - : undefined, + rangeBehavior: options.rangeBehavior ? DecorationRangeBehavior.from(options.rangeBehavior) : undefined, overviewRulerLane: options.overviewRulerLane, - light: options.light - ? ThemableDecorationRenderOptions.from(options.light) - : undefined, - dark: options.dark - ? ThemableDecorationRenderOptions.from(options.dark) - : undefined, + light: options.light ? ThemableDecorationRenderOptions.from(options.light) : undefined, + dark: options.dark ? ThemableDecorationRenderOptions.from(options.dark) : undefined, backgroundColor: options.backgroundColor, outline: options.outline, @@ -817,95 +574,64 @@ export namespace DecorationRenderOptions { color: options.color, opacity: options.opacity, letterSpacing: options.letterSpacing, - gutterIconPath: options.gutterIconPath - ? pathOrURIToURI(options.gutterIconPath) - : undefined, + gutterIconPath: options.gutterIconPath ? pathOrURIToURI(options.gutterIconPath) : undefined, gutterIconSize: options.gutterIconSize, - overviewRulerColor: ( - options.overviewRulerColor - ), - before: options.before - ? ThemableDecorationAttachmentRenderOptions.from(options.before) - : undefined, - after: options.after - ? ThemableDecorationAttachmentRenderOptions.from(options.after) - : undefined, + overviewRulerColor: options.overviewRulerColor, + before: options.before ? ThemableDecorationAttachmentRenderOptions.from(options.before) : undefined, + after: options.after ? ThemableDecorationAttachmentRenderOptions.from(options.after) : undefined, }; } } export namespace TextEdit { + export function from(edit: vscode.TextEdit): languages.TextEdit { return { text: edit.newText, eol: edit.newEol && EndOfLine.from(edit.newEol), - range: Range.from(edit.range), + range: Range.from(edit.range) }; } export function to(edit: languages.TextEdit): types.TextEdit { const result = new types.TextEdit(Range.to(edit.range), edit.text); - result.newEol = ( - typeof edit.eol === "undefined" ? undefined : EndOfLine.to(edit.eol) - )!; - + result.newEol = (typeof edit.eol === 'undefined' ? undefined : EndOfLine.to(edit.eol))!; return result; } } export namespace WorkspaceEdit { + export interface IVersionInformationProvider { getTextDocumentVersion(uri: URI): number | undefined; - getNotebookDocumentVersion(uri: URI): number | undefined; } - export function from( - value: vscode.WorkspaceEdit, - versionInfo?: IVersionInformationProvider, - ): extHostProtocol.IWorkspaceEditDto { + export function from(value: vscode.WorkspaceEdit, versionInfo?: IVersionInformationProvider): extHostProtocol.IWorkspaceEditDto { const result: extHostProtocol.IWorkspaceEditDto = { - edits: [], + edits: [] }; if (value instanceof types.WorkspaceEdit) { + // collect all files that are to be created so that their version // information (in case they exist as text model already) can be ignored const toCreate = new ResourceSet(); - for (const entry of value._allEntries()) { - if ( - entry._type === types.FileEditType.File && - URI.isUri(entry.to) && - entry.from === undefined - ) { + if (entry._type === types.FileEditType.File && URI.isUri(entry.to) && entry.from === undefined) { toCreate.add(entry.to); } } for (const entry of value._allEntries()) { - if (entry._type === types.FileEditType.File) { - let contents: - | { type: "base64"; value: string } - | { type: "dataTransferItem"; id: string } - | undefined; + if (entry._type === types.FileEditType.File) { + let contents: { type: 'base64'; value: string } | { type: 'dataTransferItem'; id: string } | undefined; if (entry.options?.contents) { if (ArrayBuffer.isView(entry.options.contents)) { - contents = { - type: "base64", - value: encodeBase64( - VSBuffer.wrap(entry.options.contents), - ), - }; + contents = { type: 'base64', value: encodeBase64(VSBuffer.wrap(entry.options.contents)) }; } else { - contents = { - type: "dataTransferItem", - id: ( - entry.options - .contents as types.DataTransferFile - )._itemId, - }; + contents = { type: 'dataTransferItem', id: (entry.options.contents as types.DataTransferFile)._itemId }; } } @@ -914,17 +640,16 @@ export namespace WorkspaceEdit { oldResource: entry.from, newResource: entry.to, options: { ...entry.options, contents }, - metadata: entry.metadata, + metadata: entry.metadata }); + } else if (entry._type === types.FileEditType.Text) { // text edits result.edits.push({ resource: entry.uri, textEdit: TextEdit.from(entry.edit), - versionId: !toCreate.has(entry.uri) - ? versionInfo?.getTextDocumentVersion(entry.uri) - : undefined, - metadata: entry.metadata, + versionId: !toCreate.has(entry.uri) ? versionInfo?.getTextDocumentVersion(entry.uri) : undefined, + metadata: entry.metadata }); } else if (entry._type === types.FileEditType.Snippet) { result.edits.push({ @@ -932,35 +657,33 @@ export namespace WorkspaceEdit { textEdit: { range: Range.from(entry.range), text: entry.edit.value, - insertAsSnippet: true, + insertAsSnippet: true }, - versionId: !toCreate.has(entry.uri) - ? versionInfo?.getTextDocumentVersion(entry.uri) - : undefined, - metadata: entry.metadata, + versionId: !toCreate.has(entry.uri) ? versionInfo?.getTextDocumentVersion(entry.uri) : undefined, + metadata: entry.metadata }); + } else if (entry._type === types.FileEditType.Cell) { // cell edit result.edits.push({ metadata: entry.metadata, resource: entry.uri, cellEdit: entry.edit, - notebookVersionId: - versionInfo?.getNotebookDocumentVersion(entry.uri), + notebookVersionId: versionInfo?.getNotebookDocumentVersion(entry.uri) }); + } else if (entry._type === types.FileEditType.CellReplace) { // cell replace result.edits.push({ metadata: entry.metadata, resource: entry.uri, - notebookVersionId: - versionInfo?.getNotebookDocumentVersion(entry.uri), + notebookVersionId: versionInfo?.getNotebookDocumentVersion(entry.uri), cellEdit: { editType: notebooks.CellEditType.Replace, index: entry.index, count: entry.count, - cells: entry.cells.map(NotebookCellData.from), - }, + cells: entry.cells.map(NotebookCellData.from) + } }); } } @@ -970,52 +693,35 @@ export namespace WorkspaceEdit { export function to(value: extHostProtocol.IWorkspaceEditDto) { const result = new types.WorkspaceEdit(); - - const edits = new ResourceMap< - (types.TextEdit | types.SnippetTextEdit)[] - >(); - + const edits = new ResourceMap<(types.TextEdit | types.SnippetTextEdit)[]>(); for (const edit of value.edits) { if ((edit).textEdit) { - const item = edit; + const item = edit; const uri = URI.revive(item.resource); - const range = Range.to(item.textEdit.range); - const text = item.textEdit.text; - const isSnippet = item.textEdit.insertAsSnippet; let editOrSnippetTest: types.TextEdit | types.SnippetTextEdit; - if (isSnippet) { - editOrSnippetTest = types.SnippetTextEdit.replace( - range, - new types.SnippetString(text), - ); + editOrSnippetTest = types.SnippetTextEdit.replace(range, new types.SnippetString(text)); } else { editOrSnippetTest = types.TextEdit.replace(range, text); } const array = edits.get(uri); - if (!array) { edits.set(uri, [editOrSnippetTest]); } else { array.push(editOrSnippetTest); } + } else { result.renameFile( - URI.revive( - (edit) - .oldResource!, - ), - URI.revive( - (edit) - .newResource!, - ), - (edit).options, + URI.revive((edit).oldResource!), + URI.revive((edit).newResource!), + (edit).options ); } } @@ -1027,9 +733,10 @@ export namespace WorkspaceEdit { } } + export namespace SymbolKind { - const _fromMapping: { [kind: number]: languages.SymbolKind } = - Object.create(null); + + const _fromMapping: { [kind: number]: languages.SymbolKind } = Object.create(null); _fromMapping[types.SymbolKind.File] = languages.SymbolKind.File; _fromMapping[types.SymbolKind.Module] = languages.SymbolKind.Module; _fromMapping[types.SymbolKind.Namespace] = languages.SymbolKind.Namespace; @@ -1038,8 +745,7 @@ export namespace SymbolKind { _fromMapping[types.SymbolKind.Method] = languages.SymbolKind.Method; _fromMapping[types.SymbolKind.Property] = languages.SymbolKind.Property; _fromMapping[types.SymbolKind.Field] = languages.SymbolKind.Field; - _fromMapping[types.SymbolKind.Constructor] = - languages.SymbolKind.Constructor; + _fromMapping[types.SymbolKind.Constructor] = languages.SymbolKind.Constructor; _fromMapping[types.SymbolKind.Enum] = languages.SymbolKind.Enum; _fromMapping[types.SymbolKind.Interface] = languages.SymbolKind.Interface; _fromMapping[types.SymbolKind.Function] = languages.SymbolKind.Function; @@ -1056,13 +762,10 @@ export namespace SymbolKind { _fromMapping[types.SymbolKind.Struct] = languages.SymbolKind.Struct; _fromMapping[types.SymbolKind.Event] = languages.SymbolKind.Event; _fromMapping[types.SymbolKind.Operator] = languages.SymbolKind.Operator; - _fromMapping[types.SymbolKind.TypeParameter] = - languages.SymbolKind.TypeParameter; + _fromMapping[types.SymbolKind.TypeParameter] = languages.SymbolKind.TypeParameter; export function from(kind: vscode.SymbolKind): languages.SymbolKind { - return typeof _fromMapping[kind] === "number" - ? _fromMapping[kind] - : languages.SymbolKind.Property; + return typeof _fromMapping[kind] === 'number' ? _fromMapping[kind] : languages.SymbolKind.Property; } export function to(kind: languages.SymbolKind): vscode.SymbolKind { @@ -1076,31 +779,28 @@ export namespace SymbolKind { } export namespace SymbolTag { + export function from(kind: types.SymbolTag): languages.SymbolTag { switch (kind) { - case types.SymbolTag.Deprecated: - return languages.SymbolTag.Deprecated; + case types.SymbolTag.Deprecated: return languages.SymbolTag.Deprecated; } } export function to(kind: languages.SymbolTag): types.SymbolTag { switch (kind) { - case languages.SymbolTag.Deprecated: - return types.SymbolTag.Deprecated; + case languages.SymbolTag.Deprecated: return types.SymbolTag.Deprecated; } } } export namespace WorkspaceSymbol { - export function from( - info: vscode.SymbolInformation, - ): search.IWorkspaceSymbol { + export function from(info: vscode.SymbolInformation): search.IWorkspaceSymbol { return { name: info.name, kind: SymbolKind.from(info.kind), tags: info.tags && info.tags.map(SymbolTag.from), containerName: info.containerName, - location: location.from(info.location), + location: location.from(info.location) }; } export function to(info: search.IWorkspaceSymbol): types.SymbolInformation { @@ -1108,27 +808,23 @@ export namespace WorkspaceSymbol { info.name, SymbolKind.to(info.kind), info.containerName, - location.to(info.location), + location.to(info.location) ); result.tags = info.tags && info.tags.map(SymbolTag.to); - return result; } } export namespace DocumentSymbol { - export function from( - info: vscode.DocumentSymbol, - ): languages.DocumentSymbol { + export function from(info: vscode.DocumentSymbol): languages.DocumentSymbol { const result: languages.DocumentSymbol = { - name: info.name || "!!MISSING: name!!", + name: info.name || '!!MISSING: name!!', detail: info.detail, range: Range.from(info.range), selectionRange: Range.from(info.selectionRange), kind: SymbolKind.from(info.kind), - tags: info.tags?.map(SymbolTag.from) ?? [], + tags: info.tags?.map(SymbolTag.from) ?? [] }; - if (info.children) { result.children = info.children.map(from); } @@ -1142,7 +838,6 @@ export namespace DocumentSymbol { Range.to(info.range), Range.to(info.selectionRange), ); - if (isNonEmptyArray(info.tags)) { result.tags = info.tags.map(SymbolTag.to); } @@ -1154,16 +849,15 @@ export namespace DocumentSymbol { } export namespace CallHierarchyItem { - export function to( - item: extHostProtocol.ICallHierarchyItemDto, - ): types.CallHierarchyItem { + + export function to(item: extHostProtocol.ICallHierarchyItemDto): types.CallHierarchyItem { const result = new types.CallHierarchyItem( SymbolKind.to(item.kind), item.name, - item.detail || "", + item.detail || '', URI.revive(item.uri), Range.to(item.range), - Range.to(item.selectionRange), + Range.to(item.selectionRange) ); result._sessionId = item._sessionId; @@ -1172,16 +866,13 @@ export namespace CallHierarchyItem { return result; } - export function from( - item: vscode.CallHierarchyItem, - sessionId?: string, - itemId?: string, - ): extHostProtocol.ICallHierarchyItemDto { + export function from(item: vscode.CallHierarchyItem, sessionId?: string, itemId?: string): extHostProtocol.ICallHierarchyItemDto { + sessionId = sessionId ?? (item)._sessionId; itemId = itemId ?? (item)._itemId; if (sessionId === undefined || itemId === undefined) { - throw new Error("invalid item"); + throw new Error('invalid item'); } return { @@ -1193,38 +884,37 @@ export namespace CallHierarchyItem { uri: item.uri, range: Range.from(item.range), selectionRange: Range.from(item.selectionRange), - tags: item.tags?.map(SymbolTag.from), + tags: item.tags?.map(SymbolTag.from) }; } } export namespace CallHierarchyIncomingCall { - export function to( - item: extHostProtocol.IIncomingCallDto, - ): types.CallHierarchyIncomingCall { + + export function to(item: extHostProtocol.IIncomingCallDto): types.CallHierarchyIncomingCall { return new types.CallHierarchyIncomingCall( CallHierarchyItem.to(item.from), - item.fromRanges.map((r) => Range.to(r)), + item.fromRanges.map(r => Range.to(r)) ); } } export namespace CallHierarchyOutgoingCall { - export function to( - item: extHostProtocol.IOutgoingCallDto, - ): types.CallHierarchyOutgoingCall { + + export function to(item: extHostProtocol.IOutgoingCallDto): types.CallHierarchyOutgoingCall { return new types.CallHierarchyOutgoingCall( CallHierarchyItem.to(item.to), - item.fromRanges.map((r) => Range.to(r)), + item.fromRanges.map(r => Range.to(r)) ); } } + export namespace location { export function from(value: vscode.Location): languages.Location { return { range: value.range && Range.from(value.range), - uri: value.uri, + uri: value.uri }; } @@ -1234,33 +924,21 @@ export namespace location { } export namespace DefinitionLink { - export function from( - value: vscode.Location | vscode.DefinitionLink, - ): languages.LocationLink { + export function from(value: vscode.Location | vscode.DefinitionLink): languages.LocationLink { const definitionLink = value; - const location = value; - return { originSelectionRange: definitionLink.originSelectionRange ? Range.from(definitionLink.originSelectionRange) : undefined, - uri: definitionLink.targetUri - ? definitionLink.targetUri - : location.uri, - range: Range.from( - definitionLink.targetRange - ? definitionLink.targetRange - : location.range, - ), + uri: definitionLink.targetUri ? definitionLink.targetUri : location.uri, + range: Range.from(definitionLink.targetRange ? definitionLink.targetRange : location.range), targetSelectionRange: definitionLink.targetSelectionRange ? Range.from(definitionLink.targetSelectionRange) : undefined, }; } - export function to( - value: extHostProtocol.ILocationLinkDto, - ): vscode.LocationLink { + export function to(value: extHostProtocol.ILocationLinkDto): vscode.LocationLink { return { targetUri: URI.revive(value.uri), targetRange: Range.to(value.range), @@ -1269,7 +947,7 @@ export namespace DefinitionLink { : undefined, originSelectionRange: value.originSelectionRange ? Range.to(value.originSelectionRange) - : undefined, + : undefined }; } } @@ -1282,72 +960,51 @@ export namespace Hover { canIncreaseVerbosity: hover.canIncreaseVerbosity, canDecreaseVerbosity: hover.canDecreaseVerbosity, }; - return convertedHover; } export function to(info: languages.Hover): types.VerboseHover { const contents = info.contents.map(MarkdownString.to); - const range = Range.to(info.range); - const canIncreaseVerbosity = info.canIncreaseVerbosity; - const canDecreaseVerbosity = info.canDecreaseVerbosity; - - return new types.VerboseHover( - contents, - range, - canIncreaseVerbosity, - canDecreaseVerbosity, - ); + return new types.VerboseHover(contents, range, canIncreaseVerbosity, canDecreaseVerbosity); } } export namespace EvaluatableExpression { - export function from( - expression: vscode.EvaluatableExpression, - ): languages.EvaluatableExpression { + export function from(expression: vscode.EvaluatableExpression): languages.EvaluatableExpression { return { range: Range.from(expression.range), - expression: expression.expression, + expression: expression.expression }; } - export function to( - info: languages.EvaluatableExpression, - ): types.EvaluatableExpression { - return new types.EvaluatableExpression( - Range.to(info.range), - info.expression, - ); + export function to(info: languages.EvaluatableExpression): types.EvaluatableExpression { + return new types.EvaluatableExpression(Range.to(info.range), info.expression); } } export namespace InlineValue { - export function from( - inlineValue: vscode.InlineValue, - ): languages.InlineValue { + export function from(inlineValue: vscode.InlineValue): languages.InlineValue { if (inlineValue instanceof types.InlineValueText) { return { - type: "text", + type: 'text', range: Range.from(inlineValue.range), - text: inlineValue.text, + text: inlineValue.text } satisfies languages.InlineValueText; } else if (inlineValue instanceof types.InlineValueVariableLookup) { return { - type: "variable", + type: 'variable', range: Range.from(inlineValue.range), variableName: inlineValue.variableName, - caseSensitiveLookup: inlineValue.caseSensitiveLookup, + caseSensitiveLookup: inlineValue.caseSensitiveLookup } satisfies languages.InlineValueVariableLookup; - } else if ( - inlineValue instanceof types.InlineValueEvaluatableExpression - ) { + } else if (inlineValue instanceof types.InlineValueEvaluatableExpression) { return { - type: "expression", + type: 'expression', range: Range.from(inlineValue.range), - expression: inlineValue.expression, + expression: inlineValue.expression } satisfies languages.InlineValueExpression; } else { throw new Error(`Unknown 'InlineValue' type`); @@ -1356,86 +1013,61 @@ export namespace InlineValue { export function to(inlineValue: languages.InlineValue): vscode.InlineValue { switch (inlineValue.type) { - case "text": + case 'text': return { range: Range.to(inlineValue.range), - text: inlineValue.text, + text: inlineValue.text } satisfies vscode.InlineValueText; - - case "variable": + case 'variable': return { range: Range.to(inlineValue.range), variableName: inlineValue.variableName, - caseSensitiveLookup: inlineValue.caseSensitiveLookup, + caseSensitiveLookup: inlineValue.caseSensitiveLookup } satisfies vscode.InlineValueVariableLookup; - - case "expression": + case 'expression': return { range: Range.to(inlineValue.range), - expression: inlineValue.expression, + expression: inlineValue.expression } satisfies vscode.InlineValueEvaluatableExpression; } } } export namespace InlineValueContext { - export function from( - inlineValueContext: vscode.InlineValueContext, - ): extHostProtocol.IInlineValueContextDto { + export function from(inlineValueContext: vscode.InlineValueContext): extHostProtocol.IInlineValueContextDto { return { frameId: inlineValueContext.frameId, - stoppedLocation: Range.from(inlineValueContext.stoppedLocation), + stoppedLocation: Range.from(inlineValueContext.stoppedLocation) }; } - export function to( - inlineValueContext: extHostProtocol.IInlineValueContextDto, - ): types.InlineValueContext { - return new types.InlineValueContext( - inlineValueContext.frameId, - Range.to(inlineValueContext.stoppedLocation), - ); + export function to(inlineValueContext: extHostProtocol.IInlineValueContextDto): types.InlineValueContext { + return new types.InlineValueContext(inlineValueContext.frameId, Range.to(inlineValueContext.stoppedLocation)); } } export namespace DocumentHighlight { - export function from( - documentHighlight: vscode.DocumentHighlight, - ): languages.DocumentHighlight { + export function from(documentHighlight: vscode.DocumentHighlight): languages.DocumentHighlight { return { range: Range.from(documentHighlight.range), - kind: documentHighlight.kind, + kind: documentHighlight.kind }; } - export function to( - occurrence: languages.DocumentHighlight, - ): types.DocumentHighlight { - return new types.DocumentHighlight( - Range.to(occurrence.range), - occurrence.kind, - ); + export function to(occurrence: languages.DocumentHighlight): types.DocumentHighlight { + return new types.DocumentHighlight(Range.to(occurrence.range), occurrence.kind); } } export namespace MultiDocumentHighlight { - export function from( - multiDocumentHighlight: vscode.MultiDocumentHighlight, - ): languages.MultiDocumentHighlight { + export function from(multiDocumentHighlight: vscode.MultiDocumentHighlight): languages.MultiDocumentHighlight { return { uri: multiDocumentHighlight.uri, - highlights: multiDocumentHighlight.highlights.map( - DocumentHighlight.from, - ), + highlights: multiDocumentHighlight.highlights.map(DocumentHighlight.from) }; } - export function to( - multiDocumentHighlight: languages.MultiDocumentHighlight, - ): types.MultiDocumentHighlight { - return new types.MultiDocumentHighlight( - URI.revive(multiDocumentHighlight.uri), - multiDocumentHighlight.highlights.map(DocumentHighlight.to), - ); + export function to(multiDocumentHighlight: languages.MultiDocumentHighlight): types.MultiDocumentHighlight { + return new types.MultiDocumentHighlight(URI.revive(multiDocumentHighlight.uri), multiDocumentHighlight.highlights.map(DocumentHighlight.to)); } } @@ -1444,12 +1076,8 @@ export namespace CompletionTriggerKind { switch (kind) { case languages.CompletionTriggerKind.TriggerCharacter: return types.CompletionTriggerKind.TriggerCharacter; - - case languages.CompletionTriggerKind - .TriggerForIncompleteCompletions: - return types.CompletionTriggerKind - .TriggerForIncompleteCompletions; - + case languages.CompletionTriggerKind.TriggerForIncompleteCompletions: + return types.CompletionTriggerKind.TriggerForIncompleteCompletions; case languages.CompletionTriggerKind.Invoke: default: return types.CompletionTriggerKind.Invoke; @@ -1458,232 +1086,110 @@ export namespace CompletionTriggerKind { } export namespace CompletionContext { - export function to( - context: languages.CompletionContext, - ): types.CompletionContext { + export function to(context: languages.CompletionContext): types.CompletionContext { return { triggerKind: CompletionTriggerKind.to(context.triggerKind), - triggerCharacter: context.triggerCharacter, + triggerCharacter: context.triggerCharacter }; } } export namespace CompletionItemTag { - export function from( - kind: types.CompletionItemTag, - ): languages.CompletionItemTag { + + export function from(kind: types.CompletionItemTag): languages.CompletionItemTag { switch (kind) { - case types.CompletionItemTag.Deprecated: - return languages.CompletionItemTag.Deprecated; + case types.CompletionItemTag.Deprecated: return languages.CompletionItemTag.Deprecated; } } - export function to( - kind: languages.CompletionItemTag, - ): types.CompletionItemTag { + export function to(kind: languages.CompletionItemTag): types.CompletionItemTag { switch (kind) { - case languages.CompletionItemTag.Deprecated: - return types.CompletionItemTag.Deprecated; + case languages.CompletionItemTag.Deprecated: return types.CompletionItemTag.Deprecated; } } } export namespace CompletionItemKind { - const _from = new Map< - types.CompletionItemKind, - languages.CompletionItemKind - >([ + + const _from = new Map([ [types.CompletionItemKind.Method, languages.CompletionItemKind.Method], - [ - types.CompletionItemKind.Function, - languages.CompletionItemKind.Function, - ], - [ - types.CompletionItemKind.Constructor, - languages.CompletionItemKind.Constructor, - ], + [types.CompletionItemKind.Function, languages.CompletionItemKind.Function], + [types.CompletionItemKind.Constructor, languages.CompletionItemKind.Constructor], [types.CompletionItemKind.Field, languages.CompletionItemKind.Field], - [ - types.CompletionItemKind.Variable, - languages.CompletionItemKind.Variable, - ], + [types.CompletionItemKind.Variable, languages.CompletionItemKind.Variable], [types.CompletionItemKind.Class, languages.CompletionItemKind.Class], - [ - types.CompletionItemKind.Interface, - languages.CompletionItemKind.Interface, - ], + [types.CompletionItemKind.Interface, languages.CompletionItemKind.Interface], [types.CompletionItemKind.Struct, languages.CompletionItemKind.Struct], [types.CompletionItemKind.Module, languages.CompletionItemKind.Module], - [ - types.CompletionItemKind.Property, - languages.CompletionItemKind.Property, - ], + [types.CompletionItemKind.Property, languages.CompletionItemKind.Property], [types.CompletionItemKind.Unit, languages.CompletionItemKind.Unit], [types.CompletionItemKind.Value, languages.CompletionItemKind.Value], - [ - types.CompletionItemKind.Constant, - languages.CompletionItemKind.Constant, - ], + [types.CompletionItemKind.Constant, languages.CompletionItemKind.Constant], [types.CompletionItemKind.Enum, languages.CompletionItemKind.Enum], - [ - types.CompletionItemKind.EnumMember, - languages.CompletionItemKind.EnumMember, - ], - [ - types.CompletionItemKind.Keyword, - languages.CompletionItemKind.Keyword, - ], - [ - types.CompletionItemKind.Snippet, - languages.CompletionItemKind.Snippet, - ], + [types.CompletionItemKind.EnumMember, languages.CompletionItemKind.EnumMember], + [types.CompletionItemKind.Keyword, languages.CompletionItemKind.Keyword], + [types.CompletionItemKind.Snippet, languages.CompletionItemKind.Snippet], [types.CompletionItemKind.Text, languages.CompletionItemKind.Text], [types.CompletionItemKind.Color, languages.CompletionItemKind.Color], [types.CompletionItemKind.File, languages.CompletionItemKind.File], - [ - types.CompletionItemKind.Reference, - languages.CompletionItemKind.Reference, - ], + [types.CompletionItemKind.Reference, languages.CompletionItemKind.Reference], [types.CompletionItemKind.Folder, languages.CompletionItemKind.Folder], [types.CompletionItemKind.Event, languages.CompletionItemKind.Event], - [ - types.CompletionItemKind.Operator, - languages.CompletionItemKind.Operator, - ], - [ - types.CompletionItemKind.TypeParameter, - languages.CompletionItemKind.TypeParameter, - ], + [types.CompletionItemKind.Operator, languages.CompletionItemKind.Operator], + [types.CompletionItemKind.TypeParameter, languages.CompletionItemKind.TypeParameter], [types.CompletionItemKind.Issue, languages.CompletionItemKind.Issue], [types.CompletionItemKind.User, languages.CompletionItemKind.User], ]); - export function from( - kind: types.CompletionItemKind, - ): languages.CompletionItemKind { + export function from(kind: types.CompletionItemKind): languages.CompletionItemKind { return _from.get(kind) ?? languages.CompletionItemKind.Property; } - const _to = new Map( - [ - [ - languages.CompletionItemKind.Method, - types.CompletionItemKind.Method, - ], - [ - languages.CompletionItemKind.Function, - types.CompletionItemKind.Function, - ], - [ - languages.CompletionItemKind.Constructor, - types.CompletionItemKind.Constructor, - ], - [ - languages.CompletionItemKind.Field, - types.CompletionItemKind.Field, - ], - [ - languages.CompletionItemKind.Variable, - types.CompletionItemKind.Variable, - ], - [ - languages.CompletionItemKind.Class, - types.CompletionItemKind.Class, - ], - [ - languages.CompletionItemKind.Interface, - types.CompletionItemKind.Interface, - ], - [ - languages.CompletionItemKind.Struct, - types.CompletionItemKind.Struct, - ], - [ - languages.CompletionItemKind.Module, - types.CompletionItemKind.Module, - ], - [ - languages.CompletionItemKind.Property, - types.CompletionItemKind.Property, - ], - [languages.CompletionItemKind.Unit, types.CompletionItemKind.Unit], - [ - languages.CompletionItemKind.Value, - types.CompletionItemKind.Value, - ], - [ - languages.CompletionItemKind.Constant, - types.CompletionItemKind.Constant, - ], - [languages.CompletionItemKind.Enum, types.CompletionItemKind.Enum], - [ - languages.CompletionItemKind.EnumMember, - types.CompletionItemKind.EnumMember, - ], - [ - languages.CompletionItemKind.Keyword, - types.CompletionItemKind.Keyword, - ], - [ - languages.CompletionItemKind.Snippet, - types.CompletionItemKind.Snippet, - ], - [languages.CompletionItemKind.Text, types.CompletionItemKind.Text], - [ - languages.CompletionItemKind.Color, - types.CompletionItemKind.Color, - ], - [languages.CompletionItemKind.File, types.CompletionItemKind.File], - [ - languages.CompletionItemKind.Reference, - types.CompletionItemKind.Reference, - ], - [ - languages.CompletionItemKind.Folder, - types.CompletionItemKind.Folder, - ], - [ - languages.CompletionItemKind.Event, - types.CompletionItemKind.Event, - ], - [ - languages.CompletionItemKind.Operator, - types.CompletionItemKind.Operator, - ], - [ - languages.CompletionItemKind.TypeParameter, - types.CompletionItemKind.TypeParameter, - ], - [languages.CompletionItemKind.User, types.CompletionItemKind.User], - [ - languages.CompletionItemKind.Issue, - types.CompletionItemKind.Issue, - ], - ], - ); - - export function to( - kind: languages.CompletionItemKind, - ): types.CompletionItemKind { + const _to = new Map([ + [languages.CompletionItemKind.Method, types.CompletionItemKind.Method], + [languages.CompletionItemKind.Function, types.CompletionItemKind.Function], + [languages.CompletionItemKind.Constructor, types.CompletionItemKind.Constructor], + [languages.CompletionItemKind.Field, types.CompletionItemKind.Field], + [languages.CompletionItemKind.Variable, types.CompletionItemKind.Variable], + [languages.CompletionItemKind.Class, types.CompletionItemKind.Class], + [languages.CompletionItemKind.Interface, types.CompletionItemKind.Interface], + [languages.CompletionItemKind.Struct, types.CompletionItemKind.Struct], + [languages.CompletionItemKind.Module, types.CompletionItemKind.Module], + [languages.CompletionItemKind.Property, types.CompletionItemKind.Property], + [languages.CompletionItemKind.Unit, types.CompletionItemKind.Unit], + [languages.CompletionItemKind.Value, types.CompletionItemKind.Value], + [languages.CompletionItemKind.Constant, types.CompletionItemKind.Constant], + [languages.CompletionItemKind.Enum, types.CompletionItemKind.Enum], + [languages.CompletionItemKind.EnumMember, types.CompletionItemKind.EnumMember], + [languages.CompletionItemKind.Keyword, types.CompletionItemKind.Keyword], + [languages.CompletionItemKind.Snippet, types.CompletionItemKind.Snippet], + [languages.CompletionItemKind.Text, types.CompletionItemKind.Text], + [languages.CompletionItemKind.Color, types.CompletionItemKind.Color], + [languages.CompletionItemKind.File, types.CompletionItemKind.File], + [languages.CompletionItemKind.Reference, types.CompletionItemKind.Reference], + [languages.CompletionItemKind.Folder, types.CompletionItemKind.Folder], + [languages.CompletionItemKind.Event, types.CompletionItemKind.Event], + [languages.CompletionItemKind.Operator, types.CompletionItemKind.Operator], + [languages.CompletionItemKind.TypeParameter, types.CompletionItemKind.TypeParameter], + [languages.CompletionItemKind.User, types.CompletionItemKind.User], + [languages.CompletionItemKind.Issue, types.CompletionItemKind.Issue], + ]); + + export function to(kind: languages.CompletionItemKind): types.CompletionItemKind { return _to.get(kind) ?? types.CompletionItemKind.Property; } } export namespace CompletionItem { - export function to( - suggestion: languages.CompletionItem, - converter?: Command.ICommandsConverter, - ): types.CompletionItem { + + export function to(suggestion: languages.CompletionItem, converter?: Command.ICommandsConverter): types.CompletionItem { + const result = new types.CompletionItem(suggestion.label); result.insertText = suggestion.insertText; result.kind = CompletionItemKind.to(suggestion.kind); result.tags = suggestion.tags?.map(CompletionItemTag.to); result.detail = suggestion.detail; - result.documentation = htmlContent.isMarkdownString( - suggestion.documentation, - ) - ? MarkdownString.to(suggestion.documentation) - : suggestion.documentation; + result.documentation = htmlContent.isMarkdownString(suggestion.documentation) ? MarkdownString.to(suggestion.documentation) : suggestion.documentation; result.sortText = suggestion.sortText; result.filterText = suggestion.filterText; result.preselect = suggestion.preselect; @@ -1692,115 +1198,74 @@ export namespace CompletionItem { // range if (editorRange.Range.isIRange(suggestion.range)) { result.range = Range.to(suggestion.range); - } else if (typeof suggestion.range === "object") { - result.range = { - inserting: Range.to(suggestion.range.insert), - replacing: Range.to(suggestion.range.replace), - }; + } else if (typeof suggestion.range === 'object') { + result.range = { inserting: Range.to(suggestion.range.insert), replacing: Range.to(suggestion.range.replace) }; } - result.keepWhitespace = - typeof suggestion.insertTextRules === "undefined" - ? false - : Boolean( - suggestion.insertTextRules & - languages.CompletionItemInsertTextRule - .KeepWhitespace, - ); + result.keepWhitespace = typeof suggestion.insertTextRules === 'undefined' ? false : Boolean(suggestion.insertTextRules & languages.CompletionItemInsertTextRule.KeepWhitespace); // 'insertText'-logic - if ( - typeof suggestion.insertTextRules !== "undefined" && - suggestion.insertTextRules & - languages.CompletionItemInsertTextRule.InsertAsSnippet - ) { + if (typeof suggestion.insertTextRules !== 'undefined' && suggestion.insertTextRules & languages.CompletionItemInsertTextRule.InsertAsSnippet) { result.insertText = new types.SnippetString(suggestion.insertText); } else { result.insertText = suggestion.insertText; - result.textEdit = - result.range instanceof types.Range - ? new types.TextEdit(result.range, result.insertText) - : undefined; + result.textEdit = result.range instanceof types.Range ? new types.TextEdit(result.range, result.insertText) : undefined; } - if ( - suggestion.additionalTextEdits && - suggestion.additionalTextEdits.length > 0 - ) { - result.additionalTextEdits = suggestion.additionalTextEdits.map( - (e) => TextEdit.to(e as languages.TextEdit), - ); + if (suggestion.additionalTextEdits && suggestion.additionalTextEdits.length > 0) { + result.additionalTextEdits = suggestion.additionalTextEdits.map(e => TextEdit.to(e as languages.TextEdit)); } - result.command = - converter && suggestion.command - ? converter.fromInternal(suggestion.command) - : undefined; + result.command = converter && suggestion.command ? converter.fromInternal(suggestion.command) : undefined; return result; } } export namespace ParameterInformation { - export function from( - info: types.ParameterInformation, - ): languages.ParameterInformation { - if (typeof info.label !== "string" && !Array.isArray(info.label)) { - throw new TypeError("Invalid label"); + export function from(info: types.ParameterInformation): languages.ParameterInformation { + if (typeof info.label !== 'string' && !Array.isArray(info.label)) { + throw new TypeError('Invalid label'); } return { label: info.label, - documentation: MarkdownString.fromStrict(info.documentation), + documentation: MarkdownString.fromStrict(info.documentation) }; } - export function to( - info: languages.ParameterInformation, - ): types.ParameterInformation { + export function to(info: languages.ParameterInformation): types.ParameterInformation { return { label: info.label, - documentation: htmlContent.isMarkdownString(info.documentation) - ? MarkdownString.to(info.documentation) - : info.documentation, + documentation: htmlContent.isMarkdownString(info.documentation) ? MarkdownString.to(info.documentation) : info.documentation }; } } export namespace SignatureInformation { - export function from( - info: types.SignatureInformation, - ): languages.SignatureInformation { + + export function from(info: types.SignatureInformation): languages.SignatureInformation { return { label: info.label, documentation: MarkdownString.fromStrict(info.documentation), - parameters: Array.isArray(info.parameters) - ? info.parameters.map(ParameterInformation.from) - : [], + parameters: Array.isArray(info.parameters) ? info.parameters.map(ParameterInformation.from) : [], activeParameter: info.activeParameter, }; } - export function to( - info: languages.SignatureInformation, - ): types.SignatureInformation { + export function to(info: languages.SignatureInformation): types.SignatureInformation { return { label: info.label, - documentation: htmlContent.isMarkdownString(info.documentation) - ? MarkdownString.to(info.documentation) - : info.documentation, - parameters: Array.isArray(info.parameters) - ? info.parameters.map(ParameterInformation.to) - : [], + documentation: htmlContent.isMarkdownString(info.documentation) ? MarkdownString.to(info.documentation) : info.documentation, + parameters: Array.isArray(info.parameters) ? info.parameters.map(ParameterInformation.to) : [], activeParameter: info.activeParameter, }; } } export namespace SignatureHelp { + export function from(help: types.SignatureHelp): languages.SignatureHelp { return { activeSignature: help.activeSignature, activeParameter: help.activeParameter, - signatures: Array.isArray(help.signatures) - ? help.signatures.map(SignatureInformation.from) - : [], + signatures: Array.isArray(help.signatures) ? help.signatures.map(SignatureInformation.from) : [], }; } @@ -1808,48 +1273,34 @@ export namespace SignatureHelp { return { activeSignature: help.activeSignature, activeParameter: help.activeParameter, - signatures: Array.isArray(help.signatures) - ? help.signatures.map(SignatureInformation.to) - : [], + signatures: Array.isArray(help.signatures) ? help.signatures.map(SignatureInformation.to) : [], }; } } export namespace InlayHint { - export function to( - converter: Command.ICommandsConverter, - hint: languages.InlayHint, - ): vscode.InlayHint { + + export function to(converter: Command.ICommandsConverter, hint: languages.InlayHint): vscode.InlayHint { const res = new types.InlayHint( Position.to(hint.position), - typeof hint.label === "string" - ? hint.label - : hint.label.map( - InlayHintLabelPart.to.bind(undefined, converter), - ), - hint.kind && InlayHintKind.to(hint.kind), + typeof hint.label === 'string' ? hint.label : hint.label.map(InlayHintLabelPart.to.bind(undefined, converter)), + hint.kind && InlayHintKind.to(hint.kind) ); res.textEdits = hint.textEdits && hint.textEdits.map(TextEdit.to); - res.tooltip = htmlContent.isMarkdownString(hint.tooltip) - ? MarkdownString.to(hint.tooltip) - : hint.tooltip; + res.tooltip = htmlContent.isMarkdownString(hint.tooltip) ? MarkdownString.to(hint.tooltip) : hint.tooltip; res.paddingLeft = hint.paddingLeft; res.paddingRight = hint.paddingRight; - return res; } } export namespace InlayHintLabelPart { - export function to( - converter: Command.ICommandsConverter, - part: languages.InlayHintLabelPart, - ): types.InlayHintLabelPart { + + export function to(converter: Command.ICommandsConverter, part: languages.InlayHintLabelPart): types.InlayHintLabelPart { const result = new types.InlayHintLabelPart(part.label); result.tooltip = htmlContent.isMarkdownString(part.tooltip) ? MarkdownString.to(part.tooltip) : part.tooltip; - if (languages.Command.is(part.command)) { result.command = converter.fromInternal(part.command); } @@ -1870,64 +1321,47 @@ export namespace InlayHintKind { } export namespace DocumentLink { + export function from(link: vscode.DocumentLink): languages.ILink { return { range: Range.from(link.range), url: link.target, - tooltip: link.tooltip, + tooltip: link.tooltip }; } export function to(link: languages.ILink): vscode.DocumentLink { let target: URI | undefined = undefined; - if (link.url) { try { - target = - typeof link.url === "string" - ? URI.parse(link.url, true) - : URI.revive(link.url); + target = typeof link.url === 'string' ? URI.parse(link.url, true) : URI.revive(link.url); } catch (err) { // ignore } } const result = new types.DocumentLink(Range.to(link.range), target); result.tooltip = link.tooltip; - return result; } } export namespace ColorPresentation { - export function to( - colorPresentation: languages.IColorPresentation, - ): types.ColorPresentation { + export function to(colorPresentation: languages.IColorPresentation): types.ColorPresentation { const cp = new types.ColorPresentation(colorPresentation.label); - if (colorPresentation.textEdit) { cp.textEdit = TextEdit.to(colorPresentation.textEdit); } if (colorPresentation.additionalTextEdits) { - cp.additionalTextEdits = colorPresentation.additionalTextEdits.map( - (value) => TextEdit.to(value), - ); + cp.additionalTextEdits = colorPresentation.additionalTextEdits.map(value => TextEdit.to(value)); } return cp; } - export function from( - colorPresentation: vscode.ColorPresentation, - ): languages.IColorPresentation { + export function from(colorPresentation: vscode.ColorPresentation): languages.IColorPresentation { return { label: colorPresentation.label, - textEdit: colorPresentation.textEdit - ? TextEdit.from(colorPresentation.textEdit) - : undefined, - additionalTextEdits: colorPresentation.additionalTextEdits - ? colorPresentation.additionalTextEdits.map((value) => - TextEdit.from(value), - ) - : undefined, + textEdit: colorPresentation.textEdit ? TextEdit.from(colorPresentation.textEdit) : undefined, + additionalTextEdits: colorPresentation.additionalTextEdits ? colorPresentation.additionalTextEdits.map(value => TextEdit.from(value)) : undefined }; } } @@ -1941,6 +1375,7 @@ export namespace Color { } } + export namespace SelectionRange { export function from(obj: vscode.SelectionRange): languages.SelectionRange { return { range: Range.from(obj.range) }; @@ -1952,14 +1387,13 @@ export namespace SelectionRange { } export namespace TextDocumentSaveReason { + export function to(reason: SaveReason): vscode.TextDocumentSaveReason { switch (reason) { case SaveReason.AUTO: return types.TextDocumentSaveReason.AfterDelay; - case SaveReason.EXPLICIT: return types.TextDocumentSaveReason.Manual; - case SaveReason.FOCUS_CHANGE: case SaveReason.WINDOW_CHANGE: return types.TextDocumentSaveReason.FocusOut; @@ -1968,37 +1402,27 @@ export namespace TextDocumentSaveReason { } export namespace TextEditorLineNumbersStyle { - export function from( - style: vscode.TextEditorLineNumbersStyle, - ): RenderLineNumbersType { + export function from(style: vscode.TextEditorLineNumbersStyle): RenderLineNumbersType { switch (style) { case types.TextEditorLineNumbersStyle.Off: return RenderLineNumbersType.Off; - case types.TextEditorLineNumbersStyle.Relative: return RenderLineNumbersType.Relative; - case types.TextEditorLineNumbersStyle.Interval: return RenderLineNumbersType.Interval; - case types.TextEditorLineNumbersStyle.On: default: return RenderLineNumbersType.On; } } - export function to( - style: RenderLineNumbersType, - ): vscode.TextEditorLineNumbersStyle { + export function to(style: RenderLineNumbersType): vscode.TextEditorLineNumbersStyle { switch (style) { case RenderLineNumbersType.Off: return types.TextEditorLineNumbersStyle.Off; - case RenderLineNumbersType.Relative: return types.TextEditorLineNumbersStyle.Relative; - case RenderLineNumbersType.Interval: return types.TextEditorLineNumbersStyle.Interval; - case RenderLineNumbersType.On: default: return types.TextEditorLineNumbersStyle.On; @@ -2007,6 +1431,7 @@ export namespace TextEditorLineNumbersStyle { } export namespace EndOfLine { + export function from(eol: vscode.EndOfLine): EndOfLineSequence | undefined { if (eol === types.EndOfLine.CRLF) { return EndOfLineSequence.CRLF; @@ -2027,22 +1452,15 @@ export namespace EndOfLine { } export namespace ProgressLocation { - export function from( - loc: vscode.ProgressLocation | { viewId: string }, - ): MainProgressLocation | string { - if (typeof loc === "object") { + export function from(loc: vscode.ProgressLocation | { viewId: string }): MainProgressLocation | string { + if (typeof loc === 'object') { return loc.viewId; } switch (loc) { - case types.ProgressLocation.SourceControl: - return MainProgressLocation.Scm; - - case types.ProgressLocation.Window: - return MainProgressLocation.Window; - - case types.ProgressLocation.Notification: - return MainProgressLocation.Notification; + case types.ProgressLocation.SourceControl: return MainProgressLocation.Scm; + case types.ProgressLocation.Window: return MainProgressLocation.Window; + case types.ProgressLocation.Notification: return MainProgressLocation.Notification; } throw new Error(`Unknown 'ProgressLocation'`); } @@ -2050,22 +1468,14 @@ export namespace ProgressLocation { export namespace FoldingRange { export function from(r: vscode.FoldingRange): languages.FoldingRange { - const range: languages.FoldingRange = { - start: r.start + 1, - end: r.end + 1, - }; - + const range: languages.FoldingRange = { start: r.start + 1, end: r.end + 1 }; if (r.kind) { range.kind = FoldingRangeKind.from(r.kind); } return range; } export function to(r: languages.FoldingRange): vscode.FoldingRange { - const range: vscode.FoldingRange = { - start: r.start - 1, - end: r.end - 1, - }; - + const range: vscode.FoldingRange = { start: r.start - 1, end: r.end - 1 }; if (r.kind) { range.kind = FoldingRangeKind.to(r.kind); } @@ -2074,34 +1484,26 @@ export namespace FoldingRange { } export namespace FoldingRangeKind { - export function from( - kind: vscode.FoldingRangeKind | undefined, - ): languages.FoldingRangeKind | undefined { + export function from(kind: vscode.FoldingRangeKind | undefined): languages.FoldingRangeKind | undefined { if (kind) { switch (kind) { case types.FoldingRangeKind.Comment: return languages.FoldingRangeKind.Comment; - case types.FoldingRangeKind.Imports: return languages.FoldingRangeKind.Imports; - case types.FoldingRangeKind.Region: return languages.FoldingRangeKind.Region; } } return undefined; } - export function to( - kind: languages.FoldingRangeKind | undefined, - ): vscode.FoldingRangeKind | undefined { + export function to(kind: languages.FoldingRangeKind | undefined): vscode.FoldingRangeKind | undefined { if (kind) { switch (kind.value) { case languages.FoldingRangeKind.Comment.value: return types.FoldingRangeKind.Comment; - case languages.FoldingRangeKind.Imports.value: return types.FoldingRangeKind.Imports; - case languages.FoldingRangeKind.Region.value: return types.FoldingRangeKind.Region; } @@ -2116,53 +1518,35 @@ export interface TextEditorOpenOptions extends vscode.TextDocumentShowOptions { } export namespace TextEditorOpenOptions { - export function from( - options?: TextEditorOpenOptions, - ): ITextEditorOptions | undefined { + + export function from(options?: TextEditorOpenOptions): ITextEditorOptions | undefined { if (options) { return { - pinned: - typeof options.preview === "boolean" - ? !options.preview - : undefined, + pinned: typeof options.preview === 'boolean' ? !options.preview : undefined, inactive: options.background, preserveFocus: options.preserveFocus, - selection: - typeof options.selection === "object" - ? Range.from(options.selection) - : undefined, - override: - typeof options.override === "boolean" - ? DEFAULT_EDITOR_ASSOCIATION.id - : undefined, + selection: typeof options.selection === 'object' ? Range.from(options.selection) : undefined, + override: typeof options.override === 'boolean' ? DEFAULT_EDITOR_ASSOCIATION.id : undefined }; } return undefined; } + } export namespace GlobPattern { - export function from( - pattern: vscode.GlobPattern, - ): string | extHostProtocol.IRelativePatternDto; + export function from(pattern: vscode.GlobPattern): string | extHostProtocol.IRelativePatternDto; export function from(pattern: undefined): undefined; - export function from(pattern: null): null; - - export function from( - pattern: vscode.GlobPattern | undefined | null, - ): string | extHostProtocol.IRelativePatternDto | undefined | null; - - export function from( - pattern: vscode.GlobPattern | undefined | null, - ): string | extHostProtocol.IRelativePatternDto | undefined | null { + export function from(pattern: vscode.GlobPattern | undefined | null): string | extHostProtocol.IRelativePatternDto | undefined | null; + export function from(pattern: vscode.GlobPattern | undefined | null): string | extHostProtocol.IRelativePatternDto | undefined | null { if (pattern instanceof types.RelativePattern) { return pattern.toJSON(); } - if (typeof pattern === "string") { + if (typeof pattern === 'string') { return pattern; } @@ -2171,83 +1555,56 @@ export namespace GlobPattern { // but given we cannot enforce classes from our vscode.d.ts, we have // to probe for objects too // Refs: https://github.com/microsoft/vscode/issues/140771 - if ( - isRelativePatternShape(pattern) || - isLegacyRelativePatternShape(pattern) - ) { - return new types.RelativePattern( - pattern.baseUri ?? pattern.base, - pattern.pattern, - ).toJSON(); + if (isRelativePatternShape(pattern) || isLegacyRelativePatternShape(pattern)) { + return new types.RelativePattern(pattern.baseUri ?? pattern.base, pattern.pattern).toJSON(); } return pattern; // preserve `undefined` and `null` } - function isRelativePatternShape( - obj: unknown, - ): obj is { base: string; baseUri: URI; pattern: string } { - const rp = obj as - | { base: string; baseUri: URI; pattern: string } - | undefined - | null; - + function isRelativePatternShape(obj: unknown): obj is { base: string; baseUri: URI; pattern: string } { + const rp = obj as { base: string; baseUri: URI; pattern: string } | undefined | null; if (!rp) { return false; } - return URI.isUri(rp.baseUri) && typeof rp.pattern === "string"; + return URI.isUri(rp.baseUri) && typeof rp.pattern === 'string'; } - function isLegacyRelativePatternShape( - obj: unknown, - ): obj is { base: string; pattern: string } { + function isLegacyRelativePatternShape(obj: unknown): obj is { base: string; pattern: string } { + // Before 1.64.x, `RelativePattern` did not have any `baseUri: Uri` // property. To preserve backwards compatibility with older extensions // we allow this old format when creating the `vscode.RelativePattern`. const rp = obj as { base: string; pattern: string } | undefined | null; - if (!rp) { return false; } - return typeof rp.base === "string" && typeof rp.pattern === "string"; + return typeof rp.base === 'string' && typeof rp.pattern === 'string'; } - export function to( - pattern: string | extHostProtocol.IRelativePatternDto, - ): vscode.GlobPattern { - if (typeof pattern === "string") { + export function to(pattern: string | extHostProtocol.IRelativePatternDto): vscode.GlobPattern { + if (typeof pattern === 'string') { return pattern; } - return new types.RelativePattern( - URI.revive(pattern.baseUri), - pattern.pattern, - ); + return new types.RelativePattern(URI.revive(pattern.baseUri), pattern.pattern); } } export namespace LanguageSelector { - export function from(selector: undefined): undefined; - - export function from( - selector: vscode.DocumentSelector, - ): languageSelector.LanguageSelector; - - export function from( - selector: vscode.DocumentSelector | undefined, - ): languageSelector.LanguageSelector | undefined; - export function from( - selector: vscode.DocumentSelector | undefined, - ): languageSelector.LanguageSelector | undefined { + export function from(selector: undefined): undefined; + export function from(selector: vscode.DocumentSelector): languageSelector.LanguageSelector; + export function from(selector: vscode.DocumentSelector | undefined): languageSelector.LanguageSelector | undefined; + export function from(selector: vscode.DocumentSelector | undefined): languageSelector.LanguageSelector | undefined { if (!selector) { return undefined; } else if (Array.isArray(selector)) { return selector.map(from); - } else if (typeof selector === "string") { + } else if (typeof selector === 'string') { return selector; } else { const filter = selector as vscode.DocumentFilter; // TODO: microsoft/TypeScript#42768 @@ -2256,92 +1613,79 @@ export namespace LanguageSelector { scheme: filter.scheme, pattern: GlobPattern.from(filter.pattern) ?? undefined, exclusive: filter.exclusive, - notebookType: filter.notebookType, + notebookType: filter.notebookType }; } } } export namespace MappedEditsContext { + export function is(v: unknown): v is vscode.MappedEditsContext { return ( - !!v && - typeof v === "object" && - "documents" in v && + !!v && typeof v === 'object' && + 'documents' in v && Array.isArray(v.documents) && v.documents.every( - (subArr) => - Array.isArray(subArr) && - subArr.every(DocumentContextItem.is), - ) + subArr => Array.isArray(subArr) && + subArr.every(DocumentContextItem.is)) ); } - export function from( - extContext: vscode.MappedEditsContext, - ): Dto { + export function from(extContext: vscode.MappedEditsContext): Dto { return { documents: extContext.documents.map((subArray) => - subArray.map(DocumentContextItem.from), - ), - conversation: extContext.conversation?.map((item) => - item.type === "request" - ? { - type: "request", - message: item.message, - } - : { - type: "response", - message: item.message, - result: item.result - ? ChatAgentResult.from(item.result) - : undefined, - references: item.references?.map( - DocumentContextItem.from, - ), - }, + subArray.map(DocumentContextItem.from) ), + conversation: extContext.conversation?.map(item => ( + (item.type === 'request') ? + { + type: 'request', + message: item.message, + } : + { + type: 'response', + message: item.message, + result: item.result ? ChatAgentResult.from(item.result) : undefined, + references: item.references?.map(DocumentContextItem.from) + } + )) }; + } } export namespace DocumentContextItem { + export function is(item: unknown): item is vscode.DocumentContextItem { return ( - typeof item === "object" && + typeof item === 'object' && item !== null && - "uri" in item && - URI.isUri(item.uri) && - "version" in item && - typeof item.version === "number" && - "ranges" in item && - Array.isArray(item.ranges) && - item.ranges.every((r: unknown) => r instanceof types.Range) + 'uri' in item && URI.isUri(item.uri) && + 'version' in item && typeof item.version === 'number' && + 'ranges' in item && Array.isArray(item.ranges) && item.ranges.every((r: unknown) => r instanceof types.Range) ); } - export function from( - item: vscode.DocumentContextItem, - ): Dto { + export function from(item: vscode.DocumentContextItem): Dto { return { uri: item.uri, version: item.version, - ranges: item.ranges.map((r) => Range.from(r)), + ranges: item.ranges.map(r => Range.from(r)), }; } - export function to( - item: Dto, - ): vscode.DocumentContextItem { + export function to(item: Dto): vscode.DocumentContextItem { return { uri: URI.revive(item.uri), version: item.version, - ranges: item.ranges.map((r) => Range.to(r)), + ranges: item.ranges.map(r => Range.to(r)), }; } } export namespace NotebookRange { + export function from(range: vscode.NotebookRange): ICellRange { return { start: range.start, end: range.end }; } @@ -2352,36 +1696,26 @@ export namespace NotebookRange { } export namespace NotebookCellExecutionSummary { - export function to( - data: notebooks.NotebookCellInternalMetadata, - ): vscode.NotebookCellExecutionSummary { + export function to(data: notebooks.NotebookCellInternalMetadata): vscode.NotebookCellExecutionSummary { return { - timing: - typeof data.runStartTime === "number" && - typeof data.runEndTime === "number" - ? { startTime: data.runStartTime, endTime: data.runEndTime } - : undefined, + timing: typeof data.runStartTime === 'number' && typeof data.runEndTime === 'number' ? { startTime: data.runStartTime, endTime: data.runEndTime } : undefined, executionOrder: data.executionOrder, - success: data.lastRunSuccess, + success: data.lastRunSuccess }; } - export function from( - data: vscode.NotebookCellExecutionSummary, - ): Partial { + export function from(data: vscode.NotebookCellExecutionSummary): Partial { return { lastRunSuccess: data.success, runStartTime: data.timing?.startTime, runEndTime: data.timing?.endTime, - executionOrder: data.executionOrder, + executionOrder: data.executionOrder }; } } export namespace NotebookCellExecutionState { - export function to( - state: notebooks.NotebookCellExecutionState, - ): vscode.NotebookCellExecutionState | undefined { + export function to(state: notebooks.NotebookCellExecutionState): vscode.NotebookCellExecutionState | undefined { if (state === notebooks.NotebookCellExecutionState.Unconfirmed) { return types.NotebookCellExecutionState.Pending; } else if (state === notebooks.NotebookCellExecutionState.Pending) { @@ -2400,7 +1734,6 @@ export namespace NotebookCellKind { switch (data) { case types.NotebookCellKind.Markup: return notebooks.CellKind.Markup; - case types.NotebookCellKind.Code: default: return notebooks.CellKind.Code; @@ -2411,7 +1744,6 @@ export namespace NotebookCellKind { switch (data) { case notebooks.CellKind.Markup: return types.NotebookCellKind.Markup; - case notebooks.CellKind.Code: default: return types.NotebookCellKind.Code; @@ -2420,14 +1752,12 @@ export namespace NotebookCellKind { } export namespace NotebookData { - export function from( - data: vscode.NotebookData, - ): extHostProtocol.NotebookDataDto { + + export function from(data: vscode.NotebookData): extHostProtocol.NotebookDataDto { const res: extHostProtocol.NotebookDataDto = { metadata: data.metadata ?? Object.create(null), cells: [], }; - for (const cell of data.cells) { types.NotebookCellData.validate(cell); res.cells.push(NotebookCellData.from(cell)); @@ -2435,11 +1765,10 @@ export namespace NotebookData { return res; } - export function to( - data: extHostProtocol.NotebookDataDto, - ): vscode.NotebookData { - const res = new types.NotebookData(data.cells.map(NotebookCellData.to)); - + export function to(data: extHostProtocol.NotebookDataDto): vscode.NotebookData { + const res = new types.NotebookData( + data.cells.map(NotebookCellData.to), + ); if (!isEmptyObject(data.metadata)) { res.metadata = data.metadata; } @@ -2448,27 +1777,20 @@ export namespace NotebookData { } export namespace NotebookCellData { - export function from( - data: vscode.NotebookCellData, - ): extHostProtocol.NotebookCellDataDto { + + export function from(data: vscode.NotebookCellData): extHostProtocol.NotebookCellDataDto { return { cellKind: NotebookCellKind.from(data.kind), language: data.languageId, mime: data.mime, source: data.value, metadata: data.metadata, - internalMetadata: NotebookCellExecutionSummary.from( - data.executionSummary ?? {}, - ), - outputs: data.outputs - ? data.outputs.map(NotebookCellOutput.from) - : [], + internalMetadata: NotebookCellExecutionSummary.from(data.executionSummary ?? {}), + outputs: data.outputs ? data.outputs.map(NotebookCellOutput.from) : [] }; } - export function to( - data: extHostProtocol.NotebookCellDataDto, - ): vscode.NotebookCellData { + export function to(data: extHostProtocol.NotebookCellDataDto): vscode.NotebookCellData { return new types.NotebookCellData( NotebookCellKind.to(data.cellKind), data.source, @@ -2476,117 +1798,46 @@ export namespace NotebookCellData { data.mime, data.outputs ? data.outputs.map(NotebookCellOutput.to) : undefined, data.metadata, - data.internalMetadata - ? NotebookCellExecutionSummary.to(data.internalMetadata) - : undefined, + data.internalMetadata ? NotebookCellExecutionSummary.to(data.internalMetadata) : undefined ); } } export namespace NotebookCellOutputItem { - export function from( - item: types.NotebookCellOutputItem, - ): extHostProtocol.NotebookOutputItemDto { + export function from(item: types.NotebookCellOutputItem): extHostProtocol.NotebookOutputItemDto { return { mime: item.mime, valueBytes: VSBuffer.wrap(item.data), }; } - export function to( - item: extHostProtocol.NotebookOutputItemDto, - ): types.NotebookCellOutputItem { - return new types.NotebookCellOutputItem( - item.valueBytes.buffer, - item.mime, - ); + export function to(item: extHostProtocol.NotebookOutputItemDto): types.NotebookCellOutputItem { + return new types.NotebookCellOutputItem(item.valueBytes.buffer, item.mime); } } export namespace NotebookCellOutput { - export function from( - output: vscode.NotebookCellOutput, - ): extHostProtocol.NotebookOutputDto { + export function from(output: vscode.NotebookCellOutput): extHostProtocol.NotebookOutputDto { return { outputId: output.id, items: output.items.map(NotebookCellOutputItem.from), - metadata: output.metadata, + metadata: output.metadata }; } - export function to( - output: extHostProtocol.NotebookOutputDto, - ): vscode.NotebookCellOutput { + export function to(output: extHostProtocol.NotebookOutputDto): vscode.NotebookCellOutput { const items = output.items.map(NotebookCellOutputItem.to); - - return new types.NotebookCellOutput( - items, - output.outputId, - output.metadata, - ); + return new types.NotebookCellOutput(items, output.outputId, output.metadata); } } -export namespace NotebookExclusiveDocumentPattern { - export function from(pattern: { - include: vscode.GlobPattern | undefined; - exclude: vscode.GlobPattern | undefined; - }): { - include: string | extHostProtocol.IRelativePatternDto | undefined; - exclude: string | extHostProtocol.IRelativePatternDto | undefined; - }; - - export function from( - pattern: vscode.GlobPattern, - ): string | extHostProtocol.IRelativePatternDto; +export namespace NotebookExclusiveDocumentPattern { + export function from(pattern: { include: vscode.GlobPattern | undefined; exclude: vscode.GlobPattern | undefined }): { include: string | extHostProtocol.IRelativePatternDto | undefined; exclude: string | extHostProtocol.IRelativePatternDto | undefined }; + export function from(pattern: vscode.GlobPattern): string | extHostProtocol.IRelativePatternDto; export function from(pattern: undefined): undefined; - - export function from( - pattern: - | { - include: vscode.GlobPattern | undefined | null; - exclude: vscode.GlobPattern | undefined; - } - | vscode.GlobPattern - | undefined, - ): - | string - | extHostProtocol.IRelativePatternDto - | { - include: - | string - | extHostProtocol.IRelativePatternDto - | undefined; - exclude: - | string - | extHostProtocol.IRelativePatternDto - | undefined; - } - | undefined; - - export function from( - pattern: - | { - include: vscode.GlobPattern | undefined | null; - exclude: vscode.GlobPattern | undefined; - } - | vscode.GlobPattern - | undefined, - ): - | string - | extHostProtocol.IRelativePatternDto - | { - include: - | string - | extHostProtocol.IRelativePatternDto - | undefined; - exclude: - | string - | extHostProtocol.IRelativePatternDto - | undefined; - } - | undefined { + export function from(pattern: { include: vscode.GlobPattern | undefined | null; exclude: vscode.GlobPattern | undefined } | vscode.GlobPattern | undefined): string | extHostProtocol.IRelativePatternDto | { include: string | extHostProtocol.IRelativePatternDto | undefined; exclude: string | extHostProtocol.IRelativePatternDto | undefined } | undefined; + export function from(pattern: { include: vscode.GlobPattern | undefined | null; exclude: vscode.GlobPattern | undefined } | vscode.GlobPattern | undefined): string | extHostProtocol.IRelativePatternDto | { include: string | extHostProtocol.IRelativePatternDto | undefined; exclude: string | extHostProtocol.IRelativePatternDto | undefined } | undefined { if (isExclusivePattern(pattern)) { return { include: GlobPattern.from(pattern.include) ?? undefined, @@ -2597,32 +1848,19 @@ export namespace NotebookExclusiveDocumentPattern { return GlobPattern.from(pattern) ?? undefined; } - export function to( - pattern: - | string - | extHostProtocol.IRelativePatternDto - | { - include: string | extHostProtocol.IRelativePatternDto; - exclude: string | extHostProtocol.IRelativePatternDto; - }, - ): - | { include: vscode.GlobPattern; exclude: vscode.GlobPattern } - | vscode.GlobPattern { + export function to(pattern: string | extHostProtocol.IRelativePatternDto | { include: string | extHostProtocol.IRelativePatternDto; exclude: string | extHostProtocol.IRelativePatternDto }): { include: vscode.GlobPattern; exclude: vscode.GlobPattern } | vscode.GlobPattern { if (isExclusivePattern(pattern)) { return { include: GlobPattern.to(pattern.include), - exclude: GlobPattern.to(pattern.exclude), + exclude: GlobPattern.to(pattern.exclude) }; } return GlobPattern.to(pattern); } - function isExclusivePattern( - obj: any, - ): obj is { include?: T; exclude?: T } { + function isExclusivePattern(obj: any): obj is { include?: T; exclude?: T } { const ep = obj as { include?: T; exclude?: T } | undefined | null; - if (!ep) { return false; } @@ -2631,101 +1869,67 @@ export namespace NotebookExclusiveDocumentPattern { } export namespace NotebookStatusBarItem { - export function from( - item: vscode.NotebookCellStatusBarItem, - commandsConverter: Command.ICommandsConverter, - disposables: DisposableStore, - ): notebooks.INotebookCellStatusBarItem { - const command = - typeof item.command === "string" - ? { title: "", command: item.command } - : item.command; - + export function from(item: vscode.NotebookCellStatusBarItem, commandsConverter: Command.ICommandsConverter, disposables: DisposableStore): notebooks.INotebookCellStatusBarItem { + const command = typeof item.command === 'string' ? { title: '', command: item.command } : item.command; return { - alignment: - item.alignment === types.NotebookCellStatusBarAlignment.Left - ? notebooks.CellStatusbarAlignment.Left - : notebooks.CellStatusbarAlignment.Right, + alignment: item.alignment === types.NotebookCellStatusBarAlignment.Left ? notebooks.CellStatusbarAlignment.Left : notebooks.CellStatusbarAlignment.Right, command: commandsConverter.toInternal(command, disposables), // TODO@roblou text: item.text, tooltip: item.tooltip, accessibilityInformation: item.accessibilityInformation, - priority: item.priority, + priority: item.priority }; } } export namespace NotebookKernelSourceAction { - export function from( - item: vscode.NotebookKernelSourceAction, - commandsConverter: Command.ICommandsConverter, - disposables: DisposableStore, - ): notebooks.INotebookKernelSourceAction { - const command = - typeof item.command === "string" - ? { title: "", command: item.command } - : item.command; + export function from(item: vscode.NotebookKernelSourceAction, commandsConverter: Command.ICommandsConverter, disposables: DisposableStore): notebooks.INotebookKernelSourceAction { + const command = typeof item.command === 'string' ? { title: '', command: item.command } : item.command; return { command: commandsConverter.toInternal(command, disposables), label: item.label, description: item.description, detail: item.detail, - documentation: item.documentation, + documentation: item.documentation }; } } export namespace NotebookDocumentContentOptions { - export function from( - options: vscode.NotebookDocumentContentOptions | undefined, - ): notebooks.TransientOptions { + export function from(options: vscode.NotebookDocumentContentOptions | undefined): notebooks.TransientOptions { return { transientOutputs: options?.transientOutputs ?? false, transientCellMetadata: options?.transientCellMetadata ?? {}, transientDocumentMetadata: options?.transientDocumentMetadata ?? {}, - cellContentMetadata: options?.cellContentMetadata ?? {}, + cellContentMetadata: options?.cellContentMetadata ?? {} }; } } export namespace NotebookRendererScript { - export function from(preload: vscode.NotebookRendererScript): { - uri: UriComponents; - provides: readonly string[]; - } { + export function from(preload: vscode.NotebookRendererScript): { uri: UriComponents; provides: readonly string[] } { return { uri: preload.uri, - provides: preload.provides, + provides: preload.provides }; } - export function to(preload: { - uri: UriComponents; - provides: readonly string[]; - }): vscode.NotebookRendererScript { - return new types.NotebookRendererScript( - URI.revive(preload.uri), - preload.provides, - ); + export function to(preload: { uri: UriComponents; provides: readonly string[] }): vscode.NotebookRendererScript { + return new types.NotebookRendererScript(URI.revive(preload.uri), preload.provides); } } export namespace TestMessage { - export function from( - message: vscode.TestMessage, - ): ITestErrorMessage.Serialized { + export function from(message: vscode.TestMessage): ITestErrorMessage.Serialized { return { - message: MarkdownString.fromStrict(message.message) || "", + message: MarkdownString.fromStrict(message.message) || '', type: TestMessageType.Error, expected: message.expectedOutput, actual: message.actualOutput, contextValue: message.contextValue, - location: message.location && { - range: Range.from(message.location.range), - uri: message.location.uri, - }, - stackTrace: message.stackTrace?.map((s) => ({ + location: message.location && ({ range: Range.from(message.location.range), uri: message.location.uri }), + stackTrace: message.stackTrace?.map(s => ({ label: s.label, position: s.position && Position.from(s.position), uri: s.uri && URI.revive(s.uri).toJSON(), @@ -2734,18 +1938,11 @@ export namespace TestMessage { } export function to(item: ITestErrorMessage.Serialized): vscode.TestMessage { - const message = new types.TestMessage( - typeof item.message === "string" - ? item.message - : MarkdownString.to(item.message), - ); + const message = new types.TestMessage(typeof item.message === 'string' ? item.message : MarkdownString.to(item.message)); message.actualOutput = item.actual; message.expectedOutput = item.expected; message.contextValue = item.contextValue; - message.location = item.location - ? location.to(item.location) - : undefined; - + message.location = item.location ? location.to(item.location) : undefined; return message; } } @@ -2756,24 +1953,43 @@ export namespace TestTag { export const denamespace = denamespaceTestTag; } +export namespace TestRunProfile { + export function from(item: types.TestRunProfileBase): ITestRunProfileReference { + return { + controllerId: item.controllerId, + profileId: item.profileId, + group: TestRunProfileKind.from(item.kind), + }; + } +} + +export namespace TestRunProfileKind { + const profileGroupToBitset: { [K in vscode.TestRunProfileKind]: TestRunProfileBitset } = { + [types.TestRunProfileKind.Coverage]: TestRunProfileBitset.Coverage, + [types.TestRunProfileKind.Debug]: TestRunProfileBitset.Debug, + [types.TestRunProfileKind.Run]: TestRunProfileBitset.Run, + }; + + export function from(kind: types.TestRunProfileKind): TestRunProfileBitset { + return profileGroupToBitset.hasOwnProperty(kind) ? profileGroupToBitset[kind] : TestRunProfileBitset.Run; + } +} + export namespace TestItem { export type Raw = vscode.TestItem; export function from(item: vscode.TestItem): ITestItem { const ctrlId = getPrivateApiFor(item).controllerId; - return { extId: TestId.fromExtHostTestItem(item, ctrlId).toString(), label: item.label, uri: URI.revive(item.uri), busy: item.busy, - tags: item.tags.map((t) => TestTag.namespace(ctrlId, t.id)), + tags: item.tags.map(t => TestTag.namespace(ctrlId, t.id)), range: editorRange.Range.lift(Range.from(item.range)), description: item.description || null, sortText: item.sortText || null, - error: item.error - ? MarkdownString.fromStrict(item.error) || null - : null, + error: item.error ? (MarkdownString.fromStrict(item.error) || null) : null, }; } @@ -2784,18 +2000,17 @@ export namespace TestItem { id: TestId.fromString(item.extId).localId, label: item.label, uri: URI.revive(item.uri), - tags: (item.tags || []).map((t) => { + tags: (item.tags || []).map(t => { const { tagId } = TestTag.denamespace(t); - return new types.TestTag(tagId); }), children: { - add: () => {}, - delete: () => {}, - forEach: () => {}, - *[Symbol.iterator]() {}, + add: () => { }, + delete: () => { }, + forEach: () => { }, + *[Symbol.iterator]() { }, get: () => undefined, - replace: () => {}, + replace: () => { }, size: 0, }, range: Range.to(item.range || undefined), @@ -2818,36 +2033,28 @@ export namespace TestTag { } export namespace TestResults { - const convertTestResultItem = ( - node: IPrefixTreeNode, - parent?: vscode.TestResultSnapshot, - ): vscode.TestResultSnapshot | undefined => { + const convertTestResultItem = (node: IPrefixTreeNode, parent?: vscode.TestResultSnapshot): vscode.TestResultSnapshot | undefined => { const item = node.value; - if (!item) { return undefined; // should be unreachable } - const snapshot: vscode.TestResultSnapshot = { + const snapshot: vscode.TestResultSnapshot = ({ ...TestItem.toPlain(item.item), parent, - taskStates: item.tasks.map((t) => ({ + taskStates: item.tasks.map(t => ({ state: t.state as number as types.TestResultState, duration: t.duration, messages: t.messages - .filter( - (m): m is ITestErrorMessage.Serialized => - m.type === TestMessageType.Error, - ) + .filter((m): m is ITestErrorMessage.Serialized => m.type === TestMessageType.Error) .map(TestMessage.to), })), children: [], - }; + }); if (node.children) { for (const child of node.children.values()) { const c = convertTestResultItem(child, snapshot); - if (c) { snapshot.children.push(c); } @@ -2857,20 +2064,15 @@ export namespace TestResults { return snapshot; }; - export function to( - serialized: ISerializedTestResults, - ): vscode.TestRunResult { + export function to(serialized: ISerializedTestResults): vscode.TestRunResult { const tree = new WellDefinedPrefixTree(); - for (const item of serialized.items) { tree.insert(TestId.fromString(item.item.extId).path, item); } // Get the first node with a value in each subtree of IDs. const queue = [tree.nodes]; - const roots: IPrefixTreeNode[] = []; - while (queue.length) { for (const node of queue.pop()!) { if (node.value) { @@ -2883,71 +2085,47 @@ export namespace TestResults { return { completedAt: serialized.completedAt, - results: roots - .map((r) => convertTestResultItem(r)) - .filter(isDefined), + results: roots.map(r => convertTestResultItem(r)).filter(isDefined), }; } } export namespace TestCoverage { - function fromCoverageCount( - count: vscode.TestCoverageCount, - ): ICoverageCount { + function fromCoverageCount(count: vscode.TestCoverageCount): ICoverageCount { return { covered: count.covered, total: count.total }; } function fromLocation(location: vscode.Range | vscode.Position) { - return "line" in location - ? Position.from(location) - : Range.from(location); + return 'line' in location ? Position.from(location) : Range.from(location); } - function toLocation( - location: IPosition | editorRange.IRange, - ): types.Position | types.Range; - - function toLocation( - location: IPosition | editorRange.IRange | undefined, - ): types.Position | types.Range | undefined; - - function toLocation( - location: IPosition | editorRange.IRange | undefined, - ): types.Position | types.Range | undefined { - if (!location) { - return undefined; - } - return "endLineNumber" in location - ? Range.to(location) - : Position.to(location); + function toLocation(location: IPosition | editorRange.IRange): types.Position | types.Range; + function toLocation(location: IPosition | editorRange.IRange | undefined): types.Position | types.Range | undefined; + function toLocation(location: IPosition | editorRange.IRange | undefined): types.Position | types.Range | undefined { + if (!location) { return undefined; } + return 'endLineNumber' in location ? Range.to(location) : Position.to(location); } - export function to( - serialized: CoverageDetails.Serialized, - ): vscode.FileCoverageDetail { + export function to(serialized: CoverageDetails.Serialized): vscode.FileCoverageDetail { if (serialized.type === DetailType.Statement) { const branches: vscode.BranchCoverage[] = []; - if (serialized.branches) { for (const branch of serialized.branches) { branches.push({ executed: branch.count, location: toLocation(branch.location), - label: branch.label, + label: branch.label }); } } return new types.StatementCoverage( serialized.count, toLocation(serialized.location), - serialized.branches?.map( - (b) => - new types.BranchCoverage( - b.count, - toLocation(b.location)!, - b.label, - ), - ), + serialized.branches?.map(b => new types.BranchCoverage( + b.count, + toLocation(b.location)!, + b.label, + )) ); } else { return new types.DeclarationCoverage( @@ -2958,24 +2136,18 @@ export namespace TestCoverage { } } - export function fromDetails( - coverage: vscode.FileCoverageDetail, - ): CoverageDetails.Serialized { - if (typeof coverage.executed === "number" && coverage.executed < 0) { + export function fromDetails(coverage: vscode.FileCoverageDetail): CoverageDetails.Serialized { + if (typeof coverage.executed === 'number' && coverage.executed < 0) { throw new Error(`Invalid coverage count ${coverage.executed}`); } - if ("branches" in coverage) { + if ('branches' in coverage) { return { count: coverage.executed, location: fromLocation(coverage.location), type: DetailType.Statement, branches: coverage.branches.length - ? coverage.branches.map((b) => ({ - count: b.executed, - location: b.location && fromLocation(b.location), - label: b.label, - })) + ? coverage.branches.map(b => ({ count: b.executed, location: b.location && fromLocation(b.location), label: b.label })) : undefined, }; } else { @@ -2988,11 +2160,7 @@ export namespace TestCoverage { } } - export function fromFile( - controllerId: string, - id: string, - coverage: vscode.FileCoverage, - ): IFileCoverage.Serialized { + export function fromFile(controllerId: string, id: string, coverage: vscode.FileCoverage): IFileCoverage.Serialized { types.validateTestCoverageCount(coverage.statementCoverage); types.validateTestCoverageCount(coverage.branchCoverage); types.validateTestCoverageCount(coverage.declarationCoverage); @@ -3001,30 +2169,17 @@ export namespace TestCoverage { id, uri: coverage.uri, statement: fromCoverageCount(coverage.statementCoverage), - branch: - coverage.branchCoverage && - fromCoverageCount(coverage.branchCoverage), - declaration: - coverage.declarationCoverage && - fromCoverageCount(coverage.declarationCoverage), - testIds: - coverage instanceof types.FileCoverage && - coverage.fromTests.length - ? coverage.fromTests.map((t) => - TestId.fromExtHostTestItem( - t, - controllerId, - ).toString(), - ) - : undefined, + branch: coverage.branchCoverage && fromCoverageCount(coverage.branchCoverage), + declaration: coverage.declarationCoverage && fromCoverageCount(coverage.declarationCoverage), + testIds: coverage instanceof types.FileCoverage && coverage.includesTests.length ? + coverage.includesTests.map(t => TestId.fromExtHostTestItem(t, controllerId).toString()) : undefined, }; } } export namespace CodeActionTriggerKind { - export function to( - value: languages.CodeActionTriggerType, - ): types.CodeActionTriggerKind { + + export function to(value: languages.CodeActionTriggerType): types.CodeActionTriggerKind { switch (value) { case languages.CodeActionTriggerType.Invoke: return types.CodeActionTriggerKind.Invoke; @@ -3036,16 +2191,15 @@ export namespace CodeActionTriggerKind { } export namespace TypeHierarchyItem { - export function to( - item: extHostProtocol.ITypeHierarchyItemDto, - ): types.TypeHierarchyItem { + + export function to(item: extHostProtocol.ITypeHierarchyItemDto): types.TypeHierarchyItem { const result = new types.TypeHierarchyItem( SymbolKind.to(item.kind), item.name, - item.detail || "", + item.detail || '', URI.revive(item.uri), Range.to(item.range), - Range.to(item.selectionRange), + Range.to(item.selectionRange) ); result._sessionId = item._sessionId; @@ -3054,16 +2208,13 @@ export namespace TypeHierarchyItem { return result; } - export function from( - item: vscode.TypeHierarchyItem, - sessionId?: string, - itemId?: string, - ): extHostProtocol.ITypeHierarchyItemDto { + export function from(item: vscode.TypeHierarchyItem, sessionId?: string, itemId?: string): extHostProtocol.ITypeHierarchyItemDto { + sessionId = sessionId ?? (item)._sessionId; itemId = itemId ?? (item)._itemId; if (sessionId === undefined || itemId === undefined) { - throw new Error("invalid item"); + throw new Error('invalid item'); } return { @@ -3071,62 +2222,44 @@ export namespace TypeHierarchyItem { _itemId: itemId, kind: SymbolKind.from(item.kind), name: item.name, - detail: item.detail ?? "", + detail: item.detail ?? '', uri: item.uri, range: Range.from(item.range), selectionRange: Range.from(item.selectionRange), - tags: item.tags?.map(SymbolTag.from), + tags: item.tags?.map(SymbolTag.from) }; } } export namespace ViewBadge { - export function from( - badge: vscode.ViewBadge | undefined, - ): IViewBadge | undefined { + export function from(badge: vscode.ViewBadge | undefined): IViewBadge | undefined { if (!badge) { return undefined; } return { value: badge.value, - tooltip: badge.tooltip, + tooltip: badge.tooltip }; } } export namespace DataTransferItem { - export function to( - mime: string, - item: extHostProtocol.DataTransferItemDTO, - resolveFileData: (id: string) => Promise, - ): types.DataTransferItem { + export function to(mime: string, item: extHostProtocol.DataTransferItemDTO, resolveFileData: (id: string) => Promise): types.DataTransferItem { const file = item.fileData; - if (file) { return new types.InternalFileDataTransferItem( - new types.DataTransferFile( - file.name, - URI.revive(file.uri), - file.id, - createSingleCallFunction(() => resolveFileData(file.id)), - ), - ); + new types.DataTransferFile(file.name, URI.revive(file.uri), file.id, createSingleCallFunction(() => resolveFileData(file.id)))); } if (mime === Mimes.uriList && item.uriListData) { - return new types.InternalDataTransferItem( - reviveUriList(item.uriListData), - ); + return new types.InternalDataTransferItem(reviveUriList(item.uriListData)); } return new types.InternalDataTransferItem(item.asString); } - export async function from( - mime: string, - item: vscode.DataTransferItem | IDataTransferItem, - ): Promise { + export async function from(mime: string, item: vscode.DataTransferItem | IDataTransferItem): Promise { const stringValue = await item.asString(); if (mime === Mimes.uriList) { @@ -3138,26 +2271,19 @@ export namespace DataTransferItem { } const fileValue = item.asFile(); - return { asString: stringValue, - fileData: fileValue - ? { - name: fileValue.name, - uri: fileValue.uri, - id: - (fileValue as types.DataTransferFile)._itemId ?? - (fileValue as IDataTransferFile).id, - } - : undefined, + fileData: fileValue ? { + name: fileValue.name, + uri: fileValue.uri, + id: (fileValue as types.DataTransferFile)._itemId ?? (fileValue as IDataTransferFile).id, + } : undefined, }; } - function serializeUriList( - stringValue: string, - ): ReadonlyArray { - return UriList.split(stringValue).map((part) => { - if (part.startsWith("#")) { + function serializeUriList(stringValue: string): ReadonlyArray { + return UriList.split(stringValue).map(part => { + if (part.startsWith('#')) { return part; } @@ -3171,50 +2297,29 @@ export namespace DataTransferItem { }); } - function reviveUriList( - parts: ReadonlyArray, - ): string { - return UriList.create( - parts.map((part) => { - return typeof part === "string" ? part : URI.revive(part); - }), - ); + function reviveUriList(parts: ReadonlyArray): string { + return UriList.create(parts.map(part => { + return typeof part === 'string' ? part : URI.revive(part); + })); } } export namespace DataTransfer { - export function toDataTransfer( - value: extHostProtocol.DataTransferDTO, - resolveFileData: (itemId: string) => Promise, - ): types.DataTransfer { + export function toDataTransfer(value: extHostProtocol.DataTransferDTO, resolveFileData: (itemId: string) => Promise): types.DataTransfer { const init = value.items.map(([type, item]) => { - return [ - type, - DataTransferItem.to(type, item, resolveFileData), - ] as const; + return [type, DataTransferItem.to(type, item, resolveFileData)] as const; }); - return new types.DataTransfer(init); } - export async function from( - dataTransfer: Iterable< - readonly [string, vscode.DataTransferItem | IDataTransferItem] - >, - ): Promise { + export async function from(dataTransfer: Iterable): Promise { const newDTO: extHostProtocol.DataTransferDTO = { items: [] }; const promises: Promise[] = []; - for (const [mime, value] of dataTransfer) { - promises.push( - (async () => { - newDTO.items.push([ - mime, - await DataTransferItem.from(mime, value), - ]); - })(), - ); + promises.push((async () => { + newDTO.items.push([mime, await DataTransferItem.from(mime, value)]); + })()); } await Promise.all(promises); @@ -3224,16 +2329,13 @@ export namespace DataTransfer { } export namespace ChatFollowup { - export function from( - followup: vscode.ChatFollowup, - request: IChatAgentRequest | undefined, - ): IChatFollowup { + export function from(followup: vscode.ChatFollowup, request: IChatAgentRequest | undefined): IChatFollowup { return { - kind: "reply", - agentId: followup.participant ?? request?.agentId ?? "", + kind: 'reply', + agentId: followup.participant ?? request?.agentId ?? '', subCommand: followup.command ?? request?.command, message: followup.prompt, - title: followup.label, + title: followup.label }; } @@ -3248,243 +2350,171 @@ export namespace ChatFollowup { } export namespace LanguageModelChatMessageRole { - export function to( - role: chatProvider.ChatMessageRole, - ): vscode.LanguageModelChatMessageRole { + export function to(role: chatProvider.ChatMessageRole): vscode.LanguageModelChatMessageRole { switch (role) { - case chatProvider.ChatMessageRole.System: - return types.LanguageModelChatMessageRole.System; - - case chatProvider.ChatMessageRole.User: - return types.LanguageModelChatMessageRole.User; - - case chatProvider.ChatMessageRole.Assistant: - return types.LanguageModelChatMessageRole.Assistant; + case chatProvider.ChatMessageRole.System: return types.LanguageModelChatMessageRole.System; + case chatProvider.ChatMessageRole.User: return types.LanguageModelChatMessageRole.User; + case chatProvider.ChatMessageRole.Assistant: return types.LanguageModelChatMessageRole.Assistant; } } - export function from( - role: vscode.LanguageModelChatMessageRole, - ): chatProvider.ChatMessageRole { + export function from(role: vscode.LanguageModelChatMessageRole): chatProvider.ChatMessageRole { switch (role) { - case types.LanguageModelChatMessageRole.System: - return chatProvider.ChatMessageRole.System; - - case types.LanguageModelChatMessageRole.User: - return chatProvider.ChatMessageRole.User; - - case types.LanguageModelChatMessageRole.Assistant: - return chatProvider.ChatMessageRole.Assistant; + case types.LanguageModelChatMessageRole.System: return chatProvider.ChatMessageRole.System; + case types.LanguageModelChatMessageRole.User: return chatProvider.ChatMessageRole.User; + case types.LanguageModelChatMessageRole.Assistant: return chatProvider.ChatMessageRole.Assistant; } return chatProvider.ChatMessageRole.User; } } export namespace LanguageModelChatMessage { - export function to( - message: chatProvider.IChatMessage, - ): vscode.LanguageModelChatMessage { - const content = message.content.map((c) => { - if (c.type === "text") { + + export function to(message: chatProvider.IChatMessage): vscode.LanguageModelChatMessage { + const content = message.content.map(c => { + if (c.type === 'text') { return new LanguageModelTextPart(c.value); - } else if (c.type === "tool_result") { - const content: ( - | LanguageModelTextPart - | LanguageModelPromptTsxPart - )[] = c.value.map((part) => { - if (part.type === "text") { + } else if (c.type === 'tool_result') { + const content: (LanguageModelTextPart | LanguageModelPromptTsxPart)[] = c.value.map(part => { + if (part.type === 'text') { return new types.LanguageModelTextPart(part.value); } else { return new types.LanguageModelPromptTsxPart(part.value); } }); - - return new types.LanguageModelToolResultPart( - c.toolCallId, - content, - c.isError, - ); + return new types.LanguageModelToolResultPart(c.toolCallId, content, c.isError); } else { - return new types.LanguageModelToolCallPart( - c.toolCallId, - c.name, - c.parameters, - ); + return new types.LanguageModelToolCallPart(c.toolCallId, c.name, c.parameters); } }); - const role = LanguageModelChatMessageRole.to(message.role); - - const result = new types.LanguageModelChatMessage( - role, - content, - message.name, - ); - + const result = new types.LanguageModelChatMessage(role, content, message.name); return result; } - export function from( - message: vscode.LanguageModelChatMessage, - ): chatProvider.IChatMessage { - const role = LanguageModelChatMessageRole.from(message.role); + export function from(message: vscode.LanguageModelChatMessage): chatProvider.IChatMessage { + const role = LanguageModelChatMessageRole.from(message.role); const name = message.name; let messageContent = message.content; - - if (typeof messageContent === "string") { + if (typeof messageContent === 'string') { messageContent = [new types.LanguageModelTextPart(messageContent)]; } - const content = messageContent.map( - (c): chatProvider.IChatMessagePart => { - if (c instanceof types.LanguageModelToolResultPart) { - return { - type: "tool_result", - toolCallId: c.callId, - value: coalesce( - c.content.map((part) => { - if ( - part instanceof types.LanguageModelTextPart - ) { - return { - type: "text", - value: part.value, - } satisfies IChatResponseTextPart; - } else if ( - part instanceof - types.LanguageModelPromptTsxPart - ) { - return { - type: "prompt_tsx", - value: part.value, - } satisfies IChatResponsePromptTsxPart; - } else { - // Strip unknown parts - return undefined; - } - }), - ), - isError: c.isError, - }; - } else if (c instanceof types.LanguageModelToolCallPart) { - return { - type: "tool_use", - toolCallId: c.callId, - name: c.name, - parameters: c.input, - }; - } else if (c instanceof types.LanguageModelTextPart) { - return { - type: "text", - value: c.value, - }; - } else { - if (typeof c !== "string") { - throw new Error("Unexpected chat message content type"); - } - - return { - type: "text", - value: c, - }; + const content = messageContent.map((c): chatProvider.IChatMessagePart => { + if (c instanceof types.LanguageModelToolResultPart) { + return { + type: 'tool_result', + toolCallId: c.callId, + value: coalesce(c.content.map(part => { + if (part instanceof types.LanguageModelTextPart) { + return { + type: 'text', + value: part.value + } satisfies IChatResponseTextPart; + } else if (part instanceof types.LanguageModelPromptTsxPart) { + return { + type: 'prompt_tsx', + value: part.value, + } satisfies IChatResponsePromptTsxPart; + } else { + // Strip unknown parts + return undefined; + } + })), + isError: c.isError + }; + } else if (c instanceof types.LanguageModelToolCallPart) { + return { + type: 'tool_use', + toolCallId: c.callId, + name: c.name, + parameters: c.input + }; + } else if (c instanceof types.LanguageModelTextPart) { + return { + type: 'text', + value: c.value + }; + } else { + if (typeof c !== 'string') { + throw new Error('Unexpected chat message content type'); } - }, - ); + + return { + type: 'text', + value: c + }; + } + }); return { role, name, - content, + content }; } } export namespace ChatResponseMarkdownPart { - export function from( - part: vscode.ChatResponseMarkdownPart, - ): Dto { + export function from(part: vscode.ChatResponseMarkdownPart): Dto { return { - kind: "markdownContent", - content: MarkdownString.from(part.value), + kind: 'markdownContent', + content: MarkdownString.from(part.value) }; } - export function to( - part: Dto, - ): vscode.ChatResponseMarkdownPart { - return new types.ChatResponseMarkdownPart( - MarkdownString.to(part.content), - ); + export function to(part: Dto): vscode.ChatResponseMarkdownPart { + return new types.ChatResponseMarkdownPart(MarkdownString.to(part.content)); } } export namespace ChatResponseCodeblockUriPart { - export function from( - part: vscode.ChatResponseCodeblockUriPart, - ): Dto { + export function from(part: vscode.ChatResponseCodeblockUriPart): Dto { return { - kind: "codeblockUri", + kind: 'codeblockUri', uri: part.value, }; } - export function to( - part: Dto, - ): vscode.ChatResponseCodeblockUriPart { + export function to(part: Dto): vscode.ChatResponseCodeblockUriPart { return new types.ChatResponseCodeblockUriPart(URI.revive(part.uri)); } } export namespace ChatResponseMarkdownWithVulnerabilitiesPart { - export function from( - part: vscode.ChatResponseMarkdownWithVulnerabilitiesPart, - ): Dto { + export function from(part: vscode.ChatResponseMarkdownWithVulnerabilitiesPart): Dto { return { - kind: "markdownVuln", + kind: 'markdownVuln', content: MarkdownString.from(part.value), vulnerabilities: part.vulnerabilities, }; } - export function to( - part: Dto, - ): vscode.ChatResponseMarkdownWithVulnerabilitiesPart { - return new types.ChatResponseMarkdownWithVulnerabilitiesPart( - MarkdownString.to(part.content), - part.vulnerabilities, - ); + export function to(part: Dto): vscode.ChatResponseMarkdownWithVulnerabilitiesPart { + return new types.ChatResponseMarkdownWithVulnerabilitiesPart(MarkdownString.to(part.content), part.vulnerabilities); } } export namespace ChatResponseDetectedParticipantPart { - export function from( - part: vscode.ChatResponseDetectedParticipantPart, - ): Dto { + export function from(part: vscode.ChatResponseDetectedParticipantPart): Dto { return { - kind: "agentDetection", + kind: 'agentDetection', agentId: part.participant, command: part.command, }; } - export function to( - part: Dto, - ): vscode.ChatResponseDetectedParticipantPart { - return new types.ChatResponseDetectedParticipantPart( - part.agentId, - part.command, - ); + export function to(part: Dto): vscode.ChatResponseDetectedParticipantPart { + return new types.ChatResponseDetectedParticipantPart(part.agentId, part.command); } } export namespace ChatResponseConfirmationPart { - export function from( - part: vscode.ChatResponseConfirmationPart, - ): Dto { + export function from(part: vscode.ChatResponseConfirmationPart): Dto { return { - kind: "confirmation", + kind: 'confirmation', title: part.title, message: part.message, data: part.data, - buttons: part.buttons, + buttons: part.buttons }; } } @@ -3492,153 +2522,113 @@ export namespace ChatResponseConfirmationPart { export namespace ChatResponseFilesPart { export function from(part: vscode.ChatResponseFileTreePart): IChatTreeData { const { value, baseUri } = part; - - function convert( - items: vscode.ChatResponseFileTree[], - baseUri: URI, - ): extHostProtocol.IChatResponseProgressFileTreeData[] { - return items.map((item) => { + function convert(items: vscode.ChatResponseFileTree[], baseUri: URI): extHostProtocol.IChatResponseProgressFileTreeData[] { + return items.map(item => { const myUri = URI.joinPath(baseUri, item.name); - return { label: item.name, uri: myUri, - children: item.children && convert(item.children, myUri), + children: item.children && convert(item.children, myUri) }; }); } return { - kind: "treeData", + kind: 'treeData', treeData: { label: basename(baseUri), uri: baseUri, - children: convert(value, baseUri), - }, + children: convert(value, baseUri) + } }; } - export function to( - part: Dto, - ): vscode.ChatResponseFileTreePart { - const treeData = - revive( - part.treeData, - ); - - function convert( - items: extHostProtocol.IChatResponseProgressFileTreeData[], - ): vscode.ChatResponseFileTree[] { - return items.map((item) => { + export function to(part: Dto): vscode.ChatResponseFileTreePart { + const treeData = revive(part.treeData); + function convert(items: extHostProtocol.IChatResponseProgressFileTreeData[]): vscode.ChatResponseFileTree[] { + return items.map(item => { return { name: item.label, - children: item.children && convert(item.children), + children: item.children && convert(item.children) }; }); } const baseUri = treeData.uri; - const items = treeData.children ? convert(treeData.children) : []; - return new types.ChatResponseFileTreePart(items, baseUri); } } export namespace ChatResponseAnchorPart { - export function from( - part: vscode.ChatResponseAnchorPart, - ): Dto { + export function from(part: vscode.ChatResponseAnchorPart): Dto { // Work around type-narrowing confusion between vscode.Uri and URI const isUri = (thing: unknown): thing is vscode.Uri => URI.isUri(thing); - - const isSymbolInformation = ( - thing: object, - ): thing is vscode.SymbolInformation => "name" in thing; + const isSymbolInformation = (thing: object): thing is vscode.SymbolInformation => 'name' in thing; return { - kind: "inlineReference", + kind: 'inlineReference', name: part.title, inlineReference: isUri(part.value) ? part.value : isSymbolInformation(part.value) ? WorkspaceSymbol.from(part.value) - : Location.from(part.value), + : Location.from(part.value) }; } - export function to( - part: Dto, - ): vscode.ChatResponseAnchorPart { + export function to(part: Dto): vscode.ChatResponseAnchorPart { const value = revive(part); - return new types.ChatResponseAnchorPart( URI.isUri(value.inlineReference) ? value.inlineReference - : "location" in value.inlineReference - ? (WorkspaceSymbol.to( - value.inlineReference, - ) as vscode.SymbolInformation) + : 'location' in value.inlineReference + ? WorkspaceSymbol.to(value.inlineReference) as vscode.SymbolInformation : Location.to(value.inlineReference), - part.name, + part.name ); } } export namespace ChatResponseProgressPart { - export function from( - part: vscode.ChatResponseProgressPart, - ): Dto { + export function from(part: vscode.ChatResponseProgressPart): Dto { return { - kind: "progressMessage", - content: MarkdownString.from(part.value), + kind: 'progressMessage', + content: MarkdownString.from(part.value) }; } - export function to( - part: Dto, - ): vscode.ChatResponseProgressPart { + export function to(part: Dto): vscode.ChatResponseProgressPart { return new types.ChatResponseProgressPart(part.content.value); } } export namespace ChatResponseWarningPart { - export function from( - part: vscode.ChatResponseWarningPart, - ): Dto { + export function from(part: vscode.ChatResponseWarningPart): Dto { return { - kind: "warning", - content: MarkdownString.from(part.value), + kind: 'warning', + content: MarkdownString.from(part.value) }; } - export function to( - part: Dto, - ): vscode.ChatResponseWarningPart { + export function to(part: Dto): vscode.ChatResponseWarningPart { return new types.ChatResponseWarningPart(part.content.value); } } export namespace ChatResponseMovePart { - export function from( - part: vscode.ChatResponseMovePart, - ): Dto { + export function from(part: vscode.ChatResponseMovePart): Dto { return { - kind: "move", + kind: 'move', uri: part.uri, range: Range.from(part.range), }; } - export function to( - part: Dto, - ): vscode.ChatResponseMovePart { - return new types.ChatResponseMovePart( - URI.revive(part.uri), - Range.to(part.range), - ); + export function to(part: Dto): vscode.ChatResponseMovePart { + return new types.ChatResponseMovePart(URI.revive(part.uri), Range.to(part.range)); } } export namespace ChatTask { export function from(part: vscode.ChatResponseProgressPart2): IChatTaskDto { return { - kind: "progressTask", + kind: 'progressTask', content: MarkdownString.from(part.value), }; } @@ -3647,168 +2637,105 @@ export namespace ChatTask { export namespace ChatTaskResult { export function from(part: string | void): Dto { return { - kind: "progressTaskResult", - content: - typeof part === "string" - ? MarkdownString.from(part) - : undefined, + kind: 'progressTaskResult', + content: typeof part === 'string' ? MarkdownString.from(part) : undefined }; } } export namespace ChatResponseCommandButtonPart { - export function from( - part: vscode.ChatResponseCommandButtonPart, - commandsConverter: CommandsConverter, - commandDisposables: DisposableStore, - ): Dto { + export function from(part: vscode.ChatResponseCommandButtonPart, commandsConverter: CommandsConverter, commandDisposables: DisposableStore): Dto { // If the command isn't in the converter, then this session may have been restored, and the command args don't exist anymore - const command = commandsConverter.toInternal( - part.value, - commandDisposables, - ) ?? { command: part.value.command, title: part.value.title }; - + const command = commandsConverter.toInternal(part.value, commandDisposables) ?? { command: part.value.command, title: part.value.title }; return { - kind: "command", - command, + kind: 'command', + command }; } - export function to( - part: Dto, - commandsConverter: CommandsConverter, - ): vscode.ChatResponseCommandButtonPart { + export function to(part: Dto, commandsConverter: CommandsConverter): vscode.ChatResponseCommandButtonPart { // If the command isn't in the converter, then this session may have been restored, and the command args don't exist anymore - return new types.ChatResponseCommandButtonPart( - commandsConverter.fromInternal(part.command) ?? { - command: part.command.id, - title: part.command.title, - }, - ); + return new types.ChatResponseCommandButtonPart(commandsConverter.fromInternal(part.command) ?? { command: part.command.id, title: part.command.title }); } } export namespace ChatResponseTextEditPart { - export function from( - part: vscode.ChatResponseTextEditPart, - ): Dto { + export function from(part: vscode.ChatResponseTextEditPart): Dto { return { - kind: "textEdit", + kind: 'textEdit', uri: part.uri, - edits: part.edits.map((e) => TextEdit.from(e)), - done: part.isDone, + edits: part.edits.map(e => TextEdit.from(e)), + done: part.isDone }; } - export function to( - part: Dto, - ): vscode.ChatResponseTextEditPart { - const result = new types.ChatResponseTextEditPart( - URI.revive(part.uri), - part.edits.map((e) => TextEdit.to(e)), - ); + export function to(part: Dto): vscode.ChatResponseTextEditPart { + const result = new types.ChatResponseTextEditPart(URI.revive(part.uri), part.edits.map(e => TextEdit.to(e))); result.isDone = part.done; - return result; } + } export namespace ChatResponseReferencePart { - export function from( - part: types.ChatResponseReferencePart, - ): Dto { - const iconPath = ThemeIcon.isThemeIcon(part.iconPath) - ? part.iconPath - : URI.isUri(part.iconPath) - ? { light: URI.revive(part.iconPath) } - : part.iconPath && - "light" in part.iconPath && - "dark" in part.iconPath && - URI.isUri(part.iconPath.light) && - URI.isUri(part.iconPath.dark) - ? { - light: URI.revive(part.iconPath.light), - dark: URI.revive(part.iconPath.dark), - } - : undefined; + export function from(part: types.ChatResponseReferencePart): Dto { + const iconPath = ThemeIcon.isThemeIcon(part.iconPath) ? part.iconPath + : URI.isUri(part.iconPath) ? { light: URI.revive(part.iconPath) } + : (part.iconPath && 'light' in part.iconPath && 'dark' in part.iconPath && URI.isUri(part.iconPath.light) && URI.isUri(part.iconPath.dark) ? { light: URI.revive(part.iconPath.light), dark: URI.revive(part.iconPath.dark) } + : undefined); - if (typeof part.value === "object" && "variableName" in part.value) { + if (typeof part.value === 'object' && 'variableName' in part.value) { return { - kind: "reference", + kind: 'reference', reference: { variableName: part.value.variableName, - value: - URI.isUri(part.value.value) || !part.value.value - ? part.value.value - : Location.from( - part.value.value as vscode.Location, - ), + value: URI.isUri(part.value.value) || !part.value.value ? + part.value.value : + Location.from(part.value.value as vscode.Location) }, iconPath, - options: part.options, + options: part.options }; } return { - kind: "reference", - reference: - URI.isUri(part.value) || typeof part.value === "string" - ? part.value - : Location.from(part.value), + kind: 'reference', + reference: URI.isUri(part.value) || typeof part.value === 'string' ? + part.value : + Location.from(part.value), iconPath, - options: part.options, + options: part.options }; } - export function to( - part: Dto, - ): vscode.ChatResponseReferencePart { + export function to(part: Dto): vscode.ChatResponseReferencePart { const value = revive(part); - const mapValue = ( - value: URI | languages.Location, - ): vscode.Uri | vscode.Location => - URI.isUri(value) ? value : Location.to(value); + const mapValue = (value: URI | languages.Location): vscode.Uri | vscode.Location => URI.isUri(value) ? + value : + Location.to(value); return new types.ChatResponseReferencePart( - typeof value.reference === "string" - ? value.reference - : "variableName" in value.reference - ? { - variableName: value.reference.variableName, - value: - value.reference.value && - mapValue(value.reference.value), - } - : mapValue(value.reference), + typeof value.reference === 'string' ? value.reference : 'variableName' in value.reference ? { + variableName: value.reference.variableName, + value: value.reference.value && mapValue(value.reference.value) + } : + mapValue(value.reference) ) as vscode.ChatResponseReferencePart; // 'value' is extended with variableName } } export namespace ChatResponseCodeCitationPart { - export function from( - part: vscode.ChatResponseCodeCitationPart, - ): Dto { + export function from(part: vscode.ChatResponseCodeCitationPart): Dto { return { - kind: "codeCitation", + kind: 'codeCitation', value: part.value, license: part.license, - snippet: part.snippet, + snippet: part.snippet }; } } export namespace ChatResponsePart { - export function from( - part: - | vscode.ChatResponsePart - | vscode.ChatResponseTextEditPart - | vscode.ChatResponseMarkdownWithVulnerabilitiesPart - | vscode.ChatResponseDetectedParticipantPart - | vscode.ChatResponseWarningPart - | vscode.ChatResponseConfirmationPart - | vscode.ChatResponseReferencePart2 - | vscode.ChatResponseMovePart, - commandsConverter: CommandsConverter, - commandDisposables: DisposableStore, - ): extHostProtocol.IChatProgressDto { + + export function from(part: vscode.ChatResponsePart | vscode.ChatResponseTextEditPart | vscode.ChatResponseMarkdownWithVulnerabilitiesPart | vscode.ChatResponseDetectedParticipantPart | vscode.ChatResponseWarningPart | vscode.ChatResponseConfirmationPart | vscode.ChatResponseReferencePart2 | vscode.ChatResponseMovePart, commandsConverter: CommandsConverter, commandDisposables: DisposableStore): extHostProtocol.IChatProgressDto { if (part instanceof types.ChatResponseMarkdownPart) { return ChatResponseMarkdownPart.from(part); } else if (part instanceof types.ChatResponseAnchorPart) { @@ -3820,16 +2747,10 @@ export namespace ChatResponsePart { } else if (part instanceof types.ChatResponseFileTreePart) { return ChatResponseFilesPart.from(part); } else if (part instanceof types.ChatResponseCommandButtonPart) { - return ChatResponseCommandButtonPart.from( - part, - commandsConverter, - commandDisposables, - ); + return ChatResponseCommandButtonPart.from(part, commandsConverter, commandDisposables); } else if (part instanceof types.ChatResponseTextEditPart) { return ChatResponseTextEditPart.from(part); - } else if ( - part instanceof types.ChatResponseMarkdownWithVulnerabilitiesPart - ) { + } else if (part instanceof types.ChatResponseMarkdownWithVulnerabilitiesPart) { return ChatResponseMarkdownWithVulnerabilitiesPart.from(part); } else if (part instanceof types.ChatResponseCodeblockUriPart) { return ChatResponseCodeblockUriPart.from(part); @@ -3846,56 +2767,31 @@ export namespace ChatResponsePart { } return { - kind: "markdownContent", - content: MarkdownString.from(""), + kind: 'markdownContent', + content: MarkdownString.from('') }; } - export function to( - part: extHostProtocol.IChatProgressDto, - commandsConverter: CommandsConverter, - ): vscode.ChatResponsePart | undefined { + export function to(part: extHostProtocol.IChatProgressDto, commandsConverter: CommandsConverter): vscode.ChatResponsePart | undefined { switch (part.kind) { - case "reference": - return ChatResponseReferencePart.to(part); - - case "markdownContent": - case "inlineReference": - case "progressMessage": - case "treeData": - case "command": + case 'reference': return ChatResponseReferencePart.to(part); + case 'markdownContent': + case 'inlineReference': + case 'progressMessage': + case 'treeData': + case 'command': return toContent(part, commandsConverter); } return undefined; } - export function toContent( - part: extHostProtocol.IChatContentProgressDto, - commandsConverter: CommandsConverter, - ): - | vscode.ChatResponseMarkdownPart - | vscode.ChatResponseFileTreePart - | vscode.ChatResponseAnchorPart - | vscode.ChatResponseCommandButtonPart - | undefined { + export function toContent(part: extHostProtocol.IChatContentProgressDto, commandsConverter: CommandsConverter): vscode.ChatResponseMarkdownPart | vscode.ChatResponseFileTreePart | vscode.ChatResponseAnchorPart | vscode.ChatResponseCommandButtonPart | undefined { switch (part.kind) { - case "markdownContent": - return ChatResponseMarkdownPart.to(part); - - case "inlineReference": - return ChatResponseAnchorPart.to(part); - - case "progressMessage": - return undefined; - - case "treeData": - return ChatResponseFilesPart.to(part); - - case "command": - return ChatResponseCommandButtonPart.to( - part, - commandsConverter, - ); + case 'markdownContent': return ChatResponseMarkdownPart.to(part); + case 'inlineReference': return ChatResponseAnchorPart.to(part); + case 'progressMessage': return undefined; + case 'treeData': return ChatResponseFilesPart.to(part); + case 'command': return ChatResponseCommandButtonPart.to(part, commandsConverter); } return undefined; @@ -3903,22 +2799,9 @@ export namespace ChatResponsePart { } export namespace ChatAgentRequest { - export function to( - request: IChatAgentRequest, - location2: - | vscode.ChatRequestEditorData - | vscode.ChatRequestNotebookData - | undefined, - model: vscode.LanguageModelChat, - ): vscode.ChatRequest { - const toolReferences = request.variables.variables.filter( - (v) => v.isTool, - ); - - const variableReferences = request.variables.variables.filter( - (v) => !v.isTool, - ); - + export function to(request: IChatAgentRequest, location2: vscode.ChatRequestEditorData | vscode.ChatRequestNotebookData | undefined, model: vscode.LanguageModelChat): vscode.ChatRequest { + const toolReferences = request.variables.variables.filter(v => v.isTool); + const variableReferences = request.variables.variables.filter(v => !v.isTool); return { prompt: request.message, command: request.command, @@ -3926,17 +2809,13 @@ export namespace ChatAgentRequest { enableCommandDetection: request.enableCommandDetection ?? true, isParticipantDetected: request.isParticipantDetected ?? false, references: variableReferences.map(ChatPromptReference.to), - toolReferences: toolReferences.map( - ChatLanguageModelToolReference.to, - ), + toolReferences: toolReferences.map(ChatLanguageModelToolReference.to), location: ChatLocation.to(request.location), acceptedConfirmationData: request.acceptedConfirmationData, rejectedConfirmationData: request.rejectedConfirmationData, location2, - toolInvocationToken: Object.freeze({ - sessionId: request.sessionId, - }) as never, - model, + toolInvocationToken: Object.freeze({ sessionId: request.sessionId }) as never, + model }; } } @@ -3945,7 +2824,7 @@ export namespace ChatRequestDraft { export function to(request: IChatRequestDraft): vscode.ChatRequestDraft { return { prompt: request.prompt, - files: request.files.map((uri) => URI.revive(uri)), + files: request.files.map((uri) => URI.revive(uri)) }; } } @@ -3953,108 +2832,60 @@ export namespace ChatRequestDraft { export namespace ChatLocation { export function to(loc: ChatAgentLocation): types.ChatLocation { switch (loc) { - case ChatAgentLocation.Notebook: - return types.ChatLocation.Notebook; - - case ChatAgentLocation.Terminal: - return types.ChatLocation.Terminal; - - case ChatAgentLocation.Panel: - return types.ChatLocation.Panel; - - case ChatAgentLocation.Editor: - return types.ChatLocation.Editor; - - case ChatAgentLocation.EditingSession: - return types.ChatLocation.EditingSession; + case ChatAgentLocation.Notebook: return types.ChatLocation.Notebook; + case ChatAgentLocation.Terminal: return types.ChatLocation.Terminal; + case ChatAgentLocation.Panel: return types.ChatLocation.Panel; + case ChatAgentLocation.Editor: return types.ChatLocation.Editor; + case ChatAgentLocation.EditingSession: return types.ChatLocation.EditingSession; } } export function from(loc: types.ChatLocation): ChatAgentLocation { switch (loc) { - case types.ChatLocation.Notebook: - return ChatAgentLocation.Notebook; - - case types.ChatLocation.Terminal: - return ChatAgentLocation.Terminal; - - case types.ChatLocation.Panel: - return ChatAgentLocation.Panel; - - case types.ChatLocation.Editor: - return ChatAgentLocation.Editor; - - case types.ChatLocation.EditingSession: - return ChatAgentLocation.EditingSession; + case types.ChatLocation.Notebook: return ChatAgentLocation.Notebook; + case types.ChatLocation.Terminal: return ChatAgentLocation.Terminal; + case types.ChatLocation.Panel: return ChatAgentLocation.Panel; + case types.ChatLocation.Editor: return ChatAgentLocation.Editor; + case types.ChatLocation.EditingSession: return ChatAgentLocation.EditingSession; } } } export namespace ChatPromptReference { - export function to( - variable: IChatRequestVariableEntry, - ): vscode.ChatPromptReference { + export function to(variable: IChatRequestVariableEntry): vscode.ChatPromptReference { const value = variable.value; - if (!value) { - throw new Error("Invalid value reference"); + throw new Error('Invalid value reference'); } return { id: variable.id, name: variable.name, - range: variable.range && [ - variable.range.start, - variable.range.endExclusive, - ], - value: isUriComponents(value) - ? URI.revive(value) - : value && - typeof value === "object" && - "uri" in value && - "range" in value && - isUriComponents(value.uri) - ? Location.to(revive(value)) - : variable.isImage - ? new types.ChatReferenceBinaryData( - variable.mimeType ?? "image/png", - () => - Promise.resolve( - new Uint8Array(Object.values(value)), - ), - ) - : value, - modelDescription: variable.modelDescription, + range: variable.range && [variable.range.start, variable.range.endExclusive], + value: isUriComponents(value) ? URI.revive(value) : + value && typeof value === 'object' && 'uri' in value && 'range' in value && isUriComponents(value.uri) ? + Location.to(revive(value)) : variable.isImage ? new types.ChatReferenceBinaryData(variable.mimeType ?? 'image/png', () => Promise.resolve(new Uint8Array(Object.values(value)))) : value, + modelDescription: variable.modelDescription }; } } export namespace ChatLanguageModelToolReference { - export function to( - variable: IChatRequestVariableEntry, - ): vscode.ChatLanguageModelToolReference { + export function to(variable: IChatRequestVariableEntry): vscode.ChatLanguageModelToolReference { const value = variable.value; - if (value) { - throw new Error("Invalid tool reference"); + throw new Error('Invalid tool reference'); } return { name: variable.id, - range: variable.range && [ - variable.range.start, - variable.range.endExclusive, - ], + range: variable.range && [variable.range.start, variable.range.endExclusive], }; } } export namespace ChatAgentCompletionItem { - export function from( - item: vscode.ChatCompletionItem, - commandsConverter: CommandsConverter, - disposables: DisposableStore, - ): extHostProtocol.IChatAgentCompletionItem { + export function from(item: vscode.ChatCompletionItem, commandsConverter: CommandsConverter, disposables: DisposableStore): extHostProtocol.IChatAgentCompletionItem { return { id: item.id, label: item.label, @@ -4085,12 +2916,10 @@ export namespace ChatAgentResult { }; } - function reviveMetadata(metadata: IChatAgentResult["metadata"]) { - return cloneAndChange(metadata, (value) => { + function reviveMetadata(metadata: IChatAgentResult['metadata']) { + return cloneAndChange(metadata, value => { if (value.$mid === MarshalledId.LanguageModelToolResult) { - return new types.LanguageModelToolResult( - cloneAndChange(value.content, reviveMetadata), - ); + return new types.LanguageModelToolResult(cloneAndChange(value.content, reviveMetadata)); } else if (value.$mid === MarshalledId.LanguageModelTextPart) { return new types.LanguageModelTextPart(value.value); } else if (value.$mid === MarshalledId.LanguageModelPromptTsxPart) { @@ -4103,66 +2932,40 @@ export namespace ChatAgentResult { } export namespace ChatAgentUserActionEvent { - export function to( - result: IChatAgentResult, - event: IChatUserActionEvent, - commandsConverter: CommandsConverter, - ): vscode.ChatUserActionEvent | undefined { - if (event.action.kind === "vote") { + export function to(result: IChatAgentResult, event: IChatUserActionEvent, commandsConverter: CommandsConverter): vscode.ChatUserActionEvent | undefined { + if (event.action.kind === 'vote') { // Is the "feedback" type return; } const ehResult = ChatAgentResult.to(result); - - if (event.action.kind === "command") { + if (event.action.kind === 'command') { const command = event.action.commandButton.command; - const commandButton = { - command: commandsConverter.fromInternal(command) ?? { - command: command.id, - title: command.title, - }, - }; - - const commandAction: vscode.ChatCommandAction = { - kind: "command", - commandButton, + command: commandsConverter.fromInternal(command) ?? { command: command.id, title: command.title }, }; - + const commandAction: vscode.ChatCommandAction = { kind: 'command', commandButton }; return { action: commandAction, result: ehResult }; - } else if (event.action.kind === "followUp") { - const followupAction: vscode.ChatFollowupAction = { - kind: "followUp", - followup: ChatFollowup.to(event.action.followup), - }; - + } else if (event.action.kind === 'followUp') { + const followupAction: vscode.ChatFollowupAction = { kind: 'followUp', followup: ChatFollowup.to(event.action.followup) }; return { action: followupAction, result: ehResult }; - } else if (event.action.kind === "inlineChat") { - return { - action: { - kind: "editor", - accepted: event.action.action === "accepted", - }, - result: ehResult, - }; - } else if (event.action.kind === "chatEditingSessionAction") { + } else if (event.action.kind === 'inlineChat') { + return { action: { kind: 'editor', accepted: event.action.action === 'accepted' }, result: ehResult }; + } else if (event.action.kind === 'chatEditingSessionAction') { + const outcomes = new Map([ - ["accepted", types.ChatEditingSessionActionOutcome.Accepted], - ["rejected", types.ChatEditingSessionActionOutcome.Rejected], - ["saved", types.ChatEditingSessionActionOutcome.Saved], + ['accepted', types.ChatEditingSessionActionOutcome.Accepted], + ['rejected', types.ChatEditingSessionActionOutcome.Rejected], + ['saved', types.ChatEditingSessionActionOutcome.Saved], ]); return { action: { - kind: "chatEditingSessionAction", - outcome: - outcomes.get(event.action.outcome) ?? - types.ChatEditingSessionActionOutcome.Rejected, + kind: 'chatEditingSessionAction', + outcome: outcomes.get(event.action.outcome) ?? types.ChatEditingSessionActionOutcome.Rejected, uri: URI.revive(event.action.uri), - hasRemainingEdits: event.action.hasRemainingEdits, - }, - result: ehResult, + hasRemainingEdits: event.action.hasRemainingEdits + }, result: ehResult }; } else { return { action: event.action, result: ehResult }; @@ -4171,25 +2974,11 @@ export namespace ChatAgentUserActionEvent { } export namespace TerminalQuickFix { - export function from( - quickFix: - | vscode.TerminalQuickFixTerminalCommand - | vscode.TerminalQuickFixOpener - | vscode.Command, - converter: Command.ICommandsConverter, - disposables: DisposableStore, - ): - | extHostProtocol.ITerminalQuickFixTerminalCommandDto - | extHostProtocol.ITerminalQuickFixOpenerDto - | extHostProtocol.ICommandDto - | undefined { - if ("terminalCommand" in quickFix) { - return { - terminalCommand: quickFix.terminalCommand, - shouldExecute: quickFix.shouldExecute, - }; + export function from(quickFix: vscode.TerminalQuickFixTerminalCommand | vscode.TerminalQuickFixOpener | vscode.Command, converter: Command.ICommandsConverter, disposables: DisposableStore): extHostProtocol.ITerminalQuickFixTerminalCommandDto | extHostProtocol.ITerminalQuickFixOpenerDto | extHostProtocol.ICommandDto | undefined { + if ('terminalCommand' in quickFix) { + return { terminalCommand: quickFix.terminalCommand, shouldExecute: quickFix.shouldExecute }; } - if ("uri" in quickFix) { + if ('uri' in quickFix) { return { uri: quickFix.uri }; } return converter.toInternal(quickFix, disposables); @@ -4197,9 +2986,7 @@ export namespace TerminalQuickFix { } export namespace PartialAcceptInfo { - export function to( - info: languages.PartialAcceptInfo, - ): types.PartialAcceptInfo { + export function to(info: languages.PartialAcceptInfo): types.PartialAcceptInfo { return { kind: PartialAcceptTriggerKind.to(info.kind), }; @@ -4207,19 +2994,14 @@ export namespace PartialAcceptInfo { } export namespace PartialAcceptTriggerKind { - export function to( - kind: languages.PartialAcceptTriggerKind, - ): types.PartialAcceptTriggerKind { + export function to(kind: languages.PartialAcceptTriggerKind): types.PartialAcceptTriggerKind { switch (kind) { case languages.PartialAcceptTriggerKind.Word: return types.PartialAcceptTriggerKind.Word; - case languages.PartialAcceptTriggerKind.Line: return types.PartialAcceptTriggerKind.Line; - case languages.PartialAcceptTriggerKind.Suggest: return types.PartialAcceptTriggerKind.Suggest; - default: return types.PartialAcceptTriggerKind.Unknown; } @@ -4227,17 +3009,13 @@ export namespace PartialAcceptTriggerKind { } export namespace DebugTreeItem { - export function from( - item: vscode.DebugTreeItem, - id: number, - ): IDebugVisualizationTreeItem { + export function from(item: vscode.DebugTreeItem, id: number): IDebugVisualizationTreeItem { return { id, label: item.label, description: item.description, canEdit: item.canEdit, - collapsibleState: (item.collapsibleState || - DebugTreeItemCollapsibleState.None) as DebugTreeItemCollapsibleState, + collapsibleState: (item.collapsibleState || DebugTreeItemCollapsibleState.None) as DebugTreeItemCollapsibleState, contextValue: item.contextValue, }; } @@ -4257,36 +3035,32 @@ export namespace LanguageModelToolDescription { export namespace LanguageModelToolResult { export function to(result: IToolResult): vscode.LanguageModelToolResult { - return new types.LanguageModelToolResult( - result.content.map((item) => { - if (item.kind === "text") { - return new types.LanguageModelTextPart(item.value); - } else { - return new types.LanguageModelPromptTsxPart(item.value); - } - }), - ); + return new types.LanguageModelToolResult(result.content.map(item => { + if (item.kind === 'text') { + return new types.LanguageModelTextPart(item.value); + } else { + return new types.LanguageModelPromptTsxPart(item.value); + } + })); } export function from(result: vscode.LanguageModelToolResult): IToolResult { return { - content: result.content.map((item) => { + content: result.content.map(item => { if (item instanceof types.LanguageModelTextPart) { return { - kind: "text", - value: item.value, + kind: 'text', + value: item.value }; } else if (item instanceof types.LanguageModelPromptTsxPart) { return { - kind: "promptTsx", + kind: 'promptTsx', value: item.value, }; } else { - throw new Error( - "Unknown LanguageModelToolResult part type", - ); + throw new Error('Unknown LanguageModelToolResult part type'); } - }), + }) }; } } diff --git a/Source/vs/workbench/api/common/extHostTypes.ts b/Source/vs/workbench/api/common/extHostTypes.ts index 23b609b428f36..c44c20af49525 100644 --- a/Source/vs/workbench/api/common/extHostTypes.ts +++ b/Source/vs/workbench/api/common/extHostTypes.ts @@ -5081,6 +5081,14 @@ export enum TestRunProfileKind { Coverage = 3, } +export class TestRunProfileBase { + constructor( + public readonly controllerId: string, + public readonly profileId: number, + public readonly kind: vscode.TestRunProfileKind, + ) { } +} + @es5ClassCompat export class TestRunRequest implements vscode.TestRunRequest { constructor( @@ -5210,8 +5218,9 @@ export class FileCoverage implements vscode.FileCoverage { public statementCoverage: vscode.TestCoverageCount, public branchCoverage?: vscode.TestCoverageCount, public declarationCoverage?: vscode.TestCoverageCount, - public fromTests: vscode.TestItem[] = [], - ) {} + public includesTests: vscode.TestItem[] = [], + ) { + } } export class StatementCoverage implements vscode.StatementCoverage { diff --git a/Source/vs/workbench/browser/dnd.ts b/Source/vs/workbench/browser/dnd.ts index 949dca584ca2b..239e5bb89ac51 100644 --- a/Source/vs/workbench/browser/dnd.ts +++ b/Source/vs/workbench/browser/dnd.ts @@ -2,114 +2,74 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BroadcastDataChannel } from "../../base/browser/broadcast.js"; -import { DataTransfers, IDragAndDropData } from "../../base/browser/dnd.js"; -import { - addDisposableListener, - DragAndDropObserver, - EventType, - onDidRegisterWindow, -} from "../../base/browser/dom.js"; -import { DragMouseEvent } from "../../base/browser/mouseEvent.js"; -import { IListDragAndDrop } from "../../base/browser/ui/list/list.js"; -import { - ElementsDragAndDropData, - ListViewTargetSector, -} from "../../base/browser/ui/list/listView.js"; -import { ITreeDragOverReaction } from "../../base/browser/ui/tree/tree.js"; -import { mainWindow } from "../../base/browser/window.js"; -import { coalesce } from "../../base/common/arrays.js"; -import { UriList, VSDataTransfer } from "../../base/common/dataTransfer.js"; -import { Emitter, Event } from "../../base/common/event.js"; -import { - Disposable, - DisposableStore, - IDisposable, - markAsSingleton, -} from "../../base/common/lifecycle.js"; -import { stringify } from "../../base/common/marshalling.js"; -import { Mimes } from "../../base/common/mime.js"; -import { FileAccess, Schemas } from "../../base/common/network.js"; -import { isWindows } from "../../base/common/platform.js"; -import { basename, isEqual } from "../../base/common/resources.js"; -import { URI } from "../../base/common/uri.js"; -import { - CodeDataTransfers, - createDraggedEditorInputFromRawResourcesData, - Extensions, - extractEditorsAndFilesDropData, - IDragAndDropContributionRegistry, - IDraggedResourceEditorInput, - IResourceStat, - LocalSelectionTransfer, -} from "../../platform/dnd/browser/dnd.js"; -import { IEditorOptions } from "../../platform/editor/common/editor.js"; -import { IFileService } from "../../platform/files/common/files.js"; -import { - IInstantiationService, - ServicesAccessor, -} from "../../platform/instantiation/common/instantiation.js"; -import { ILabelService } from "../../platform/label/common/label.js"; -import { extractSelection } from "../../platform/opener/common/opener.js"; -import { Registry } from "../../platform/registry/common/platform.js"; -import { IWindowOpenable } from "../../platform/window/common/window.js"; -import { - hasWorkspaceFileExtension, - isTemporaryWorkspace, - IWorkspaceContextService, -} from "../../platform/workspace/common/workspace.js"; -import { - IWorkspaceFolderCreationData, - IWorkspacesService, -} from "../../platform/workspaces/common/workspaces.js"; -import { - EditorResourceAccessor, - GroupIdentifier, - IEditorIdentifier, - isEditorIdentifier, - isResourceDiffEditorInput, - isResourceMergeEditorInput, - isResourceSideBySideEditorInput, -} from "../common/editor.js"; -import { IEditorGroup } from "../services/editor/common/editorGroupsService.js"; -import { IEditorService } from "../services/editor/common/editorService.js"; -import { IHostService } from "../services/host/browser/host.js"; -import { ITextFileService } from "../services/textfile/common/textfiles.js"; -import { IWorkspaceEditingService } from "../services/workspaces/common/workspaceEditing.js"; + +import { DataTransfers, IDragAndDropData } from '../../base/browser/dnd.js'; +import { DragAndDropObserver, EventType, addDisposableListener, onDidRegisterWindow } from '../../base/browser/dom.js'; +import { DragMouseEvent } from '../../base/browser/mouseEvent.js'; +import { IListDragAndDrop } from '../../base/browser/ui/list/list.js'; +import { ElementsDragAndDropData, ListViewTargetSector } from '../../base/browser/ui/list/listView.js'; +import { ITreeDragOverReaction } from '../../base/browser/ui/tree/tree.js'; +import { coalesce } from '../../base/common/arrays.js'; +import { UriList, VSDataTransfer } from '../../base/common/dataTransfer.js'; +import { Emitter, Event } from '../../base/common/event.js'; +import { Disposable, DisposableStore, IDisposable, markAsSingleton } from '../../base/common/lifecycle.js'; +import { stringify } from '../../base/common/marshalling.js'; +import { Mimes } from '../../base/common/mime.js'; +import { FileAccess, Schemas } from '../../base/common/network.js'; +import { isWindows } from '../../base/common/platform.js'; +import { basename, isEqual } from '../../base/common/resources.js'; +import { URI } from '../../base/common/uri.js'; +import { CodeDataTransfers, Extensions, IDragAndDropContributionRegistry, IDraggedResourceEditorInput, IResourceStat, LocalSelectionTransfer, createDraggedEditorInputFromRawResourcesData, extractEditorsAndFilesDropData } from '../../platform/dnd/browser/dnd.js'; +import { IFileService } from '../../platform/files/common/files.js'; +import { IInstantiationService, ServicesAccessor } from '../../platform/instantiation/common/instantiation.js'; +import { ILabelService } from '../../platform/label/common/label.js'; +import { extractSelection, withSelection } from '../../platform/opener/common/opener.js'; +import { Registry } from '../../platform/registry/common/platform.js'; +import { IWindowOpenable } from '../../platform/window/common/window.js'; +import { IWorkspaceContextService, hasWorkspaceFileExtension, isTemporaryWorkspace } from '../../platform/workspace/common/workspace.js'; +import { IWorkspaceFolderCreationData, IWorkspacesService } from '../../platform/workspaces/common/workspaces.js'; +import { EditorResourceAccessor, GroupIdentifier, IEditorIdentifier, isEditorIdentifier, isResourceDiffEditorInput, isResourceMergeEditorInput, isResourceSideBySideEditorInput } from '../common/editor.js'; +import { IEditorGroup } from '../services/editor/common/editorGroupsService.js'; +import { IEditorService } from '../services/editor/common/editorService.js'; +import { IHostService } from '../services/host/browser/host.js'; +import { ITextFileService } from '../services/textfile/common/textfiles.js'; +import { IWorkspaceEditingService } from '../services/workspaces/common/workspaceEditing.js'; +import { IEditorOptions } from '../../platform/editor/common/editor.js'; +import { mainWindow } from '../../base/browser/window.js'; +import { BroadcastDataChannel } from '../../base/browser/broadcast.js'; //#region Editor / Resources DND + export class DraggedEditorIdentifier { - constructor(readonly identifier: IEditorIdentifier) {} + + constructor(readonly identifier: IEditorIdentifier) { } } + export class DraggedEditorGroupIdentifier { - constructor(readonly identifier: GroupIdentifier) {} + + constructor(readonly identifier: GroupIdentifier) { } } -export async function extractTreeDropData( - dataTransfer: VSDataTransfer, -): Promise> { - const editors: IDraggedResourceEditorInput[] = []; +export async function extractTreeDropData(dataTransfer: VSDataTransfer): Promise> { + const editors: IDraggedResourceEditorInput[] = []; const resourcesKey = Mimes.uriList.toLowerCase(); + // Data Transfer: Resources if (dataTransfer.has(resourcesKey)) { try { const asString = await dataTransfer.get(resourcesKey)?.asString(); - - const rawResourcesData = JSON.stringify( - UriList.parse(asString ?? ""), - ); - editors.push( - ...createDraggedEditorInputFromRawResourcesData( - rawResourcesData, - ), - ); + const rawResourcesData = JSON.stringify(UriList.parse(asString ?? '')); + editors.push(...createDraggedEditorInputFromRawResourcesData(rawResourcesData)); } catch (error) { // Invalid transfer } } + return editors; } + export interface IResourcesDropHandlerOptions { + /** * Whether we probe for the dropped resource to be a workspace * (i.e. code-workspace file or even a folder), allowing to @@ -117,362 +77,269 @@ export interface IResourcesDropHandlerOptions { */ readonly allowWorkspaceOpen: boolean; } + /** * Shared function across some components to handle drag & drop of resources. * E.g. of folders and workspace files to open them in the window instead of * the editor or to handle dirty editors being dropped between instances of Code. */ export class ResourcesDropHandler { + constructor( private readonly options: IResourcesDropHandlerOptions, - @IFileService - private readonly fileService: IFileService, - @IWorkspacesService - private readonly workspacesService: IWorkspacesService, - @IEditorService - private readonly editorService: IEditorService, - @IWorkspaceEditingService - private readonly workspaceEditingService: IWorkspaceEditingService, - @IHostService - private readonly hostService: IHostService, - @IWorkspaceContextService - private readonly contextService: IWorkspaceContextService, - @IInstantiationService - private readonly instantiationService: IInstantiationService, - ) {} - async handleDrop( - event: DragEvent, - targetWindow: Window, - resolveTargetGroup?: () => IEditorGroup | undefined, - afterDrop?: (targetGroup: IEditorGroup | undefined) => void, - options?: IEditorOptions, - ): Promise { - const editors = await this.instantiationService.invokeFunction( - (accessor) => extractEditorsAndFilesDropData(accessor, event), - ); + @IFileService private readonly fileService: IFileService, + @IWorkspacesService private readonly workspacesService: IWorkspacesService, + @IEditorService private readonly editorService: IEditorService, + @IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService, + @IHostService private readonly hostService: IHostService, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { + } + async handleDrop(event: DragEvent, targetWindow: Window, resolveTargetGroup?: () => IEditorGroup | undefined, afterDrop?: (targetGroup: IEditorGroup | undefined) => void, options?: IEditorOptions): Promise { + const editors = await this.instantiationService.invokeFunction(accessor => extractEditorsAndFilesDropData(accessor, event)); if (!editors.length) { return; } + // Make the window active to handle the drop properly within await this.hostService.focus(targetWindow); + // Check for workspace file / folder being dropped if we are allowed to do so if (this.options.allowWorkspaceOpen) { - const localFilesAllowedToOpenAsWorkspace = coalesce( - editors - .filter( - (editor) => - editor.allowWorkspaceOpen && - editor.resource?.scheme === Schemas.file, - ) - .map((editor) => editor.resource), - ); - + const localFilesAllowedToOpenAsWorkspace = coalesce(editors.filter(editor => editor.allowWorkspaceOpen && editor.resource?.scheme === Schemas.file).map(editor => editor.resource)); if (localFilesAllowedToOpenAsWorkspace.length > 0) { - const isWorkspaceOpening = await this.handleWorkspaceDrop( - localFilesAllowedToOpenAsWorkspace, - ); - + const isWorkspaceOpening = await this.handleWorkspaceDrop(localFilesAllowedToOpenAsWorkspace); if (isWorkspaceOpening) { return; // return early if the drop operation resulted in this window changing to a workspace } } } - // Add external ones to recently open list unless dropped resource is a workspace - const externalLocalFiles = coalesce( - editors - .filter( - (editor) => - editor.isExternal && - editor.resource?.scheme === Schemas.file, - ) - .map((editor) => editor.resource), - ); + // Add external ones to recently open list unless dropped resource is a workspace + const externalLocalFiles = coalesce(editors.filter(editor => editor.isExternal && editor.resource?.scheme === Schemas.file).map(editor => editor.resource)); if (externalLocalFiles.length) { - this.workspacesService.addRecentlyOpened( - externalLocalFiles.map((resource) => ({ fileUri: resource })), - ); + this.workspacesService.addRecentlyOpened(externalLocalFiles.map(resource => ({ fileUri: resource }))); } + // Open in Editor const targetGroup = resolveTargetGroup?.(); - await this.editorService.openEditors( - editors.map((editor) => ({ - ...editor, - resource: editor.resource, - options: { - ...editor.options, - ...options, - pinned: true, - }, - })), - targetGroup, - { validateTrust: true }, - ); + await this.editorService.openEditors(editors.map(editor => ({ + ...editor, + resource: editor.resource, + options: { + ...editor.options, + ...options, + pinned: true + } + })), targetGroup, { validateTrust: true }); + // Finish with provided function afterDrop?.(targetGroup); } + private async handleWorkspaceDrop(resources: URI[]): Promise { const toOpen: IWindowOpenable[] = []; - const folderURIs: IWorkspaceFolderCreationData[] = []; - await Promise.all( - resources.map(async (resource) => { - // Check for Workspace - if (hasWorkspaceFileExtension(resource)) { - toOpen.push({ workspaceUri: resource }); - return; - } - // Check for Folder - try { - const stat = await this.fileService.stat(resource); + await Promise.all(resources.map(async resource => { - if (stat.isDirectory) { - toOpen.push({ folderUri: stat.resource }); - folderURIs.push({ uri: stat.resource }); - } - } catch (error) { - // Ignore error + // Check for Workspace + if (hasWorkspaceFileExtension(resource)) { + toOpen.push({ workspaceUri: resource }); + + return; + } + + // Check for Folder + try { + const stat = await this.fileService.stat(resource); + if (stat.isDirectory) { + toOpen.push({ folderUri: stat.resource }); + folderURIs.push({ uri: stat.resource }); } - }), - ); + } catch (error) { + // Ignore error + } + })); + // Return early if no external resource is a folder or workspace if (toOpen.length === 0) { return false; } + // Open in separate windows if we drop workspaces or just one folder if (toOpen.length > folderURIs.length || folderURIs.length === 1) { await this.hostService.openWindow(toOpen); } + // Add to workspace if we are in a temporary workspace else if (isTemporaryWorkspace(this.contextService.getWorkspace())) { await this.workspaceEditingService.addFolders(folderURIs); } + // Finally, enter untitled workspace when dropping >1 folders else { - await this.workspaceEditingService.createAndEnterWorkspace( - folderURIs, - ); + await this.workspaceEditingService.createAndEnterWorkspace(folderURIs); } + return true; } } -export function fillEditorsDragData( - accessor: ServicesAccessor, - resources: URI[], - event: DragMouseEvent | DragEvent, - options?: { - disableStandardTransfer: boolean; - }, -): void; -export function fillEditorsDragData( - accessor: ServicesAccessor, - resources: IResourceStat[], - event: DragMouseEvent | DragEvent, - options?: { - disableStandardTransfer: boolean; - }, -): void; -export function fillEditorsDragData( - accessor: ServicesAccessor, - editors: IEditorIdentifier[], - event: DragMouseEvent | DragEvent, - options?: { - disableStandardTransfer: boolean; - }, -): void; -export function fillEditorsDragData( - accessor: ServicesAccessor, - resourcesOrEditors: Array, - event: DragMouseEvent | DragEvent, - options?: { - disableStandardTransfer: boolean; - }, -): void { + +export function fillEditorsDragData(accessor: ServicesAccessor, resources: URI[], event: DragMouseEvent | DragEvent, options?: { disableStandardTransfer: boolean }): void; +export function fillEditorsDragData(accessor: ServicesAccessor, resources: IResourceStat[], event: DragMouseEvent | DragEvent, options?: { disableStandardTransfer: boolean }): void; +export function fillEditorsDragData(accessor: ServicesAccessor, editors: IEditorIdentifier[], event: DragMouseEvent | DragEvent, options?: { disableStandardTransfer: boolean }): void; +export function fillEditorsDragData(accessor: ServicesAccessor, resourcesOrEditors: Array, event: DragMouseEvent | DragEvent, options?: { disableStandardTransfer: boolean }): void { if (resourcesOrEditors.length === 0 || !event.dataTransfer) { return; } - const textFileService = accessor.get(ITextFileService); + const textFileService = accessor.get(ITextFileService); const editorService = accessor.get(IEditorService); - const fileService = accessor.get(IFileService); - const labelService = accessor.get(ILabelService); + // Extract resources from URIs or Editors that // can be handled by the file service - const resources = coalesce( - resourcesOrEditors.map((resourceOrEditor) => { - if (URI.isUri(resourceOrEditor)) { - return { resource: resourceOrEditor }; - } - if (isEditorIdentifier(resourceOrEditor)) { - if (URI.isUri(resourceOrEditor.editor.resource)) { - return { resource: resourceOrEditor.editor.resource }; - } - return undefined; // editor without resource + const resources = coalesce(resourcesOrEditors.map((resourceOrEditor): IResourceStat | undefined => { + if (URI.isUri(resourceOrEditor)) { + return { resource: resourceOrEditor }; + } + + if (isEditorIdentifier(resourceOrEditor)) { + if (URI.isUri(resourceOrEditor.editor.resource)) { + return { resource: resourceOrEditor.editor.resource }; } - return resourceOrEditor; - }), - ); - const fileSystemResources = resources.filter(({ resource }) => - fileService.hasProvider(resource), - ); + return undefined; // editor without resource + } + + return { ...resourceOrEditor, resource: resourceOrEditor.selection ? withSelection(resourceOrEditor.resource, resourceOrEditor.selection) : resourceOrEditor.resource }; + })); + const fileSystemResources = resources.filter(({ resource }) => fileService.hasProvider(resource)); if (!options?.disableStandardTransfer) { + // Text: allows to paste into text-capable areas - const lineDelimiter = isWindows ? "\r\n" : "\n"; - event.dataTransfer.setData( - DataTransfers.TEXT, - fileSystemResources - .map(({ resource }) => - labelService.getUriLabel(resource, { noPrefix: true }), - ) - .join(lineDelimiter), - ); + const lineDelimiter = isWindows ? '\r\n' : '\n'; + event.dataTransfer.setData(DataTransfers.TEXT, fileSystemResources.map(({ resource }) => labelService.getUriLabel(resource, { noPrefix: true })).join(lineDelimiter)); + // Download URL: enables support to drag a tab as file to desktop // Requirements: // - Chrome/Edge only // - only a single file is supported // - only file:/ resources are supported - const firstFile = fileSystemResources.find( - ({ isDirectory }) => !isDirectory, - ); - + const firstFile = fileSystemResources.find(({ isDirectory }) => !isDirectory); if (firstFile) { const firstFileUri = FileAccess.uriToFileUri(firstFile.resource); // enforce `file:` URIs if (firstFileUri.scheme === Schemas.file) { - event.dataTransfer.setData( - DataTransfers.DOWNLOAD_URL, - [ - Mimes.binary, - basename(firstFile.resource), - firstFileUri.toString(), - ].join(":"), - ); + event.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [Mimes.binary, basename(firstFile.resource), firstFileUri.toString()].join(':')); } } } + // Resource URLs: allows to drop multiple file resources to a target in VS Code const files = fileSystemResources.filter(({ isDirectory }) => !isDirectory); - if (files.length) { - event.dataTransfer.setData( - DataTransfers.RESOURCES, - JSON.stringify(files.map(({ resource }) => resource.toString())), - ); + event.dataTransfer.setData(DataTransfers.RESOURCES, JSON.stringify(files.map(({ resource }) => resource.toString()))); } - // Contributions - const contributions = Registry.as( - Extensions.DragAndDropContribution, - ).getAll(); + // Contributions + const contributions = Registry.as(Extensions.DragAndDropContribution).getAll(); for (const contribution of contributions) { contribution.setData(resources, event); } + // Editors: enables cross window DND of editors // into the editor area while presering UI state const draggedEditors: IDraggedResourceEditorInput[] = []; for (const resourceOrEditor of resourcesOrEditors) { + // Extract resource editor from provided object or URI let editor: IDraggedResourceEditorInput | undefined = undefined; - if (isEditorIdentifier(resourceOrEditor)) { - const untypedEditor = resourceOrEditor.editor.toUntyped({ - preserveViewState: resourceOrEditor.groupId, - }); - + const untypedEditor = resourceOrEditor.editor.toUntyped({ preserveViewState: resourceOrEditor.groupId }); if (untypedEditor) { - editor = { - ...untypedEditor, - resource: - EditorResourceAccessor.getCanonicalUri(untypedEditor), - }; + editor = { ...untypedEditor, resource: EditorResourceAccessor.getCanonicalUri(untypedEditor) }; } } else if (URI.isUri(resourceOrEditor)) { const { selection, uri } = extractSelection(resourceOrEditor); + editor = { resource: uri, options: selection ? { selection } : undefined }; + } else if (!resourceOrEditor.isDirectory) { editor = { - resource: uri, - options: selection ? { selection } : undefined, + resource: resourceOrEditor.resource, + options: { + selection: resourceOrEditor.selection, + } }; - } else if (!resourceOrEditor.isDirectory) { - editor = { resource: resourceOrEditor.resource }; } + if (!editor) { continue; // skip over editors that cannot be transferred via dnd } + // Fill in some properties if they are not there already by accessing // some well known things from the text file universe. // This is not ideal for custom editors, but those have a chance to // provide everything from the `toUntyped` method. { const resource = editor.resource; - if (resource) { const textFileModel = textFileService.files.get(resource); - if (textFileModel) { + // language - if (typeof editor.languageId !== "string") { + if (typeof editor.languageId !== 'string') { editor.languageId = textFileModel.getLanguageId(); } + // encoding - if (typeof editor.encoding !== "string") { + if (typeof editor.encoding !== 'string') { editor.encoding = textFileModel.getEncoding(); } + // contents (only if dirty and not too large) - if ( - typeof editor.contents !== "string" && - textFileModel.isDirty() && - !textFileModel.textEditorModel.isTooLargeForHeapOperation() - ) { - editor.contents = - textFileModel.textEditorModel.getValue(); + if (typeof editor.contents !== 'string' && textFileModel.isDirty() && !textFileModel.textEditorModel.isTooLargeForHeapOperation()) { + editor.contents = textFileModel.textEditorModel.getValue(); } } + // viewState if (!editor.options?.viewState) { editor.options = { ...editor.options, viewState: (() => { for (const visibleEditorPane of editorService.visibleEditorPanes) { - if ( - isEqual( - visibleEditorPane.input.resource, - resource, - ) - ) { - const viewState = - visibleEditorPane.getViewState(); - + if (isEqual(visibleEditorPane.input.resource, resource)) { + const viewState = visibleEditorPane.getViewState(); if (viewState) { return viewState; } } } + return undefined; - })(), + })() }; } } } + // Add as dragged editor draggedEditors.push(editor); } + if (draggedEditors.length) { - event.dataTransfer.setData( - CodeDataTransfers.EDITORS, - stringify(draggedEditors), - ); + event.dataTransfer.setData(CodeDataTransfers.EDITORS, stringify(draggedEditors)); + // Add a URI list entry const uriListEntries: URI[] = []; - for (const editor of draggedEditors) { if (editor.resource) { - uriListEntries.push(editor.resource); + uriListEntries.push(editor.options?.selection ? withSelection(editor.resource, editor.options.selection) : editor.resource); } else if (isResourceDiffEditorInput(editor)) { if (editor.modified.resource) { uriListEntries.push(editor.modified.resource); @@ -485,46 +352,33 @@ export function fillEditorsDragData( uriListEntries.push(editor.result.resource); } } + // Due to https://bugs.chromium.org/p/chromium/issues/detail?id=239745, we can only set // a single uri for the real `text/uri-list` type. Otherwise all uris end up joined together // However we write the full uri-list to an internal type so that other parts of VS Code // can use the full list. if (!options?.disableStandardTransfer) { - event.dataTransfer.setData( - Mimes.uriList, - UriList.create(uriListEntries.slice(0, 1)), - ); + event.dataTransfer.setData(Mimes.uriList, UriList.create(uriListEntries.slice(0, 1))); } - event.dataTransfer.setData( - DataTransfers.INTERNAL_URI_LIST, - UriList.create(uriListEntries), - ); + event.dataTransfer.setData(DataTransfers.INTERNAL_URI_LIST, UriList.create(uriListEntries)); } } + //#endregion + //#region Composites DND + export type Before2D = { readonly verticallyBefore: boolean; readonly horizontallyBefore: boolean; }; + export interface ICompositeDragAndDrop { - drop( - data: IDragAndDropData, - target: string | undefined, - originalEvent: DragEvent, - before?: Before2D, - ): void; - onDragOver( - data: IDragAndDropData, - target: string | undefined, - originalEvent: DragEvent, - ): boolean; - onDragEnter( - data: IDragAndDropData, - target: string | undefined, - originalEvent: DragEvent, - ): boolean; + drop(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent, before?: Before2D): void; + onDragOver(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): boolean; + onDragEnter(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): boolean; } + export interface ICompositeDragAndDropObserverCallbacks { onDragEnter?: (e: IDraggedCompositeData) => void; onDragLeave?: (e: IDraggedCompositeData) => void; @@ -533,461 +387,335 @@ export interface ICompositeDragAndDropObserverCallbacks { onDragStart?: (e: IDraggedCompositeData) => void; onDragEnd?: (e: IDraggedCompositeData) => void; } + export class CompositeDragAndDropData implements IDragAndDropData { - constructor( - private type: "view" | "composite", - private id: string, - ) {} + + constructor(private type: 'view' | 'composite', private id: string) { } + update(dataTransfer: DataTransfer): void { // no-op } + getData(): { - type: "view" | "composite"; + type: 'view' | 'composite'; id: string; } { return { type: this.type, id: this.id }; } } + export interface IDraggedCompositeData { readonly eventData: DragEvent; readonly dragAndDropData: CompositeDragAndDropData; } + export class DraggedCompositeIdentifier { - constructor(private compositeId: string) {} + + constructor(private compositeId: string) { } + get id(): string { return this.compositeId; } } + export class DraggedViewIdentifier { - constructor(private viewId: string) {} + + constructor(private viewId: string) { } + get id(): string { return this.viewId; } } -export type ViewType = "composite" | "view"; + +export type ViewType = 'composite' | 'view'; + export class CompositeDragAndDropObserver extends Disposable { + private static instance: CompositeDragAndDropObserver | undefined; + static get INSTANCE(): CompositeDragAndDropObserver { if (!CompositeDragAndDropObserver.instance) { - CompositeDragAndDropObserver.instance = - new CompositeDragAndDropObserver(); + CompositeDragAndDropObserver.instance = new CompositeDragAndDropObserver(); markAsSingleton(CompositeDragAndDropObserver.instance); } + return CompositeDragAndDropObserver.instance; } - private readonly transferData = LocalSelectionTransfer.getInstance< - DraggedCompositeIdentifier | DraggedViewIdentifier - >(); - private readonly onDragStart = this._register( - new Emitter(), - ); - private readonly onDragEnd = this._register( - new Emitter(), - ); - private constructor() { - super(); - this._register( - this.onDragEnd.event((e) => { - const id = e.dragAndDropData.getData().id; - const type = e.dragAndDropData.getData().type; + private readonly transferData = LocalSelectionTransfer.getInstance(); - const data = this.readDragData(type); + private readonly onDragStart = this._register(new Emitter()); + private readonly onDragEnd = this._register(new Emitter()); - if (data?.getData().id === id) { - this.transferData.clearData( - type === "view" - ? DraggedViewIdentifier.prototype - : DraggedCompositeIdentifier.prototype, - ); - } - }), - ); + private constructor() { + super(); + + this._register(this.onDragEnd.event(e => { + const id = e.dragAndDropData.getData().id; + const type = e.dragAndDropData.getData().type; + const data = this.readDragData(type); + if (data?.getData().id === id) { + this.transferData.clearData(type === 'view' ? DraggedViewIdentifier.prototype : DraggedCompositeIdentifier.prototype); + } + })); } - private readDragData(type: ViewType): CompositeDragAndDropData | undefined { - if ( - this.transferData.hasData( - type === "view" - ? DraggedViewIdentifier.prototype - : DraggedCompositeIdentifier.prototype, - ) - ) { - const data = this.transferData.getData( - type === "view" - ? DraggedViewIdentifier.prototype - : DraggedCompositeIdentifier.prototype, - ); + private readDragData(type: ViewType): CompositeDragAndDropData | undefined { + if (this.transferData.hasData(type === 'view' ? DraggedViewIdentifier.prototype : DraggedCompositeIdentifier.prototype)) { + const data = this.transferData.getData(type === 'view' ? DraggedViewIdentifier.prototype : DraggedCompositeIdentifier.prototype); if (data && data[0]) { return new CompositeDragAndDropData(type, data[0].id); } } + return undefined; } + private writeDragData(id: string, type: ViewType): void { - this.transferData.setData( - [ - type === "view" - ? new DraggedViewIdentifier(id) - : new DraggedCompositeIdentifier(id), - ], - type === "view" - ? DraggedViewIdentifier.prototype - : DraggedCompositeIdentifier.prototype, - ); + this.transferData.setData([type === 'view' ? new DraggedViewIdentifier(id) : new DraggedCompositeIdentifier(id)], type === 'view' ? DraggedViewIdentifier.prototype : DraggedCompositeIdentifier.prototype); } - registerTarget( - element: HTMLElement, - callbacks: ICompositeDragAndDropObserverCallbacks, - ): IDisposable { + + registerTarget(element: HTMLElement, callbacks: ICompositeDragAndDropObserverCallbacks): IDisposable { const disposableStore = new DisposableStore(); - disposableStore.add( - new DragAndDropObserver(element, { - onDragEnter: (e) => { - e.preventDefault(); - - if (callbacks.onDragEnter) { - const data = - this.readDragData("composite") || - this.readDragData("view"); - - if (data) { - callbacks.onDragEnter({ - eventData: e, - dragAndDropData: data, - }); - } - } - }, - onDragLeave: (e) => { - const data = - this.readDragData("composite") || - this.readDragData("view"); - - if (callbacks.onDragLeave && data) { - callbacks.onDragLeave({ - eventData: e, - dragAndDropData: data, - }); + disposableStore.add(new DragAndDropObserver(element, { + onDragEnter: e => { + e.preventDefault(); + + if (callbacks.onDragEnter) { + const data = this.readDragData('composite') || this.readDragData('view'); + if (data) { + callbacks.onDragEnter({ eventData: e, dragAndDropData: data }); } - }, - onDrop: (e) => { - if (callbacks.onDrop) { - const data = - this.readDragData("composite") || - this.readDragData("view"); - - if (!data) { - return; - } - callbacks.onDrop({ - eventData: e, - dragAndDropData: data, - }); - // Fire drag event in case drop handler destroys the dragged element - this.onDragEnd.fire({ - eventData: e, - dragAndDropData: data, - }); + } + }, + onDragLeave: e => { + const data = this.readDragData('composite') || this.readDragData('view'); + if (callbacks.onDragLeave && data) { + callbacks.onDragLeave({ eventData: e, dragAndDropData: data }); + } + }, + onDrop: e => { + if (callbacks.onDrop) { + const data = this.readDragData('composite') || this.readDragData('view'); + if (!data) { + return; } - }, - onDragOver: (e) => { - e.preventDefault(); - - if (callbacks.onDragOver) { - const data = - this.readDragData("composite") || - this.readDragData("view"); - - if (!data) { - return; - } - callbacks.onDragOver({ - eventData: e, - dragAndDropData: data, - }); + + callbacks.onDrop({ eventData: e, dragAndDropData: data }); + + // Fire drag event in case drop handler destroys the dragged element + this.onDragEnd.fire({ eventData: e, dragAndDropData: data }); + } + }, + onDragOver: e => { + e.preventDefault(); + + if (callbacks.onDragOver) { + const data = this.readDragData('composite') || this.readDragData('view'); + if (!data) { + return; } - }, - }), - ); + + callbacks.onDragOver({ eventData: e, dragAndDropData: data }); + } + } + })); if (callbacks.onDragStart) { - this.onDragStart.event( - (e) => { - callbacks.onDragStart!(e); - }, - this, - disposableStore, - ); + this.onDragStart.event(e => { + callbacks.onDragStart!(e); + }, this, disposableStore); } + if (callbacks.onDragEnd) { - this.onDragEnd.event( - (e) => { - callbacks.onDragEnd!(e); - }, - this, - disposableStore, - ); + this.onDragEnd.event(e => { + callbacks.onDragEnd!(e); + }, this, disposableStore); } + return this._register(disposableStore); } - registerDraggable( - element: HTMLElement, - draggedItemProvider: () => { - type: ViewType; - id: string; - }, - callbacks: ICompositeDragAndDropObserverCallbacks, - ): IDisposable { + + registerDraggable(element: HTMLElement, draggedItemProvider: () => { type: ViewType; id: string }, callbacks: ICompositeDragAndDropObserverCallbacks): IDisposable { element.draggable = true; const disposableStore = new DisposableStore(); - disposableStore.add( - new DragAndDropObserver(element, { - onDragStart: (e) => { - const { id, type } = draggedItemProvider(); - this.writeDragData(id, type); - e.dataTransfer?.setDragImage(element, 0, 0); - this.onDragStart.fire({ - eventData: e, - dragAndDropData: this.readDragData(type)!, - }); - }, - onDragEnd: (e) => { - const { type } = draggedItemProvider(); - - const data = this.readDragData(type); + disposableStore.add(new DragAndDropObserver(element, { + onDragStart: e => { + const { id, type } = draggedItemProvider(); + this.writeDragData(id, type); + + e.dataTransfer?.setDragImage(element, 0, 0); + + this.onDragStart.fire({ eventData: e, dragAndDropData: this.readDragData(type)! }); + }, + onDragEnd: e => { + const { type } = draggedItemProvider(); + const data = this.readDragData(type); + if (!data) { + return; + } + + this.onDragEnd.fire({ eventData: e, dragAndDropData: data }); + }, + onDragEnter: e => { + if (callbacks.onDragEnter) { + const data = this.readDragData('composite') || this.readDragData('view'); if (!data) { return; } - this.onDragEnd.fire({ - eventData: e, - dragAndDropData: data, - }); - }, - onDragEnter: (e) => { - if (callbacks.onDragEnter) { - const data = - this.readDragData("composite") || - this.readDragData("view"); - - if (!data) { - return; - } - if (data) { - callbacks.onDragEnter({ - eventData: e, - dragAndDropData: data, - }); - } + + if (data) { + callbacks.onDragEnter({ eventData: e, dragAndDropData: data }); } - }, - onDragLeave: (e) => { - const data = - this.readDragData("composite") || - this.readDragData("view"); + } + }, + onDragLeave: e => { + const data = this.readDragData('composite') || this.readDragData('view'); + if (!data) { + return; + } + callbacks.onDragLeave?.({ eventData: e, dragAndDropData: data }); + }, + onDrop: e => { + if (callbacks.onDrop) { + const data = this.readDragData('composite') || this.readDragData('view'); if (!data) { return; } - callbacks.onDragLeave?.({ - eventData: e, - dragAndDropData: data, - }); - }, - onDrop: (e) => { - if (callbacks.onDrop) { - const data = - this.readDragData("composite") || - this.readDragData("view"); - - if (!data) { - return; - } - callbacks.onDrop({ - eventData: e, - dragAndDropData: data, - }); - // Fire drag event in case drop handler destroys the dragged element - this.onDragEnd.fire({ - eventData: e, - dragAndDropData: data, - }); - } - }, - onDragOver: (e) => { - if (callbacks.onDragOver) { - const data = - this.readDragData("composite") || - this.readDragData("view"); - - if (!data) { - return; - } - callbacks.onDragOver({ - eventData: e, - dragAndDropData: data, - }); + + callbacks.onDrop({ eventData: e, dragAndDropData: data }); + + // Fire drag event in case drop handler destroys the dragged element + this.onDragEnd.fire({ eventData: e, dragAndDropData: data }); + } + }, + onDragOver: e => { + if (callbacks.onDragOver) { + const data = this.readDragData('composite') || this.readDragData('view'); + if (!data) { + return; } - }, - }), - ); + + callbacks.onDragOver({ eventData: e, dragAndDropData: data }); + } + } + })); if (callbacks.onDragStart) { - this.onDragStart.event( - (e) => { - callbacks.onDragStart!(e); - }, - this, - disposableStore, - ); + this.onDragStart.event(e => { + callbacks.onDragStart!(e); + }, this, disposableStore); } + if (callbacks.onDragEnd) { - this.onDragEnd.event( - (e) => { - callbacks.onDragEnd!(e); - }, - this, - disposableStore, - ); + this.onDragEnd.event(e => { + callbacks.onDragEnd!(e); + }, this, disposableStore); } + return this._register(disposableStore); } } -export function toggleDropEffect( - dataTransfer: DataTransfer | null, - dropEffect: "none" | "copy" | "link" | "move", - shouldHaveIt: boolean, -) { + +export function toggleDropEffect(dataTransfer: DataTransfer | null, dropEffect: 'none' | 'copy' | 'link' | 'move', shouldHaveIt: boolean) { if (!dataTransfer) { return; } - dataTransfer.dropEffect = shouldHaveIt ? dropEffect : "none"; + + dataTransfer.dropEffect = shouldHaveIt ? dropEffect : 'none'; } + export class ResourceListDnDHandler implements IListDragAndDrop { constructor( private readonly toResource: (e: T) => URI | null, - @IInstantiationService - private readonly instantiationService: IInstantiationService, - ) {} + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { } + getDragURI(element: T): string | null { const resource = this.toResource(element); - return resource ? resource.toString() : null; } + getDragLabel(elements: T[]): string | undefined { const resources = coalesce(elements.map(this.toResource)); - - return resources.length === 1 - ? basename(resources[0]) - : resources.length > 1 - ? String(resources.length) - : undefined; + return resources.length === 1 ? basename(resources[0]) : resources.length > 1 ? String(resources.length) : undefined; } + onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void { const resources: URI[] = []; - for (const element of (data as ElementsDragAndDropData).elements) { const resource = this.toResource(element); - if (resource) { resources.push(resource); } } if (resources.length) { // Apply some datatransfer types to allow for dragging the element outside of the application - this.instantiationService.invokeFunction((accessor) => - fillEditorsDragData(accessor, resources, originalEvent), - ); + this.instantiationService.invokeFunction(accessor => fillEditorsDragData(accessor, resources, originalEvent)); } } - onDragOver( - data: IDragAndDropData, - targetElement: T, - targetIndex: number, - targetSector: ListViewTargetSector | undefined, - originalEvent: DragEvent, - ): boolean | ITreeDragOverReaction { + + onDragOver(data: IDragAndDropData, targetElement: T, targetIndex: number, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction { return false; } - drop( - data: IDragAndDropData, - targetElement: T, - targetIndex: number, - targetSector: ListViewTargetSector | undefined, - originalEvent: DragEvent, - ): void {} - dispose(): void {} + + drop(data: IDragAndDropData, targetElement: T, targetIndex: number, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): void { } + + dispose(): void { } } + //#endregion + class GlobalWindowDraggedOverTracker extends Disposable { - private static readonly CHANNEL_NAME = - "monaco-workbench-global-dragged-over"; - private readonly broadcaster = this._register( - new BroadcastDataChannel( - GlobalWindowDraggedOverTracker.CHANNEL_NAME, - ), - ); + + private static readonly CHANNEL_NAME = 'monaco-workbench-global-dragged-over'; + + private readonly broadcaster = this._register(new BroadcastDataChannel(GlobalWindowDraggedOverTracker.CHANNEL_NAME)); constructor() { super(); + this.registerListeners(); } + private registerListeners(): void { - this._register( - Event.runAndSubscribe( - onDidRegisterWindow, - ({ window, disposables }) => { - disposables.add( - addDisposableListener( - window, - EventType.DRAG_OVER, - () => this.markDraggedOver(false), - true, - ), - ); - disposables.add( - addDisposableListener( - window, - EventType.DRAG_LEAVE, - () => this.clearDraggedOver(false), - true, - ), - ); - }, - { window: mainWindow, disposables: this._store }, - ), - ); - this._register( - this.broadcaster.onDidReceiveData((data) => { - if (data === true) { - this.markDraggedOver(true); - } else { - this.clearDraggedOver(true); - } - }), - ); + this._register(Event.runAndSubscribe(onDidRegisterWindow, ({ window, disposables }) => { + disposables.add(addDisposableListener(window, EventType.DRAG_OVER, () => this.markDraggedOver(false), true)); + disposables.add(addDisposableListener(window, EventType.DRAG_LEAVE, () => this.clearDraggedOver(false), true)); + }, { window: mainWindow, disposables: this._store })); + + this._register(this.broadcaster.onDidReceiveData(data => { + if (data === true) { + this.markDraggedOver(true); + } else { + this.clearDraggedOver(true); + } + })); } + private draggedOver = false; + get isDraggedOver(): boolean { return this.draggedOver; } - get isDraggedOver(): boolean { - return this.draggedOver; - } private markDraggedOver(fromBroadcast: boolean): void { if (this.draggedOver === true) { return; // alrady marked } + this.draggedOver = true; if (!fromBroadcast) { this.broadcaster.postData(true); } } + private clearDraggedOver(fromBroadcast: boolean): void { if (this.draggedOver === false) { return; // alrady cleared } + this.draggedOver = false; if (!fromBroadcast) { @@ -995,7 +723,9 @@ class GlobalWindowDraggedOverTracker extends Disposable { } } } + const globalDraggedOverTracker = new GlobalWindowDraggedOverTracker(); + /** * Returns whether the workbench is currently dragged over in any of * the opened windows (main windows and auxiliary windows). diff --git a/Source/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/Source/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index ec10ce33255a8..9d72759aba2a9 100644 --- a/Source/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/Source/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -3,114 +3,57 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as dom from "../../../../base/browser/dom.js"; -import { StandardMouseEvent } from "../../../../base/browser/mouseEvent.js"; -import { - BreadcrumbsItem, - BreadcrumbsWidget, - IBreadcrumbsItemEvent, - IBreadcrumbsWidgetStyles, -} from "../../../../base/browser/ui/breadcrumbs/breadcrumbsWidget.js"; -import { timeout } from "../../../../base/common/async.js"; -import { KeyCode, KeyMod } from "../../../../base/common/keyCodes.js"; -import { - combinedDisposable, - DisposableStore, - IDisposable, - MutableDisposable, - toDisposable, -} from "../../../../base/common/lifecycle.js"; -import { basename, extUri } from "../../../../base/common/resources.js"; -import { URI } from "../../../../base/common/uri.js"; - -import "./media/breadcrumbscontrol.css"; - -import { DataTransfers } from "../../../../base/browser/dnd.js"; -import { $ } from "../../../../base/browser/dom.js"; -import { PixelRatio } from "../../../../base/browser/pixelRatio.js"; -import { IHoverDelegate } from "../../../../base/browser/ui/hover/hoverDelegate.js"; -import { getDefaultHoverDelegate } from "../../../../base/browser/ui/hover/hoverDelegateFactory.js"; -import { Codicon } from "../../../../base/common/codicons.js"; -import { Emitter } from "../../../../base/common/event.js"; -import { OutlineElement } from "../../../../editor/contrib/documentSymbols/browser/outlineModel.js"; -import { localize, localize2 } from "../../../../nls.js"; -import { Categories } from "../../../../platform/action/common/actionCommonCategories.js"; -import { - Action2, - MenuId, - registerAction2, -} from "../../../../platform/actions/common/actions.js"; -import { IConfigurationService } from "../../../../platform/configuration/common/configuration.js"; -import { - ContextKeyExpr, - IContextKey, - IContextKeyService, - RawContextKey, -} from "../../../../platform/contextkey/common/contextkey.js"; -import { IContextViewService } from "../../../../platform/contextview/browser/contextView.js"; -import { - CodeDataTransfers, - DocumentSymbolTransferData, -} from "../../../../platform/dnd/browser/dnd.js"; -import { - FileKind, - IFileService, - IFileStat, -} from "../../../../platform/files/common/files.js"; -import { - IInstantiationService, - ServicesAccessor, -} from "../../../../platform/instantiation/common/instantiation.js"; -import { - KeybindingsRegistry, - KeybindingWeight, -} from "../../../../platform/keybinding/common/keybindingsRegistry.js"; -import { ILabelService } from "../../../../platform/label/common/label.js"; -import { - IListService, - WorkbenchAsyncDataTree, - WorkbenchDataTree, - WorkbenchListFocusContextKey, -} from "../../../../platform/list/browser/listService.js"; -import { withSelection } from "../../../../platform/opener/common/opener.js"; -import { IQuickInputService } from "../../../../platform/quickinput/common/quickInput.js"; -import { defaultBreadcrumbsWidgetStyles } from "../../../../platform/theme/browser/defaultStyles.js"; -import { registerIcon } from "../../../../platform/theme/common/iconRegistry.js"; -import { - EditorResourceAccessor, - IEditorPartOptions, - SideBySideEditor, -} from "../../../common/editor.js"; -import { IEditorGroupsService } from "../../../services/editor/common/editorGroupsService.js"; -import { - ACTIVE_GROUP, - ACTIVE_GROUP_TYPE, - IEditorService, - SIDE_GROUP, - SIDE_GROUP_TYPE, -} from "../../../services/editor/common/editorService.js"; -import { IOutline } from "../../../services/outline/browser/outline.js"; -import { DEFAULT_LABELS_CONTAINER, ResourceLabels } from "../../labels.js"; -import { BreadcrumbsConfig, IBreadcrumbsService } from "./breadcrumbs.js"; -import { - BreadcrumbsModel, - FileElement, - OutlineElement2, -} from "./breadcrumbsModel.js"; -import { - BreadcrumbsFilePicker, - BreadcrumbsOutlinePicker, - BreadcrumbsPicker, -} from "./breadcrumbsPicker.js"; -import { IEditorGroupView } from "./editor.js"; +import * as dom from '../../../../base/browser/dom.js'; +import { StandardMouseEvent } from '../../../../base/browser/mouseEvent.js'; +import { BreadcrumbsItem, BreadcrumbsWidget, IBreadcrumbsItemEvent, IBreadcrumbsWidgetStyles } from '../../../../base/browser/ui/breadcrumbs/breadcrumbsWidget.js'; +import { timeout } from '../../../../base/common/async.js'; +import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; +import { combinedDisposable, DisposableStore, IDisposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; +import { basename, extUri } from '../../../../base/common/resources.js'; +import { URI } from '../../../../base/common/uri.js'; +import './media/breadcrumbscontrol.css'; +import { localize, localize2 } from '../../../../nls.js'; +import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextViewService } from '../../../../platform/contextview/browser/contextView.js'; +import { FileKind, IFileService, IFileStat } from '../../../../platform/files/common/files.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { KeybindingsRegistry, KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { IListService, WorkbenchAsyncDataTree, WorkbenchDataTree, WorkbenchListFocusContextKey } from '../../../../platform/list/browser/listService.js'; +import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js'; +import { DEFAULT_LABELS_CONTAINER, ResourceLabels } from '../../labels.js'; +import { BreadcrumbsConfig, IBreadcrumbsService } from './breadcrumbs.js'; +import { BreadcrumbsModel, FileElement, OutlineElement2 } from './breadcrumbsModel.js'; +import { BreadcrumbsFilePicker, BreadcrumbsOutlinePicker, BreadcrumbsPicker } from './breadcrumbsPicker.js'; +import { IEditorPartOptions, EditorResourceAccessor, SideBySideEditor } from '../../../common/editor.js'; +import { ACTIVE_GROUP, ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP, SIDE_GROUP_TYPE } from '../../../services/editor/common/editorService.js'; +import { IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js'; +import { IEditorGroupView } from './editor.js'; +import { PixelRatio } from '../../../../base/browser/pixelRatio.js'; +import { ILabelService } from '../../../../platform/label/common/label.js'; +import { Categories } from '../../../../platform/action/common/actionCommonCategories.js'; +import { IOutline } from '../../../services/outline/browser/outline.js'; +import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { defaultBreadcrumbsWidgetStyles } from '../../../../platform/theme/browser/defaultStyles.js'; +import { Emitter } from '../../../../base/common/event.js'; +import { IHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegate.js'; +import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; +import { DataTransfers } from '../../../../base/browser/dnd.js'; +import { $ } from '../../../../base/browser/dom.js'; +import { OutlineElement } from '../../../../editor/contrib/documentSymbols/browser/outlineModel.js'; +import { CodeDataTransfers, DocumentSymbolTransferData } from '../../../../platform/dnd/browser/dnd.js'; +import { withSelection } from '../../../../platform/opener/common/opener.js'; class OutlineItem extends BreadcrumbsItem { + private readonly _disposables = new DisposableStore(); constructor( readonly model: BreadcrumbsModel, readonly element: OutlineElement2, - readonly options: IBreadcrumbsControlOptions, + readonly options: IBreadcrumbsControlOptions ) { super(); } @@ -123,54 +66,41 @@ class OutlineItem extends BreadcrumbsItem { if (!(other instanceof OutlineItem)) { return false; } - return ( - this.element.element === other.element.element && + return this.element.element === other.element.element && this.options.showFileIcons === other.options.showFileIcons && - this.options.showSymbolIcons === other.options.showSymbolIcons - ); + this.options.showSymbolIcons === other.options.showSymbolIcons; } render(container: HTMLElement): void { const { element, outline } = this.element; if (element === outline) { - const element = dom.$("span", undefined, "…"); + const element = dom.$('span', undefined, '…'); container.appendChild(element); return; } const templateId = outline.config.delegate.getTemplateId(element); - const renderer = outline.config.renderers.find( - (renderer) => renderer.templateId === templateId, - ); + const renderer = outline.config.renderers.find(renderer => renderer.templateId === templateId); if (!renderer) { - container.innerText = "<>"; + container.innerText = '<>'; return; } const template = renderer.renderTemplate(container); - renderer.renderElement( - { - element, - children: [], - depth: 0, - visibleChildrenCount: 0, - visibleChildIndex: 0, - collapsible: false, - collapsed: false, - visible: true, - filterData: undefined, - }, - 0, - template, - undefined, - ); - - this._disposables.add( - toDisposable(() => { - renderer.disposeTemplate(template); - }), - ); + renderer.renderElement({ + element, + children: [], + depth: 0, + visibleChildrenCount: 0, + visibleChildIndex: 0, + collapsible: false, + collapsed: false, + visible: true, + filterData: undefined + }, 0, template, undefined); + + this._disposables.add(toDisposable(() => { renderer.disposeTemplate(template); })); if (element instanceof OutlineElement && outline.uri) { const symbolUri = withSelection(outline.uri, element.symbol.range); @@ -178,25 +108,20 @@ class OutlineItem extends BreadcrumbsItem { name: element.symbol.name, fsPath: outline.uri.fsPath, range: element.symbol.range, - kind: element.symbol.kind, + kind: element.symbol.kind }; const dataTransfers: DataTransfer[] = [ [CodeDataTransfers.SYMBOLS, [symbolTransferData]], - [DataTransfers.RESOURCES, [symbolUri]], + [DataTransfers.RESOURCES, [symbolUri.toString()]] ]; - this._disposables.add( - createBreadcrumbDndObserver( - container, - element.symbol.name, - symbolUri.toString(), - dataTransfers, - ), - ); + const textData = symbolUri.fsPath + (symbolUri.fragment ? '#' + symbolUri.fragment : ''); + this._disposables.add(createBreadcrumbDndObserver(container, element.symbol.name, textData, dataTransfers)); } } } class FileItem extends BreadcrumbsItem { + private readonly _disposables = new DisposableStore(); constructor( @@ -204,7 +129,7 @@ class FileItem extends BreadcrumbsItem { readonly element: FileElement, readonly options: IBreadcrumbsControlOptions, private readonly _labels: ResourceLabels, - private readonly _hoverDelegate: IHoverDelegate, + private readonly _hoverDelegate: IHoverDelegate ) { super(); } @@ -217,28 +142,20 @@ class FileItem extends BreadcrumbsItem { if (!(other instanceof FileItem)) { return false; } - return ( - extUri.isEqual(this.element.uri, other.element.uri) && + return (extUri.isEqual(this.element.uri, other.element.uri) && this.options.showFileIcons === other.options.showFileIcons && - this.options.showSymbolIcons === other.options.showSymbolIcons - ); + this.options.showSymbolIcons === other.options.showSymbolIcons); + } render(container: HTMLElement): void { // file/folder - const label = this._labels.create(container, { - hoverDelegate: this._hoverDelegate, - }); + const label = this._labels.create(container, { hoverDelegate: this._hoverDelegate }); label.setFile(this.element.uri, { hidePath: true, - hideIcon: - this.element.kind === FileKind.FOLDER || - !this.options.showFileIcons, + hideIcon: this.element.kind === FileKind.FOLDER || !this.options.showFileIcons, fileKind: this.element.kind, - fileDecorations: { - colors: this.options.showDecorationColors, - badges: false, - }, + fileDecorations: { colors: this.options.showDecorationColors, badges: false }, }); container.classList.add(FileKind[this.element.kind].toLowerCase()); this._disposables.add(label); @@ -247,45 +164,35 @@ class FileItem extends BreadcrumbsItem { [CodeDataTransfers.FILES, [this.element.uri.fsPath]], [DataTransfers.RESOURCES, [this.element.uri.toString()]], ]; - const dndObserver = createBreadcrumbDndObserver( - container, - basename(this.element.uri), - this.element.uri.toString(), - dataTransfers, - ); + const dndObserver = createBreadcrumbDndObserver(container, basename(this.element.uri), this.element.uri.fsPath, dataTransfers); this._disposables.add(dndObserver); } } type DataTransfer = [string, any[]]; -function createBreadcrumbDndObserver( - container: HTMLElement, - label: string, - textData: string, - dataTransfers: DataTransfer[], -): IDisposable { +function createBreadcrumbDndObserver(container: HTMLElement, label: string, textData: string, dataTransfers: DataTransfer[]): IDisposable { container.draggable = true; return new dom.DragAndDropObserver(container, { - onDragStart: (event) => { + onDragStart: event => { if (!event.dataTransfer) { return; } // Set data transfer - event.dataTransfer.effectAllowed = "copyMove"; + event.dataTransfer.effectAllowed = 'copyMove'; event.dataTransfer.setData(DataTransfers.TEXT, textData); for (const [type, data] of dataTransfers) { event.dataTransfer.setData(type, JSON.stringify(data)); } // Create drag image and remove when dropped - const dragImage = $(".monaco-drag-image"); + const dragImage = $('.monaco-drag-image'); dragImage.textContent = label; const getDragImageContainer = (e: HTMLElement | null) => { - while (e && !e.classList.contains("monaco-workbench")) { + while (e && !e.classList.contains('monaco-workbench')) { e = e.parentElement; } return e || container.ownerDocument; @@ -295,7 +202,7 @@ function createBreadcrumbDndObserver( dragContainer.appendChild(dragImage); event.dataTransfer.setDragImage(dragImage, -10, -10); setTimeout(() => dragImage.remove(), 0); - }, + } }); } @@ -307,45 +214,24 @@ export interface IBreadcrumbsControlOptions { readonly widgetStyles?: IBreadcrumbsWidgetStyles; } -const separatorIcon = registerIcon( - "breadcrumb-separator", - Codicon.chevronRight, - localize("separatorIcon", "Icon for the separator in the breadcrumbs."), -); +const separatorIcon = registerIcon('breadcrumb-separator', Codicon.chevronRight, localize('separatorIcon', 'Icon for the separator in the breadcrumbs.')); export class BreadcrumbsControl { + static readonly HEIGHT = 22; private static readonly SCROLLBAR_SIZES = { default: 3, - large: 8, + large: 8 }; static readonly Payload_Reveal = {}; static readonly Payload_RevealAside = {}; static readonly Payload_Pick = {}; - static readonly CK_BreadcrumbsPossible = new RawContextKey( - "breadcrumbsPossible", - false, - localize( - "breadcrumbsPossible", - "Whether the editor can show breadcrumbs", - ), - ); - static readonly CK_BreadcrumbsVisible = new RawContextKey( - "breadcrumbsVisible", - false, - localize( - "breadcrumbsVisible", - "Whether breadcrumbs are currently visible", - ), - ); - static readonly CK_BreadcrumbsActive = new RawContextKey( - "breadcrumbsActive", - false, - localize("breadcrumbsActive", "Whether breadcrumbs have focus"), - ); + static readonly CK_BreadcrumbsPossible = new RawContextKey('breadcrumbsPossible', false, localize('breadcrumbsPossible', "Whether the editor can show breadcrumbs")); + static readonly CK_BreadcrumbsVisible = new RawContextKey('breadcrumbsVisible', false, localize('breadcrumbsVisible', "Whether breadcrumbs are currently visible")); + static readonly CK_BreadcrumbsActive = new RawContextKey('breadcrumbsActive', false, localize('breadcrumbsActive', "Whether breadcrumbs have focus")); private readonly _ckBreadcrumbsPossible: IContextKey; private readonly _ckBreadcrumbsVisible: IContextKey; @@ -353,9 +239,7 @@ export class BreadcrumbsControl { private readonly _cfUseQuickPick: BreadcrumbsConfig; private readonly _cfShowIcons: BreadcrumbsConfig; - private readonly _cfTitleScrollbarSizing: BreadcrumbsConfig< - IEditorPartOptions["titleScrollbarSizing"] - >; + private readonly _cfTitleScrollbarSizing: BreadcrumbsConfig; readonly domNode: HTMLDivElement; private readonly _widget: BreadcrumbsWidget; @@ -369,89 +253,47 @@ export class BreadcrumbsControl { private readonly _hoverDelegate: IHoverDelegate; - private readonly _onDidVisibilityChange = this._disposables.add( - new Emitter(), - ); - get onDidVisibilityChange() { - return this._onDidVisibilityChange.event; - } + private readonly _onDidVisibilityChange = this._disposables.add(new Emitter()); + get onDidVisibilityChange() { return this._onDidVisibilityChange.event; } constructor( container: HTMLElement, private readonly _options: IBreadcrumbsControlOptions, private readonly _editorGroup: IEditorGroupView, - @IContextKeyService - private readonly _contextKeyService: IContextKeyService, - @IContextViewService - private readonly _contextViewService: IContextViewService, - @IInstantiationService - private readonly _instantiationService: IInstantiationService, - @IQuickInputService - private readonly _quickInputService: IQuickInputService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IContextViewService private readonly _contextViewService: IContextViewService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IQuickInputService private readonly _quickInputService: IQuickInputService, @IFileService private readonly _fileService: IFileService, @IEditorService private readonly _editorService: IEditorService, @ILabelService private readonly _labelService: ILabelService, @IConfigurationService configurationService: IConfigurationService, - @IBreadcrumbsService breadcrumbsService: IBreadcrumbsService, + @IBreadcrumbsService breadcrumbsService: IBreadcrumbsService ) { - this.domNode = document.createElement("div"); - this.domNode.classList.add("breadcrumbs-control"); + this.domNode = document.createElement('div'); + this.domNode.classList.add('breadcrumbs-control'); dom.append(container, this.domNode); - this._cfUseQuickPick = - BreadcrumbsConfig.UseQuickPick.bindTo(configurationService); - this._cfShowIcons = - BreadcrumbsConfig.Icons.bindTo(configurationService); - this._cfTitleScrollbarSizing = - BreadcrumbsConfig.TitleScrollbarSizing.bindTo(configurationService); + this._cfUseQuickPick = BreadcrumbsConfig.UseQuickPick.bindTo(configurationService); + this._cfShowIcons = BreadcrumbsConfig.Icons.bindTo(configurationService); + this._cfTitleScrollbarSizing = BreadcrumbsConfig.TitleScrollbarSizing.bindTo(configurationService); - this._labels = this._instantiationService.createInstance( - ResourceLabels, - DEFAULT_LABELS_CONTAINER, - ); + this._labels = this._instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER); - const sizing = this._cfTitleScrollbarSizing.getValue() ?? "default"; + const sizing = this._cfTitleScrollbarSizing.getValue() ?? 'default'; const styles = _options.widgetStyles ?? defaultBreadcrumbsWidgetStyles; - this._widget = new BreadcrumbsWidget( - this.domNode, - BreadcrumbsControl.SCROLLBAR_SIZES[sizing], - separatorIcon, - styles, - ); - this._widget.onDidSelectItem( - this._onSelectEvent, - this, - this._disposables, - ); - this._widget.onDidFocusItem( - this._onFocusEvent, - this, - this._disposables, - ); - this._widget.onDidChangeFocus( - this._updateCkBreadcrumbsActive, - this, - this._disposables, - ); + this._widget = new BreadcrumbsWidget(this.domNode, BreadcrumbsControl.SCROLLBAR_SIZES[sizing], separatorIcon, styles); + this._widget.onDidSelectItem(this._onSelectEvent, this, this._disposables); + this._widget.onDidFocusItem(this._onFocusEvent, this, this._disposables); + this._widget.onDidChangeFocus(this._updateCkBreadcrumbsActive, this, this._disposables); - this._ckBreadcrumbsPossible = - BreadcrumbsControl.CK_BreadcrumbsPossible.bindTo( - this._contextKeyService, - ); - this._ckBreadcrumbsVisible = - BreadcrumbsControl.CK_BreadcrumbsVisible.bindTo( - this._contextKeyService, - ); - this._ckBreadcrumbsActive = - BreadcrumbsControl.CK_BreadcrumbsActive.bindTo( - this._contextKeyService, - ); - - this._hoverDelegate = getDefaultHoverDelegate("mouse"); - - this._disposables.add( - breadcrumbsService.register(this._editorGroup.id, this._widget), - ); + this._ckBreadcrumbsPossible = BreadcrumbsControl.CK_BreadcrumbsPossible.bindTo(this._contextKeyService); + this._ckBreadcrumbsVisible = BreadcrumbsControl.CK_BreadcrumbsVisible.bindTo(this._contextKeyService); + this._ckBreadcrumbsActive = BreadcrumbsControl.CK_BreadcrumbsActive.bindTo(this._contextKeyService); + + this._hoverDelegate = getDefaultHoverDelegate('mouse'); + + this._disposables.add(breadcrumbsService.register(this._editorGroup.id, this._widget)); this.hide(); } @@ -477,7 +319,7 @@ export class BreadcrumbsControl { } isHidden(): boolean { - return this.domNode.classList.contains("hidden"); + return this.domNode.classList.contains('hidden'); } hide(): void { @@ -485,7 +327,7 @@ export class BreadcrumbsControl { this._breadcrumbsDisposables.clear(); this._ckBreadcrumbsVisible.set(false); - this.domNode.classList.toggle("hidden", true); + this.domNode.classList.toggle('hidden', true); if (!wasHidden) { this._onDidVisibilityChange.fire(); @@ -496,7 +338,7 @@ export class BreadcrumbsControl { const wasHidden = this.isHidden(); this._ckBreadcrumbsVisible.set(true); - this.domNode.classList.toggle("hidden", false); + this.domNode.classList.toggle('hidden', false); if (wasHidden) { this._onDidVisibilityChange.fire(); @@ -511,10 +353,7 @@ export class BreadcrumbsControl { this._breadcrumbsDisposables.clear(); // honor diff editors and such - const uri = EditorResourceAccessor.getCanonicalUri( - this._editorGroup.activeEditor, - { supportSideBySide: SideBySideEditor.PRIMARY }, - ); + const uri = EditorResourceAccessor.getCanonicalUri(this._editorGroup.activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY }); const wasHidden = this.isHidden(); if (!uri || !this._fileService.hasProvider(uri)) { @@ -530,63 +369,41 @@ export class BreadcrumbsControl { } // display uri which can be derived from certain inputs - const fileInfoUri = EditorResourceAccessor.getOriginalUri( - this._editorGroup.activeEditor, - { supportSideBySide: SideBySideEditor.PRIMARY }, - ); + const fileInfoUri = EditorResourceAccessor.getOriginalUri(this._editorGroup.activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY }); this.show(); this._ckBreadcrumbsPossible.set(true); - const model = this._instantiationService.createInstance( - BreadcrumbsModel, + const model = this._instantiationService.createInstance(BreadcrumbsModel, fileInfoUri ?? uri, - this._editorGroup.activeEditorPane, + this._editorGroup.activeEditorPane ); this._model.value = model; - this.domNode.classList.toggle( - "backslash-path", - this._labelService.getSeparator(uri.scheme, uri.authority) === "\\", - ); + this.domNode.classList.toggle('backslash-path', this._labelService.getSeparator(uri.scheme, uri.authority) === '\\'); const updateBreadcrumbs = () => { - this.domNode.classList.toggle("relative-path", model.isRelative()); + this.domNode.classList.toggle('relative-path', model.isRelative()); const showIcons = this._cfShowIcons.getValue(); const options: IBreadcrumbsControlOptions = { ...this._options, showFileIcons: this._options.showFileIcons && showIcons, - showSymbolIcons: this._options.showSymbolIcons && showIcons, + showSymbolIcons: this._options.showSymbolIcons && showIcons }; - const items = model - .getElements() - .map((element) => - element instanceof FileElement - ? new FileItem( - model, - element, - options, - this._labels, - this._hoverDelegate, - ) - : new OutlineItem(model, element, options), - ); + const items = model.getElements().map(element => element instanceof FileElement ? new FileItem(model, element, options, this._labels, this._hoverDelegate) : new OutlineItem(model, element, options)); if (items.length === 0) { this._widget.setEnabled(false); - this._widget.setItems([ - new (class extends BreadcrumbsItem { - render(container: HTMLElement): void { - container.innerText = localize( - "empty", - "no elements", - ); - } - equals(other: BreadcrumbsItem): boolean { - return other === this; - } - dispose(): void {} - })(), - ]); + this._widget.setItems([new class extends BreadcrumbsItem { + render(container: HTMLElement): void { + container.innerText = localize('empty', "no elements"); + } + equals(other: BreadcrumbsItem): boolean { + return other === this; + } + dispose(): void { + + } + }]); } else { this._widget.setEnabled(true); this._widget.setItems(items); @@ -598,23 +415,16 @@ export class BreadcrumbsControl { updateBreadcrumbs(); this._breadcrumbsDisposables.clear(); this._breadcrumbsDisposables.add(listener); - this._breadcrumbsDisposables.add( - toDisposable(() => this._model.clear()), - ); + this._breadcrumbsDisposables.add(toDisposable(() => this._model.clear())); this._breadcrumbsDisposables.add(configListener); - this._breadcrumbsDisposables.add( - toDisposable(() => this._widget.setItems([])), - ); + this._breadcrumbsDisposables.add(toDisposable(() => this._widget.setItems([]))); const updateScrollbarSizing = () => { - const sizing = this._cfTitleScrollbarSizing.getValue() ?? "default"; - this._widget.setHorizontalScrollbarSize( - BreadcrumbsControl.SCROLLBAR_SIZES[sizing], - ); + const sizing = this._cfTitleScrollbarSizing.getValue() ?? 'default'; + this._widget.setHorizontalScrollbarSize(BreadcrumbsControl.SCROLLBAR_SIZES[sizing]); }; updateScrollbarSizing(); - const updateScrollbarSizeListener = - this._cfTitleScrollbarSizing.onDidChange(updateScrollbarSizing); + const updateScrollbarSizeListener = this._cfTitleScrollbarSizing.onDidChange(updateScrollbarSizing); this._breadcrumbsDisposables.add(updateScrollbarSizeListener); // close picker on hide/update @@ -623,7 +433,7 @@ export class BreadcrumbsControl { if (this._breadcrumbsPickerShowing) { this._contextViewService.hideContextView({ source: this }); } - }, + } }); return wasHidden !== this.isHidden(); @@ -664,9 +474,7 @@ export class BreadcrumbsControl { // using quick pick this._widget.setFocused(undefined); this._widget.setSelection(undefined); - this._quickInputService.quickAccess.show( - element instanceof OutlineElement2 ? "@" : "", - ); + this._quickInputService.quickAccess.show(element instanceof OutlineElement2 ? '@' : ''); return; } @@ -674,43 +482,22 @@ export class BreadcrumbsControl { let picker: BreadcrumbsPicker; let pickerAnchor: { x: number; y: number }; - interface IHideData { - didPick?: boolean; - source?: BreadcrumbsControl; - } + interface IHideData { didPick?: boolean; source?: BreadcrumbsControl } this._contextViewService.showContextView({ render: (parent: HTMLElement) => { if (event.item instanceof FileItem) { - picker = this._instantiationService.createInstance( - BreadcrumbsFilePicker, - parent, - event.item.model.resource, - ); + picker = this._instantiationService.createInstance(BreadcrumbsFilePicker, parent, event.item.model.resource); } else if (event.item instanceof OutlineItem) { - picker = this._instantiationService.createInstance( - BreadcrumbsOutlinePicker, - parent, - event.item.model.resource, - ); + picker = this._instantiationService.createInstance(BreadcrumbsOutlinePicker, parent, event.item.model.resource); } - const selectListener = picker.onWillPickElement(() => - this._contextViewService.hideContextView({ - source: this, - didPick: true, - }), - ); - const zoomListener = PixelRatio.getInstance( - dom.getWindow(this.domNode), - ).onDidChange(() => - this._contextViewService.hideContextView({ source: this }), - ); + const selectListener = picker.onWillPickElement(() => this._contextViewService.hideContextView({ source: this, didPick: true })); + const zoomListener = PixelRatio.getInstance(dom.getWindow(this.domNode)).onDidChange(() => this._contextViewService.hideContextView({ source: this })); const focusTracker = dom.trackFocus(parent); const blurListener = focusTracker.onDidBlur(() => { - this._breadcrumbsPickerIgnoreOnceItem = - this._widget.isDOMFocused() ? event.item : undefined; + this._breadcrumbsPickerIgnoreOnceItem = this._widget.isDOMFocused() ? event.item : undefined; this._contextViewService.hideContextView({ source: this }); }); @@ -722,58 +509,39 @@ export class BreadcrumbsControl { selectListener, zoomListener, focusTracker, - blurListener, + blurListener ); }, getAnchor: () => { if (!pickerAnchor) { const window = dom.getWindow(this.domNode); - const maxInnerWidth = - window.innerWidth - 8; /*a little less the full widget*/ + const maxInnerWidth = window.innerWidth - 8 /*a little less the full widget*/; let maxHeight = Math.min(window.innerHeight * 0.7, 300); - const pickerWidth = Math.min( - maxInnerWidth, - Math.max(240, maxInnerWidth / 4.17), - ); + const pickerWidth = Math.min(maxInnerWidth, Math.max(240, maxInnerWidth / 4.17)); const pickerArrowSize = 8; let pickerArrowOffset: number; - const data = dom.getDomNodePagePosition( - event.node.firstChild as HTMLElement, - ); + const data = dom.getDomNodePagePosition(event.node.firstChild as HTMLElement); const y = data.top + data.height + pickerArrowSize; if (y + maxHeight >= window.innerHeight) { - maxHeight = - window.innerHeight - - y - - 30 /* room for shadow and status bar*/; + maxHeight = window.innerHeight - y - 30 /* room for shadow and status bar*/; } let x = data.left; if (x + pickerWidth >= maxInnerWidth) { x = maxInnerWidth - pickerWidth; } if (event.payload instanceof StandardMouseEvent) { - const maxPickerArrowOffset = - pickerWidth - 2 * pickerArrowSize; + const maxPickerArrowOffset = pickerWidth - 2 * pickerArrowSize; pickerArrowOffset = event.payload.posx - x; if (pickerArrowOffset > maxPickerArrowOffset) { - x = Math.min( - maxInnerWidth - pickerWidth, - x + pickerArrowOffset - maxPickerArrowOffset, - ); + x = Math.min(maxInnerWidth - pickerWidth, x + pickerArrowOffset - maxPickerArrowOffset); pickerArrowOffset = maxPickerArrowOffset; } } else { - pickerArrowOffset = data.left + data.width * 0.3 - x; + pickerArrowOffset = (data.left + (data.width * 0.3)) - x; } - picker.show( - element, - maxHeight, - pickerWidth, - pickerArrowSize, - Math.max(0, pickerArrowOffset), - ); + picker.show(element, maxHeight, pickerWidth, pickerArrowSize, Math.max(0, pickerArrowOffset)); pickerAnchor = { x, y }; } return pickerAnchor; @@ -789,51 +557,33 @@ export class BreadcrumbsControl { this._widget.setSelection(undefined); } picker.dispose(); - }, + } }); } private _updateCkBreadcrumbsActive(): void { - const value = - this._widget.isDOMFocused() || this._breadcrumbsPickerShowing; + const value = this._widget.isDOMFocused() || this._breadcrumbsPickerShowing; this._ckBreadcrumbsActive.set(value); } - private async _revealInEditor( - event: IBreadcrumbsItemEvent, - element: FileElement | OutlineElement2, - group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined, - pinned: boolean = false, - ): Promise { + private async _revealInEditor(event: IBreadcrumbsItemEvent, element: FileElement | OutlineElement2, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined, pinned: boolean = false): Promise { + if (element instanceof FileElement) { if (element.kind === FileKind.FILE) { - await this._editorService.openEditor( - { resource: element.uri, options: { pinned } }, - group, - ); + await this._editorService.openEditor({ resource: element.uri, options: { pinned } }, group); } else { // show next picker const items = this._widget.getItems(); const idx = items.indexOf(event.item); this._widget.setFocused(items[idx + 1]); - this._widget.setSelection( - items[idx + 1], - BreadcrumbsControl.Payload_Pick, - ); + this._widget.setSelection(items[idx + 1], BreadcrumbsControl.Payload_Pick); } } else { - element.outline.reveal( - element, - { pinned }, - group === SIDE_GROUP, - false, - ); + element.outline.reveal(element, { pinned }, group === SIDE_GROUP, false); } } - private _getEditorGroup( - data: object, - ): SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined { + private _getEditorGroup(data: object): SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined { if (data === BreadcrumbsControl.Payload_RevealAside) { return SIDE_GROUP; } else if (data === BreadcrumbsControl.Payload_Reveal) { @@ -845,89 +595,59 @@ export class BreadcrumbsControl { } export class BreadcrumbsControlFactory { + private readonly _disposables = new DisposableStore(); private readonly _controlDisposables = new DisposableStore(); private _control: BreadcrumbsControl | undefined; - get control() { - return this._control; - } + get control() { return this._control; } - private readonly _onDidEnablementChange = this._disposables.add( - new Emitter(), - ); - get onDidEnablementChange() { - return this._onDidEnablementChange.event; - } + private readonly _onDidEnablementChange = this._disposables.add(new Emitter()); + get onDidEnablementChange() { return this._onDidEnablementChange.event; } - private readonly _onDidVisibilityChange = this._disposables.add( - new Emitter(), - ); - get onDidVisibilityChange() { - return this._onDidVisibilityChange.event; - } + private readonly _onDidVisibilityChange = this._disposables.add(new Emitter()); + get onDidVisibilityChange() { return this._onDidVisibilityChange.event; } constructor( private readonly _container: HTMLElement, private readonly _editorGroup: IEditorGroupView, private readonly _options: IBreadcrumbsControlOptions, @IConfigurationService configurationService: IConfigurationService, - @IInstantiationService - private readonly _instantiationService: IInstantiationService, - @IFileService fileService: IFileService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IFileService fileService: IFileService ) { - const config = this._disposables.add( - BreadcrumbsConfig.IsEnabled.bindTo(configurationService), - ); - this._disposables.add( - config.onDidChange(() => { - const value = config.getValue(); - if (!value && this._control) { - this._controlDisposables.clear(); - this._control = undefined; - this._onDidEnablementChange.fire(); - } else if (value && !this._control) { - this._control = this.createControl(); - this._control.update(); - this._onDidEnablementChange.fire(); - } - }), - ); + const config = this._disposables.add(BreadcrumbsConfig.IsEnabled.bindTo(configurationService)); + this._disposables.add(config.onDidChange(() => { + const value = config.getValue(); + if (!value && this._control) { + this._controlDisposables.clear(); + this._control = undefined; + this._onDidEnablementChange.fire(); + } else if (value && !this._control) { + this._control = this.createControl(); + this._control.update(); + this._onDidEnablementChange.fire(); + } + })); if (config.getValue()) { this._control = this.createControl(); } - this._disposables.add( - fileService.onDidChangeFileSystemProviderRegistrations((e) => { - if ( - this._control?.model && - this._control.model.resource.scheme !== e.scheme - ) { - // ignore if the scheme of the breadcrumbs resource is not affected - return; - } - if (this._control?.update()) { - this._onDidEnablementChange.fire(); - } - }), - ); + this._disposables.add(fileService.onDidChangeFileSystemProviderRegistrations(e => { + if (this._control?.model && this._control.model.resource.scheme !== e.scheme) { + // ignore if the scheme of the breadcrumbs resource is not affected + return; + } + if (this._control?.update()) { + this._onDidEnablementChange.fire(); + } + })); } private createControl(): BreadcrumbsControl { - const control = this._controlDisposables.add( - this._instantiationService.createInstance( - BreadcrumbsControl, - this._container, - this._options, - this._editorGroup, - ), - ); - this._controlDisposables.add( - control.onDidVisibilityChange(() => - this._onDidVisibilityChange.fire(), - ), - ); + const control = this._controlDisposables.add(this._instantiationService.createInstance(BreadcrumbsControl, this._container, this._options, this._editorGroup)); + this._controlDisposables.add(control.onDidVisibilityChange(() => this._onDidVisibilityChange.fire())); return control; } @@ -941,71 +661,41 @@ export class BreadcrumbsControlFactory { //#region commands // toggle command -registerAction2( - class ToggleBreadcrumb extends Action2 { - constructor() { - super({ - id: "breadcrumbs.toggle", - title: { - ...localize2("cmd.toggle", "Toggle Breadcrumbs"), - mnemonicTitle: localize( - { - key: "miBreadcrumbs", - comment: ["&& denotes a mnemonic"], - }, - "Toggle &&Breadcrumbs", - ), - }, - category: Categories.View, - toggled: { - condition: ContextKeyExpr.equals( - "config.breadcrumbs.enabled", - true, - ), - title: localize("cmd.toggle2", "Toggle Breadcrumbs"), - mnemonicTitle: localize( - { - key: "miBreadcrumbs2", - comment: ["&& denotes a mnemonic"], - }, - "Toggle &&Breadcrumbs", - ), - }, - menu: [ - { id: MenuId.CommandPalette }, - { - id: MenuId.MenubarAppearanceMenu, - group: "4_editor", - order: 2, - }, - { - id: MenuId.NotebookToolbar, - group: "notebookLayout", - order: 2, - }, - { id: MenuId.StickyScrollContext }, - { - id: MenuId.NotebookStickyScrollContext, - group: "notebookView", - order: 2, - }, - ], - }); - } +registerAction2(class ToggleBreadcrumb extends Action2 { + + constructor() { + super({ + id: 'breadcrumbs.toggle', + title: { + ...localize2('cmd.toggle', "Toggle Breadcrumbs"), + mnemonicTitle: localize({ key: 'miBreadcrumbs', comment: ['&& denotes a mnemonic'] }, "Toggle &&Breadcrumbs"), + }, + category: Categories.View, + toggled: { + condition: ContextKeyExpr.equals('config.breadcrumbs.enabled', true), + title: localize('cmd.toggle2', "Toggle Breadcrumbs"), + mnemonicTitle: localize({ key: 'miBreadcrumbs2', comment: ['&& denotes a mnemonic'] }, "Toggle &&Breadcrumbs") + }, + menu: [ + { id: MenuId.CommandPalette }, + { id: MenuId.MenubarAppearanceMenu, group: '4_editor', order: 2 }, + { id: MenuId.NotebookToolbar, group: 'notebookLayout', order: 2 }, + { id: MenuId.StickyScrollContext }, + { id: MenuId.NotebookStickyScrollContext, group: 'notebookView', order: 2 } + ] + }); + } - run(accessor: ServicesAccessor): void { - const config = accessor.get(IConfigurationService); - const value = BreadcrumbsConfig.IsEnabled.bindTo(config).getValue(); - BreadcrumbsConfig.IsEnabled.bindTo(config).updateValue(!value); - } - }, -); + run(accessor: ServicesAccessor): void { + const config = accessor.get(IConfigurationService); + const value = BreadcrumbsConfig.IsEnabled.bindTo(config).getValue(); + BreadcrumbsConfig.IsEnabled.bindTo(config).updateValue(!value); + } + +}); // focus/focus-and-select -function focusAndSelectHandler( - accessor: ServicesAccessor, - select: boolean, -): void { +function focusAndSelectHandler(accessor: ServicesAccessor, select: boolean): void { // find widget and focus/select const groups = accessor.get(IEditorGroupsService); const breadcrumbs = accessor.get(IBreadcrumbsService); @@ -1018,59 +708,52 @@ function focusAndSelectHandler( } } } -registerAction2( - class FocusAndSelectBreadcrumbs extends Action2 { - constructor() { - super({ - id: "breadcrumbs.focusAndSelect", - title: localize2( - "cmd.focusAndSelect", - "Focus and Select Breadcrumbs", - ), - precondition: BreadcrumbsControl.CK_BreadcrumbsVisible, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Period, - when: BreadcrumbsControl.CK_BreadcrumbsPossible, - }, - f1: true, - }); - } - run(accessor: ServicesAccessor, ...args: any[]): void { - focusAndSelectHandler(accessor, true); - } - }, -); - -registerAction2( - class FocusBreadcrumbs extends Action2 { - constructor() { - super({ - id: "breadcrumbs.focus", - title: localize2("cmd.focus", "Focus Breadcrumbs"), - precondition: BreadcrumbsControl.CK_BreadcrumbsVisible, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Semicolon, - when: BreadcrumbsControl.CK_BreadcrumbsPossible, - }, - f1: true, - }); - } - run(accessor: ServicesAccessor, ...args: any[]): void { - focusAndSelectHandler(accessor, false); - } - }, -); +registerAction2(class FocusAndSelectBreadcrumbs extends Action2 { + constructor() { + super({ + id: 'breadcrumbs.focusAndSelect', + title: localize2('cmd.focusAndSelect', "Focus and Select Breadcrumbs"), + precondition: BreadcrumbsControl.CK_BreadcrumbsVisible, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Period, + when: BreadcrumbsControl.CK_BreadcrumbsPossible, + }, + f1: true + }); + } + run(accessor: ServicesAccessor, ...args: any[]): void { + focusAndSelectHandler(accessor, true); + } +}); + +registerAction2(class FocusBreadcrumbs extends Action2 { + constructor() { + super({ + id: 'breadcrumbs.focus', + title: localize2('cmd.focus', "Focus Breadcrumbs"), + precondition: BreadcrumbsControl.CK_BreadcrumbsVisible, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Semicolon, + when: BreadcrumbsControl.CK_BreadcrumbsPossible, + }, + f1: true + }); + } + run(accessor: ServicesAccessor, ...args: any[]): void { + focusAndSelectHandler(accessor, false); + } +}); // this commands is only enabled when breadcrumbs are // disabled which it then enables and focuses KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: "breadcrumbs.toggleToOn", + id: 'breadcrumbs.toggleToOn', weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Period, - when: ContextKeyExpr.not("config.breadcrumbs.enabled"), - handler: async (accessor) => { + when: ContextKeyExpr.not('config.breadcrumbs.enabled'), + handler: async accessor => { const instant = accessor.get(IInstantiationService); const config = accessor.get(IConfigurationService); // check if enabled and iff not enable @@ -1080,12 +763,12 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ await timeout(50); // hacky - the widget might not be ready yet... } return instant.invokeFunction(focusAndSelectHandler, true); - }, + } }); // navigation KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: "breadcrumbs.focusNext", + id: 'breadcrumbs.focusNext', weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.RightArrow, secondary: [KeyMod.CtrlCmd | KeyCode.RightArrow], @@ -1093,10 +776,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: KeyCode.RightArrow, secondary: [KeyMod.Alt | KeyCode.RightArrow], }, - when: ContextKeyExpr.and( - BreadcrumbsControl.CK_BreadcrumbsVisible, - BreadcrumbsControl.CK_BreadcrumbsActive, - ), + when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive), handler(accessor) { const groups = accessor.get(IEditorGroupsService); const breadcrumbs = accessor.get(IBreadcrumbsService); @@ -1105,10 +785,10 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ return; } widget.focusNext(); - }, + } }); KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: "breadcrumbs.focusPrevious", + id: 'breadcrumbs.focusPrevious', weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.LeftArrow, secondary: [KeyMod.CtrlCmd | KeyCode.LeftArrow], @@ -1116,10 +796,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: KeyCode.LeftArrow, secondary: [KeyMod.Alt | KeyCode.LeftArrow], }, - when: ContextKeyExpr.and( - BreadcrumbsControl.CK_BreadcrumbsVisible, - BreadcrumbsControl.CK_BreadcrumbsActive, - ), + when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive), handler(accessor) { const groups = accessor.get(IEditorGroupsService); const breadcrumbs = accessor.get(IBreadcrumbsService); @@ -1128,20 +805,16 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ return; } widget.focusPrev(); - }, + } }); KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: "breadcrumbs.focusNextWithPicker", + id: 'breadcrumbs.focusNextWithPicker', weight: KeybindingWeight.WorkbenchContrib + 1, primary: KeyMod.CtrlCmd | KeyCode.RightArrow, mac: { primary: KeyMod.Alt | KeyCode.RightArrow, }, - when: ContextKeyExpr.and( - BreadcrumbsControl.CK_BreadcrumbsVisible, - BreadcrumbsControl.CK_BreadcrumbsActive, - WorkbenchListFocusContextKey, - ), + when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive, WorkbenchListFocusContextKey), handler(accessor) { const groups = accessor.get(IEditorGroupsService); const breadcrumbs = accessor.get(IBreadcrumbsService); @@ -1150,20 +823,16 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ return; } widget.focusNext(); - }, + } }); KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: "breadcrumbs.focusPreviousWithPicker", + id: 'breadcrumbs.focusPreviousWithPicker', weight: KeybindingWeight.WorkbenchContrib + 1, primary: KeyMod.CtrlCmd | KeyCode.LeftArrow, mac: { primary: KeyMod.Alt | KeyCode.LeftArrow, }, - when: ContextKeyExpr.and( - BreadcrumbsControl.CK_BreadcrumbsVisible, - BreadcrumbsControl.CK_BreadcrumbsActive, - WorkbenchListFocusContextKey, - ), + when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive, WorkbenchListFocusContextKey), handler(accessor) { const groups = accessor.get(IEditorGroupsService); const breadcrumbs = accessor.get(IBreadcrumbsService); @@ -1172,17 +841,14 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ return; } widget.focusPrev(); - }, + } }); KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: "breadcrumbs.selectFocused", + id: 'breadcrumbs.selectFocused', weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.Enter, secondary: [KeyCode.DownArrow], - when: ContextKeyExpr.and( - BreadcrumbsControl.CK_BreadcrumbsVisible, - BreadcrumbsControl.CK_BreadcrumbsActive, - ), + when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive), handler(accessor) { const groups = accessor.get(IEditorGroupsService); const breadcrumbs = accessor.get(IBreadcrumbsService); @@ -1190,21 +856,15 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ if (!widget) { return; } - widget.setSelection( - widget.getFocused(), - BreadcrumbsControl.Payload_Pick, - ); - }, + widget.setSelection(widget.getFocused(), BreadcrumbsControl.Payload_Pick); + } }); KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: "breadcrumbs.revealFocused", + id: 'breadcrumbs.revealFocused', weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.Space, secondary: [KeyMod.CtrlCmd | KeyCode.Enter], - when: ContextKeyExpr.and( - BreadcrumbsControl.CK_BreadcrumbsVisible, - BreadcrumbsControl.CK_BreadcrumbsActive, - ), + when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive), handler(accessor) { const groups = accessor.get(IEditorGroupsService); const breadcrumbs = accessor.get(IBreadcrumbsService); @@ -1212,20 +872,14 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ if (!widget) { return; } - widget.setSelection( - widget.getFocused(), - BreadcrumbsControl.Payload_Reveal, - ); - }, + widget.setSelection(widget.getFocused(), BreadcrumbsControl.Payload_Reveal); + } }); KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: "breadcrumbs.selectEditor", + id: 'breadcrumbs.selectEditor', weight: KeybindingWeight.WorkbenchContrib + 1, primary: KeyCode.Escape, - when: ContextKeyExpr.and( - BreadcrumbsControl.CK_BreadcrumbsVisible, - BreadcrumbsControl.CK_BreadcrumbsActive, - ), + when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive), handler(accessor) { const groups = accessor.get(IEditorGroupsService); const breadcrumbs = accessor.get(IBreadcrumbsService); @@ -1236,26 +890,19 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ widget.setFocused(undefined); widget.setSelection(undefined); groups.activeGroup.activeEditorPane?.focus(); - }, + } }); KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: "breadcrumbs.revealFocusedFromTreeAside", + id: 'breadcrumbs.revealFocusedFromTreeAside', weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.Enter, - when: ContextKeyExpr.and( - BreadcrumbsControl.CK_BreadcrumbsVisible, - BreadcrumbsControl.CK_BreadcrumbsActive, - WorkbenchListFocusContextKey, - ), + when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive, WorkbenchListFocusContextKey), handler(accessor) { const editors = accessor.get(IEditorService); const lists = accessor.get(IListService); const tree = lists.lastFocusedList; - if ( - !(tree instanceof WorkbenchDataTree) && - !(tree instanceof WorkbenchAsyncDataTree) - ) { + if (!(tree instanceof WorkbenchDataTree) && !(tree instanceof WorkbenchAsyncDataTree)) { return; } @@ -1263,28 +910,20 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ if (URI.isUri((element)?.resource)) { // IFileStat: open file in editor - return editors.openEditor( - { - resource: (element).resource, - options: { pinned: true }, - }, - SIDE_GROUP, - ); + return editors.openEditor({ + resource: (element).resource, + options: { pinned: true } + }, SIDE_GROUP); } // IOutline: check if this the outline and iff so reveal element const input = tree.getInput(); - if (input && typeof (>input).outlineKind === "string") { - return (>input).reveal( - element, - { - pinned: true, - preserveFocus: false, - }, - true, - false, - ); + if (input && typeof (>input).outlineKind === 'string') { + return (>input).reveal(element, { + pinned: true, + preserveFocus: false + }, true, false); } - }, + } }); //#endregion diff --git a/Source/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/Source/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index 713c79700a4b2..e01631d15529e 100644 --- a/Source/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/Source/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -2,86 +2,35 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { compareFileNames } from "../../../../base/common/comparers.js"; -import { onUnexpectedError } from "../../../../base/common/errors.js"; -import { Emitter, Event } from "../../../../base/common/event.js"; -import { createMatches, FuzzyScore } from "../../../../base/common/filters.js"; -import * as glob from "../../../../base/common/glob.js"; -import { - Disposable, - DisposableStore, - IDisposable, - MutableDisposable, -} from "../../../../base/common/lifecycle.js"; -import { posix, relative } from "../../../../base/common/path.js"; -import { - basename, - dirname, - isEqual, -} from "../../../../base/common/resources.js"; -import { URI } from "../../../../base/common/uri.js"; - -import "./media/breadcrumbscontrol.css"; - -import { - IIdentityProvider, - IKeyboardNavigationLabelProvider, - IListVirtualDelegate, -} from "../../../../base/browser/ui/list/list.js"; -import { IListAccessibilityProvider } from "../../../../base/browser/ui/list/listWidget.js"; -import { - IAsyncDataSource, - ITreeFilter, - ITreeNode, - ITreeRenderer, - ITreeSorter, - TreeVisibility, -} from "../../../../base/browser/ui/tree/tree.js"; -import { ITextResourceConfigurationService } from "../../../../editor/common/services/textResourceConfiguration.js"; -import { localize } from "../../../../nls.js"; -import { IConfigurationService } from "../../../../platform/configuration/common/configuration.js"; -import { IEditorOptions } from "../../../../platform/editor/common/editor.js"; -import { - FileKind, - IFileService, - IFileStat, -} from "../../../../platform/files/common/files.js"; -import { IInstantiationService } from "../../../../platform/instantiation/common/instantiation.js"; -import { - WorkbenchAsyncDataTree, - WorkbenchDataTree, -} from "../../../../platform/list/browser/listService.js"; -import { - breadcrumbsPickerBackground, - widgetBorder, - widgetShadow, -} from "../../../../platform/theme/common/colorRegistry.js"; -import { - IFileIconTheme, - IThemeService, -} from "../../../../platform/theme/common/themeService.js"; -import { - isWorkspace, - isWorkspaceFolder, - IWorkspace, - IWorkspaceContextService, - IWorkspaceFolder, -} from "../../../../platform/workspace/common/workspace.js"; -import { - IEditorService, - SIDE_GROUP, -} from "../../../services/editor/common/editorService.js"; -import { - IOutline, - IOutlineComparator, -} from "../../../services/outline/browser/outline.js"; -import { - DEFAULT_LABELS_CONTAINER, - IResourceLabel, - ResourceLabels, -} from "../../labels.js"; -import { BreadcrumbsConfig } from "./breadcrumbs.js"; -import { FileElement, OutlineElement2 } from "./breadcrumbsModel.js"; + +import { compareFileNames } from '../../../../base/common/comparers.js'; +import { onUnexpectedError } from '../../../../base/common/errors.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; +import { createMatches, FuzzyScore } from '../../../../base/common/filters.js'; +import * as glob from '../../../../base/common/glob.js'; +import { IDisposable, DisposableStore, MutableDisposable, Disposable } from '../../../../base/common/lifecycle.js'; +import { posix, relative } from '../../../../base/common/path.js'; +import { basename, dirname, isEqual } from '../../../../base/common/resources.js'; +import { URI } from '../../../../base/common/uri.js'; +import './media/breadcrumbscontrol.css'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { FileKind, IFileService, IFileStat } from '../../../../platform/files/common/files.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { WorkbenchDataTree, WorkbenchAsyncDataTree } from '../../../../platform/list/browser/listService.js'; +import { breadcrumbsPickerBackground, widgetBorder, widgetShadow } from '../../../../platform/theme/common/colorRegistry.js'; +import { isWorkspace, isWorkspaceFolder, IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from '../../../../platform/workspace/common/workspace.js'; +import { ResourceLabels, IResourceLabel, DEFAULT_LABELS_CONTAINER } from '../../labels.js'; +import { BreadcrumbsConfig } from './breadcrumbs.js'; +import { OutlineElement2, FileElement } from './breadcrumbsModel.js'; +import { IAsyncDataSource, ITreeRenderer, ITreeNode, ITreeFilter, TreeVisibility, ITreeSorter } from '../../../../base/browser/ui/tree/tree.js'; +import { IIdentityProvider, IListVirtualDelegate, IKeyboardNavigationLabelProvider } from '../../../../base/browser/ui/list/list.js'; +import { IFileIconTheme, IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js'; +import { localize } from '../../../../nls.js'; +import { IOutline, IOutlineComparator } from '../../../services/outline/browser/outline.js'; +import { IEditorOptions } from '../../../../platform/editor/common/editor.js'; +import { IEditorService, SIDE_GROUP } from '../../../services/editor/common/editorService.js'; +import { ITextResourceConfigurationService } from '../../../../editor/common/services/textResourceConfiguration.js'; interface ILayoutInfo { maxHeight: number; @@ -90,105 +39,85 @@ interface ILayoutInfo { arrowOffset: number; inputHeight: number; } -type Tree = - | WorkbenchDataTree - | WorkbenchAsyncDataTree; + +type Tree = WorkbenchDataTree | WorkbenchAsyncDataTree; + export interface SelectEvent { target: any; browserEvent: UIEvent; } + export abstract class BreadcrumbsPicker { + protected readonly _disposables = new DisposableStore(); protected readonly _domNode: HTMLDivElement; protected _arrow!: HTMLDivElement; protected _treeContainer!: HTMLDivElement; protected _tree!: Tree; - protected _fakeEvent = new UIEvent("fakeEvent"); + protected _fakeEvent = new UIEvent('fakeEvent'); protected _layoutInfo!: ILayoutInfo; + protected readonly _onWillPickElement = new Emitter(); readonly onWillPickElement: Event = this._onWillPickElement.event; + private readonly _previewDispoables = new MutableDisposable(); constructor( parent: HTMLElement, protected resource: URI, - @IInstantiationService - protected readonly _instantiationService: IInstantiationService, - @IThemeService - protected readonly _themeService: IThemeService, - @IConfigurationService - protected readonly _configurationService: IConfigurationService, + @IInstantiationService protected readonly _instantiationService: IInstantiationService, + @IThemeService protected readonly _themeService: IThemeService, + @IConfigurationService protected readonly _configurationService: IConfigurationService, ) { - this._domNode = document.createElement("div"); - this._domNode.className = "monaco-breadcrumbs-picker show-file-icons"; + this._domNode = document.createElement('div'); + this._domNode.className = 'monaco-breadcrumbs-picker show-file-icons'; parent.appendChild(this._domNode); } + dispose(): void { this._disposables.dispose(); this._previewDispoables.dispose(); this._onWillPickElement.dispose(); this._domNode.remove(); - setTimeout(() => this._tree.dispose(), 0); // tree cannot be disposed while being opened... } - async show( - input: any, - maxHeight: number, - width: number, - arrowSize: number, - arrowOffset: number, - ): Promise { - const theme = this._themeService.getColorTheme(); + async show(input: any, maxHeight: number, width: number, arrowSize: number, arrowOffset: number): Promise { + + const theme = this._themeService.getColorTheme(); const color = theme.getColor(breadcrumbsPickerBackground); - this._arrow = document.createElement("div"); - this._arrow.className = "arrow"; - this._arrow.style.borderColor = `transparent transparent ${color ? color.toString() : ""}`; + + this._arrow = document.createElement('div'); + this._arrow.className = 'arrow'; + this._arrow.style.borderColor = `transparent transparent ${color ? color.toString() : ''}`; this._domNode.appendChild(this._arrow); - this._treeContainer = document.createElement("div"); - this._treeContainer.style.background = color ? color.toString() : ""; - this._treeContainer.style.paddingTop = "2px"; - this._treeContainer.style.borderRadius = "3px"; + + this._treeContainer = document.createElement('div'); + this._treeContainer.style.background = color ? color.toString() : ''; + this._treeContainer.style.paddingTop = '2px'; + this._treeContainer.style.borderRadius = '3px'; this._treeContainer.style.boxShadow = `0 0 8px 2px ${this._themeService.getColorTheme().getColor(widgetShadow)}`; this._treeContainer.style.border = `1px solid ${this._themeService.getColorTheme().getColor(widgetBorder)}`; this._domNode.appendChild(this._treeContainer); - this._layoutInfo = { - maxHeight, - width, - arrowSize, - arrowOffset, - inputHeight: 0, - }; + + this._layoutInfo = { maxHeight, width, arrowSize, arrowOffset, inputHeight: 0 }; this._tree = this._createTree(this._treeContainer, input); - this._disposables.add( - this._tree.onDidOpen(async (e) => { - const { element, editorOptions, sideBySide } = e; - const didReveal = await this._revealElement( - element, - { ...editorOptions, preserveFocus: false }, - sideBySide, - ); + this._disposables.add(this._tree.onDidOpen(async e => { + const { element, editorOptions, sideBySide } = e; + const didReveal = await this._revealElement(element, { ...editorOptions, preserveFocus: false }, sideBySide); + if (!didReveal) { + return; + } + })); + this._disposables.add(this._tree.onDidChangeFocus(e => { + this._previewDispoables.value = this._previewElement(e.elements[0]); + })); + this._disposables.add(this._tree.onDidChangeContentHeight(() => { + this._layout(); + })); - if (!didReveal) { - return; - } - }), - ); - this._disposables.add( - this._tree.onDidChangeFocus((e) => { - this._previewDispoables.value = this._previewElement( - e.elements[0], - ); - }), - ); - this._disposables.add( - this._tree.onDidChangeContentHeight(() => { - this._layout(); - }), - ); this._domNode.focus(); - try { await this._setInput(input); this._layout(); @@ -196,15 +125,13 @@ export abstract class BreadcrumbsPicker { onUnexpectedError(err); } } - protected _layout(): void { - const headerHeight = 2 * this._layoutInfo.arrowSize; - const treeHeight = Math.min( - this._layoutInfo.maxHeight - headerHeight, - this._tree.contentHeight, - ); + protected _layout(): void { + const headerHeight = 2 * this._layoutInfo.arrowSize; + const treeHeight = Math.min(this._layoutInfo.maxHeight - headerHeight, this._tree.contentHeight); const totalHeight = treeHeight + headerHeight; + this._domNode.style.height = `${totalHeight}px`; this._domNode.style.width = `${this._layoutInfo.width}px`; this._arrow.style.top = `-${2 * this._layoutInfo.arrowSize}px`; @@ -214,39 +141,29 @@ export abstract class BreadcrumbsPicker { this._treeContainer.style.width = `${this._layoutInfo.width}px`; this._tree.layout(treeHeight, this._layoutInfo.width); } - restoreViewState(): void {} - protected abstract _setInput( - element: FileElement | OutlineElement2, - ): Promise; - protected abstract _createTree( - container: HTMLElement, - input: any, - ): Tree; + + restoreViewState(): void { } + + protected abstract _setInput(element: FileElement | OutlineElement2): Promise; + protected abstract _createTree(container: HTMLElement, input: any): Tree; protected abstract _previewElement(element: any): IDisposable; - protected abstract _revealElement( - element: any, - options: IEditorOptions, - sideBySide: boolean, - ): Promise; + protected abstract _revealElement(element: any, options: IEditorOptions, sideBySide: boolean): Promise; + } + //#region - Files -class FileVirtualDelegate - implements IListVirtualDelegate -{ + +class FileVirtualDelegate implements IListVirtualDelegate { getHeight(_element: IFileStat | IWorkspaceFolder) { return 22; } getTemplateId(_element: IFileStat | IWorkspaceFolder): string { - return "FileStat"; + return 'FileStat'; } } -class FileIdentityProvider - implements - IIdentityProvider -{ - getId(element: IWorkspace | IWorkspaceFolder | IFileStat | URI): { - toString(): string; - } { + +class FileIdentityProvider implements IIdentityProvider { + getId(element: IWorkspace | IWorkspaceFolder | IFileStat | URI): { toString(): string } { if (URI.isUri(element)) { return element.toString(); } else if (isWorkspace(element)) { @@ -258,31 +175,26 @@ class FileIdentityProvider } } } -class FileDataSource - implements IAsyncDataSource -{ + + +class FileDataSource implements IAsyncDataSource { + constructor( - @IFileService - private readonly _fileService: IFileService, - ) {} - hasChildren( - element: IWorkspace | URI | IWorkspaceFolder | IFileStat, - ): boolean { - return ( - URI.isUri(element) || - isWorkspace(element) || - isWorkspaceFolder(element) || - element.isDirectory - ); + @IFileService private readonly _fileService: IFileService, + ) { } + + hasChildren(element: IWorkspace | URI | IWorkspaceFolder | IFileStat): boolean { + return URI.isUri(element) + || isWorkspace(element) + || isWorkspaceFolder(element) + || element.isDirectory; } - async getChildren( - element: IWorkspace | URI | IWorkspaceFolder | IFileStat, - ): Promise<(IWorkspaceFolder | IFileStat)[]> { + + async getChildren(element: IWorkspace | URI | IWorkspaceFolder | IFileStat): Promise<(IWorkspaceFolder | IFileStat)[]> { if (isWorkspace(element)) { return element.folders; } let uri: URI; - if (isWorkspaceFolder(element)) { uri = element.uri; } else if (URI.isUri(element)) { @@ -291,40 +203,29 @@ class FileDataSource uri = element.resource; } const stat = await this._fileService.resolve(uri); - return stat.children ?? []; } } -class FileRenderer - implements - ITreeRenderer -{ - readonly templateId: string = "FileStat"; + +class FileRenderer implements ITreeRenderer { + + readonly templateId: string = 'FileStat'; constructor( private readonly _labels: ResourceLabels, - @IConfigurationService - private readonly _configService: IConfigurationService, - ) {} + @IConfigurationService private readonly _configService: IConfigurationService, + ) { } + + renderTemplate(container: HTMLElement): IResourceLabel { return this._labels.create(container, { supportHighlights: true }); } - renderElement( - node: ITreeNode, - index: number, - templateData: IResourceLabel, - ): void { - const fileDecorations = this._configService.getValue<{ - colors: boolean; - badges: boolean; - }>("explorer.decorations"); + renderElement(node: ITreeNode, index: number, templateData: IResourceLabel): void { + const fileDecorations = this._configService.getValue<{ colors: boolean; badges: boolean }>('explorer.decorations'); const { element } = node; - let resource: URI; - let fileKind: FileKind; - if (isWorkspaceFolder(element)) { resource = element.uri; fileKind = FileKind.ROOT_FOLDER; @@ -337,115 +238,94 @@ class FileRenderer hidePath: true, fileDecorations: fileDecorations, matches: createMatches(node.filterData), - extraClasses: ["picker-item"], + extraClasses: ['picker-item'] }); } + disposeTemplate(templateData: IResourceLabel): void { templateData.dispose(); } } -class FileNavigationLabelProvider - implements IKeyboardNavigationLabelProvider -{ - getKeyboardNavigationLabel(element: IWorkspaceFolder | IFileStat): { - toString(): string; - } { + +class FileNavigationLabelProvider implements IKeyboardNavigationLabelProvider { + + getKeyboardNavigationLabel(element: IWorkspaceFolder | IFileStat): { toString(): string } { return element.name; } } -class FileAccessibilityProvider - implements IListAccessibilityProvider -{ + +class FileAccessibilityProvider implements IListAccessibilityProvider { + getWidgetAriaLabel(): string { - return localize("breadcrumbs", "Breadcrumbs"); + return localize('breadcrumbs', "Breadcrumbs"); } + getAriaLabel(element: IWorkspaceFolder | IFileStat): string | null { return element.name; } } + class FileFilter implements ITreeFilter { - private readonly _cachedExpressions = new Map< - string, - glob.ParsedExpression - >(); + + private readonly _cachedExpressions = new Map(); private readonly _disposables = new DisposableStore(); constructor( - @IWorkspaceContextService - private readonly _workspaceService: IWorkspaceContextService, - @IConfigurationService - configService: IConfigurationService, + @IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService, + @IConfigurationService configService: IConfigurationService, ) { const config = BreadcrumbsConfig.FileExcludes.bindTo(configService); - const update = () => { - _workspaceService.getWorkspace().folders.forEach((folder) => { - const excludesConfig = config.getValue({ - resource: folder.uri, - }); - + _workspaceService.getWorkspace().folders.forEach(folder => { + const excludesConfig = config.getValue({ resource: folder.uri }); if (!excludesConfig) { return; } // adjust patterns to be absolute in case they aren't // free floating (**/) const adjustedConfig: glob.IExpression = {}; - for (const pattern in excludesConfig) { - if (typeof excludesConfig[pattern] !== "boolean") { + if (typeof excludesConfig[pattern] !== 'boolean') { continue; } - const patternAbs = - pattern.indexOf("**/") !== 0 - ? posix.join(folder.uri.path, pattern) - : pattern; + const patternAbs = pattern.indexOf('**/') !== 0 + ? posix.join(folder.uri.path, pattern) + : pattern; + adjustedConfig[patternAbs] = excludesConfig[pattern]; } - this._cachedExpressions.set( - folder.uri.toString(), - glob.parse(adjustedConfig), - ); + this._cachedExpressions.set(folder.uri.toString(), glob.parse(adjustedConfig)); }); }; update(); this._disposables.add(config); this._disposables.add(config.onDidChange(update)); - this._disposables.add( - _workspaceService.onDidChangeWorkspaceFolders(update), - ); + this._disposables.add(_workspaceService.onDidChangeWorkspaceFolders(update)); } + dispose(): void { this._disposables.dispose(); } - filter( - element: IWorkspaceFolder | IFileStat, - _parentVisibility: TreeVisibility, - ): boolean { + + filter(element: IWorkspaceFolder | IFileStat, _parentVisibility: TreeVisibility): boolean { if (isWorkspaceFolder(element)) { // not a file return true; } - const folder = this._workspaceService.getWorkspaceFolder( - element.resource, - ); - + const folder = this._workspaceService.getWorkspaceFolder(element.resource); if (!folder || !this._cachedExpressions.has(folder.uri.toString())) { // no folder or no filer return true; } - const expression = this._cachedExpressions.get(folder.uri.toString())!; - return !expression( - relative(folder.uri.path, element.resource.path), - basename(element.resource), - ); + const expression = this._cachedExpressions.get(folder.uri.toString())!; + return !expression(relative(folder.uri.path, element.resource.path), basename(element.resource)); } } + + export class FileSorter implements ITreeSorter { - compare( - a: IFileStat | IWorkspaceFolder, - b: IFileStat | IWorkspaceFolder, - ): number { + compare(a: IFileStat | IWorkspaceFolder, b: IFileStat | IWorkspaceFolder): number { if (isWorkspaceFolder(a) && isWorkspaceFolder(b)) { return a.index - b.index; } @@ -459,64 +339,39 @@ export class FileSorter implements ITreeSorter { } } } + export class BreadcrumbsFilePicker extends BreadcrumbsPicker { + constructor( parent: HTMLElement, resource: URI, - @IInstantiationService - instantiationService: IInstantiationService, - @IThemeService - themeService: IThemeService, - @IConfigurationService - configService: IConfigurationService, - @IWorkspaceContextService - private readonly _workspaceService: IWorkspaceContextService, - @IEditorService - private readonly _editorService: IEditorService, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IConfigurationService configService: IConfigurationService, + @IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService, + @IEditorService private readonly _editorService: IEditorService, ) { - super( - parent, - resource, - instantiationService, - themeService, - configService, - ); + super(parent, resource, instantiationService, themeService, configService); } + protected _createTree(container: HTMLElement) { - // tree icon theme specials - this._treeContainer.classList.add("file-icon-themable-tree"); - this._treeContainer.classList.add("show-file-icons"); + // tree icon theme specials + this._treeContainer.classList.add('file-icon-themable-tree'); + this._treeContainer.classList.add('show-file-icons'); const onFileIconThemeChange = (fileIconTheme: IFileIconTheme) => { - this._treeContainer.classList.toggle( - "align-icons-and-twisties", - fileIconTheme.hasFileIcons && !fileIconTheme.hasFolderIcons, - ); - this._treeContainer.classList.toggle( - "hide-arrows", - fileIconTheme.hidesExplorerArrows === true, - ); + this._treeContainer.classList.toggle('align-icons-and-twisties', fileIconTheme.hasFileIcons && !fileIconTheme.hasFolderIcons); + this._treeContainer.classList.toggle('hide-arrows', fileIconTheme.hidesExplorerArrows === true); }; - this._disposables.add( - this._themeService.onDidFileIconThemeChange(onFileIconThemeChange), - ); + this._disposables.add(this._themeService.onDidFileIconThemeChange(onFileIconThemeChange)); onFileIconThemeChange(this._themeService.getFileIconTheme()); - const labels = this._instantiationService.createInstance( - ResourceLabels, - DEFAULT_LABELS_CONTAINER /* TODO@Jo visibility propagation */, - ); + const labels = this._instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER /* TODO@Jo visibility propagation */); this._disposables.add(labels); - return < - WorkbenchAsyncDataTree< - IWorkspace | URI, - IWorkspaceFolder | IFileStat, - FuzzyScore - > - >this._instantiationService.createInstance( - WorkbenchAsyncDataTree, - "BreadcrumbsFilePicker", + return this._instantiationService.createInstance( + WorkbenchAsyncDataTree, + 'BreadcrumbsFilePicker', container, new FileVirtualDelegate(), [this._instantiationService.createInstance(FileRenderer, labels)], @@ -526,48 +381,33 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { sorter: new FileSorter(), filter: this._instantiationService.createInstance(FileFilter), identityProvider: new FileIdentityProvider(), - keyboardNavigationLabelProvider: - new FileNavigationLabelProvider(), - accessibilityProvider: - this._instantiationService.createInstance( - FileAccessibilityProvider, - ), + keyboardNavigationLabelProvider: new FileNavigationLabelProvider(), + accessibilityProvider: this._instantiationService.createInstance(FileAccessibilityProvider), showNotFoundMessage: false, overrideStyles: { - listBackground: breadcrumbsPickerBackground, + listBackground: breadcrumbsPickerBackground }, - }, - ); + }); } - protected async _setInput( - element: FileElement | OutlineElement2, - ): Promise { - const { uri, kind } = element as FileElement; + protected async _setInput(element: FileElement | OutlineElement2): Promise { + const { uri, kind } = (element as FileElement); let input: IWorkspace | URI; - if (kind === FileKind.ROOT_FOLDER) { input = this._workspaceService.getWorkspace(); } else { input = dirname(uri); } - const tree = this._tree as WorkbenchAsyncDataTree< - IWorkspace | URI, - IWorkspaceFolder | IFileStat, - FuzzyScore - >; - await tree.setInput(input); + const tree = this._tree as WorkbenchAsyncDataTree; + await tree.setInput(input); let focusElement: IWorkspaceFolder | IFileStat | undefined; - for (const { element } of tree.getNode().children) { if (isWorkspaceFolder(element) && isEqual(element.uri, uri)) { focusElement = element; - break; } else if (isEqual((element as IFileStat).resource, uri)) { focusElement = element as IFileStat; - break; } } @@ -577,92 +417,79 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { } tree.domFocus(); } + protected _previewElement(_element: any): IDisposable { return Disposable.None; } - protected async _revealElement( - element: IFileStat | IWorkspaceFolder, - options: IEditorOptions, - sideBySide: boolean, - ): Promise { + + protected async _revealElement(element: IFileStat | IWorkspaceFolder, options: IEditorOptions, sideBySide: boolean): Promise { if (!isWorkspaceFolder(element) && element.isFile) { this._onWillPickElement.fire(); - await this._editorService.openEditor( - { resource: element.resource, options }, - sideBySide ? SIDE_GROUP : undefined, - ); - + await this._editorService.openEditor({ resource: element.resource, options }, sideBySide ? SIDE_GROUP : undefined); return true; } return false; } } //#endregion + //#region - Outline + class OutlineTreeSorter implements ITreeSorter { - private _order: "name" | "type" | "position"; + + private _order: 'name' | 'type' | 'position'; constructor( private comparator: IOutlineComparator, uri: URI | undefined, - @ITextResourceConfigurationService - configService: ITextResourceConfigurationService, + @ITextResourceConfigurationService configService: ITextResourceConfigurationService, ) { - this._order = configService.getValue( - uri, - "breadcrumbs.symbolSortOrder", - ); + this._order = configService.getValue(uri, 'breadcrumbs.symbolSortOrder'); } + compare(a: E, b: E): number { - if (this._order === "name") { + if (this._order === 'name') { return this.comparator.compareByName(a, b); - } else if (this._order === "type") { + } else if (this._order === 'type') { return this.comparator.compareByType(a, b); } else { return this.comparator.compareByPosition(a, b); } } } + export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { + protected _createTree(container: HTMLElement, input: OutlineElement2) { + const { config } = input.outline; - return , any, FuzzyScore>>( - this._instantiationService.createInstance( - WorkbenchDataTree, - "BreadcrumbsOutlinePicker", - container, - config.delegate, - config.renderers, - config.treeDataSource, - { - ...config.options, - sorter: this._instantiationService.createInstance( - OutlineTreeSorter, - config.comparator, - undefined, - ), - collapseByDefault: true, - expandOnlyOnTwistieClick: true, - multipleSelectionSupport: false, - showNotFoundMessage: false, - }, - ) + return this._instantiationService.createInstance( + WorkbenchDataTree, any, FuzzyScore>, + 'BreadcrumbsOutlinePicker', + container, + config.delegate, + config.renderers, + config.treeDataSource, + { + ...config.options, + sorter: this._instantiationService.createInstance(OutlineTreeSorter, config.comparator, undefined), + collapseByDefault: true, + expandOnlyOnTwistieClick: true, + multipleSelectionSupport: false, + showNotFoundMessage: false + } ); } + protected _setInput(input: OutlineElement2): Promise { + const viewState = input.outline.captureViewState(); - this.restoreViewState = () => { - viewState.dispose(); - }; + this.restoreViewState = () => { viewState.dispose(); }; - const tree = this._tree as WorkbenchDataTree< - IOutline, - any, - FuzzyScore - >; - tree.setInput(input.outline); + const tree = this._tree as WorkbenchDataTree, any, FuzzyScore>; + tree.setInput(input.outline); if (input.element !== input.outline) { tree.reveal(input.element, 0.5); tree.setFocus([input.element], this._fakeEvent); @@ -671,22 +498,18 @@ export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { return Promise.resolve(); } + protected _previewElement(element: any): IDisposable { const outline: IOutline = this._tree.getInput(); - return outline.preview(element); } - protected async _revealElement( - element: any, - options: IEditorOptions, - sideBySide: boolean, - ): Promise { - this._onWillPickElement.fire(); + protected async _revealElement(element: any, options: IEditorOptions, sideBySide: boolean): Promise { + this._onWillPickElement.fire(); const outline: IOutline = this._tree.getInput(); await outline.reveal(element, options, sideBySide, false); - return true; } } + //#endregion diff --git a/Source/vs/workbench/browser/parts/notifications/notificationsList.ts b/Source/vs/workbench/browser/parts/notifications/notificationsList.ts index b21c3f1d76f81..d0893c26d1d7f 100644 --- a/Source/vs/workbench/browser/parts/notifications/notificationsList.ts +++ b/Source/vs/workbench/browser/parts/notifications/notificationsList.ts @@ -2,41 +2,32 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import "./media/notificationsList.css"; - -import { - getWindow, - isAncestorOfActiveElement, - trackFocus, -} from "../../../../base/browser/dom.js"; -import { AriaRole } from "../../../../base/browser/ui/aria/aria.js"; -import { - IListAccessibilityProvider, - IListOptions, -} from "../../../../base/browser/ui/list/listWidget.js"; -import { Disposable } from "../../../../base/common/lifecycle.js"; -import { assertAllDefined } from "../../../../base/common/types.js"; -import { localize } from "../../../../nls.js"; -import { IConfigurationService } from "../../../../platform/configuration/common/configuration.js"; -import { IContextMenuService } from "../../../../platform/contextview/browser/contextView.js"; -import { IInstantiationService } from "../../../../platform/instantiation/common/instantiation.js"; -import { IKeybindingService } from "../../../../platform/keybinding/common/keybinding.js"; -import { WorkbenchList } from "../../../../platform/list/browser/listService.js"; -import { NotificationFocusedContext } from "../../../common/contextkeys.js"; -import { INotificationViewItem } from "../../../common/notifications.js"; -import { NOTIFICATIONS_BACKGROUND } from "../../../common/theme.js"; -import { CopyNotificationMessageAction } from "./notificationsActions.js"; -import { NotificationActionRunner } from "./notificationsCommands.js"; -import { - NotificationRenderer, - NotificationsListDelegate, -} from "./notificationsViewer.js"; - -export interface INotificationsListOptions - extends IListOptions { + +import './media/notificationsList.css'; +import { localize } from '../../../../nls.js'; +import { getWindow, isAncestorOfActiveElement, trackFocus } from '../../../../base/browser/dom.js'; +import { WorkbenchList } from '../../../../platform/list/browser/listService.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { IListAccessibilityProvider, IListOptions } from '../../../../base/browser/ui/list/listWidget.js'; +import { NOTIFICATIONS_BACKGROUND } from '../../../common/theme.js'; +import { INotificationViewItem } from '../../../common/notifications.js'; +import { NotificationsListDelegate, NotificationRenderer } from './notificationsViewer.js'; +import { CopyNotificationMessageAction } from './notificationsActions.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; +import { assertAllDefined } from '../../../../base/common/types.js'; +import { NotificationFocusedContext } from '../../../common/contextkeys.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { AriaRole } from '../../../../base/browser/ui/aria/aria.js'; +import { NotificationActionRunner } from './notificationsCommands.js'; +import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; + +export interface INotificationsListOptions extends IListOptions { readonly widgetAriaLabel?: string; } + export class NotificationsList extends Disposable { + private listContainer: HTMLElement | undefined; private list: WorkbenchList | undefined; private listDelegate: NotificationsListDelegate | undefined; @@ -46,301 +37,236 @@ export class NotificationsList extends Disposable { constructor( private readonly container: HTMLElement, private readonly options: INotificationsListOptions, - @IInstantiationService - private readonly instantiationService: IInstantiationService, - @IContextMenuService - private readonly contextMenuService: IContextMenuService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IContextMenuService private readonly contextMenuService: IContextMenuService ) { super(); } + show(): void { if (this.isVisible) { return; // already visible } + // Lazily create if showing for the first time if (!this.list) { this.createNotificationsList(); } + // Make visible this.isVisible = true; } + private createNotificationsList(): void { + // List Container - this.listContainer = document.createElement("div"); - this.listContainer.classList.add("notifications-list-container"); + this.listContainer = document.createElement('div'); + this.listContainer.classList.add('notifications-list-container'); + + const actionRunner = this._register(this.instantiationService.createInstance(NotificationActionRunner)); - const actionRunner = this._register( - this.instantiationService.createInstance(NotificationActionRunner), - ); // Notification Renderer - const renderer = this.instantiationService.createInstance( - NotificationRenderer, - actionRunner, - ); + const renderer = this.instantiationService.createInstance(NotificationRenderer, actionRunner); + // List - const listDelegate = (this.listDelegate = new NotificationsListDelegate( + const listDelegate = this.listDelegate = new NotificationsListDelegate(this.listContainer); + const options = this.options; + const list = this.list = this._register(this.instantiationService.createInstance( + WorkbenchList, + 'NotificationsList', this.listContainer, + listDelegate, + [renderer], + { + ...options, + setRowLineHeight: false, + horizontalScrolling: false, + overrideStyles: { + listBackground: NOTIFICATIONS_BACKGROUND + }, + accessibilityProvider: this.instantiationService.createInstance(NotificationAccessibilityProvider, options) + } )); - const options = this.options; - - const list = (this.list = >( - this._register( - this.instantiationService.createInstance( - WorkbenchList, - "NotificationsList", - this.listContainer, - listDelegate, - [renderer], - { - ...options, - setRowLineHeight: false, - horizontalScrolling: false, - overrideStyles: { - listBackground: NOTIFICATIONS_BACKGROUND, - }, - accessibilityProvider: - this.instantiationService.createInstance( - NotificationAccessibilityProvider, - options, - ), - }, - ), - ) - )); // Context menu to copy message - const copyAction = this._register( - this.instantiationService.createInstance( - CopyNotificationMessageAction, - CopyNotificationMessageAction.ID, - CopyNotificationMessageAction.LABEL, - ), - ); - this._register( - list.onContextMenu((e) => { - if (!e.element) { - return; - } - this.contextMenuService.showContextMenu({ - getAnchor: () => e.anchor, - getActions: () => [copyAction], - getActionsContext: () => e.element, - actionRunner, - }); - }), - ); + const copyAction = this._register(this.instantiationService.createInstance(CopyNotificationMessageAction, CopyNotificationMessageAction.ID, CopyNotificationMessageAction.LABEL)); + this._register((list.onContextMenu(e => { + if (!e.element) { + return; + } + + this.contextMenuService.showContextMenu({ + getAnchor: () => e.anchor, + getActions: () => [copyAction], + getActionsContext: () => e.element, + actionRunner + }); + }))); + // Toggle on double click - this._register( - list.onMouseDblClick((event) => - (event.element as INotificationViewItem).toggle(), - ), - ); + this._register((list.onMouseDblClick(event => (event.element as INotificationViewItem).toggle()))); + // Clear focus when DOM focus moves out // Use document.hasFocus() to not clear the focus when the entire window lost focus // This ensures that when the focus comes back, the notification is still focused - const listFocusTracker = this._register( - trackFocus(list.getHTMLElement()), - ); - this._register( - listFocusTracker.onDidBlur(() => { - if (getWindow(this.listContainer).document.hasFocus()) { - list.setFocus([]); - } - }), - ); + const listFocusTracker = this._register(trackFocus(list.getHTMLElement())); + this._register(listFocusTracker.onDidBlur(() => { + if (getWindow(this.listContainer).document.hasFocus()) { + list.setFocus([]); + } + })); + // Context key NotificationFocusedContext.bindTo(list.contextKeyService); + // Only allow for focus in notifications, as the // selection is too strong over the contents of // the notification - this._register( - list.onDidChangeSelection((e) => { - if (e.indexes.length > 0) { - list.setSelection([]); - } - }), - ); + this._register(list.onDidChangeSelection(e => { + if (e.indexes.length > 0) { + list.setSelection([]); + } + })); + this.container.appendChild(this.listContainer); } - updateNotificationsList( - start: number, - deleteCount: number, - items: INotificationViewItem[] = [], - ) { - const [list, listContainer] = assertAllDefined( - this.list, - this.listContainer, - ); + updateNotificationsList(start: number, deleteCount: number, items: INotificationViewItem[] = []) { + const [list, listContainer] = assertAllDefined(this.list, this.listContainer); const listHasDOMFocus = isAncestorOfActiveElement(listContainer); + // Remember focus and relative top of that item const focusedIndex = list.getFocus()[0]; - const focusedItem = this.viewModel[focusedIndex]; let focusRelativeTop: number | null = null; - - if (typeof focusedIndex === "number") { + if (typeof focusedIndex === 'number') { focusRelativeTop = list.getRelativeTop(focusedIndex); } + // Update view model this.viewModel.splice(start, deleteCount, ...items); + // Update list list.splice(start, deleteCount, items); list.layout(); + // Hide if no more notifications to show if (this.viewModel.length === 0) { this.hide(); } + // Otherwise restore focus if we had - else if (typeof focusedIndex === "number") { + else if (typeof focusedIndex === 'number') { let indexToFocus = 0; - if (focusedItem) { let indexToFocusCandidate = this.viewModel.indexOf(focusedItem); - if (indexToFocusCandidate === -1) { indexToFocusCandidate = focusedIndex - 1; // item could have been removed } - if ( - indexToFocusCandidate < this.viewModel.length && - indexToFocusCandidate >= 0 - ) { + + if (indexToFocusCandidate < this.viewModel.length && indexToFocusCandidate >= 0) { indexToFocus = indexToFocusCandidate; } } - if (typeof focusRelativeTop === "number") { + + if (typeof focusRelativeTop === 'number') { list.reveal(indexToFocus, focusRelativeTop); } + list.setFocus([indexToFocus]); } + // Restore DOM focus if we had focus before if (this.isVisible && listHasDOMFocus) { list.domFocus(); } } + updateNotificationHeight(item: INotificationViewItem): void { const index = this.viewModel.indexOf(item); - if (index === -1) { return; } - const [list, listDelegate] = assertAllDefined( - this.list, - this.listDelegate, - ); + + const [list, listDelegate] = assertAllDefined(this.list, this.listDelegate); list.updateElementHeight(index, listDelegate.getHeight(item)); list.layout(); } + hide(): void { if (!this.isVisible || !this.list) { return; // already hidden } + // Hide this.isVisible = false; + // Clear list this.list.splice(0, this.viewModel.length); + // Clear view model this.viewModel = []; } + focusFirst(): void { if (!this.list) { return; // not created yet } + this.list.focusFirst(); this.list.domFocus(); } + hasFocus(): boolean { if (!this.listContainer) { return false; // not created yet } + return isAncestorOfActiveElement(this.listContainer); } + layout(width: number, maxHeight?: number): void { if (this.listContainer && this.list) { this.listContainer.style.width = `${width}px`; - if (typeof maxHeight === "number") { + if (typeof maxHeight === 'number') { this.list.getHTMLElement().style.maxHeight = `${maxHeight}px`; } + this.list.layout(); } } + override dispose(): void { this.hide(); super.dispose(); } } -class NotificationAccessibilityProvider - implements IListAccessibilityProvider -{ + +class NotificationAccessibilityProvider implements IListAccessibilityProvider { constructor( private readonly _options: INotificationsListOptions, - @IKeybindingService - private readonly _keybindingService: IKeybindingService, - @IConfigurationService - private readonly _configurationService: IConfigurationService, - ) {} + @IKeybindingService private readonly _keybindingService: IKeybindingService, + @IConfigurationService private readonly _configurationService: IConfigurationService + ) { } getAriaLabel(element: INotificationViewItem): string { let accessibleViewHint: string | undefined; - - const keybinding = this._keybindingService - .lookupKeybinding("editor.action.accessibleView") - ?.getAriaLabel(); - - if ( - this._configurationService.getValue( - "accessibility.verbosity.notification", - ) - ) { - accessibleViewHint = keybinding - ? localize( - "notificationAccessibleViewHint", - "Inspect the response in the accessible view with {0}", - keybinding, - ) - : localize( - "notificationAccessibleViewHintNoKb", - "Inspect the response in the accessible view via the command Open Accessible View which is currently not triggerable via keybinding", - ); + const keybinding = this._keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel(); + if (this._configurationService.getValue('accessibility.verbosity.notification')) { + accessibleViewHint = keybinding ? localize('notificationAccessibleViewHint', "Inspect the response in the accessible view with {0}", keybinding) : localize('notificationAccessibleViewHintNoKb', "Inspect the response in the accessible view via the command Open Accessible View which is currently not triggerable via keybinding"); } if (!element.source) { - return accessibleViewHint - ? localize( - "notificationAriaLabelHint", - "{0}, notification, {1}", - element.message.raw, - accessibleViewHint, - ) - : localize( - "notificationAriaLabel", - "{0}, notification", - element.message.raw, - ); + return accessibleViewHint ? localize('notificationAriaLabelHint', "{0}, notification, {1}", element.message.raw, accessibleViewHint) : localize('notificationAriaLabel', "{0}, notification", element.message.raw); } - return accessibleViewHint - ? localize( - "notificationWithSourceAriaLabelHint", - "{0}, source: {1}, notification, {2}", - element.message.raw, - element.source, - accessibleViewHint, - ) - : localize( - "notificationWithSourceAriaLabel", - "{0}, source: {1}, notification", - element.message.raw, - element.source, - ); + + return accessibleViewHint ? localize('notificationWithSourceAriaLabelHint', "{0}, source: {1}, notification, {2}", element.message.raw, element.source, accessibleViewHint) : localize('notificationWithSourceAriaLabel', "{0}, source: {1}, notification", element.message.raw, element.source); } getWidgetAriaLabel(): string { - return ( - this._options.widgetAriaLabel ?? - localize("notificationsList", "Notifications List") - ); + return this._options.widgetAriaLabel ?? localize('notificationsList', "Notifications List"); } getRole(): AriaRole { - return "dialog"; // https://github.com/microsoft/vscode/issues/82728 + return 'dialog'; // https://github.com/microsoft/vscode/issues/82728 } } diff --git a/Source/vs/workbench/contrib/bulkEdit/browser/bulkEditService.ts b/Source/vs/workbench/contrib/bulkEdit/browser/bulkEditService.ts index 236b3c4ae4af9..b20903fbab38d 100644 --- a/Source/vs/workbench/contrib/bulkEdit/browser/bulkEditService.ts +++ b/Source/vs/workbench/contrib/bulkEdit/browser/bulkEditService.ts @@ -2,65 +2,36 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken } from "../../../../base/common/cancellation.js"; -import { - IDisposable, - toDisposable, -} from "../../../../base/common/lifecycle.js"; -import { LinkedList } from "../../../../base/common/linkedList.js"; -import { ResourceMap, ResourceSet } from "../../../../base/common/map.js"; -import { URI } from "../../../../base/common/uri.js"; -import { - ICodeEditor, - isCodeEditor, - isDiffEditor, -} from "../../../../editor/browser/editorBrowser.js"; -import { - IBulkEditOptions, - IBulkEditPreviewHandler, - IBulkEditResult, - IBulkEditService, - ResourceEdit, - ResourceFileEdit, - ResourceTextEdit, -} from "../../../../editor/browser/services/bulkEditService.js"; -import { EditorOption } from "../../../../editor/common/config/editorOptions.js"; -import { WorkspaceEdit } from "../../../../editor/common/languages.js"; -import { localize } from "../../../../nls.js"; -import { IConfigurationService } from "../../../../platform/configuration/common/configuration.js"; -import { - Extensions, - IConfigurationRegistry, -} from "../../../../platform/configuration/common/configurationRegistry.js"; -import { IDialogService } from "../../../../platform/dialogs/common/dialogs.js"; -import { - InstantiationType, - registerSingleton, -} from "../../../../platform/instantiation/common/extensions.js"; -import { IInstantiationService } from "../../../../platform/instantiation/common/instantiation.js"; -import { ILogService } from "../../../../platform/log/common/log.js"; -import { - IProgress, - IProgressStep, - Progress, -} from "../../../../platform/progress/common/progress.js"; -import { Registry } from "../../../../platform/registry/common/platform.js"; -import { - UndoRedoGroup, - UndoRedoSource, -} from "../../../../platform/undoRedo/common/undoRedo.js"; -import { IEditorService } from "../../../services/editor/common/editorService.js"; -import { - ILifecycleService, - ShutdownReason, -} from "../../../services/lifecycle/common/lifecycle.js"; -import { IWorkingCopyService } from "../../../services/workingCopy/common/workingCopyService.js"; -import { BulkCellEdits, ResourceNotebookCellEdit } from "./bulkCellEdits.js"; -import { BulkFileEdits } from "./bulkFileEdits.js"; -import { BulkTextEdits } from "./bulkTextEdits.js"; + +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; +import { LinkedList } from '../../../../base/common/linkedList.js'; +import { ResourceMap, ResourceSet } from '../../../../base/common/map.js'; +import { URI } from '../../../../base/common/uri.js'; +import { ICodeEditor, isCodeEditor, isDiffEditor } from '../../../../editor/browser/editorBrowser.js'; +import { IBulkEditOptions, IBulkEditPreviewHandler, IBulkEditResult, IBulkEditService, ResourceEdit, ResourceFileEdit, ResourceTextEdit } from '../../../../editor/browser/services/bulkEditService.js'; +import { EditorOption } from '../../../../editor/common/config/editorOptions.js'; +import { WorkspaceEdit } from '../../../../editor/common/languages.js'; +import { localize } from '../../../../nls.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { Extensions, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js'; +import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; +import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; +import { IProgress, IProgressStep, Progress } from '../../../../platform/progress/common/progress.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { UndoRedoGroup, UndoRedoSource } from '../../../../platform/undoRedo/common/undoRedo.js'; +import { BulkCellEdits, ResourceNotebookCellEdit } from './bulkCellEdits.js'; +import { BulkFileEdits } from './bulkFileEdits.js'; +import { BulkTextEdits } from './bulkTextEdits.js'; +import { IEditorService } from '../../../services/editor/common/editorService.js'; +import { ILifecycleService, ShutdownReason } from '../../../services/lifecycle/common/lifecycle.js'; +import { IWorkingCopyService } from '../../../services/workingCopy/common/workingCopyService.js'; +import { OpaqueEdits, ResourceAttachmentEdit } from './opaqueEdits.js'; function liftEdits(edits: ResourceEdit[]): ResourceEdit[] { - return edits.map((edit) => { + return edits.map(edit => { if (ResourceTextEdit.is(edit)) { return ResourceTextEdit.lift(edit); } @@ -70,10 +41,17 @@ function liftEdits(edits: ResourceEdit[]): ResourceEdit[] { if (ResourceNotebookCellEdit.is(edit)) { return ResourceNotebookCellEdit.lift(edit); } - throw new Error("Unsupported edit"); + + if (ResourceAttachmentEdit.is(edit)) { + return ResourceAttachmentEdit.lift(edit); + } + + throw new Error('Unsupported edit'); }); } + class BulkEdit { + constructor( private readonly _label: string | undefined, private readonly _code: string | undefined, @@ -84,18 +62,17 @@ class BulkEdit { private readonly _undoRedoGroup: UndoRedoGroup, private readonly _undoRedoSource: UndoRedoSource | undefined, private readonly _confirmBeforeUndo: boolean, - @IInstantiationService - private readonly _instaService: IInstantiationService, - @ILogService - private readonly _logService: ILogService, - ) {} + @IInstantiationService private readonly _instaService: IInstantiationService, + @ILogService private readonly _logService: ILogService, + ) { + + } + ariaMessage(): string { - const otherResources = new ResourceMap(); + const otherResources = new ResourceMap(); const textEditResources = new ResourceMap(); - let textEditCount = 0; - for (const edit of this._edits) { if (edit instanceof ResourceTextEdit) { textEditCount += 1; @@ -105,257 +82,164 @@ class BulkEdit { } } if (this._edits.length === 0) { - return localize("summary.0", "Made no edits"); + return localize('summary.0', "Made no edits"); } else if (otherResources.size === 0) { if (textEditCount > 1 && textEditResources.size > 1) { - return localize( - "summary.nm", - "Made {0} text edits in {1} files", - textEditCount, - textEditResources.size, - ); + return localize('summary.nm', "Made {0} text edits in {1} files", textEditCount, textEditResources.size); } else { - return localize( - "summary.n0", - "Made {0} text edits in one file", - textEditCount, - ); + return localize('summary.n0', "Made {0} text edits in one file", textEditCount); } } else { - return localize( - "summary.textFiles", - "Made {0} text edits in {1} files, also created or deleted {2} files", - textEditCount, - textEditResources.size, - otherResources.size, - ); + return localize('summary.textFiles', "Made {0} text edits in {1} files, also created or deleted {2} files", textEditCount, textEditResources.size, otherResources.size); } } + async perform(): Promise { + if (this._edits.length === 0) { return []; } - const ranges: number[] = [1]; + const ranges: number[] = [1]; for (let i = 1; i < this._edits.length; i++) { - if ( - Object.getPrototypeOf(this._edits[i - 1]) === - Object.getPrototypeOf(this._edits[i]) - ) { + if (Object.getPrototypeOf(this._edits[i - 1]) === Object.getPrototypeOf(this._edits[i])) { ranges[ranges.length - 1]++; } else { ranges.push(1); } } + // Show infinte progress when there is only 1 item since we do not know how long it takes const increment = this._edits.length > 1 ? 0 : undefined; this._progress.report({ increment, total: 100 }); // Increment by percentage points since progress API expects that - const progress: IProgress = { - report: (_) => - this._progress.report({ increment: 100 / this._edits.length }), - }; + const progress: IProgress = { report: _ => this._progress.report({ increment: 100 / this._edits.length }) }; const resources: (readonly URI[])[] = []; - let index = 0; - for (const range of ranges) { if (this._token.isCancellationRequested) { break; } const group = this._edits.slice(index, index + range); - if (group[0] instanceof ResourceFileEdit) { - resources.push( - await this._performFileEdits( - group, - this._undoRedoGroup, - this._undoRedoSource, - this._confirmBeforeUndo, - progress, - ), - ); + resources.push(await this._performFileEdits(group, this._undoRedoGroup, this._undoRedoSource, this._confirmBeforeUndo, progress)); } else if (group[0] instanceof ResourceTextEdit) { - resources.push( - await this._performTextEdits( - group, - this._undoRedoGroup, - this._undoRedoSource, - progress, - ), - ); + resources.push(await this._performTextEdits(group, this._undoRedoGroup, this._undoRedoSource, progress)); } else if (group[0] instanceof ResourceNotebookCellEdit) { - resources.push( - await this._performCellEdits( - group, - this._undoRedoGroup, - this._undoRedoSource, - progress, - ), - ); + resources.push(await this._performCellEdits(group, this._undoRedoGroup, this._undoRedoSource, progress)); + } else if (group[0] instanceof ResourceAttachmentEdit) { + resources.push(await this._performOpaqueEdits(group, this._undoRedoGroup, this._undoRedoSource, progress)); } else { - console.log("UNKNOWN EDIT"); + console.log('UNKNOWN EDIT'); } index = index + range; } + return resources.flat(); } - private async _performFileEdits( - edits: ResourceFileEdit[], - undoRedoGroup: UndoRedoGroup, - undoRedoSource: UndoRedoSource | undefined, - confirmBeforeUndo: boolean, - progress: IProgress, - ): Promise { - this._logService.debug("_performFileEdits", JSON.stringify(edits)); - - const model = this._instaService.createInstance( - BulkFileEdits, - this._label || localize("workspaceEdit", "Workspace Edit"), - this._code || "undoredo.workspaceEdit", - undoRedoGroup, - undoRedoSource, - confirmBeforeUndo, - progress, - this._token, - edits, - ); + private async _performFileEdits(edits: ResourceFileEdit[], undoRedoGroup: UndoRedoGroup, undoRedoSource: UndoRedoSource | undefined, confirmBeforeUndo: boolean, progress: IProgress): Promise { + this._logService.debug('_performFileEdits', JSON.stringify(edits)); + const model = this._instaService.createInstance(BulkFileEdits, this._label || localize('workspaceEdit', "Workspace Edit"), this._code || 'undoredo.workspaceEdit', undoRedoGroup, undoRedoSource, confirmBeforeUndo, progress, this._token, edits); return await model.apply(); } - private async _performTextEdits( - edits: ResourceTextEdit[], - undoRedoGroup: UndoRedoGroup, - undoRedoSource: UndoRedoSource | undefined, - progress: IProgress, - ): Promise { - this._logService.debug("_performTextEdits", JSON.stringify(edits)); - - const model = this._instaService.createInstance( - BulkTextEdits, - this._label || localize("workspaceEdit", "Workspace Edit"), - this._code || "undoredo.workspaceEdit", - this._editor, - undoRedoGroup, - undoRedoSource, - progress, - this._token, - edits, - ); + private async _performTextEdits(edits: ResourceTextEdit[], undoRedoGroup: UndoRedoGroup, undoRedoSource: UndoRedoSource | undefined, progress: IProgress): Promise { + this._logService.debug('_performTextEdits', JSON.stringify(edits)); + const model = this._instaService.createInstance(BulkTextEdits, this._label || localize('workspaceEdit', "Workspace Edit"), this._code || 'undoredo.workspaceEdit', this._editor, undoRedoGroup, undoRedoSource, progress, this._token, edits); return await model.apply(); } - private async _performCellEdits( - edits: ResourceNotebookCellEdit[], - undoRedoGroup: UndoRedoGroup, - undoRedoSource: UndoRedoSource | undefined, - progress: IProgress, - ): Promise { - this._logService.debug("_performCellEdits", JSON.stringify(edits)); - - const model = this._instaService.createInstance( - BulkCellEdits, - undoRedoGroup, - undoRedoSource, - progress, - this._token, - edits, - ); + private async _performCellEdits(edits: ResourceNotebookCellEdit[], undoRedoGroup: UndoRedoGroup, undoRedoSource: UndoRedoSource | undefined, progress: IProgress): Promise { + this._logService.debug('_performCellEdits', JSON.stringify(edits)); + const model = this._instaService.createInstance(BulkCellEdits, undoRedoGroup, undoRedoSource, progress, this._token, edits); + return await model.apply(); + } + + private async _performOpaqueEdits(edits: ResourceAttachmentEdit[], undoRedoGroup: UndoRedoGroup, undoRedoSource: UndoRedoSource | undefined, progress: IProgress): Promise { + this._logService.debug('_performOpaqueEdits', JSON.stringify(edits)); + const model = this._instaService.createInstance(OpaqueEdits, undoRedoGroup, undoRedoSource, progress, this._token, edits); return await model.apply(); } } + export class BulkEditService implements IBulkEditService { + declare readonly _serviceBrand: undefined; + private readonly _activeUndoRedoGroups = new LinkedList(); private _previewHandler?: IBulkEditPreviewHandler; constructor( - @IInstantiationService - private readonly _instaService: IInstantiationService, - @ILogService - private readonly _logService: ILogService, - @IEditorService - private readonly _editorService: IEditorService, - @ILifecycleService - private readonly _lifecycleService: ILifecycleService, - @IDialogService - private readonly _dialogService: IDialogService, - @IWorkingCopyService - private readonly _workingCopyService: IWorkingCopyService, - @IConfigurationService - private readonly _configService: IConfigurationService, - ) {} + @IInstantiationService private readonly _instaService: IInstantiationService, + @ILogService private readonly _logService: ILogService, + @IEditorService private readonly _editorService: IEditorService, + @ILifecycleService private readonly _lifecycleService: ILifecycleService, + @IDialogService private readonly _dialogService: IDialogService, + @IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService, + @IConfigurationService private readonly _configService: IConfigurationService, + ) { } + setPreviewHandler(handler: IBulkEditPreviewHandler): IDisposable { this._previewHandler = handler; - return toDisposable(() => { if (this._previewHandler === handler) { this._previewHandler = undefined; } }); } + hasPreviewHandler(): boolean { return Boolean(this._previewHandler); } - async apply( - editsIn: ResourceEdit[] | WorkspaceEdit, - options?: IBulkEditOptions, - ): Promise { + + async apply(editsIn: ResourceEdit[] | WorkspaceEdit, options?: IBulkEditOptions): Promise { let edits = liftEdits(Array.isArray(editsIn) ? editsIn : editsIn.edits); if (edits.length === 0) { - return { - ariaSummary: localize("nothing", "Made no edits"), - isApplied: false, - }; + return { ariaSummary: localize('nothing', "Made no edits"), isApplied: false }; } - if ( - this._previewHandler && - (options?.showPreview || - edits.some((value) => value.metadata?.needsConfirmation)) - ) { + + if (this._previewHandler && (options?.showPreview || edits.some(value => value.metadata?.needsConfirmation))) { edits = await this._previewHandler(edits, options); } + let codeEditor = options?.editor; // try to find code editor if (!codeEditor) { const candidate = this._editorService.activeTextEditorControl; - if (isCodeEditor(candidate)) { codeEditor = candidate; } else if (isDiffEditor(candidate)) { codeEditor = candidate.getModifiedEditor(); } } + if (codeEditor && codeEditor.getOption(EditorOption.readOnly)) { // If the code editor is readonly still allow bulk edits to be applied #68549 codeEditor = undefined; } + // undo-redo-group: if a group id is passed then try to find it // in the list of active edits. otherwise (or when not found) // create a separate undo-redo-group let undoRedoGroup: UndoRedoGroup | undefined; - - let undoRedoGroupRemove = () => {}; - - if (typeof options?.undoRedoGroupId === "number") { + let undoRedoGroupRemove = () => { }; + if (typeof options?.undoRedoGroupId === 'number') { for (const candidate of this._activeUndoRedoGroups) { if (candidate.id === options.undoRedoGroupId) { undoRedoGroup = candidate; - break; } } } if (!undoRedoGroup) { undoRedoGroup = new UndoRedoGroup(); - undoRedoGroupRemove = - this._activeUndoRedoGroups.push(undoRedoGroup); + undoRedoGroupRemove = this._activeUndoRedoGroups.push(undoRedoGroup); } - const label = options?.quotableLabel || options?.label; + const label = options?.quotableLabel || options?.label; const bulkEdit = this._instaService.createInstance( BulkEdit, label, @@ -366,157 +250,91 @@ export class BulkEditService implements IBulkEditService { edits, undoRedoGroup, options?.undoRedoSource, - !!options?.confirmBeforeUndo, + !!options?.confirmBeforeUndo ); let listener: IDisposable | undefined; - try { - listener = this._lifecycleService.onBeforeShutdown((e) => - e.veto( - this._shouldVeto(label, e.reason), - "veto.blukEditService", - ), - ); - + listener = this._lifecycleService.onBeforeShutdown(e => e.veto(this._shouldVeto(label, e.reason), 'veto.blukEditService')); const resources = await bulkEdit.perform(); + // when enabled (option AND setting) loop over all dirty working copies and trigger save // for those that were involved in this bulk edit operation. - if ( - options?.respectAutoSaveConfig && - this._configService.getValue(autoSaveSetting) === true && - resources.length > 1 - ) { + if (options?.respectAutoSaveConfig && this._configService.getValue(autoSaveSetting) === true && resources.length > 1) { await this._saveAll(resources); } - return { - ariaSummary: bulkEdit.ariaMessage(), - isApplied: edits.length > 0, - }; + + return { ariaSummary: bulkEdit.ariaMessage(), isApplied: edits.length > 0 }; } catch (err) { // console.log('apply FAILED'); // console.log(err); this._logService.error(err); - throw err; } finally { listener?.dispose(); undoRedoGroupRemove(); } } + private async _saveAll(resources: readonly URI[]) { const set = new ResourceSet(resources); - - const saves = this._workingCopyService.dirtyWorkingCopies.map( - async (copy) => { - if (set.has(copy.resource)) { - await copy.save(); - } - }, - ); + const saves = this._workingCopyService.dirtyWorkingCopies.map(async (copy) => { + if (set.has(copy.resource)) { + await copy.save(); + } + }); const result = await Promise.allSettled(saves); - for (const item of result) { - if (item.status === "rejected") { + if (item.status === 'rejected') { this._logService.warn(item.reason); } } } - private async _shouldVeto( - label: string | undefined, - reason: ShutdownReason, - ): Promise { - let message: string; + private async _shouldVeto(label: string | undefined, reason: ShutdownReason): Promise { + let message: string; let primaryButton: string; - switch (reason) { case ShutdownReason.CLOSE: - message = localize( - "closeTheWindow.message", - "Are you sure you want to close the window?", - ); - primaryButton = localize( - { - key: "closeTheWindow", - comment: ["&& denotes a mnemonic"], - }, - "&&Close Window", - ); - + message = localize('closeTheWindow.message', "Are you sure you want to close the window?"); + primaryButton = localize({ key: 'closeTheWindow', comment: ['&& denotes a mnemonic'] }, "&&Close Window"); break; - case ShutdownReason.LOAD: - message = localize( - "changeWorkspace.message", - "Are you sure you want to change the workspace?", - ); - primaryButton = localize( - { - key: "changeWorkspace", - comment: ["&& denotes a mnemonic"], - }, - "Change &&Workspace", - ); - + message = localize('changeWorkspace.message', "Are you sure you want to change the workspace?"); + primaryButton = localize({ key: 'changeWorkspace', comment: ['&& denotes a mnemonic'] }, "Change &&Workspace"); break; - case ShutdownReason.RELOAD: - message = localize( - "reloadTheWindow.message", - "Are you sure you want to reload the window?", - ); - primaryButton = localize( - { - key: "reloadTheWindow", - comment: ["&& denotes a mnemonic"], - }, - "&&Reload Window", - ); - + message = localize('reloadTheWindow.message', "Are you sure you want to reload the window?"); + primaryButton = localize({ key: 'reloadTheWindow', comment: ['&& denotes a mnemonic'] }, "&&Reload Window"); break; - default: - message = localize( - "quit.message", - "Are you sure you want to quit?", - ); - primaryButton = localize( - { key: "quit", comment: ["&& denotes a mnemonic"] }, - "&&Quit", - ); - + message = localize('quit.message', "Are you sure you want to quit?"); + primaryButton = localize({ key: 'quit', comment: ['&& denotes a mnemonic'] }, "&&Quit"); break; } + const result = await this._dialogService.confirm({ message, - detail: localize( - "areYouSureQuiteBulkEdit.detail", - "'{0}' is in progress.", - label || localize("fileOperation", "File operation"), - ), - primaryButton, + detail: localize('areYouSureQuiteBulkEdit.detail', "'{0}' is in progress.", label || localize('fileOperation', "File operation")), + primaryButton }); return !result.confirmed; } } + registerSingleton(IBulkEditService, BulkEditService, InstantiationType.Delayed); -const autoSaveSetting = "files.refactoring.autoSave"; -Registry.as( - Extensions.Configuration, -).registerConfiguration({ - id: "files", +const autoSaveSetting = 'files.refactoring.autoSave'; + +Registry.as(Extensions.Configuration).registerConfiguration({ + id: 'files', properties: { [autoSaveSetting]: { - description: localize( - "refactoring.autoSave", - "Controls if files that were part of a refactoring are saved automatically", - ), + description: localize('refactoring.autoSave', "Controls if files that were part of a refactoring are saved automatically"), default: true, - type: "boolean", - }, - }, + type: 'boolean' + } + } }); diff --git a/Source/vs/workbench/contrib/bulkEdit/browser/opaqueEdits.ts b/Source/vs/workbench/contrib/bulkEdit/browser/opaqueEdits.ts new file mode 100644 index 0000000000000..a8615ee859ef7 --- /dev/null +++ b/Source/vs/workbench/contrib/bulkEdit/browser/opaqueEdits.ts @@ -0,0 +1,79 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { isObject } from '../../../../base/common/types.js'; +import { URI } from '../../../../base/common/uri.js'; +import { ResourceEdit } from '../../../../editor/browser/services/bulkEditService.js'; +import { ICustomEdit, WorkspaceEditMetadata } from '../../../../editor/common/languages.js'; +import { IProgress } from '../../../../platform/progress/common/progress.js'; +import { IUndoRedoService, UndoRedoElementType, UndoRedoGroup, UndoRedoSource } from '../../../../platform/undoRedo/common/undoRedo.js'; + +export class ResourceAttachmentEdit extends ResourceEdit implements ICustomEdit { + + static is(candidate: any): candidate is ICustomEdit { + if (candidate instanceof ResourceAttachmentEdit) { + return true; + } else { + return isObject(candidate) + && (Boolean((candidate).undo && (candidate).redo)); + } + } + + static lift(edit: ICustomEdit): ResourceAttachmentEdit { + if (edit instanceof ResourceAttachmentEdit) { + return edit; + } else { + return new ResourceAttachmentEdit(edit.resource, edit.undo, edit.redo, edit.metadata); + } + } + + constructor( + readonly resource: URI, + readonly undo: () => Promise | void, + readonly redo: () => Promise | void, + metadata?: WorkspaceEditMetadata + ) { + super(metadata); + } +} + +export class OpaqueEdits { + + constructor( + private readonly _undoRedoGroup: UndoRedoGroup, + private readonly _undoRedoSource: UndoRedoSource | undefined, + private readonly _progress: IProgress, + private readonly _token: CancellationToken, + private readonly _edits: ResourceAttachmentEdit[], + @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, + ) { } + + async apply(): Promise { + const resources: URI[] = []; + + for (const edit of this._edits) { + if (this._token.isCancellationRequested) { + break; + } + + await edit.redo(); + + this._undoRedoService.pushElement({ + type: UndoRedoElementType.Resource, + resource: edit.resource, + label: edit.metadata?.label || 'Custom Edit', + code: 'paste', + undo: edit.undo, + redo: edit.redo, + }, this._undoRedoGroup, this._undoRedoSource); + + this._progress.report(undefined); + resources.push(edit.resource); + } + + return resources; + } +} diff --git a/Source/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/Source/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index e2a090e45d087..20566aac91b31 100644 --- a/Source/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/Source/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -2,122 +2,71 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ButtonBar } from "../../../../../base/browser/ui/button/button.js"; -import type { IAsyncDataTreeViewState } from "../../../../../base/browser/ui/tree/asyncDataTree.js"; -import { ITreeContextMenuEvent } from "../../../../../base/browser/ui/tree/tree.js"; -import { - CachedFunction, - LRUCachedFunction, -} from "../../../../../base/common/cache.js"; -import { CancellationToken } from "../../../../../base/common/cancellation.js"; -import { FuzzyScore } from "../../../../../base/common/filters.js"; -import { DisposableStore } from "../../../../../base/common/lifecycle.js"; -import { Mutable } from "../../../../../base/common/types.js"; -import { URI } from "../../../../../base/common/uri.js"; - -import "./bulkEdit.css"; - -import { ResourceEdit } from "../../../../../editor/browser/services/bulkEditService.js"; -import { - IMultiDiffEditorOptions, - IMultiDiffResourceId, -} from "../../../../../editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.js"; -import { IRange } from "../../../../../editor/common/core/range.js"; -import { ITextModelService } from "../../../../../editor/common/services/resolverService.js"; -import { localize } from "../../../../../nls.js"; -import { MenuId } from "../../../../../platform/actions/common/actions.js"; -import { IConfigurationService } from "../../../../../platform/configuration/common/configuration.js"; -import { - IContextKey, - IContextKeyService, - RawContextKey, -} from "../../../../../platform/contextkey/common/contextkey.js"; -import { IContextMenuService } from "../../../../../platform/contextview/browser/contextView.js"; -import { IDialogService } from "../../../../../platform/dialogs/common/dialogs.js"; -import { IHoverService } from "../../../../../platform/hover/browser/hover.js"; -import { IInstantiationService } from "../../../../../platform/instantiation/common/instantiation.js"; -import { IKeybindingService } from "../../../../../platform/keybinding/common/keybinding.js"; -import { ILabelService } from "../../../../../platform/label/common/label.js"; -import { - IOpenEvent, - WorkbenchAsyncDataTree, -} from "../../../../../platform/list/browser/listService.js"; -import { IOpenerService } from "../../../../../platform/opener/common/opener.js"; -import { - IStorageService, - StorageScope, - StorageTarget, -} from "../../../../../platform/storage/common/storage.js"; -import { ITelemetryService } from "../../../../../platform/telemetry/common/telemetry.js"; -import { defaultButtonStyles } from "../../../../../platform/theme/browser/defaultStyles.js"; -import { IThemeService } from "../../../../../platform/theme/common/themeService.js"; -import { ResourceLabels } from "../../../../browser/labels.js"; -import { ViewPane } from "../../../../browser/parts/views/viewPane.js"; -import { IViewletViewOptions } from "../../../../browser/parts/views/viewsViewlet.js"; -import { - IMultiDiffEditorResource, - IResourceDiffEditorInput, -} from "../../../../common/editor.js"; -import { IViewDescriptorService } from "../../../../common/views.js"; -import { - ACTIVE_GROUP, - IEditorService, - SIDE_GROUP, -} from "../../../../services/editor/common/editorService.js"; -import { - BulkEditPreviewProvider, - BulkFileOperation, - BulkFileOperations, - BulkFileOperationType, -} from "./bulkEditPreview.js"; -import { - BulkEditAccessibilityProvider, - BulkEditDataSource, - BulkEditDelegate, - BulkEditElement, - BulkEditIdentityProvider, - BulkEditNaviLabelProvider, - BulkEditSorter, - CategoryElement, - CategoryElementRenderer, - compareBulkFileOperations, - FileElement, - FileElementRenderer, - TextEditElement, - TextEditElementRenderer, -} from "./bulkEditTree.js"; + +import { ButtonBar } from '../../../../../base/browser/ui/button/button.js'; +import type { IAsyncDataTreeViewState } from '../../../../../base/browser/ui/tree/asyncDataTree.js'; +import { ITreeContextMenuEvent } from '../../../../../base/browser/ui/tree/tree.js'; +import { CachedFunction, LRUCachedFunction } from '../../../../../base/common/cache.js'; +import { CancellationToken } from '../../../../../base/common/cancellation.js'; +import { FuzzyScore } from '../../../../../base/common/filters.js'; +import { DisposableStore } from '../../../../../base/common/lifecycle.js'; +import { Mutable } from '../../../../../base/common/types.js'; +import { URI } from '../../../../../base/common/uri.js'; +import './bulkEdit.css'; +import { ResourceEdit } from '../../../../../editor/browser/services/bulkEditService.js'; +import { IMultiDiffEditorOptions, IMultiDiffResourceId } from '../../../../../editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.js'; +import { IRange } from '../../../../../editor/common/core/range.js'; +import { ITextModelService } from '../../../../../editor/common/services/resolverService.js'; +import { localize } from '../../../../../nls.js'; +import { MenuId } from '../../../../../platform/actions/common/actions.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { IContextKey, IContextKeyService, RawContextKey } from '../../../../../platform/contextkey/common/contextkey.js'; +import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js'; +import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js'; +import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js'; +import { ILabelService } from '../../../../../platform/label/common/label.js'; +import { IOpenEvent, WorkbenchAsyncDataTree } from '../../../../../platform/list/browser/listService.js'; +import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; +import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; +import { defaultButtonStyles } from '../../../../../platform/theme/browser/defaultStyles.js'; +import { IThemeService } from '../../../../../platform/theme/common/themeService.js'; +import { ResourceLabels } from '../../../../browser/labels.js'; +import { ViewPane } from '../../../../browser/parts/views/viewPane.js'; +import { IViewletViewOptions } from '../../../../browser/parts/views/viewsViewlet.js'; +import { IMultiDiffEditorResource, IResourceDiffEditorInput } from '../../../../common/editor.js'; +import { IViewDescriptorService } from '../../../../common/views.js'; +import { BulkEditPreviewProvider, BulkFileOperation, BulkFileOperations, BulkFileOperationType } from './bulkEditPreview.js'; +import { BulkEditAccessibilityProvider, BulkEditDataSource, BulkEditDelegate, BulkEditElement, BulkEditIdentityProvider, BulkEditNaviLabelProvider, BulkEditSorter, CategoryElement, CategoryElementRenderer, compareBulkFileOperations, FileElement, FileElementRenderer, TextEditElement, TextEditElementRenderer } from './bulkEditTree.js'; +import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from '../../../../services/editor/common/editorService.js'; const enum State { - Data = "data", - Message = "message", + Data = 'data', + Message = 'message' } + export class BulkEditPane extends ViewPane { - static readonly ID = "refactorPreview"; - static readonly Schema = "vscode-bulkeditpreview-multieditor"; - static readonly ctxHasCategories = new RawContextKey( - "refactorPreview.hasCategories", - false, - ); - static readonly ctxGroupByFile = new RawContextKey( - "refactorPreview.groupByFile", - true, - ); - static readonly ctxHasCheckedChanges = new RawContextKey( - "refactorPreview.hasCheckedChanges", - true, - ); + + static readonly ID = 'refactorPreview'; + static readonly Schema = 'vscode-bulkeditpreview-multieditor'; + + static readonly ctxHasCategories = new RawContextKey('refactorPreview.hasCategories', false); + static readonly ctxGroupByFile = new RawContextKey('refactorPreview.groupByFile', true); + static readonly ctxHasCheckedChanges = new RawContextKey('refactorPreview.hasCheckedChanges', true); + private static readonly _memGroupByFile = `${this.ID}.groupByFile`; - private _tree!: WorkbenchAsyncDataTree< - BulkFileOperations, - BulkEditElement, - FuzzyScore - >; + + private _tree!: WorkbenchAsyncDataTree; private _treeDataSource!: BulkEditDataSource; private _treeViewStates = new Map(); private _message!: HTMLSpanElement; + private readonly _ctxHasCategories: IContextKey; private readonly _ctxGroupByFile: IContextKey; private readonly _ctxHasCheckedChanges: IContextKey; + private readonly _disposables = new DisposableStore(); private readonly _sessionDisposables = new DisposableStore(); private _currentResolve?: (edit?: ResourceEdit[]) => void; @@ -126,182 +75,123 @@ export class BulkEditPane extends ViewPane { constructor( options: IViewletViewOptions, - @IInstantiationService - private readonly _instaService: IInstantiationService, - @IEditorService - private readonly _editorService: IEditorService, - @ILabelService - private readonly _labelService: ILabelService, - @ITextModelService - private readonly _textModelService: ITextModelService, - @IDialogService - private readonly _dialogService: IDialogService, - @IContextMenuService - private readonly _contextMenuService: IContextMenuService, - @IStorageService - private readonly _storageService: IStorageService, - @IContextKeyService - contextKeyService: IContextKeyService, - @IViewDescriptorService - viewDescriptorService: IViewDescriptorService, - @IKeybindingService - keybindingService: IKeybindingService, - @IContextMenuService - contextMenuService: IContextMenuService, - @IConfigurationService - configurationService: IConfigurationService, - @IOpenerService - openerService: IOpenerService, - @IThemeService - themeService: IThemeService, - @ITelemetryService - telemetryService: ITelemetryService, - @IHoverService - hoverService: IHoverService, + @IInstantiationService private readonly _instaService: IInstantiationService, + @IEditorService private readonly _editorService: IEditorService, + @ILabelService private readonly _labelService: ILabelService, + @ITextModelService private readonly _textModelService: ITextModelService, + @IDialogService private readonly _dialogService: IDialogService, + @IContextMenuService private readonly _contextMenuService: IContextMenuService, + @IStorageService private readonly _storageService: IStorageService, + @IContextKeyService contextKeyService: IContextKeyService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IConfigurationService configurationService: IConfigurationService, + @IOpenerService openerService: IOpenerService, + @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, + @IHoverService hoverService: IHoverService, ) { super( { ...options, titleMenuId: MenuId.BulkEditTitle }, - keybindingService, - contextMenuService, - configurationService, - contextKeyService, - viewDescriptorService, - _instaService, - openerService, - themeService, - telemetryService, - hoverService, + keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, _instaService, openerService, themeService, telemetryService, hoverService ); - this.element.classList.add("bulk-edit-panel", "show-file-icons"); - this._ctxHasCategories = - BulkEditPane.ctxHasCategories.bindTo(contextKeyService); - this._ctxGroupByFile = - BulkEditPane.ctxGroupByFile.bindTo(contextKeyService); - this._ctxHasCheckedChanges = - BulkEditPane.ctxHasCheckedChanges.bindTo(contextKeyService); + + this.element.classList.add('bulk-edit-panel', 'show-file-icons'); + this._ctxHasCategories = BulkEditPane.ctxHasCategories.bindTo(contextKeyService); + this._ctxGroupByFile = BulkEditPane.ctxGroupByFile.bindTo(contextKeyService); + this._ctxHasCheckedChanges = BulkEditPane.ctxHasCheckedChanges.bindTo(contextKeyService); // telemetry type BulkEditPaneOpened = { - owner: "aiday-mar"; - comment: "Report when the bulk edit pane has been opened"; + owner: 'aiday-mar'; + comment: 'Report when the bulk edit pane has been opened'; }; - this.telemetryService.publicLog2<{}, BulkEditPaneOpened>( - "views.bulkEditPane", - ); + this.telemetryService.publicLog2<{}, BulkEditPaneOpened>('views.bulkEditPane'); } + override dispose(): void { this._tree.dispose(); this._disposables.dispose(); - super.dispose(); } + protected override renderBody(parent: HTMLElement): void { super.renderBody(parent); const resourceLabels = this._instaService.createInstance( ResourceLabels, - { onDidChangeVisibility: this.onDidChangeBodyVisibility }, + { onDidChangeVisibility: this.onDidChangeBodyVisibility } ); this._disposables.add(resourceLabels); - const contentContainer = document.createElement("div"); - contentContainer.className = "content"; + const contentContainer = document.createElement('div'); + contentContainer.className = 'content'; parent.appendChild(contentContainer); + // tree - const treeContainer = document.createElement("div"); + const treeContainer = document.createElement('div'); contentContainer.appendChild(treeContainer); - this._treeDataSource = - this._instaService.createInstance(BulkEditDataSource); - this._treeDataSource.groupByFile = this._storageService.getBoolean( - BulkEditPane._memGroupByFile, - StorageScope.PROFILE, - true, - ); + + this._treeDataSource = this._instaService.createInstance(BulkEditDataSource); + this._treeDataSource.groupByFile = this._storageService.getBoolean(BulkEditPane._memGroupByFile, StorageScope.PROFILE, true); this._ctxGroupByFile.set(this._treeDataSource.groupByFile); - this._tree = < - WorkbenchAsyncDataTree< - BulkFileOperations, - BulkEditElement, - FuzzyScore - > - >this._instaService.createInstance( - WorkbenchAsyncDataTree, - this.id, - treeContainer, + + this._tree = this._instaService.createInstance( + WorkbenchAsyncDataTree, this.id, treeContainer, new BulkEditDelegate(), - [ - this._instaService.createInstance(TextEditElementRenderer), - this._instaService.createInstance( - FileElementRenderer, - resourceLabels, - ), - this._instaService.createInstance(CategoryElementRenderer), - ], + [this._instaService.createInstance(TextEditElementRenderer), this._instaService.createInstance(FileElementRenderer, resourceLabels), this._instaService.createInstance(CategoryElementRenderer)], this._treeDataSource, { - accessibilityProvider: this._instaService.createInstance( - BulkEditAccessibilityProvider, - ), + accessibilityProvider: this._instaService.createInstance(BulkEditAccessibilityProvider), identityProvider: new BulkEditIdentityProvider(), expandOnlyOnTwistieClick: true, multipleSelectionSupport: false, - keyboardNavigationLabelProvider: - new BulkEditNaviLabelProvider(), + keyboardNavigationLabelProvider: new BulkEditNaviLabelProvider(), sorter: new BulkEditSorter(), - selectionNavigation: true, - }, - ); - this._disposables.add( - this._tree.onContextMenu(this._onContextMenu, this), - ); - this._disposables.add( - this._tree.onDidOpen((e) => this._openElementInMultiDiffEditor(e)), + selectionNavigation: true + } ); + + this._disposables.add(this._tree.onContextMenu(this._onContextMenu, this)); + this._disposables.add(this._tree.onDidOpen(e => this._openElementInMultiDiffEditor(e))); + // buttons - const buttonsContainer = document.createElement("div"); - buttonsContainer.className = "buttons"; + const buttonsContainer = document.createElement('div'); + buttonsContainer.className = 'buttons'; contentContainer.appendChild(buttonsContainer); - const buttonBar = new ButtonBar(buttonsContainer); this._disposables.add(buttonBar); - const btnConfirm = buttonBar.addButton({ - supportIcons: true, - ...defaultButtonStyles, - }); - btnConfirm.label = localize("ok", "Apply"); + const btnConfirm = buttonBar.addButton({ supportIcons: true, ...defaultButtonStyles }); + btnConfirm.label = localize('ok', 'Apply'); btnConfirm.onDidClick(() => this.accept(), this, this._disposables); - const btnCancel = buttonBar.addButton({ - ...defaultButtonStyles, - secondary: true, - }); - btnCancel.label = localize("cancel", "Discard"); + const btnCancel = buttonBar.addButton({ ...defaultButtonStyles, secondary: true }); + btnCancel.label = localize('cancel', 'Discard'); btnCancel.onDidClick(() => this.discard(), this, this._disposables); + // message - this._message = document.createElement("span"); - this._message.className = "message"; - this._message.innerText = localize( - "empty.msg", - "Invoke a code action, like rename, to see a preview of its changes here.", - ); + this._message = document.createElement('span'); + this._message.className = 'message'; + this._message.innerText = localize('empty.msg', "Invoke a code action, like rename, to see a preview of its changes here."); parent.appendChild(this._message); + // this._setState(State.Message); } + protected override layoutBody(height: number, width: number): void { super.layoutBody(height, width); - const treeHeight = height - 50; this._tree.getHTMLElement().parentElement!.style.height = `${treeHeight}px`; this._tree.layout(treeHeight, width); } + private _setState(state: State): void { - this.element.dataset["state"] = state; + this.element.dataset['state'] = state; } - async setInput( - edit: ResourceEdit[], - token: CancellationToken, - ): Promise { + + async setInput(edit: ResourceEdit[], token: CancellationToken): Promise { this._setState(State.Data); this._sessionDisposables.clear(); this._treeViewStates.clear(); @@ -310,58 +200,53 @@ export class BulkEditPane extends ViewPane { this._currentResolve(undefined); this._currentResolve = undefined; } - const input = await this._instaService.invokeFunction( - BulkFileOperations.create, - edit, - ); - this._currentProvider = this._instaService.createInstance( - BulkEditPreviewProvider, - input, - ); + + const input = await this._instaService.invokeFunction(BulkFileOperations.create, edit); + this._currentProvider = this._instaService.createInstance(BulkEditPreviewProvider, input); this._sessionDisposables.add(this._currentProvider); this._sessionDisposables.add(input); + // const hasCategories = input.categories.length > 1; this._ctxHasCategories.set(hasCategories); - this._treeDataSource.groupByFile = - !hasCategories || this._treeDataSource.groupByFile; + this._treeDataSource.groupByFile = !hasCategories || this._treeDataSource.groupByFile; this._ctxHasCheckedChanges.set(input.checked.checkedCount > 0); + this._currentInput = input; - return new Promise((resolve) => { + return new Promise(resolve => { + token.onCancellationRequested(() => resolve(undefined)); + this._currentResolve = resolve; this._setTreeInput(input); + // refresh when check state changes - this._sessionDisposables.add( - input.checked.onDidChange(() => { - this._tree.updateChildren(); - this._ctxHasCheckedChanges.set( - input.checked.checkedCount > 0, - ); - }), - ); + this._sessionDisposables.add(input.checked.onDidChange(() => { + this._tree.updateChildren(); + this._ctxHasCheckedChanges.set(input.checked.checkedCount > 0); + })); }); } + hasInput(): boolean { return Boolean(this._currentInput); } + private async _setTreeInput(input: BulkFileOperations) { - const viewState = this._treeViewStates.get( - this._treeDataSource.groupByFile, - ); + + const viewState = this._treeViewStates.get(this._treeDataSource.groupByFile); await this._tree.setInput(input, viewState); this._tree.domFocus(); if (viewState) { return; } + // async expandAll (max=10) is the default when no view state is given const expand = [...this._tree.getNode(input).children].slice(0, 10); - while (expand.length > 0) { const { element } = expand.shift()!; - if (element instanceof FileElement) { await this._tree.expand(element, true); } @@ -371,103 +256,85 @@ export class BulkEditPane extends ViewPane { } } } + accept(): void { + const conflicts = this._currentInput?.conflicts.list(); if (!conflicts || conflicts.length === 0) { this._done(true); - return; } - let message: string; + let message: string; if (conflicts.length === 1) { - message = localize( - "conflict.1", - "Cannot apply refactoring because '{0}' has changed in the meantime.", - this._labelService.getUriLabel(conflicts[0], { - relative: true, - }), - ); + message = localize('conflict.1', "Cannot apply refactoring because '{0}' has changed in the meantime.", this._labelService.getUriLabel(conflicts[0], { relative: true })); } else { - message = localize( - "conflict.N", - "Cannot apply refactoring because {0} other files have changed in the meantime.", - conflicts.length, - ); + message = localize('conflict.N', "Cannot apply refactoring because {0} other files have changed in the meantime.", conflicts.length); } + this._dialogService.warn(message).finally(() => this._done(false)); } + discard() { this._done(false); } + private _done(accept: boolean): void { - this._currentResolve?.( - accept ? this._currentInput?.getWorkspaceEdit() : undefined, - ); + this._currentResolve?.(accept ? this._currentInput?.getWorkspaceEdit() : undefined); this._currentInput = undefined; this._setState(State.Message); this._sessionDisposables.clear(); } + toggleChecked() { const [first] = this._tree.getFocus(); - - if ( - (first instanceof FileElement || - first instanceof TextEditElement) && - !first.isDisabled() - ) { + if ((first instanceof FileElement || first instanceof TextEditElement) && !first.isDisabled()) { first.setChecked(!first.isChecked()); } else if (first instanceof CategoryElement) { first.setChecked(!first.isChecked()); } } + groupByFile(): void { if (!this._treeDataSource.groupByFile) { this.toggleGrouping(); } } + groupByType(): void { if (this._treeDataSource.groupByFile) { this.toggleGrouping(); } } + toggleGrouping() { const input = this._tree.getInput(); - if (input) { + // (1) capture view state const oldViewState = this._tree.getViewState(); - this._treeViewStates.set( - this._treeDataSource.groupByFile, - oldViewState, - ); + this._treeViewStates.set(this._treeDataSource.groupByFile, oldViewState); + // (2) toggle and update - this._treeDataSource.groupByFile = - !this._treeDataSource.groupByFile; + this._treeDataSource.groupByFile = !this._treeDataSource.groupByFile; this._setTreeInput(input); + // (3) remember preference - this._storageService.store( - BulkEditPane._memGroupByFile, - this._treeDataSource.groupByFile, - StorageScope.PROFILE, - StorageTarget.USER, - ); + this._storageService.store(BulkEditPane._memGroupByFile, this._treeDataSource.groupByFile, StorageScope.PROFILE, StorageTarget.USER); this._ctxGroupByFile.set(this._treeDataSource.groupByFile); } } - private async _openElementInMultiDiffEditor( - e: IOpenEvent, - ): Promise { - const fileOperations = this._currentInput?.fileOperations; + private async _openElementInMultiDiffEditor(e: IOpenEvent): Promise { + + const fileOperations = this._currentInput?.fileOperations; if (!fileOperations) { return; } - let selection: IRange | undefined = undefined; + let selection: IRange | undefined = undefined; let fileElement: FileElement; - if (e.element instanceof TextEditElement) { fileElement = e.element.parent; selection = e.element.edit.textEdit.textEdit.range; @@ -478,56 +345,37 @@ export class BulkEditPane extends ViewPane { // invalid event return; } - const result = - await this._computeResourceDiffEditorInputs.get(fileOperations); - - const resourceId = await result.getResourceDiffEditorInputIdOfOperation( - fileElement.edit, - ); + const result = await this._computeResourceDiffEditorInputs.get(fileOperations); + const resourceId = await result.getResourceDiffEditorInputIdOfOperation(fileElement.edit); const options: Mutable = { ...e.editorOptions, viewState: { revealData: { resource: resourceId, range: selection, - }, - }, + } + } }; - const multiDiffSource = URI.from({ scheme: BulkEditPane.Schema }); - - const label = "Refactor Preview"; - this._editorService.openEditor( - { - multiDiffSource, - label, - options, - isTransient: true, - description: label, - resources: result.resources, - }, - e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP, - ); + const label = 'Refactor Preview'; + this._editorService.openEditor({ + multiDiffSource, + label, + options, + isTransient: true, + description: label, + resources: result.resources + }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } + private readonly _computeResourceDiffEditorInputs = new LRUCachedFunction< BulkFileOperation[], - Promise<{ - resources: IMultiDiffEditorResource[]; - - getResourceDiffEditorInputIdOfOperation: ( - operation: BulkFileOperation, - ) => Promise; - }> + Promise<{ resources: IMultiDiffEditorResource[]; getResourceDiffEditorInputIdOfOperation: (operation: BulkFileOperation) => Promise }> >(async (fileOperations) => { - const computeDiffEditorInput = new CachedFunction< - BulkFileOperation, - Promise - >(async (fileOperation) => { + const computeDiffEditorInput = new CachedFunction>(async (fileOperation) => { const fileOperationUri = fileOperation.uri; - - const previewUri = - this._currentProvider!.asPreviewUri(fileOperationUri); + const previewUri = this._currentProvider!.asPreviewUri(fileOperationUri); // delete if (fileOperation.type & BulkFileOperationType.Delete) { return { @@ -535,17 +383,13 @@ export class BulkEditPane extends ViewPane { modified: { resource: undefined }, goToFileResource: fileOperation.uri, } satisfies IMultiDiffEditorResource; + } // rename, create, edits else { let leftResource: URI | undefined; - try { - ( - await this._textModelService.createModelReference( - fileOperationUri, - ) - ).dispose(); + (await this._textModelService.createModelReference(fileOperationUri)).dispose(); leftResource = fileOperationUri; } catch { leftResource = BulkEditPreviewProvider.emptyPreview; @@ -558,36 +402,27 @@ export class BulkEditPane extends ViewPane { } }); - const sortedFileOperations = fileOperations - .slice() - .sort(compareBulkFileOperations); - + const sortedFileOperations = fileOperations.slice().sort(compareBulkFileOperations); const resources: IResourceDiffEditorInput[] = []; - for (const operation of sortedFileOperations) { resources.push(await computeDiffEditorInput.get(operation)); } - const getResourceDiffEditorInputIdOfOperation = async ( - operation: BulkFileOperation, - ): Promise => { + const getResourceDiffEditorInputIdOfOperation = async (operation: BulkFileOperation): Promise => { const resource = await computeDiffEditorInput.get(operation); - - return { - original: resource.original.resource, - modified: resource.modified.resource, - }; + return { original: resource.original.resource, modified: resource.modified.resource }; }; - return { resources, - getResourceDiffEditorInputIdOfOperation, + getResourceDiffEditorInputIdOfOperation }; }); + private _onContextMenu(e: ITreeContextMenuEvent): void { + this._contextMenuService.showContextMenu({ menuId: MenuId.BulkEditContext, contextKeyService: this.contextKeyService, - getAnchor: () => e.anchor, + getAnchor: () => e.anchor }); } } diff --git a/Source/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/Source/vs/workbench/contrib/chat/browser/actions/chatActions.ts index ed82df6daaa7b..09050b213f6cb 100644 --- a/Source/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/Source/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -3,84 +3,51 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { toAction } from "../../../../../base/common/actions.js"; -import { coalesce } from "../../../../../base/common/arrays.js"; -import { Codicon } from "../../../../../base/common/codicons.js"; -import { fromNowByDay } from "../../../../../base/common/date.js"; -import { KeyCode, KeyMod } from "../../../../../base/common/keyCodes.js"; -import { DisposableStore } from "../../../../../base/common/lifecycle.js"; -import { ThemeIcon } from "../../../../../base/common/themables.js"; -import { URI } from "../../../../../base/common/uri.js"; -import { ICodeEditor } from "../../../../../editor/browser/editorBrowser.js"; -import { EditorAction2 } from "../../../../../editor/browser/editorExtensions.js"; -import { Position } from "../../../../../editor/common/core/position.js"; -import { SuggestController } from "../../../../../editor/contrib/suggest/browser/suggestController.js"; -import { localize, localize2 } from "../../../../../nls.js"; -import { IActionViewItemService } from "../../../../../platform/actions/browser/actionViewItemService.js"; -import { DropdownWithPrimaryActionViewItem } from "../../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js"; -import { - Action2, - MenuId, - MenuItemAction, - MenuRegistry, - registerAction2, - SubmenuItemAction, -} from "../../../../../platform/actions/common/actions.js"; -import { ContextKeyExpr } from "../../../../../platform/contextkey/common/contextkey.js"; -import { - IsLinuxContext, - IsWindowsContext, -} from "../../../../../platform/contextkey/common/contextkeys.js"; -import { - IInstantiationService, - ServicesAccessor, -} from "../../../../../platform/instantiation/common/instantiation.js"; -import { KeybindingWeight } from "../../../../../platform/keybinding/common/keybindingsRegistry.js"; -import { IOpenerService } from "../../../../../platform/opener/common/opener.js"; -import product from "../../../../../platform/product/common/product.js"; -import { - IQuickInputButton, - IQuickInputService, - IQuickPickItem, - IQuickPickSeparator, -} from "../../../../../platform/quickinput/common/quickInput.js"; -import { ToggleTitleBarConfigAction } from "../../../../browser/parts/titlebar/titlebarActions.js"; -import { IWorkbenchContribution } from "../../../../common/contributions.js"; -import { IEditorGroupsService } from "../../../../services/editor/common/editorGroupsService.js"; -import { - ACTIVE_GROUP, - IEditorService, -} from "../../../../services/editor/common/editorService.js"; -import { IHostService } from "../../../../services/host/browser/host.js"; -import { IViewsService } from "../../../../services/views/common/viewsService.js"; -import { - ChatAgentLocation, - IChatAgentService, -} from "../../common/chatAgents.js"; -import { ChatContextKeys } from "../../common/chatContextKeys.js"; -import { extractAgentAndCommand } from "../../common/chatParserTypes.js"; -import { IChatDetail, IChatService } from "../../common/chatService.js"; -import { IChatVariablesService } from "../../common/chatVariables.js"; -import { - IChatRequestViewModel, - IChatResponseViewModel, - isRequestVM, -} from "../../common/chatViewModel.js"; -import { IChatWidgetHistoryService } from "../../common/chatWidgetHistoryService.js"; -import { - ChatViewId, - IChatWidget, - IChatWidgetService, - showChatView, -} from "../chat.js"; -import { IChatEditorOptions } from "../chatEditor.js"; -import { ChatEditorInput } from "../chatEditorInput.js"; -import { ChatViewPane } from "../chatViewPane.js"; -import { convertBufferToScreenshotVariable } from "../contrib/screenshot.js"; -import { clearChatEditor } from "./chatClear.js"; - -export const CHAT_CATEGORY = localize2("chat.category", "Chat"); -export const CHAT_OPEN_ACTION_ID = "workbench.action.chat.open"; +import { toAction } from '../../../../../base/common/actions.js'; +import { coalesce } from '../../../../../base/common/arrays.js'; +import { Codicon } from '../../../../../base/common/codicons.js'; +import { fromNowByDay } from '../../../../../base/common/date.js'; +import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; +import { DisposableStore } from '../../../../../base/common/lifecycle.js'; +import { ThemeIcon } from '../../../../../base/common/themables.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js'; +import { EditorAction2 } from '../../../../../editor/browser/editorExtensions.js'; +import { Position } from '../../../../../editor/common/core/position.js'; +import { SuggestController } from '../../../../../editor/contrib/suggest/browser/suggestController.js'; +import { localize, localize2 } from '../../../../../nls.js'; +import { IActionViewItemService } from '../../../../../platform/actions/browser/actionViewItemService.js'; +import { DropdownWithPrimaryActionViewItem } from '../../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js'; +import { Action2, MenuId, MenuItemAction, MenuRegistry, registerAction2, SubmenuItemAction } from '../../../../../platform/actions/common/actions.js'; +import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; +import { IsLinuxContext, IsWindowsContext } from '../../../../../platform/contextkey/common/contextkeys.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; +import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; +import product from '../../../../../platform/product/common/product.js'; +import { IQuickInputButton, IQuickInputService, IQuickPickItem, IQuickPickSeparator } from '../../../../../platform/quickinput/common/quickInput.js'; +import { ToggleTitleBarConfigAction } from '../../../../browser/parts/titlebar/titlebarActions.js'; +import { IWorkbenchContribution } from '../../../../common/contributions.js'; +import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; +import { ACTIVE_GROUP, IEditorService } from '../../../../services/editor/common/editorService.js'; +import { IHostService } from '../../../../services/host/browser/host.js'; +import { IViewsService } from '../../../../services/views/common/viewsService.js'; +import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; +import { extractAgentAndCommand } from '../../common/chatParserTypes.js'; +import { IChatDetail, IChatService } from '../../common/chatService.js'; +import { IChatVariablesService } from '../../common/chatVariables.js'; +import { IChatRequestViewModel, IChatResponseViewModel, isRequestVM } from '../../common/chatViewModel.js'; +import { IChatWidgetHistoryService } from '../../common/chatWidgetHistoryService.js'; +import { ChatViewId, IChatWidget, IChatWidgetService, showChatView } from '../chat.js'; +import { IChatEditorOptions } from '../chatEditor.js'; +import { ChatEditorInput } from '../chatEditorInput.js'; +import { ChatViewPane } from '../chatViewPane.js'; +import { convertBufferToScreenshotVariable } from '../contrib/screenshot.js'; +import { clearChatEditor } from './chatClear.js'; + +export const CHAT_CATEGORY = localize2('chat.category', 'Chat'); +export const CHAT_OPEN_ACTION_ID = 'workbench.action.chat.open'; export interface IChatViewOpenOptions { /** @@ -112,7 +79,8 @@ export interface IChatViewOpenRequestEntry { } class OpenChatGlobalAction extends Action2 { - static readonly TITLE = localize2("openChat", "Open Chat"); + + static readonly TITLE = localize2('openChat', "Open Chat"); constructor() { super({ @@ -122,29 +90,26 @@ class OpenChatGlobalAction extends Action2 { f1: true, precondition: ContextKeyExpr.or( ChatContextKeys.Setup.installed, - ChatContextKeys.panelParticipantRegistered, + ChatContextKeys.panelParticipantRegistered ), category: CHAT_CATEGORY, keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyI, mac: { - primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KeyI, - }, + primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KeyI + } }, menu: { id: MenuId.ChatCommandCenter, - group: "a_open", - order: 1, - }, + group: 'a_open', + order: 1 + } }); } - override async run( - accessor: ServicesAccessor, - opts?: string | IChatViewOpenOptions, - ): Promise { - opts = typeof opts === "string" ? { query: opts } : opts; + override async run(accessor: ServicesAccessor, opts?: string | IChatViewOpenOptions): Promise { + opts = typeof opts === 'string' ? { query: opts } : opts; const chatService = accessor.get(IChatService); const chatVariablesService = accessor.get(IChatVariablesService); @@ -157,21 +122,13 @@ class OpenChatGlobalAction extends Action2 { } if (opts?.previousRequests?.length && chatWidget.viewModel) { for (const { request, response } of opts.previousRequests) { - chatService.addCompleteRequest( - chatWidget.viewModel.sessionId, - request, - undefined, - 0, - { message: response }, - ); + chatService.addCompleteRequest(chatWidget.viewModel.sessionId, request, undefined, 0, { message: response }); } } if (opts?.attachScreenshot) { const screenshot = await hostService.getScreenshot(); if (screenshot) { - chatWidget.attachmentModel.addContext( - convertBufferToScreenshotVariable(screenshot), - ); + chatWidget.attachmentModel.addContext(convertBufferToScreenshotVariable(screenshot)); } } if (opts?.query) { @@ -182,18 +139,16 @@ class OpenChatGlobalAction extends Action2 { } } if (opts?.variableIds && opts.variableIds.length > 0) { - const actualVariables = chatVariablesService.getVariables( - ChatAgentLocation.Panel, - ); + const actualVariables = chatVariablesService.getVariables(ChatAgentLocation.Panel); for (const actualVariable of actualVariables) { if (opts.variableIds.includes(actualVariable.id)) { chatWidget.attachmentModel.addContext({ range: undefined, - id: actualVariable.id ?? "", + id: actualVariable.id ?? '', value: undefined, fullName: actualVariable.fullName, name: actualVariable.name, - icon: actualVariable.icon, + icon: actualVariable.icon }); } } @@ -207,17 +162,17 @@ class ChatHistoryAction extends Action2 { constructor() { super({ id: `workbench.action.chat.history`, - title: localize2("chat.history.label", "Show Chats..."), + title: localize2('chat.history.label', "Show Chats..."), menu: { id: MenuId.ViewTitle, - when: ContextKeyExpr.equals("view", ChatViewId), - group: "navigation", - order: 2, + when: ContextKeyExpr.equals('view', ChatViewId), + group: 'navigation', + order: 2 }, category: CHAT_CATEGORY, icon: Codicon.history, f1: true, - precondition: ChatContextKeys.enabled, + precondition: ChatContextKeys.enabled }); } @@ -230,21 +185,15 @@ class ChatHistoryAction extends Action2 { const showPicker = () => { const openInEditorButton: IQuickInputButton = { iconClass: ThemeIcon.asClassName(Codicon.file), - tooltip: localize( - "interactiveSession.history.editor", - "Open in Editor", - ), + tooltip: localize('interactiveSession.history.editor', "Open in Editor"), }; const deleteButton: IQuickInputButton = { iconClass: ThemeIcon.asClassName(Codicon.x), - tooltip: localize( - "interactiveSession.history.delete", - "Delete", - ), + tooltip: localize('interactiveSession.history.delete', "Delete"), }; const renameButton: IQuickInputButton = { iconClass: ThemeIcon.asClassName(Codicon.pencil), - tooltip: localize("chat.history.rename", "Rename"), + tooltip: localize('chat.history.rename', "Rename"), }; interface IChatPickerItem extends IQuickPickItem { @@ -253,113 +202,66 @@ class ChatHistoryAction extends Action2 { const getPicks = () => { const items = chatService.getHistory(); - items.sort( - (a, b) => - (b.lastMessageDate ?? 0) - (a.lastMessageDate ?? 0), - ); + items.sort((a, b) => (b.lastMessageDate ?? 0) - (a.lastMessageDate ?? 0)); let lastDate: string | undefined = undefined; - const picks = items.flatMap( - (i): [IQuickPickSeparator | undefined, IChatPickerItem] => { - const timeAgoStr = fromNowByDay( - i.lastMessageDate, - true, - true, - ); - const separator: IQuickPickSeparator | undefined = - timeAgoStr !== lastDate - ? { - type: "separator", - label: timeAgoStr, - } - : undefined; - lastDate = timeAgoStr; - return [ - separator, - { - label: i.title, - description: i.isActive - ? `(${localize("currentChatLabel", "current")})` - : "", - chat: i, - buttons: i.isActive - ? [renameButton] - : [ - renameButton, - openInEditorButton, - deleteButton, - ], - }, - ]; - }, - ); + const picks = items.flatMap((i): [IQuickPickSeparator | undefined, IChatPickerItem] => { + const timeAgoStr = fromNowByDay(i.lastMessageDate, true, true); + const separator: IQuickPickSeparator | undefined = timeAgoStr !== lastDate ? { + type: 'separator', label: timeAgoStr, + } : undefined; + lastDate = timeAgoStr; + return [ + separator, + { + label: i.title, + description: i.isActive ? `(${localize('currentChatLabel', 'current')})` : '', + chat: i, + buttons: i.isActive ? [renameButton] : [ + renameButton, + openInEditorButton, + deleteButton, + ] + } + ]; + }); return coalesce(picks); }; const store = new DisposableStore(); - const picker = store.add( - quickInputService.createQuickPick({ - useSeparators: true, - }), - ); - picker.placeholder = localize( - "interactiveSession.history.pick", - "Switch to chat", - ); + const picker = store.add(quickInputService.createQuickPick({ useSeparators: true })); + picker.placeholder = localize('interactiveSession.history.pick', "Switch to chat"); const picks = getPicks(); picker.items = picks; - store.add( - picker.onDidTriggerItemButton(async (context) => { - if (context.button === openInEditorButton) { - const options: IChatEditorOptions = { - target: { sessionId: context.item.chat.sessionId }, - pinned: true, - }; - editorService.openEditor( - { - resource: ChatEditorInput.getNewEditorUri(), - options, - }, - ACTIVE_GROUP, - ); - picker.hide(); - } else if (context.button === deleteButton) { - chatService.removeHistoryEntry( - context.item.chat.sessionId, - ); - picker.items = getPicks(); - } else if (context.button === renameButton) { - const title = await quickInputService.input({ - title: localize("newChatTitle", "New chat title"), - value: context.item.chat.title, - }); - if (title) { - chatService.setChatSessionTitle( - context.item.chat.sessionId, - title, - ); - } - - // The quick input hides the picker, it gets disposed, so we kick it off from scratch - showPicker(); - } - }), - ); - store.add( - picker.onDidAccept(async () => { - try { - const item = picker.selectedItems[0]; - const sessionId = item.chat.sessionId; - const view = (await viewsService.openView( - ChatViewId, - )) as ChatViewPane; - view.loadSession(sessionId); - } finally { - picker.hide(); + store.add(picker.onDidTriggerItemButton(async context => { + if (context.button === openInEditorButton) { + const options: IChatEditorOptions = { target: { sessionId: context.item.chat.sessionId }, pinned: true }; + editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options }, ACTIVE_GROUP); + picker.hide(); + } else if (context.button === deleteButton) { + chatService.removeHistoryEntry(context.item.chat.sessionId); + picker.items = getPicks(); + } else if (context.button === renameButton) { + const title = await quickInputService.input({ title: localize('newChatTitle', "New chat title"), value: context.item.chat.title }); + if (title) { + chatService.setChatSessionTitle(context.item.chat.sessionId, title); } - }), - ); + + // The quick input hides the picker, it gets disposed, so we kick it off from scratch + showPicker(); + } + })); + store.add(picker.onDidAccept(async () => { + try { + const item = picker.selectedItems[0]; + const sessionId = item.chat.sessionId; + const view = await viewsService.openView(ChatViewId) as ChatViewPane; + view.loadSession(sessionId); + } finally { + picker.hide(); + } + })); store.add(picker.onDidHide(() => store.dispose())); picker.show(); @@ -372,45 +274,38 @@ class OpenChatEditorAction extends Action2 { constructor() { super({ id: `workbench.action.openChat`, - title: localize2("interactiveSession.open", "Open Editor"), + title: localize2('interactiveSession.open', "Open Editor"), f1: true, category: CHAT_CATEGORY, - precondition: ChatContextKeys.enabled, + precondition: ChatContextKeys.enabled }); } async run(accessor: ServicesAccessor) { const editorService = accessor.get(IEditorService); - await editorService.openEditor({ - resource: ChatEditorInput.getNewEditorUri(), - options: { pinned: true } satisfies IChatEditorOptions, - }); + await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { pinned: true } satisfies IChatEditorOptions }); } } + class ChatAddAction extends Action2 { constructor() { super({ - id: "workbench.action.chat.addParticipant", - title: localize2("chatWith", "Chat with Extension"), + id: 'workbench.action.chat.addParticipant', + title: localize2('chatWith', "Chat with Extension"), icon: Codicon.mention, f1: false, category: CHAT_CATEGORY, menu: { id: MenuId.ChatInput, - when: ChatContextKeys.location.isEqualTo( - ChatAgentLocation.Panel, - ), - group: "navigation", - order: 1, - }, + when: ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel), + group: 'navigation', + order: 1 + } }); } - override async run( - accessor: ServicesAccessor, - ...args: any[] - ): Promise { + override async run(accessor: ServicesAccessor, ...args: any[]): Promise { const widgetService = accessor.get(IChatWidgetService); const context: { widget?: IChatWidget } | undefined = args[0]; const widget = context?.widget ?? widgetService.lastFocusedWidget; @@ -426,8 +321,8 @@ class ChatAddAction extends Action2 { const suggestCtrl = SuggestController.get(widget.inputEditor); if (suggestCtrl) { const curText = widget.inputEditor.getValue(); - const newValue = curText ? `@ ${curText}` : "@"; - if (!curText.startsWith("@")) { + const newValue = curText ? `@ ${curText}` : '@'; + if (!curText.startsWith('@')) { widget.inputEditor.setValue(newValue); } @@ -439,10 +334,10 @@ class ChatAddAction extends Action2 { MenuRegistry.appendMenuItem(MenuId.ViewTitle, { command: { - id: "update.showCurrentReleaseNotes", - title: localize2("chat.releaseNotes.label", "Explore New Features"), + id: 'update.showCurrentReleaseNotes', + title: localize2('chat.releaseNotes.label', "Explore New Features"), }, - when: ContextKeyExpr.equals("view", ChatViewId), + when: ContextKeyExpr.equals('view', ChatViewId) }); export function registerChatActions() { @@ -451,322 +346,230 @@ export function registerChatActions() { registerAction2(OpenChatEditorAction); registerAction2(ChatAddAction); - registerAction2( - class ClearChatInputHistoryAction extends Action2 { - constructor() { - super({ - id: "workbench.action.chat.clearInputHistory", - title: localize2( - "interactiveSession.clearHistory.label", - "Clear Input History", - ), - precondition: ChatContextKeys.enabled, - category: CHAT_CATEGORY, - f1: true, - }); - } - async run(accessor: ServicesAccessor, ...args: any[]) { - const historyService = accessor.get(IChatWidgetHistoryService); - historyService.clearHistory(); - } - }, - ); - - registerAction2( - class ClearChatHistoryAction extends Action2 { - constructor() { - super({ - id: "workbench.action.chat.clearHistory", - title: localize2( - "chat.clear.label", - "Clear All Workspace Chats", - ), - precondition: ChatContextKeys.enabled, - category: CHAT_CATEGORY, - f1: true, - }); - } - async run(accessor: ServicesAccessor, ...args: any[]) { - const editorGroupsService = accessor.get(IEditorGroupsService); - const viewsService = accessor.get(IViewsService); - - const chatService = accessor.get(IChatService); - chatService.clearAllHistoryEntries(); - - const chatView = viewsService.getViewWithId(ChatViewId) as - | ChatViewPane - | undefined; - if (chatView) { - chatView.widget.clear(); - } + registerAction2(class ClearChatInputHistoryAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.chat.clearInputHistory', + title: localize2('interactiveSession.clearHistory.label', "Clear Input History"), + precondition: ChatContextKeys.enabled, + category: CHAT_CATEGORY, + f1: true, + }); + } + async run(accessor: ServicesAccessor, ...args: any[]) { + const historyService = accessor.get(IChatWidgetHistoryService); + historyService.clearHistory(); + } + }); - // Clear all chat editors. Have to go this route because the chat editor may be in the background and - // not have a ChatEditorInput. - editorGroupsService.groups.forEach((group) => { - group.editors.forEach((editor) => { - if (editor instanceof ChatEditorInput) { - clearChatEditor(accessor, editor); - } - }); - }); - } - }, - ); - - registerAction2( - class FocusChatAction extends EditorAction2 { - constructor() { - super({ - id: "chat.action.focus", - title: localize2( - "actions.interactiveSession.focus", - "Focus Chat List", - ), - precondition: ContextKeyExpr.and( - ChatContextKeys.inChatInput, - ), - category: CHAT_CATEGORY, - keybinding: [ - // On mac, require that the cursor is at the top of the input, to avoid stealing cmd+up to move the cursor to the top - { - when: ContextKeyExpr.and( - ChatContextKeys.inputCursorAtTop, - ChatContextKeys.inQuickChat.negate(), - ), - primary: KeyMod.CtrlCmd | KeyCode.UpArrow, - weight: KeybindingWeight.EditorContrib, - }, - // On win/linux, ctrl+up can always focus the chat list - { - when: ContextKeyExpr.and( - ContextKeyExpr.or( - IsWindowsContext, - IsLinuxContext, - ), - ChatContextKeys.inQuickChat.negate(), - ), - primary: KeyMod.CtrlCmd | KeyCode.UpArrow, - weight: KeybindingWeight.EditorContrib, - }, - { - when: ContextKeyExpr.and( - ChatContextKeys.inChatSession, - ChatContextKeys.inQuickChat, - ), - primary: KeyMod.CtrlCmd | KeyCode.DownArrow, - weight: KeybindingWeight.WorkbenchContrib, - }, - ], - }); - } + registerAction2(class ClearChatHistoryAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.chat.clearHistory', + title: localize2('chat.clear.label', "Clear All Workspace Chats"), + precondition: ChatContextKeys.enabled, + category: CHAT_CATEGORY, + f1: true, + }); + } + async run(accessor: ServicesAccessor, ...args: any[]) { + const editorGroupsService = accessor.get(IEditorGroupsService); + const viewsService = accessor.get(IViewsService); - runEditorCommand( - accessor: ServicesAccessor, - editor: ICodeEditor, - ): void | Promise { - const editorUri = editor.getModel()?.uri; - if (editorUri) { - const widgetService = accessor.get(IChatWidgetService); - widgetService - .getWidgetByInputUri(editorUri) - ?.focusLastMessage(); - } + const chatService = accessor.get(IChatService); + chatService.clearAllHistoryEntries(); + + const chatView = viewsService.getViewWithId(ChatViewId) as ChatViewPane | undefined; + if (chatView) { + chatView.widget.clear(); } - }, - ); - - registerAction2( - class FocusChatInputAction extends Action2 { - constructor() { - super({ - id: "workbench.action.chat.focusInput", - title: localize2( - "interactiveSession.focusInput.label", - "Focus Chat Input", - ), - f1: false, - keybinding: [ - { - primary: KeyMod.CtrlCmd | KeyCode.DownArrow, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and( - ChatContextKeys.inChatSession, - ChatContextKeys.inChatInput.negate(), - ChatContextKeys.inQuickChat.negate(), - ), - }, - { - when: ContextKeyExpr.and( - ChatContextKeys.inChatSession, - ChatContextKeys.inChatInput.negate(), - ChatContextKeys.inQuickChat, - ), - primary: KeyMod.CtrlCmd | KeyCode.UpArrow, - weight: KeybindingWeight.WorkbenchContrib, - }, - ], + + // Clear all chat editors. Have to go this route because the chat editor may be in the background and + // not have a ChatEditorInput. + editorGroupsService.groups.forEach(group => { + group.editors.forEach(editor => { + if (editor instanceof ChatEditorInput) { + clearChatEditor(accessor, editor); + } }); - } - run(accessor: ServicesAccessor, ...args: any[]) { + }); + } + }); + + registerAction2(class FocusChatAction extends EditorAction2 { + constructor() { + super({ + id: 'chat.action.focus', + title: localize2('actions.interactiveSession.focus', 'Focus Chat List'), + precondition: ContextKeyExpr.and(ChatContextKeys.inChatInput), + category: CHAT_CATEGORY, + keybinding: [ + // On mac, require that the cursor is at the top of the input, to avoid stealing cmd+up to move the cursor to the top + { + when: ContextKeyExpr.and(ChatContextKeys.inputCursorAtTop, ChatContextKeys.inQuickChat.negate()), + primary: KeyMod.CtrlCmd | KeyCode.UpArrow, + weight: KeybindingWeight.EditorContrib, + }, + // On win/linux, ctrl+up can always focus the chat list + { + when: ContextKeyExpr.and(ContextKeyExpr.or(IsWindowsContext, IsLinuxContext), ChatContextKeys.inQuickChat.negate()), + primary: KeyMod.CtrlCmd | KeyCode.UpArrow, + weight: KeybindingWeight.EditorContrib, + }, + { + when: ContextKeyExpr.and(ChatContextKeys.inChatSession, ChatContextKeys.inQuickChat), + primary: KeyMod.CtrlCmd | KeyCode.DownArrow, + weight: KeybindingWeight.WorkbenchContrib, + } + ] + }); + } + + runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor): void | Promise { + const editorUri = editor.getModel()?.uri; + if (editorUri) { const widgetService = accessor.get(IChatWidgetService); - widgetService.lastFocusedWidget?.focusInput(); + widgetService.getWidgetByInputUri(editorUri)?.focusLastMessage(); } - }, - ); - - registerAction2( - class LearnMoreChatAction extends Action2 { - static readonly ID = "workbench.action.chat.learnMore"; - static readonly TITLE = localize2("learnMore", "Learn More"); - - constructor() { - super({ - id: LearnMoreChatAction.ID, - title: LearnMoreChatAction.TITLE, - category: CHAT_CATEGORY, - menu: { - id: MenuId.ChatCommandCenter, - group: "z_learn", - order: 1, + } + }); + + registerAction2(class FocusChatInputAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.chat.focusInput', + title: localize2('interactiveSession.focusInput.label', "Focus Chat Input"), + f1: false, + keybinding: [ + { + primary: KeyMod.CtrlCmd | KeyCode.DownArrow, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(ChatContextKeys.inChatSession, ChatContextKeys.inChatInput.negate(), ChatContextKeys.inQuickChat.negate()), }, - }); - } + { + when: ContextKeyExpr.and(ChatContextKeys.inChatSession, ChatContextKeys.inChatInput.negate(), ChatContextKeys.inQuickChat), + primary: KeyMod.CtrlCmd | KeyCode.UpArrow, + weight: KeybindingWeight.WorkbenchContrib, + } + ] + }); + } + run(accessor: ServicesAccessor, ...args: any[]) { + const widgetService = accessor.get(IChatWidgetService); + widgetService.lastFocusedWidget?.focusInput(); + } + }); - override async run(accessor: ServicesAccessor): Promise { - const openerService = accessor.get(IOpenerService); - if (defaultChat.documentationUrl) { - openerService.open(URI.parse(defaultChat.documentationUrl)); - } + registerAction2(class LearnMoreChatAction extends Action2 { + + static readonly ID = 'workbench.action.chat.learnMore'; + static readonly TITLE = localize2('learnMore', "Learn More"); + + constructor() { + super({ + id: LearnMoreChatAction.ID, + title: LearnMoreChatAction.TITLE, + category: CHAT_CATEGORY, + menu: [ + { + id: MenuId.ChatCommandCenter, + group: 'z_end', + order: 1 + } + ] + }); + } + + override async run(accessor: ServicesAccessor): Promise { + const openerService = accessor.get(IOpenerService); + if (defaultChat.documentationUrl) { + openerService.open(URI.parse(defaultChat.documentationUrl)); } - }, - ); + } + }); } -export function stringifyItem( - item: IChatRequestViewModel | IChatResponseViewModel, - includeName = true, -): string { +export function stringifyItem(item: IChatRequestViewModel | IChatResponseViewModel, includeName = true): string { if (isRequestVM(item)) { - return (includeName ? `${item.username}: ` : "") + item.messageText; + return (includeName ? `${item.username}: ` : '') + item.messageText; } else { - return ( - (includeName ? `${item.username}: ` : "") + item.response.toString() - ); + return (includeName ? `${item.username}: ` : '') + item.response.toString(); } } + // --- command center chat const defaultChat = { - name: product.defaultChatAgent?.name ?? "", - icon: Codicon[ - (product.defaultChatAgent?.icon as keyof typeof Codicon) ?? - "commentDiscussion" - ], - documentationUrl: product.defaultChatAgent?.documentationUrl ?? "", + name: product.defaultChatAgent?.name ?? '', + icon: Codicon[product.defaultChatAgent?.icon as keyof typeof Codicon ?? 'commentDiscussion'], + documentationUrl: product.defaultChatAgent?.documentationUrl ?? '', }; MenuRegistry.appendMenuItem(MenuId.CommandCenter, { submenu: MenuId.ChatCommandCenter, - title: localize("title4", "Chat"), + title: localize('title4', "Chat"), icon: defaultChat.icon, when: ContextKeyExpr.and( - ContextKeyExpr.has("config.chat.commandCenter.enabled"), + ContextKeyExpr.has('config.chat.commandCenter.enabled'), ContextKeyExpr.or( ChatContextKeys.Setup.installed, ChatContextKeys.Setup.entitled, - ContextKeyExpr.has("config.chat.experimental.offerSetup"), - ChatContextKeys.panelParticipantRegistered, - ), + ContextKeyExpr.has('config.chat.experimental.offerSetup'), + ChatContextKeys.panelParticipantRegistered + ) ), order: 10001, }); -registerAction2( - class ToggleChatControl extends ToggleTitleBarConfigAction { - constructor() { - super( - "chat.commandCenter.enabled", - localize("toggle.chatControl", "Chat Controls"), - localize( - "toggle.chatControlsDescription", - "Toggle visibility of the Chat Controls in title bar", - ), - 4, - false, - ContextKeyExpr.and( - ContextKeyExpr.has("config.window.commandCenter"), - ContextKeyExpr.or( - ChatContextKeys.Setup.installed, - ChatContextKeys.Setup.entitled, - ContextKeyExpr.has( - "config.chat.experimental.offerSetup", - ), - ChatContextKeys.panelParticipantRegistered, - ), - ), - ); - } - }, -); +registerAction2(class ToggleChatControl extends ToggleTitleBarConfigAction { + constructor() { + super( + 'chat.commandCenter.enabled', + localize('toggle.chatControl', 'Chat Controls'), + localize('toggle.chatControlsDescription', "Toggle visibility of the Chat Controls in title bar"), 4, false, + ContextKeyExpr.and( + ContextKeyExpr.has('config.window.commandCenter'), + ContextKeyExpr.or( + ChatContextKeys.Setup.installed, + ChatContextKeys.Setup.entitled, + ContextKeyExpr.has('config.chat.experimental.offerSetup'), + ChatContextKeys.panelParticipantRegistered + ) + ) + ); + } +}); export class ChatCommandCenterRendering implements IWorkbenchContribution { - static readonly ID = "chat.commandCenterRendering"; + + static readonly ID = 'chat.commandCenterRendering'; constructor( @IActionViewItemService actionViewItemService: IActionViewItemService, @IChatAgentService agentService: IChatAgentService, @IInstantiationService instantiationService: IInstantiationService, ) { - actionViewItemService.register( - MenuId.CommandCenter, - MenuId.ChatCommandCenter, - (action, options) => { - if (!(action instanceof SubmenuItemAction)) { - return undefined; - } + actionViewItemService.register(MenuId.CommandCenter, MenuId.ChatCommandCenter, (action, options) => { + if (!(action instanceof SubmenuItemAction)) { + return undefined; + } - const dropdownAction = toAction({ - id: "chat.commandCenter.more", - label: localize("more", "More..."), - run() {}, - }); + const dropdownAction = toAction({ + id: 'chat.commandCenter.more', + label: localize('more', "More..."), + run() { } + }); - const chatExtensionInstalled = agentService - .getAgents() - .some((agent) => agent.isDefault); + const chatExtensionInstalled = agentService.getAgents().some(agent => agent.isDefault); - const primaryAction = instantiationService.createInstance( - MenuItemAction, - { - id: chatExtensionInstalled - ? CHAT_OPEN_ACTION_ID - : "workbench.action.chat.triggerSetup", - title: chatExtensionInstalled - ? OpenChatGlobalAction.TITLE - : localize2( - "triggerChatSetup", - "Setup {0}...", - defaultChat.name, - ), - icon: defaultChat.icon, - }, - undefined, - undefined, - undefined, - undefined, - ); - - return instantiationService.createInstance( - DropdownWithPrimaryActionViewItem, - primaryAction, - dropdownAction, - action.actions, - "", - { ...options, skipTelemetry: true }, - ); - }, - agentService.onDidChangeAgents, - ); + const primaryAction = instantiationService.createInstance(MenuItemAction, { + id: chatExtensionInstalled ? CHAT_OPEN_ACTION_ID : 'workbench.action.chat.triggerSetup', + title: chatExtensionInstalled ? OpenChatGlobalAction.TITLE : localize2('triggerChatSetup', "Use AI features with {0}...", defaultChat.name), + icon: defaultChat.icon, + }, undefined, undefined, undefined, undefined); + + return instantiationService.createInstance(DropdownWithPrimaryActionViewItem, primaryAction, dropdownAction, action.actions, '', { ...options, skipTelemetry: true }); + }, agentService.onDidChangeAgents); } } diff --git a/Source/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts b/Source/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts index 7735309b78cc7..e67ca53cb1691 100644 --- a/Source/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts +++ b/Source/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts @@ -3,508 +3,351 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Codicon } from "../../../../../base/common/codicons.js"; -import { KeyCode, KeyMod } from "../../../../../base/common/keyCodes.js"; -import { ServicesAccessor } from "../../../../../editor/browser/editorExtensions.js"; -import { localize, localize2 } from "../../../../../nls.js"; -import { - AccessibilitySignal, - IAccessibilitySignalService, -} from "../../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js"; -import { - Action2, - MenuId, - registerAction2, -} from "../../../../../platform/actions/common/actions.js"; -import { ContextKeyExpr } from "../../../../../platform/contextkey/common/contextkey.js"; -import { IDialogService } from "../../../../../platform/dialogs/common/dialogs.js"; -import { KeybindingWeight } from "../../../../../platform/keybinding/common/keybindingsRegistry.js"; -import { ActiveEditorContext } from "../../../../common/contextkeys.js"; -import { IViewsService } from "../../../../services/views/common/viewsService.js"; -import { isChatViewTitleActionContext } from "../../common/chatActions.js"; -import { ChatAgentLocation } from "../../common/chatAgents.js"; -import { ChatContextKeys } from "../../common/chatContextKeys.js"; -import { - hasAppliedChatEditsContextKey, - hasUndecidedChatEditingResourceContextKey, - IChatEditingService, - WorkingSetEntryState, -} from "../../common/chatEditingService.js"; -import { ChatViewId, EditsViewId, IChatWidgetService } from "../chat.js"; -import { ChatEditorInput } from "../chatEditorInput.js"; -import { ChatViewPane } from "../chatViewPane.js"; -import { CHAT_CATEGORY } from "./chatActions.js"; -import { clearChatEditor } from "./chatClear.js"; +import { Codicon } from '../../../../../base/common/codicons.js'; +import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; +import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js'; +import { localize, localize2 } from '../../../../../nls.js'; +import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js'; +import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; +import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; +import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js'; +import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { ActiveEditorContext } from '../../../../common/contextkeys.js'; +import { IViewsService } from '../../../../services/views/common/viewsService.js'; +import { isChatViewTitleActionContext } from '../../common/chatActions.js'; +import { ChatAgentLocation } from '../../common/chatAgents.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; +import { hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, WorkingSetEntryState } from '../../common/chatEditingService.js'; +import { ChatViewId, EditsViewId, IChatWidgetService } from '../chat.js'; +import { ChatEditorInput } from '../chatEditorInput.js'; +import { ChatViewPane } from '../chatViewPane.js'; +import { CHAT_CATEGORY } from './chatActions.js'; +import { clearChatEditor } from './chatClear.js'; export const ACTION_ID_NEW_CHAT = `workbench.action.chat.newChat`; export const ACTION_ID_NEW_EDIT_SESSION = `workbench.action.chat.newEditSession`; export function registerNewChatActions() { - registerAction2( - class NewChatEditorAction extends Action2 { - constructor() { - super({ - id: "workbench.action.chatEditor.newChat", - title: localize2("chat.newChat.label", "New Chat"), - icon: Codicon.plus, - f1: false, - precondition: ChatContextKeys.enabled, - menu: [ - { - id: MenuId.EditorTitle, - group: "navigation", - order: 0, - when: ActiveEditorContext.isEqualTo( - ChatEditorInput.EditorID, - ), - }, - ], - }); - } - async run(accessor: ServicesAccessor, ...args: any[]) { - announceChatCleared(accessor.get(IAccessibilitySignalService)); - await clearChatEditor(accessor); - } - }, - ); + registerAction2(class NewChatEditorAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.chatEditor.newChat', + title: localize2('chat.newChat.label', "New Chat"), + icon: Codicon.plus, + f1: false, + precondition: ChatContextKeys.enabled, + menu: [{ + id: MenuId.EditorTitle, + group: 'navigation', + order: 0, + when: ActiveEditorContext.isEqualTo(ChatEditorInput.EditorID), + }] + }); + } + async run(accessor: ServicesAccessor, ...args: any[]) { + announceChatCleared(accessor.get(IAccessibilitySignalService)); + await clearChatEditor(accessor); + } + }); - registerAction2( - class GlobalClearChatAction extends Action2 { - constructor() { - super({ - id: ACTION_ID_NEW_CHAT, - title: localize2("chat.newChat.label", "New Chat"), - category: CHAT_CATEGORY, - icon: Codicon.plus, - precondition: ContextKeyExpr.and( - ChatContextKeys.enabled, - ChatContextKeys.location.notEqualsTo( - ChatAgentLocation.EditingSession, - ), - ), - f1: true, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyCode.KeyL, - mac: { - primary: KeyMod.WinCtrl | KeyCode.KeyL, - }, - when: ChatContextKeys.inChatSession, + registerAction2(class GlobalClearChatAction extends Action2 { + constructor() { + super({ + id: ACTION_ID_NEW_CHAT, + title: localize2('chat.newChat.label', "New Chat"), + category: CHAT_CATEGORY, + icon: Codicon.plus, + precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ChatContextKeys.location.notEqualsTo(ChatAgentLocation.EditingSession)), + f1: true, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyCode.KeyL, + mac: { + primary: KeyMod.WinCtrl | KeyCode.KeyL }, - menu: [ - { - id: MenuId.ChatContext, - group: "z_clear", - }, - { - id: MenuId.ViewTitle, - when: ContextKeyExpr.equals("view", ChatViewId), - group: "navigation", - order: -1, - }, - ], - }); - } - - async run(accessor: ServicesAccessor, ...args: any[]) { - const context = args[0]; - const accessibilitySignalService = accessor.get( - IAccessibilitySignalService, - ); - const widgetService = accessor.get(IChatWidgetService); + when: ChatContextKeys.inChatSession + }, + menu: [{ + id: MenuId.ChatContext, + group: 'z_clear' + }, + { + id: MenuId.ViewTitle, + when: ContextKeyExpr.equals('view', ChatViewId), + group: 'navigation', + order: -1 + }] + }); + } - let widget = widgetService.lastFocusedWidget; + async run(accessor: ServicesAccessor, ...args: any[]) { + const context = args[0]; + const accessibilitySignalService = accessor.get(IAccessibilitySignalService); + const widgetService = accessor.get(IChatWidgetService); - if (isChatViewTitleActionContext(context)) { - // Is running in the Chat view title - widget = widgetService.getWidgetBySessionId( - context.sessionId, - ); - } + let widget = widgetService.lastFocusedWidget; - if (widget) { - announceChatCleared(accessibilitySignalService); - widget.clear(); - widget.focusInput(); - } + if (isChatViewTitleActionContext(context)) { + // Is running in the Chat view title + widget = widgetService.getWidgetBySessionId(context.sessionId); } - }, - ); - registerAction2( - class NewEditSessionAction extends Action2 { - constructor() { - super({ - id: ACTION_ID_NEW_EDIT_SESSION, - title: localize2("chat.newEdits.label", "New Edit Session"), - category: CHAT_CATEGORY, - icon: Codicon.plus, - precondition: ContextKeyExpr.and( - ChatContextKeys.enabled, - ChatContextKeys.editingParticipantRegistered, - ), - f1: true, - menu: [ - { - id: MenuId.ChatContext, - group: "z_clear", - }, - { - id: MenuId.ViewTitle, - when: ContextKeyExpr.equals("view", EditsViewId), - group: "navigation", - order: -1, - }, - ], - }); + if (widget) { + announceChatCleared(accessibilitySignalService); + widget.clear(); + widget.focusInput(); } + } + }); - /** - * - * @returns false if the user had edits and did not action the dialog to take action on them, true otherwise - */ - private async _handleCurrentEditingSession( - chatEditingService: IChatEditingService, - dialogService: IDialogService, - ): Promise { - const currentEditingSession = - chatEditingService.currentEditingSessionObs.get(); - const currentEdits = currentEditingSession?.entries.get(); - const currentEditCount = currentEdits?.length; + registerAction2(class NewEditSessionAction extends Action2 { + constructor() { + super({ + id: ACTION_ID_NEW_EDIT_SESSION, + title: localize2('chat.newEdits.label', "New Edit Session"), + category: CHAT_CATEGORY, + icon: Codicon.plus, + precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ChatContextKeys.editingParticipantRegistered), + f1: true, + menu: [{ + id: MenuId.ChatContext, + group: 'z_clear' + }, + { + id: MenuId.ViewTitle, + when: ContextKeyExpr.equals('view', EditsViewId), + group: 'navigation', + order: -1 + }, + ] + }); + } - if (currentEditingSession && currentEditCount) { - const undecidedEdits = currentEdits.filter( - (edit) => - edit.state.get() === WorkingSetEntryState.Modified, - ); - if (undecidedEdits.length) { - const { result } = await dialogService.prompt({ - title: localize( - "chat.startEditing.confirmation.title", - "Start new editing session?", - ), - message: localize( - "chat.startEditing.confirmation.pending.message.2", - "Starting a new editing session will end your current session. Do you want to accept pending edits to {0} files?", - undecidedEdits.length, - ), - type: "info", - cancelButton: true, - buttons: [ - { - label: localize( - "chat.startEditing.confirmation.acceptEdits", - "Accept & Continue", - ), - run: async () => { - await currentEditingSession.accept(); - return true; - }, - }, - { - label: localize( - "chat.startEditing.confirmation.discardEdits", - "Discard & Continue", - ), - run: async () => { - await currentEditingSession.reject(); - return true; - }, - }, - ], - }); + /** + * + * @returns false if the user had edits and did not action the dialog to take action on them, true otherwise + */ + private async _handleCurrentEditingSession(chatEditingService: IChatEditingService, dialogService: IDialogService): Promise { + const currentEditingSession = chatEditingService.currentEditingSessionObs.get(); + const currentEdits = currentEditingSession?.entries.get(); + const currentEditCount = currentEdits?.length; - return Boolean(result); - } - } + if (currentEditingSession && currentEditCount) { + const undecidedEdits = currentEdits.filter((edit) => edit.state.get() === WorkingSetEntryState.Modified); + if (undecidedEdits.length) { + const { result } = await dialogService.prompt({ + title: localize('chat.startEditing.confirmation.title', "Start new editing session?"), + message: localize('chat.startEditing.confirmation.pending.message.2', "Starting a new editing session will end your current session. Do you want to accept pending edits to {0} files?", undecidedEdits.length), + type: 'info', + cancelButton: true, + buttons: [ + { + label: localize('chat.startEditing.confirmation.acceptEdits', "Accept & Continue"), + run: async () => { + await currentEditingSession.accept(); + return true; + } + }, + { + label: localize('chat.startEditing.confirmation.discardEdits', "Discard & Continue"), + run: async () => { + await currentEditingSession.reject(); + return true; + } + } + ], + }); - return true; + return Boolean(result); + } } - async run(accessor: ServicesAccessor, ...args: any[]) { - const context = args[0]; - const accessibilitySignalService = accessor.get( - IAccessibilitySignalService, - ); - const widgetService = accessor.get(IChatWidgetService); - const chatEditingService = accessor.get(IChatEditingService); - const dialogService = accessor.get(IDialogService); - const viewsService = accessor.get(IViewsService); - if ( - !(await this._handleCurrentEditingSession( - chatEditingService, - dialogService, - )) - ) { - return; - } - if (isChatViewTitleActionContext(context)) { - // Is running in the Chat view title - announceChatCleared(accessibilitySignalService); - const widget = widgetService.getWidgetBySessionId( - context.sessionId, - ); - if (widget) { - chatEditingService.currentEditingSessionObs - .get() - ?.stop(); - widget.clear(); - widget.attachmentModel.clear(); - widget.focusInput(); - } - } else { - // Is running from f1 or keybinding - const chatView = (await viewsService.openView( - EditsViewId, - )) as ChatViewPane; - const widget = chatView.widget; + return true; + } - announceChatCleared(accessibilitySignalService); + async run(accessor: ServicesAccessor, ...args: any[]) { + const context = args[0]; + const accessibilitySignalService = accessor.get(IAccessibilitySignalService); + const widgetService = accessor.get(IChatWidgetService); + const chatEditingService = accessor.get(IChatEditingService); + const dialogService = accessor.get(IDialogService); + const viewsService = accessor.get(IViewsService); + if (!(await this._handleCurrentEditingSession(chatEditingService, dialogService))) { + return; + } + if (isChatViewTitleActionContext(context)) { + // Is running in the Chat view title + announceChatCleared(accessibilitySignalService); + const widget = widgetService.getWidgetBySessionId(context.sessionId); + if (widget) { chatEditingService.currentEditingSessionObs.get()?.stop(); widget.clear(); widget.attachmentModel.clear(); widget.focusInput(); } - } - }, - ); + } else { + // Is running from f1 or keybinding + const chatView = await viewsService.openView(EditsViewId) as ChatViewPane; + const widget = chatView.widget; - registerAction2( - class GlobalEditsDoneAction extends Action2 { - constructor() { - super({ - id: "workbench.action.chat.done", - title: localize2("chat.done.label", "Done"), - category: CHAT_CATEGORY, - precondition: ContextKeyExpr.and( - ChatContextKeys.enabled, - ChatContextKeys.editingParticipantRegistered, - ), - f1: false, - menu: [ - { - id: MenuId.ChatEditingWidgetToolbar, - when: ContextKeyExpr.and( - hasUndecidedChatEditingResourceContextKey.negate(), - hasAppliedChatEditsContextKey, - ChatContextKeys.editingParticipantRegistered, - ChatContextKeys.location.isEqualTo( - ChatAgentLocation.EditingSession, - ), - ), - group: "navigation", - order: 0, - }, - ], - }); + announceChatCleared(accessibilitySignalService); + chatEditingService.currentEditingSessionObs.get()?.stop(); + widget.clear(); + widget.attachmentModel.clear(); + widget.focusInput(); } + } + }); - async run(accessor: ServicesAccessor, ...args: any[]) { - const context = args[0]; - const accessibilitySignalService = accessor.get( - IAccessibilitySignalService, - ); - const widgetService = accessor.get(IChatWidgetService); - if (isChatViewTitleActionContext(context)) { - // Is running in the Chat view title - announceChatCleared(accessibilitySignalService); - const widget = widgetService.getWidgetBySessionId( - context.sessionId, - ); - if (widget) { - widget.clear(); - widget.attachmentModel.clear(); - widget.focusInput(); - } - } else { - // Is running from f1 or keybinding - const viewsService = accessor.get(IViewsService); - - const chatView = (await viewsService.openView( - EditsViewId, - )) as ChatViewPane; - const widget = chatView.widget; + registerAction2(class GlobalEditsDoneAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.chat.done', + title: localize2('chat.done.label', "Done"), + category: CHAT_CATEGORY, + precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ChatContextKeys.editingParticipantRegistered), + f1: false, + menu: [{ + id: MenuId.ChatEditingWidgetToolbar, + when: ContextKeyExpr.and(hasUndecidedChatEditingResourceContextKey.negate(), hasAppliedChatEditsContextKey, ChatContextKeys.editingParticipantRegistered, ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession)), + group: 'navigation', + order: 0 + }] + }); + } - announceChatCleared(accessibilitySignalService); + async run(accessor: ServicesAccessor, ...args: any[]) { + const context = args[0]; + const accessibilitySignalService = accessor.get(IAccessibilitySignalService); + const widgetService = accessor.get(IChatWidgetService); + if (isChatViewTitleActionContext(context)) { + // Is running in the Chat view title + announceChatCleared(accessibilitySignalService); + const widget = widgetService.getWidgetBySessionId(context.sessionId); + if (widget) { widget.clear(); widget.attachmentModel.clear(); widget.focusInput(); } - } - }, - ); + } else { + // Is running from f1 or keybinding + const viewsService = accessor.get(IViewsService); - registerAction2( - class UndoChatEditInteractionAction extends Action2 { - constructor() { - super({ - id: "workbench.action.chat.undoEdit", - title: localize2("chat.undoEdit.label", "Undo Last Edit"), - category: CHAT_CATEGORY, - icon: Codicon.discard, - precondition: ContextKeyExpr.and( - ChatContextKeys.chatEditingCanUndo, - ChatContextKeys.enabled, - ChatContextKeys.editingParticipantRegistered, - ), - f1: true, - menu: [ - { - id: MenuId.ViewTitle, - when: ContextKeyExpr.equals("view", EditsViewId), - group: "navigation", - order: -3, - }, - ], - }); - } + const chatView = await viewsService.openView(EditsViewId) as ChatViewPane; + const widget = chatView.widget; - async run(accessor: ServicesAccessor, ...args: any[]) { - const chatEditingService = accessor.get(IChatEditingService); - const currentEditingSession = - chatEditingService.currentEditingSession; - if (!currentEditingSession) { - return; - } - await currentEditingSession.undoInteraction(); + announceChatCleared(accessibilitySignalService); + widget.clear(); + widget.attachmentModel.clear(); + widget.focusInput(); } - }, - ); + } + }); - registerAction2( - class RedoChatEditInteractionAction extends Action2 { - constructor() { - super({ - id: "workbench.action.chat.redoEdit", - title: localize2("chat.redoEdit.label", "Redo Last Edit"), - category: CHAT_CATEGORY, - icon: Codicon.redo, - precondition: ContextKeyExpr.and( - ChatContextKeys.chatEditingCanRedo, - ChatContextKeys.enabled, - ChatContextKeys.editingParticipantRegistered, - ), - f1: true, - menu: [ - { - id: MenuId.ViewTitle, - when: ContextKeyExpr.equals("view", EditsViewId), - group: "navigation", - order: -2, - }, - ], - }); + registerAction2(class UndoChatEditInteractionAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.chat.undoEdit', + title: localize2('chat.undoEdit.label', "Undo Last Edit"), + category: CHAT_CATEGORY, + icon: Codicon.discard, + precondition: ContextKeyExpr.and(ChatContextKeys.chatEditingCanUndo, ChatContextKeys.enabled, ChatContextKeys.editingParticipantRegistered), + f1: true, + menu: [{ + id: MenuId.ViewTitle, + when: ContextKeyExpr.equals('view', EditsViewId), + group: 'navigation', + order: -3 + }] + }); + } + + async run(accessor: ServicesAccessor, ...args: any[]) { + const chatEditingService = accessor.get(IChatEditingService); + const currentEditingSession = chatEditingService.currentEditingSession; + if (!currentEditingSession) { + return; } + await currentEditingSession.undoInteraction(); + } + }); - async run(accessor: ServicesAccessor, ...args: any[]) { - const chatEditingService = accessor.get(IChatEditingService); - const currentEditingSession = - chatEditingService.currentEditingSession; - if (!currentEditingSession) { - return; - } - await chatEditingService.currentEditingSession?.redoInteraction(); + registerAction2(class RedoChatEditInteractionAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.chat.redoEdit', + title: localize2('chat.redoEdit.label', "Redo Last Edit"), + category: CHAT_CATEGORY, + icon: Codicon.redo, + precondition: ContextKeyExpr.and(ChatContextKeys.chatEditingCanRedo, ChatContextKeys.enabled, ChatContextKeys.editingParticipantRegistered), + f1: true, + menu: [{ + id: MenuId.ViewTitle, + when: ContextKeyExpr.equals('view', EditsViewId), + group: 'navigation', + order: -2 + }] + }); + } + + async run(accessor: ServicesAccessor, ...args: any[]) { + const chatEditingService = accessor.get(IChatEditingService); + const currentEditingSession = chatEditingService.currentEditingSession; + if (!currentEditingSession) { + return; } - }, - ); + await chatEditingService.currentEditingSession?.redoInteraction(); + } + }); - registerAction2( - class GlobalOpenEditsAction extends Action2 { - constructor() { - super({ - id: "workbench.action.chat.openEditSession", - title: localize2( - "chat.openEdits.label", - "Open {0}", - "Copilot Edits", - ), - category: CHAT_CATEGORY, - icon: Codicon.goToEditingSession, - precondition: ContextKeyExpr.and( - ChatContextKeys.enabled, - ChatContextKeys.editingParticipantRegistered, - ), - f1: true, - menu: [ - { - id: MenuId.ViewTitle, - when: ContextKeyExpr.and( - ContextKeyExpr.equals("view", ChatViewId), - ChatContextKeys.editingParticipantRegistered, - ContextKeyExpr.equals( - `view.${EditsViewId}.visible`, - false, - ), - ContextKeyExpr.or( - ContextKeyExpr.and( - ContextKeyExpr.equals( - `workbench.panel.chat.defaultViewContainerLocation`, - true, - ), - ContextKeyExpr.equals( - `workbench.panel.chatEditing.defaultViewContainerLocation`, - false, - ), - ), - ContextKeyExpr.and( - ContextKeyExpr.equals( - `workbench.panel.chat.defaultViewContainerLocation`, - false, - ), - ContextKeyExpr.equals( - `workbench.panel.chatEditing.defaultViewContainerLocation`, - true, - ), - ), - ), - ), - group: "navigation", - order: 1, - }, - { - id: MenuId.ChatCommandCenter, - when: ChatContextKeys.editingParticipantRegistered, - group: "a_open", - order: 2, - }, - { - id: MenuId.ChatEditingEditorContent, - group: "navigate", - order: 4, - }, - ], - keybinding: { - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyI, - linux: { - primary: - KeyMod.CtrlCmd | - KeyMod.Alt | - KeyMod.Shift | - KeyCode.KeyI, - }, - when: ContextKeyExpr.and( - ContextKeyExpr.notEquals("view", EditsViewId), - ChatContextKeys.editingParticipantRegistered, - ), + registerAction2(class GlobalOpenEditsAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.chat.openEditSession', + title: localize2('chat.openEdits.label', "Open {0}", 'Copilot Edits'), + category: CHAT_CATEGORY, + icon: Codicon.goToEditingSession, + precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ChatContextKeys.editingParticipantRegistered), + f1: true, + menu: [{ + id: MenuId.ViewTitle, + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', ChatViewId), ChatContextKeys.editingParticipantRegistered, + ContextKeyExpr.equals(`view.${EditsViewId}.visible`, false), + ContextKeyExpr.or( + ContextKeyExpr.and(ContextKeyExpr.equals(`workbench.panel.chat.defaultViewContainerLocation`, true), ContextKeyExpr.equals(`workbench.panel.chatEditing.defaultViewContainerLocation`, false)), + ContextKeyExpr.and(ContextKeyExpr.equals(`workbench.panel.chat.defaultViewContainerLocation`, false), ContextKeyExpr.equals(`workbench.panel.chatEditing.defaultViewContainerLocation`, true)), + )), + group: 'navigation', + order: 1 + }, { + id: MenuId.ChatCommandCenter, + group: 'a_open', + order: 2 + }, { + id: MenuId.ChatEditingEditorContent, + group: 'navigate', + order: 4, + }], + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyI, + linux: { + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.KeyI }, - }); - } + when: ContextKeyExpr.and(ContextKeyExpr.notEquals('view', EditsViewId), ChatContextKeys.editingParticipantRegistered) + } + }); + } - async run(accessor: ServicesAccessor, ...args: any[]) { - const viewsService = accessor.get(IViewsService); - const chatView = (await viewsService.openView( - EditsViewId, - )) as ChatViewPane; - chatView.widget.focusInput(); - } - }, - ); + async run(accessor: ServicesAccessor, ...args: any[]) { + const viewsService = accessor.get(IViewsService); + const chatView = await viewsService.openView(EditsViewId) as ChatViewPane; + chatView.widget.focusInput(); + } + }); } -function announceChatCleared( - accessibilitySignalService: IAccessibilitySignalService, -): void { +function announceChatCleared(accessibilitySignalService: IAccessibilitySignalService): void { accessibilitySignalService.playSignal(AccessibilitySignal.clear); } diff --git a/Source/vs/workbench/contrib/chat/browser/chat.contribution.ts b/Source/vs/workbench/contrib/chat/browser/chat.contribution.ts index bfcce68e7c56a..8900c8509a8c3 100644 --- a/Source/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/Source/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -2,362 +2,221 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { timeout } from "../../../../base/common/async.js"; -import { - isMarkdownString, - MarkdownString, -} from "../../../../base/common/htmlContent.js"; -import { Disposable } from "../../../../base/common/lifecycle.js"; -import { Schemas } from "../../../../base/common/network.js"; -import { isMacintosh } from "../../../../base/common/platform.js"; -import { - EditorContributionInstantiation, - registerEditorContribution, -} from "../../../../editor/browser/editorExtensions.js"; -import { registerEditorFeature } from "../../../../editor/common/editorFeatures.js"; -import * as nls from "../../../../nls.js"; -import { AccessibleViewRegistry } from "../../../../platform/accessibility/browser/accessibleViewRegistry.js"; -import { ICommandService } from "../../../../platform/commands/common/commands.js"; -import { - Extensions as ConfigurationExtensions, - ConfigurationScope, - IConfigurationRegistry, -} from "../../../../platform/configuration/common/configurationRegistry.js"; -import { SyncDescriptor } from "../../../../platform/instantiation/common/descriptors.js"; -import { - InstantiationType, - registerSingleton, -} from "../../../../platform/instantiation/common/extensions.js"; -import { IInstantiationService } from "../../../../platform/instantiation/common/instantiation.js"; -import { Registry } from "../../../../platform/registry/common/platform.js"; -import { - EditorPaneDescriptor, - IEditorPaneRegistry, -} from "../../../browser/editor.js"; -import { - registerWorkbenchContribution2, - WorkbenchPhase, -} from "../../../common/contributions.js"; -import { - EditorExtensions, - IEditorFactoryRegistry, -} from "../../../common/editor.js"; -import { - IEditorResolverService, - RegisteredEditorPriority, -} from "../../../services/editor/common/editorResolverService.js"; -import { - ChatAgentLocation, - ChatAgentNameService, - ChatAgentService, - IChatAgentNameService, - IChatAgentService, -} from "../common/chatAgents.js"; -import { - CodeMapperService, - ICodeMapperService, -} from "../common/chatCodeMapperService.js"; -import "../common/chatColors.js"; - -import { IChatEditingService } from "../common/chatEditingService.js"; -import { chatVariableLeader } from "../common/chatParserTypes.js"; -import { IChatService } from "../common/chatService.js"; -import { ChatService } from "../common/chatServiceImpl.js"; -import { - ChatSlashCommandService, - IChatSlashCommandService, -} from "../common/chatSlashCommands.js"; -import { IChatVariablesService } from "../common/chatVariables.js"; -import { - ChatWidgetHistoryService, - IChatWidgetHistoryService, -} from "../common/chatWidgetHistoryService.js"; -import { - ILanguageModelsService, - LanguageModelsService, -} from "../common/languageModels.js"; -import { - ILanguageModelStatsService, - LanguageModelStatsService, -} from "../common/languageModelStats.js"; -import { ILanguageModelToolsService } from "../common/languageModelToolsService.js"; -import { LanguageModelToolsExtensionPointHandler } from "../common/tools/languageModelToolsContribution.js"; -import { - IVoiceChatService, - VoiceChatService, -} from "../common/voiceChatService.js"; -import { - PanelChatAccessibilityHelp, - QuickChatAccessibilityHelp, -} from "./actions/chatAccessibilityHelp.js"; -import { - ChatCommandCenterRendering, - registerChatActions, -} from "./actions/chatActions.js"; -import { - ACTION_ID_NEW_CHAT, - registerNewChatActions, -} from "./actions/chatClearActions.js"; -import { - registerChatCodeBlockActions, - registerChatCodeCompareBlockActions, -} from "./actions/chatCodeblockActions.js"; -import { registerChatContextActions } from "./actions/chatContextActions.js"; -import { registerChatCopyActions } from "./actions/chatCopyActions.js"; -import { registerChatDeveloperActions } from "./actions/chatDeveloperActions.js"; -import { - ChatSubmitAction, - registerChatExecuteActions, -} from "./actions/chatExecuteActions.js"; -import { registerChatFileTreeActions } from "./actions/chatFileTreeActions.js"; -import { registerChatExportActions } from "./actions/chatImportExport.js"; -import { registerMoveActions } from "./actions/chatMoveActions.js"; -import { registerQuickChatActions } from "./actions/chatQuickInputActions.js"; -import { registerChatTitleActions } from "./actions/chatTitleActions.js"; -import { - IChatAccessibilityService, - IChatCodeBlockContextProviderService, - IChatWidgetService, - IQuickChatService, -} from "./chat.js"; -import { ChatAccessibilityService } from "./chatAccessibilityService.js"; - -import "./chatAttachmentModel.js"; - -import { - ChatMarkdownAnchorService, - IChatMarkdownAnchorService, -} from "./chatContentParts/chatMarkdownAnchorService.js"; -import { ChatEditingService } from "./chatEditing/chatEditingService.js"; -import { ChatEditor, IChatEditorOptions } from "./chatEditor.js"; -import { registerChatEditorActions } from "./chatEditorActions.js"; -import { ChatEditorController } from "./chatEditorController.js"; -import { - ChatEditorInput, - ChatEditorInputSerializer, -} from "./chatEditorInput.js"; -import { ChatEditorSaving } from "./chatEditorSaving.js"; -import { - agentSlashCommandToMarkdown, - agentToMarkdown, -} from "./chatMarkdownDecorationsRenderer.js"; -import { - ChatCompatibilityNotifier, - ChatExtensionPointHandler, -} from "./chatParticipantContributions.js"; -import { ChatPasteProvidersFeature } from "./chatPasteProviders.js"; -import { QuickChatService } from "./chatQuick.js"; -import { ChatResponseAccessibleView } from "./chatResponseAccessibleView.js"; -import { ChatVariablesService } from "./chatVariables.js"; -import { ChatWidgetService } from "./chatWidget.js"; -import { ChatCodeBlockContextProviderService } from "./codeBlockContextProviderService.js"; - -import "./contrib/chatInputCompletions.js"; -import "./contrib/chatInputEditorContrib.js"; -import "./contrib/chatInputEditorHover.js"; - -import { - Extensions, - IConfigurationMigrationRegistry, -} from "../../../common/configuration.js"; -import { - ILanguageModelIgnoredFilesService, - LanguageModelIgnoredFilesService, -} from "../common/ignoredFiles.js"; -import { ChatGettingStartedContribution } from "./actions/chatGettingStarted.js"; -import { ChatEditorOverlayController } from "./chatEditorOverlay.js"; -import { ChatImplicitContextContribution } from "./contrib/chatImplicitContext.js"; -import { ChatRelatedFilesContribution } from "./contrib/chatInputRelatedFilesContrib.js"; -import { LanguageModelToolsService } from "./languageModelToolsService.js"; -import { ChatViewsWelcomeHandler } from "./viewsWelcome/chatViewsWelcomeContributions.js"; +import { timeout } from '../../../../base/common/async.js'; +import { MarkdownString, isMarkdownString } from '../../../../base/common/htmlContent.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { Schemas } from '../../../../base/common/network.js'; +import { isMacintosh } from '../../../../base/common/platform.js'; +import { EditorContributionInstantiation, registerEditorContribution } from '../../../../editor/browser/editorExtensions.js'; +import { registerEditorFeature } from '../../../../editor/common/editorFeatures.js'; +import * as nls from '../../../../nls.js'; +import { AccessibleViewRegistry } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js'; +import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js'; +import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; +import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { EditorPaneDescriptor, IEditorPaneRegistry } from '../../../browser/editor.js'; +import { WorkbenchPhase, registerWorkbenchContribution2 } from '../../../common/contributions.js'; +import { EditorExtensions, IEditorFactoryRegistry } from '../../../common/editor.js'; +import { IEditorResolverService, RegisteredEditorPriority } from '../../../services/editor/common/editorResolverService.js'; +import { ChatAgentLocation, ChatAgentNameService, ChatAgentService, IChatAgentNameService, IChatAgentService } from '../common/chatAgents.js'; +import { CodeMapperService, ICodeMapperService } from '../common/chatCodeMapperService.js'; +import '../common/chatColors.js'; +import { IChatEditingService } from '../common/chatEditingService.js'; +import { chatVariableLeader } from '../common/chatParserTypes.js'; +import { IChatService } from '../common/chatService.js'; +import { ChatService } from '../common/chatServiceImpl.js'; +import { ChatSlashCommandService, IChatSlashCommandService } from '../common/chatSlashCommands.js'; +import { IChatVariablesService } from '../common/chatVariables.js'; +import { ChatWidgetHistoryService, IChatWidgetHistoryService } from '../common/chatWidgetHistoryService.js'; +import { ILanguageModelsService, LanguageModelsService } from '../common/languageModels.js'; +import { ILanguageModelStatsService, LanguageModelStatsService } from '../common/languageModelStats.js'; +import { ILanguageModelToolsService } from '../common/languageModelToolsService.js'; +import { LanguageModelToolsExtensionPointHandler } from '../common/tools/languageModelToolsContribution.js'; +import { IVoiceChatService, VoiceChatService } from '../common/voiceChatService.js'; +import { PanelChatAccessibilityHelp, QuickChatAccessibilityHelp } from './actions/chatAccessibilityHelp.js'; +import { ChatCommandCenterRendering, registerChatActions } from './actions/chatActions.js'; +import { ACTION_ID_NEW_CHAT, registerNewChatActions } from './actions/chatClearActions.js'; +import { registerChatCodeBlockActions, registerChatCodeCompareBlockActions } from './actions/chatCodeblockActions.js'; +import { registerChatContextActions } from './actions/chatContextActions.js'; +import { registerChatCopyActions } from './actions/chatCopyActions.js'; +import { registerChatDeveloperActions } from './actions/chatDeveloperActions.js'; +import { ChatSubmitAction, registerChatExecuteActions } from './actions/chatExecuteActions.js'; +import { registerChatFileTreeActions } from './actions/chatFileTreeActions.js'; +import { registerChatExportActions } from './actions/chatImportExport.js'; +import { registerMoveActions } from './actions/chatMoveActions.js'; +import { registerQuickChatActions } from './actions/chatQuickInputActions.js'; +import { registerChatTitleActions } from './actions/chatTitleActions.js'; +import { IChatAccessibilityService, IChatCodeBlockContextProviderService, IChatWidgetService, IQuickChatService } from './chat.js'; +import { ChatAccessibilityService } from './chatAccessibilityService.js'; +import './chatAttachmentModel.js'; +import { ChatMarkdownAnchorService, IChatMarkdownAnchorService } from './chatContentParts/chatMarkdownAnchorService.js'; +import { ChatEditingService } from './chatEditing/chatEditingService.js'; +import { ChatEditor, IChatEditorOptions } from './chatEditor.js'; +import { registerChatEditorActions } from './chatEditorActions.js'; +import { ChatEditorController } from './chatEditorController.js'; +import { ChatEditorInput, ChatEditorInputSerializer } from './chatEditorInput.js'; +import { ChatInputBoxContentProvider } from './chatEdinputInputContentProvider.js'; +import { ChatEditorSaving } from './chatEditorSaving.js'; +import { agentSlashCommandToMarkdown, agentToMarkdown } from './chatMarkdownDecorationsRenderer.js'; +import { ChatCompatibilityNotifier, ChatExtensionPointHandler } from './chatParticipant.contribution.js'; +import { ChatPasteProvidersFeature } from './chatPasteProviders.js'; +import { QuickChatService } from './chatQuick.js'; +import { ChatResponseAccessibleView } from './chatResponseAccessibleView.js'; +import { ChatVariablesService } from './chatVariables.js'; +import { ChatWidgetService } from './chatWidget.js'; +import { ChatCodeBlockContextProviderService } from './codeBlockContextProviderService.js'; +import './contrib/chatInputCompletions.js'; +import './contrib/chatInputEditorContrib.js'; +import './contrib/chatInputEditorHover.js'; +import { ChatImplicitContextContribution } from './contrib/chatImplicitContext.js'; +import { LanguageModelToolsService } from './languageModelToolsService.js'; +import { ChatViewsWelcomeHandler } from './viewsWelcome/chatViewsWelcomeHandler.js'; +import { ILanguageModelIgnoredFilesService, LanguageModelIgnoredFilesService } from '../common/ignoredFiles.js'; +import { ChatGettingStartedContribution } from './actions/chatGettingStarted.js'; +import { Extensions, IConfigurationMigrationRegistry } from '../../../common/configuration.js'; +import { ChatEditorOverlayController } from './chatEditorOverlay.js'; +import { ChatRelatedFilesContribution } from './contrib/chatInputRelatedFilesContrib.js'; +import product from '../../../../platform/product/common/product.js'; // Register configuration -const configurationRegistry = Registry.as( - ConfigurationExtensions.Configuration, -); +const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); configurationRegistry.registerConfiguration({ - id: "chatSidebar", - title: nls.localize("interactiveSessionConfigurationTitle", "Chat"), - type: "object", + id: 'chatSidebar', + title: nls.localize('interactiveSessionConfigurationTitle', "Chat"), + type: 'object', properties: { - "chat.editor.fontSize": { - type: "number", - description: nls.localize( - "interactiveSession.editor.fontSize", - "Controls the font size in pixels in chat codeblocks.", - ), + 'chat.editor.fontSize': { + type: 'number', + description: nls.localize('interactiveSession.editor.fontSize', "Controls the font size in pixels in chat codeblocks."), default: isMacintosh ? 12 : 14, }, - "chat.editor.fontFamily": { - type: "string", - description: nls.localize( - "interactiveSession.editor.fontFamily", - "Controls the font family in chat codeblocks.", - ), - default: "default", + 'chat.editor.fontFamily': { + type: 'string', + description: nls.localize('interactiveSession.editor.fontFamily', "Controls the font family in chat codeblocks."), + default: 'default' }, - "chat.editor.fontWeight": { - type: "string", - description: nls.localize( - "interactiveSession.editor.fontWeight", - "Controls the font weight in chat codeblocks.", - ), - default: "default", + 'chat.editor.fontWeight': { + type: 'string', + description: nls.localize('interactiveSession.editor.fontWeight', "Controls the font weight in chat codeblocks."), + default: 'default' }, - "chat.editor.wordWrap": { - type: "string", - description: nls.localize( - "interactiveSession.editor.wordWrap", - "Controls whether lines should wrap in chat codeblocks.", - ), - default: "off", - enum: ["on", "off"], + 'chat.editor.wordWrap': { + type: 'string', + description: nls.localize('interactiveSession.editor.wordWrap', "Controls whether lines should wrap in chat codeblocks."), + default: 'off', + enum: ['on', 'off'] }, - "chat.editor.lineHeight": { - type: "number", - description: nls.localize( - "interactiveSession.editor.lineHeight", - "Controls the line height in pixels in chat codeblocks. Use 0 to compute the line height from the font size.", - ), - default: 0, + 'chat.editor.lineHeight': { + type: 'number', + description: nls.localize('interactiveSession.editor.lineHeight', "Controls the line height in pixels in chat codeblocks. Use 0 to compute the line height from the font size."), + default: 0 }, - "chat.commandCenter.enabled": { - type: "boolean", - tags: ["preview"], - markdownDescription: nls.localize( - "chat.commandCenter.enabled", - "Controls whether the command center shows a menu for chat actions (requires {0}).", - "`#window.commandCenter#`", - ), - default: true, + 'chat.commandCenter.enabled': { + type: 'boolean', + tags: ['preview'], + markdownDescription: nls.localize('chat.commandCenter.enabled', "Controls whether the command center shows a menu for chat actions to control {0} (requires {1}).", product.defaultChatAgent?.chatName, '`#window.commandCenter#`'), + default: true }, - "chat.experimental.offerSetup": { - type: "boolean", + 'chat.experimental.offerSetup': { + type: 'boolean', default: false, - markdownDescription: nls.localize( - "chat.experimental.offerSetup", - "Controls whether setup is offered for Chat if not done already.", - ), - tags: ["experimental", "onExP"], + markdownDescription: nls.localize('chat.experimental.offerSetup', "Controls whether setup is offered for Chat if not done already."), + tags: ['experimental', 'onExP'] }, - "chat.editing.alwaysSaveWithGeneratedChanges": { - type: "boolean", + 'chat.editing.alwaysSaveWithGeneratedChanges': { + type: 'boolean', scope: ConfigurationScope.APPLICATION, - markdownDescription: nls.localize( - "chat.editing.alwaysSaveWithGeneratedChanges", - "Whether to always ask before saving files with changes made by chat.", - ), + markdownDescription: nls.localize('chat.editing.alwaysSaveWithGeneratedChanges', "Whether to always ask before saving files with changes made by chat."), default: false, }, - "chat.editing.confirmEditRequestRemoval": { - type: "boolean", + 'chat.editing.confirmEditRequestRemoval': { + type: 'boolean', scope: ConfigurationScope.APPLICATION, - markdownDescription: nls.localize( - "chat.editing.confirmEditRequestRemoval", - "Whether to show a confirmation before removing a request and its associated edits.", - ), + markdownDescription: nls.localize('chat.editing.confirmEditRequestRemoval', "Whether to show a confirmation before removing a request and its associated edits."), default: true, }, - "chat.editing.confirmEditRequestRetry": { - type: "boolean", + 'chat.editing.confirmEditRequestRetry': { + type: 'boolean', scope: ConfigurationScope.APPLICATION, - markdownDescription: nls.localize( - "chat.editing.confirmEditRequestRetry", - "Whether to show a confirmation before retrying a request and its associated edits.", - ), + markdownDescription: nls.localize('chat.editing.confirmEditRequestRetry', "Whether to show a confirmation before retrying a request and its associated edits."), default: true, }, - "chat.experimental.detectParticipant.enabled": { - type: "boolean", - deprecationMessage: nls.localize( - "chat.experimental.detectParticipant.enabled.deprecated", - "This setting is deprecated. Please use `chat.detectParticipant.enabled` instead.", - ), - description: nls.localize( - "chat.experimental.detectParticipant.enabled", - "Enables chat participant autodetection for panel chat.", - ), - default: null, + 'chat.experimental.detectParticipant.enabled': { + type: 'boolean', + deprecationMessage: nls.localize('chat.experimental.detectParticipant.enabled.deprecated', "This setting is deprecated. Please use `chat.detectParticipant.enabled` instead."), + description: nls.localize('chat.experimental.detectParticipant.enabled', "Enables chat participant autodetection for panel chat."), + default: null }, - "chat.detectParticipant.enabled": { - type: "boolean", - description: nls.localize( - "chat.detectParticipant.enabled", - "Enables chat participant autodetection for panel chat.", - ), - default: true, + 'chat.detectParticipant.enabled': { + type: 'boolean', + description: nls.localize('chat.detectParticipant.enabled', "Enables chat participant autodetection for panel chat."), + default: true }, - }, + } }); -Registry.as( - EditorExtensions.EditorPane, -).registerEditorPane( +Registry.as(EditorExtensions.EditorPane).registerEditorPane( EditorPaneDescriptor.create( ChatEditor, ChatEditorInput.EditorID, - nls.localize("chat", "Chat"), + nls.localize('chat', "Chat") ), - [new SyncDescriptor(ChatEditorInput)], + [ + new SyncDescriptor(ChatEditorInput) + ] ); -Registry.as( - Extensions.ConfigurationMigration, -).registerConfigurationMigrations([ +Registry.as(Extensions.ConfigurationMigration).registerConfigurationMigrations([ { - key: "chat.experimental.detectParticipant.enabled", - migrateFn: (value, _accessor) => [ - [ - "chat.experimental.detectParticipant.enabled", - { value: undefined }, - ], - ["chat.detectParticipant.enabled", { value: value !== false }], - ], - }, + key: 'chat.experimental.detectParticipant.enabled', + migrateFn: (value, _accessor) => ([ + ['chat.experimental.detectParticipant.enabled', { value: undefined }], + ['chat.detectParticipant.enabled', { value: value !== false }] + ]) + } ]); + class ChatResolverContribution extends Disposable { - static readonly ID = "workbench.contrib.chatResolver"; + + static readonly ID = 'workbench.contrib.chatResolver'; constructor( - @IEditorResolverService - editorResolverService: IEditorResolverService, - @IInstantiationService - instantiationService: IInstantiationService, + @IEditorResolverService editorResolverService: IEditorResolverService, + @IInstantiationService instantiationService: IInstantiationService, ) { super(); - this._register( - editorResolverService.registerEditor( - `${Schemas.vscodeChatSesssion}:**/**`, - { - id: ChatEditorInput.EditorID, - label: nls.localize("chat", "Chat"), - priority: RegisteredEditorPriority.builtin, - }, - { - singlePerResource: true, - canSupportResource: (resource) => - resource.scheme === Schemas.vscodeChatSesssion, - }, - { - createEditorInput: ({ resource, options }) => { - return { - editor: instantiationService.createInstance( - ChatEditorInput, - resource, - options as IChatEditorOptions, - ), - options, - }; - }, - }, - ), - ); + + this._register(editorResolverService.registerEditor( + `${Schemas.vscodeChatSesssion}:**/**`, + { + id: ChatEditorInput.EditorID, + label: nls.localize('chat', "Chat"), + priority: RegisteredEditorPriority.builtin + }, + { + singlePerResource: true, + canSupportResource: resource => resource.scheme === Schemas.vscodeChatSesssion + }, + { + createEditorInput: ({ resource, options }) => { + return { editor: instantiationService.createInstance(ChatEditorInput, resource, options as IChatEditorOptions), options }; + } + } + )); } } + AccessibleViewRegistry.register(new ChatResponseAccessibleView()); AccessibleViewRegistry.register(new PanelChatAccessibilityHelp()); AccessibleViewRegistry.register(new QuickChatAccessibilityHelp()); + +registerEditorFeature(ChatInputBoxContentProvider); + class ChatSlashStaticSlashCommandsContribution extends Disposable { - static readonly ID = "workbench.contrib.chatSlashStaticSlashCommands"; + + static readonly ID = 'workbench.contrib.chatSlashStaticSlashCommands'; constructor( @IChatSlashCommandService slashCommandService: IChatSlashCommandService, @@ -367,260 +226,102 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { @IInstantiationService instantiationService: IInstantiationService, ) { super(); - this._store.add( - slashCommandService.registerSlashCommand( - { - command: "clear", - detail: nls.localize("clear", "Start a new chat"), - sortText: "z2_clear", - executeImmediately: true, - locations: [ChatAgentLocation.Panel], - }, - async () => { - commandService.executeCommand(ACTION_ID_NEW_CHAT); - }, - ), - ); - this._store.add( - slashCommandService.registerSlashCommand( - { - command: "help", - detail: "", - sortText: "z1_help", - executeImmediately: true, - locations: [ChatAgentLocation.Panel], - }, - async (prompt, progress) => { - const defaultAgent = chatAgentService.getDefaultAgent( - ChatAgentLocation.Panel, - ); - - const agents = chatAgentService.getAgents(); - - // Report prefix - if (defaultAgent?.metadata.helpTextPrefix) { - if ( - isMarkdownString( - defaultAgent.metadata.helpTextPrefix, - ) - ) { - progress.report({ - content: defaultAgent.metadata.helpTextPrefix, - kind: "markdownContent", - }); - } else { - progress.report({ - content: new MarkdownString( - defaultAgent.metadata.helpTextPrefix, - ), - kind: "markdownContent", - }); - } - progress.report({ - content: new MarkdownString("\n\n"), - kind: "markdownContent", - }); - } - - // Report agent list - const agentText = ( - await Promise.all( - agents - .filter((a) => a.id !== defaultAgent?.id) - .filter((a) => - a.locations.includes( - ChatAgentLocation.Panel, - ), - ) - .map(async (a) => { - const description = a.description - ? `- ${a.description}` - : ""; - - const agentMarkdown = - instantiationService.invokeFunction( - (accessor) => - agentToMarkdown( - a, - true, - accessor, - ), - ); - - const agentLine = `- ${agentMarkdown} ${description}`; + this._store.add(slashCommandService.registerSlashCommand({ + command: 'clear', + detail: nls.localize('clear', "Start a new chat"), + sortText: 'z2_clear', + executeImmediately: true, + locations: [ChatAgentLocation.Panel] + }, async () => { + commandService.executeCommand(ACTION_ID_NEW_CHAT); + })); + this._store.add(slashCommandService.registerSlashCommand({ + command: 'help', + detail: '', + sortText: 'z1_help', + executeImmediately: true, + locations: [ChatAgentLocation.Panel] + }, async (prompt, progress) => { + const defaultAgent = chatAgentService.getDefaultAgent(ChatAgentLocation.Panel); + const agents = chatAgentService.getAgents(); - const commandText = a.slashCommands - .map((c) => { - const description = c.description - ? `- ${c.description}` - : ""; + // Report prefix + if (defaultAgent?.metadata.helpTextPrefix) { + if (isMarkdownString(defaultAgent.metadata.helpTextPrefix)) { + progress.report({ content: defaultAgent.metadata.helpTextPrefix, kind: 'markdownContent' }); + } else { + progress.report({ content: new MarkdownString(defaultAgent.metadata.helpTextPrefix), kind: 'markdownContent' }); + } + progress.report({ content: new MarkdownString('\n\n'), kind: 'markdownContent' }); + } - return `\t* ${agentSlashCommandToMarkdown(a, c)} ${description}`; - }) - .join("\n"); + // Report agent list + const agentText = (await Promise.all(agents + .filter(a => a.id !== defaultAgent?.id) + .filter(a => a.locations.includes(ChatAgentLocation.Panel)) + .map(async a => { + const description = a.description ? `- ${a.description}` : ''; + const agentMarkdown = instantiationService.invokeFunction(accessor => agentToMarkdown(a, true, accessor)); + const agentLine = `- ${agentMarkdown} ${description}`; + const commandText = a.slashCommands.map(c => { + const description = c.description ? `- ${c.description}` : ''; + return `\t* ${agentSlashCommandToMarkdown(a, c)} ${description}`; + }).join('\n'); - return ( - agentLine + - "\n" + - commandText - ).trim(); - }), - ) - ).join("\n"); - progress.report({ - content: new MarkdownString(agentText, { - isTrusted: { - enabledCommands: [ChatSubmitAction.ID], - }, - }), - kind: "markdownContent", - }); + return (agentLine + '\n' + commandText).trim(); + }))).join('\n'); + progress.report({ content: new MarkdownString(agentText, { isTrusted: { enabledCommands: [ChatSubmitAction.ID] } }), kind: 'markdownContent' }); - // Report variables - if (defaultAgent?.metadata.helpTextVariablesPrefix) { - progress.report({ - content: new MarkdownString("\n\n"), - kind: "markdownContent", - }); + // Report variables + if (defaultAgent?.metadata.helpTextVariablesPrefix) { + progress.report({ content: new MarkdownString('\n\n'), kind: 'markdownContent' }); + if (isMarkdownString(defaultAgent.metadata.helpTextVariablesPrefix)) { + progress.report({ content: defaultAgent.metadata.helpTextVariablesPrefix, kind: 'markdownContent' }); + } else { + progress.report({ content: new MarkdownString(defaultAgent.metadata.helpTextVariablesPrefix), kind: 'markdownContent' }); + } - if ( - isMarkdownString( - defaultAgent.metadata.helpTextVariablesPrefix, - ) - ) { - progress.report({ - content: - defaultAgent.metadata - .helpTextVariablesPrefix, - kind: "markdownContent", - }); - } else { - progress.report({ - content: new MarkdownString( - defaultAgent.metadata.helpTextVariablesPrefix, - ), - kind: "markdownContent", - }); - } + const variables = [ + ...chatVariablesService.getVariables(ChatAgentLocation.Panel), + { name: 'file', description: nls.localize('file', "Choose a file in the workspace") } + ]; + const variableText = variables + .map(v => `* \`${chatVariableLeader}${v.name}\` - ${v.description}`) + .join('\n'); + progress.report({ content: new MarkdownString('\n' + variableText), kind: 'markdownContent' }); + } - const variables = [ - ...chatVariablesService.getVariables( - ChatAgentLocation.Panel, - ), - { - name: "file", - description: nls.localize( - "file", - "Choose a file in the workspace", - ), - }, - ]; + // Report help text ending + if (defaultAgent?.metadata.helpTextPostfix) { + progress.report({ content: new MarkdownString('\n\n'), kind: 'markdownContent' }); + if (isMarkdownString(defaultAgent.metadata.helpTextPostfix)) { + progress.report({ content: defaultAgent.metadata.helpTextPostfix, kind: 'markdownContent' }); + } else { + progress.report({ content: new MarkdownString(defaultAgent.metadata.helpTextPostfix), kind: 'markdownContent' }); + } + } - const variableText = variables - .map( - (v) => - `* \`${chatVariableLeader}${v.name}\` - ${v.description}`, - ) - .join("\n"); - progress.report({ - content: new MarkdownString("\n" + variableText), - kind: "markdownContent", - }); - } - - // Report help text ending - if (defaultAgent?.metadata.helpTextPostfix) { - progress.report({ - content: new MarkdownString("\n\n"), - kind: "markdownContent", - }); - - if ( - isMarkdownString( - defaultAgent.metadata.helpTextPostfix, - ) - ) { - progress.report({ - content: defaultAgent.metadata.helpTextPostfix, - kind: "markdownContent", - }); - } else { - progress.report({ - content: new MarkdownString( - defaultAgent.metadata.helpTextPostfix, - ), - kind: "markdownContent", - }); - } - } - - // Without this, the response will be done before it renders and so it will not stream. This ensures that if the response starts - // rendering during the next 200ms, then it will be streamed. Once it starts streaming, the whole response streams even after - // it has received all response data has been received. - await timeout(200); - }, - ), - ); + // Without this, the response will be done before it renders and so it will not stream. This ensures that if the response starts + // rendering during the next 200ms, then it will be streamed. Once it starts streaming, the whole response streams even after + // it has received all response data has been received. + await timeout(200); + })); } } -Registry.as( - EditorExtensions.EditorFactory, -).registerEditorSerializer(ChatEditorInput.TypeID, ChatEditorInputSerializer); -registerWorkbenchContribution2( - ChatResolverContribution.ID, - ChatResolverContribution, - WorkbenchPhase.BlockStartup, -); -registerWorkbenchContribution2( - ChatSlashStaticSlashCommandsContribution.ID, - ChatSlashStaticSlashCommandsContribution, - WorkbenchPhase.Eventually, -); -registerWorkbenchContribution2( - ChatExtensionPointHandler.ID, - ChatExtensionPointHandler, - WorkbenchPhase.BlockStartup, -); -registerWorkbenchContribution2( - LanguageModelToolsExtensionPointHandler.ID, - LanguageModelToolsExtensionPointHandler, - WorkbenchPhase.BlockRestore, -); -registerWorkbenchContribution2( - ChatCompatibilityNotifier.ID, - ChatCompatibilityNotifier, - WorkbenchPhase.Eventually, -); -registerWorkbenchContribution2( - ChatCommandCenterRendering.ID, - ChatCommandCenterRendering, - WorkbenchPhase.BlockRestore, -); -registerWorkbenchContribution2( - ChatImplicitContextContribution.ID, - ChatImplicitContextContribution, - WorkbenchPhase.Eventually, -); -registerWorkbenchContribution2( - ChatRelatedFilesContribution.ID, - ChatRelatedFilesContribution, - WorkbenchPhase.Eventually, -); -registerWorkbenchContribution2( - ChatEditorSaving.ID, - ChatEditorSaving, - WorkbenchPhase.AfterRestored, -); -registerWorkbenchContribution2( - ChatViewsWelcomeHandler.ID, - ChatViewsWelcomeHandler, - WorkbenchPhase.BlockStartup, -); -registerWorkbenchContribution2( - ChatGettingStartedContribution.ID, - ChatGettingStartedContribution, - WorkbenchPhase.Eventually, -); +Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(ChatEditorInput.TypeID, ChatEditorInputSerializer); + +registerWorkbenchContribution2(ChatResolverContribution.ID, ChatResolverContribution, WorkbenchPhase.BlockStartup); +registerWorkbenchContribution2(ChatSlashStaticSlashCommandsContribution.ID, ChatSlashStaticSlashCommandsContribution, WorkbenchPhase.Eventually); +registerWorkbenchContribution2(ChatExtensionPointHandler.ID, ChatExtensionPointHandler, WorkbenchPhase.BlockStartup); +registerWorkbenchContribution2(LanguageModelToolsExtensionPointHandler.ID, LanguageModelToolsExtensionPointHandler, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(ChatCompatibilityNotifier.ID, ChatCompatibilityNotifier, WorkbenchPhase.Eventually); +registerWorkbenchContribution2(ChatCommandCenterRendering.ID, ChatCommandCenterRendering, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(ChatImplicitContextContribution.ID, ChatImplicitContextContribution, WorkbenchPhase.Eventually); +registerWorkbenchContribution2(ChatRelatedFilesContribution.ID, ChatRelatedFilesContribution, WorkbenchPhase.Eventually); +registerWorkbenchContribution2(ChatEditorSaving.ID, ChatEditorSaving, WorkbenchPhase.AfterRestored); +registerWorkbenchContribution2(ChatViewsWelcomeHandler.ID, ChatViewsWelcomeHandler, WorkbenchPhase.BlockStartup); +registerWorkbenchContribution2(ChatGettingStartedContribution.ID, ChatGettingStartedContribution, WorkbenchPhase.Eventually); + registerChatActions(); registerChatCopyActions(); registerChatCodeBlockActions(); @@ -635,101 +336,26 @@ registerNewChatActions(); registerChatContextActions(); registerChatDeveloperActions(); registerChatEditorActions(); + registerEditorFeature(ChatPasteProvidersFeature); -registerEditorContribution( - ChatEditorController.ID, - ChatEditorController, - EditorContributionInstantiation.Eventually, -); -registerEditorContribution( - ChatEditorOverlayController.ID, - ChatEditorOverlayController, - EditorContributionInstantiation.Eventually, -); +registerEditorContribution(ChatEditorController.ID, ChatEditorController, EditorContributionInstantiation.Eventually); +registerEditorContribution(ChatEditorOverlayController.ID, ChatEditorOverlayController, EditorContributionInstantiation.Eventually); registerSingleton(IChatService, ChatService, InstantiationType.Delayed); -registerSingleton( - IChatWidgetService, - ChatWidgetService, - InstantiationType.Delayed, -); -registerSingleton( - IQuickChatService, - QuickChatService, - InstantiationType.Delayed, -); -registerSingleton( - IChatAccessibilityService, - ChatAccessibilityService, - InstantiationType.Delayed, -); -registerSingleton( - IChatWidgetHistoryService, - ChatWidgetHistoryService, - InstantiationType.Delayed, -); -registerSingleton( - ILanguageModelsService, - LanguageModelsService, - InstantiationType.Delayed, -); -registerSingleton( - ILanguageModelStatsService, - LanguageModelStatsService, - InstantiationType.Delayed, -); -registerSingleton( - IChatSlashCommandService, - ChatSlashCommandService, - InstantiationType.Delayed, -); -registerSingleton( - IChatAgentService, - ChatAgentService, - InstantiationType.Delayed, -); -registerSingleton( - IChatAgentNameService, - ChatAgentNameService, - InstantiationType.Delayed, -); -registerSingleton( - IChatVariablesService, - ChatVariablesService, - InstantiationType.Delayed, -); -registerSingleton( - ILanguageModelToolsService, - LanguageModelToolsService, - InstantiationType.Delayed, -); -registerSingleton( - IVoiceChatService, - VoiceChatService, - InstantiationType.Delayed, -); -registerSingleton( - IChatCodeBlockContextProviderService, - ChatCodeBlockContextProviderService, - InstantiationType.Delayed, -); -registerSingleton( - ICodeMapperService, - CodeMapperService, - InstantiationType.Delayed, -); -registerSingleton( - IChatEditingService, - ChatEditingService, - InstantiationType.Delayed, -); -registerSingleton( - IChatMarkdownAnchorService, - ChatMarkdownAnchorService, - InstantiationType.Delayed, -); -registerSingleton( - ILanguageModelIgnoredFilesService, - LanguageModelIgnoredFilesService, - InstantiationType.Delayed, -); +registerSingleton(IChatWidgetService, ChatWidgetService, InstantiationType.Delayed); +registerSingleton(IQuickChatService, QuickChatService, InstantiationType.Delayed); +registerSingleton(IChatAccessibilityService, ChatAccessibilityService, InstantiationType.Delayed); +registerSingleton(IChatWidgetHistoryService, ChatWidgetHistoryService, InstantiationType.Delayed); +registerSingleton(ILanguageModelsService, LanguageModelsService, InstantiationType.Delayed); +registerSingleton(ILanguageModelStatsService, LanguageModelStatsService, InstantiationType.Delayed); +registerSingleton(IChatSlashCommandService, ChatSlashCommandService, InstantiationType.Delayed); +registerSingleton(IChatAgentService, ChatAgentService, InstantiationType.Delayed); +registerSingleton(IChatAgentNameService, ChatAgentNameService, InstantiationType.Delayed); +registerSingleton(IChatVariablesService, ChatVariablesService, InstantiationType.Delayed); +registerSingleton(ILanguageModelToolsService, LanguageModelToolsService, InstantiationType.Delayed); +registerSingleton(IVoiceChatService, VoiceChatService, InstantiationType.Delayed); +registerSingleton(IChatCodeBlockContextProviderService, ChatCodeBlockContextProviderService, InstantiationType.Delayed); +registerSingleton(ICodeMapperService, CodeMapperService, InstantiationType.Delayed); +registerSingleton(IChatEditingService, ChatEditingService, InstantiationType.Delayed); +registerSingleton(IChatMarkdownAnchorService, ChatMarkdownAnchorService, InstantiationType.Delayed); +registerSingleton(ILanguageModelIgnoredFilesService, LanguageModelIgnoredFilesService, InstantiationType.Delayed); diff --git a/Source/vs/workbench/contrib/chat/browser/chatAttachmentModel.ts b/Source/vs/workbench/contrib/chat/browser/chatAttachmentModel.ts index bbcbdb4eb1dc8..2034f0bf467e1 100644 --- a/Source/vs/workbench/contrib/chat/browser/chatAttachmentModel.ts +++ b/Source/vs/workbench/contrib/chat/browser/chatAttachmentModel.ts @@ -78,8 +78,8 @@ export class EditsAttachmentModel extends ChatAttachmentModel { private _onFileLimitExceeded = this._register(new Emitter()); readonly onFileLimitExceeded = this._onFileLimitExceeded.event; - private get fileAttachments() { - return this.attachments.filter((attachment) => attachment.isFile); + get fileAttachments() { + return this.attachments.filter(attachment => attachment.isFile); } private readonly _excludedFileAttachments: IChatRequestVariableEntry[] = []; diff --git a/Source/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts b/Source/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts index d093cf06fdf6c..1e4a93328e3c9 100644 --- a/Source/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts +++ b/Source/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts @@ -3,61 +3,49 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as dom from "../../../../../base/browser/dom.js"; -import { createInstantHoverDelegate } from "../../../../../base/browser/ui/hover/hoverDelegateFactory.js"; -import { Emitter } from "../../../../../base/common/event.js"; -import { - Disposable, - DisposableStore, -} from "../../../../../base/common/lifecycle.js"; -import { basename, dirname } from "../../../../../base/common/path.js"; -import { URI } from "../../../../../base/common/uri.js"; -import { IRange, Range } from "../../../../../editor/common/core/range.js"; -import { localize } from "../../../../../nls.js"; -import { ICommandService } from "../../../../../platform/commands/common/commands.js"; -import { ITextEditorOptions } from "../../../../../platform/editor/common/editor.js"; -import { - FileKind, - IFileService, -} from "../../../../../platform/files/common/files.js"; -import { IHoverService } from "../../../../../platform/hover/browser/hover.js"; -import { IInstantiationService } from "../../../../../platform/instantiation/common/instantiation.js"; -import { - IOpenerService, - OpenInternalOptions, -} from "../../../../../platform/opener/common/opener.js"; -import { - FolderThemeIcon, - IThemeService, -} from "../../../../../platform/theme/common/themeService.js"; -import { ResourceLabels } from "../../../../browser/labels.js"; -import { revealInSideBarCommand } from "../../../files/browser/fileActions.contribution.js"; -import { IChatRequestVariableEntry } from "../../common/chatModel.js"; -import { - ChatResponseReferencePartStatusKind, - IChatContentReference, -} from "../../common/chatService.js"; +import * as dom from '../../../../../base/browser/dom.js'; +import { StandardMouseEvent } from '../../../../../base/browser/mouseEvent.js'; +import { IManagedHoverTooltipMarkdownString } from '../../../../../base/browser/ui/hover/hover.js'; +import { createInstantHoverDelegate } from '../../../../../base/browser/ui/hover/hoverDelegateFactory.js'; +import { Emitter } from '../../../../../base/common/event.js'; +import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; +import { basename, dirname } from '../../../../../base/common/path.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js'; +import { IRange, Range } from '../../../../../editor/common/core/range.js'; +import { ILanguageService } from '../../../../../editor/common/languages/language.js'; +import { IModelService } from '../../../../../editor/common/services/model.js'; +import { localize } from '../../../../../nls.js'; +import { getFlatContextMenuActions } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { IMenuService, MenuId } from '../../../../../platform/actions/common/actions.js'; +import { ICommandService } from '../../../../../platform/commands/common/commands.js'; +import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; +import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js'; +import { ITextEditorOptions } from '../../../../../platform/editor/common/editor.js'; +import { FileKind, IFileService } from '../../../../../platform/files/common/files.js'; +import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { IOpenerService, OpenInternalOptions } from '../../../../../platform/opener/common/opener.js'; +import { FolderThemeIcon, IThemeService } from '../../../../../platform/theme/common/themeService.js'; +import { fillEditorsDragData } from '../../../../browser/dnd.js'; +import { ResourceLabels } from '../../../../browser/labels.js'; +import { ResourceContextKey } from '../../../../common/contextkeys.js'; +import { revealInSideBarCommand } from '../../../files/browser/fileActions.contribution.js'; +import { IChatRequestVariableEntry, isPasteVariableEntry } from '../../common/chatModel.js'; +import { ChatResponseReferencePartStatusKind, IChatContentReference } from '../../common/chatService.js'; export class ChatAttachmentsContentPart extends Disposable { - private readonly attachedContextDisposables = this._register( - new DisposableStore(), - ); - - private readonly _onDidChangeVisibility = this._register( - new Emitter(), - ); - private readonly _contextResourceLabels = - this.instantiationService.createInstance(ResourceLabels, { - onDidChangeVisibility: this._onDidChangeVisibility.event, - }); + private readonly attachedContextDisposables = this._register(new DisposableStore()); + + private readonly _onDidChangeVisibility = this._register(new Emitter()); + private readonly _contextResourceLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this._onDidChangeVisibility.event }); constructor( private readonly variables: IChatRequestVariableEntry[], private readonly contentReferences: ReadonlyArray = [], private readonly workingSet: ReadonlyArray = [], - public readonly domNode: HTMLElement = dom.$(".chat-attached-context"), - @IInstantiationService - private readonly instantiationService: IInstantiationService, + public readonly domNode: HTMLElement = dom.$('.chat-attached-context'), + @IInstantiationService private readonly instantiationService: IInstantiationService, @IOpenerService private readonly openerService: IOpenerService, @IHoverService private readonly hoverService: IHoverService, @IFileService private readonly fileService: IFileService, @@ -72,258 +60,137 @@ export class ChatAttachmentsContentPart extends Disposable { private initAttachedContext(container: HTMLElement) { dom.clearNode(container); this.attachedContextDisposables.clear(); - dom.setVisibility(Boolean(this.variables.length), this.domNode); - - const hoverDelegate = this.attachedContextDisposables.add( - createInstantHoverDelegate(), - ); + const hoverDelegate = this.attachedContextDisposables.add(createInstantHoverDelegate()); this.variables.forEach(async (attachment) => { - const resource = URI.isUri(attachment.value) - ? attachment.value - : attachment.value && - typeof attachment.value === "object" && - "uri" in attachment.value && - URI.isUri(attachment.value.uri) - ? attachment.value.uri - : undefined; - - const range = - attachment.value && - typeof attachment.value === "object" && - "range" in attachment.value && - Range.isIRange(attachment.value.range) - ? attachment.value.range - : undefined; - - if ( - resource && - attachment.isFile && - this.workingSet.find( - (entry) => entry.toString() === resource.toString(), - ) - ) { + const resource = URI.isUri(attachment.value) ? attachment.value : attachment.value && typeof attachment.value === 'object' && 'uri' in attachment.value && URI.isUri(attachment.value.uri) ? attachment.value.uri : undefined; + const range = attachment.value && typeof attachment.value === 'object' && 'range' in attachment.value && Range.isIRange(attachment.value.range) ? attachment.value.range : undefined; + if (resource && attachment.isFile && this.workingSet.find(entry => entry.toString() === resource.toString())) { // Don't render attachment if it's in the working set return; } - const widget = dom.append( - container, - dom.$(".chat-attached-context-attachment.show-file-icons"), - ); - - const label = this._contextResourceLabels.create(widget, { - supportIcons: true, - hoverDelegate, - hoverTargetOverride: widget, - }); - - const correspondingContentReference = this.contentReferences.find( - (ref) => - typeof ref.reference === "object" && - "variableName" in ref.reference && - ref.reference.variableName === attachment.name, - ); - - const isAttachmentOmitted = - correspondingContentReference?.options?.status?.kind === - ChatResponseReferencePartStatusKind.Omitted; - - const isAttachmentPartialOrOmitted = - isAttachmentOmitted || - correspondingContentReference?.options?.status?.kind === - ChatResponseReferencePartStatusKind.Partial; + const widget = dom.append(container, dom.$('.chat-attached-context-attachment.show-file-icons')); + const label = this._contextResourceLabels.create(widget, { supportIcons: true, hoverDelegate, hoverTargetOverride: widget }); + + const correspondingContentReference = this.contentReferences.find((ref) => typeof ref.reference === 'object' && 'variableName' in ref.reference && ref.reference.variableName === attachment.name); + const isAttachmentOmitted = correspondingContentReference?.options?.status?.kind === ChatResponseReferencePartStatusKind.Omitted; + const isAttachmentPartialOrOmitted = isAttachmentOmitted || correspondingContentReference?.options?.status?.kind === ChatResponseReferencePartStatusKind.Partial; let ariaLabel: string | undefined; if (resource && (attachment.isFile || attachment.isDirectory)) { const fileBasename = basename(resource.path); - const fileDirname = dirname(resource.path); - const friendlyName = `${fileBasename} ${fileDirname}`; if (isAttachmentOmitted) { - ariaLabel = range - ? localize( - "chat.omittedFileAttachmentWithRange", - "Omitted: {0}, line {1} to line {2}.", - friendlyName, - range.startLineNumber, - range.endLineNumber, - ) - : localize( - "chat.omittedFileAttachment", - "Omitted: {0}.", - friendlyName, - ); + ariaLabel = range ? localize('chat.omittedFileAttachmentWithRange', "Omitted: {0}, line {1} to line {2}.", friendlyName, range.startLineNumber, range.endLineNumber) : localize('chat.omittedFileAttachment', "Omitted: {0}.", friendlyName); } else if (isAttachmentPartialOrOmitted) { - ariaLabel = range - ? localize( - "chat.partialFileAttachmentWithRange", - "Partially attached: {0}, line {1} to line {2}.", - friendlyName, - range.startLineNumber, - range.endLineNumber, - ) - : localize( - "chat.partialFileAttachment", - "Partially attached: {0}.", - friendlyName, - ); + ariaLabel = range ? localize('chat.partialFileAttachmentWithRange', "Partially attached: {0}, line {1} to line {2}.", friendlyName, range.startLineNumber, range.endLineNumber) : localize('chat.partialFileAttachment', "Partially attached: {0}.", friendlyName); } else { - ariaLabel = range - ? localize( - "chat.fileAttachmentWithRange3", - "Attached: {0}, line {1} to line {2}.", - friendlyName, - range.startLineNumber, - range.endLineNumber, - ) - : localize( - "chat.fileAttachment3", - "Attached: {0}.", - friendlyName, - ); + ariaLabel = range ? localize('chat.fileAttachmentWithRange3', "Attached: {0}, line {1} to line {2}.", friendlyName, range.startLineNumber, range.endLineNumber) : localize('chat.fileAttachment3', "Attached: {0}.", friendlyName); } const fileOptions = { hidePath: true, - title: correspondingContentReference?.options?.status - ?.description, + title: correspondingContentReference?.options?.status?.description }; - label.setFile( - resource, - attachment.isFile - ? { - ...fileOptions, - fileKind: FileKind.FILE, - range, - } - : { - ...fileOptions, - fileKind: FileKind.FOLDER, - icon: !this.themeService.getFileIconTheme() - .hasFolderIcons - ? FolderThemeIcon - : undefined, - }, - ); + label.setFile(resource, attachment.isFile ? { + ...fileOptions, + fileKind: FileKind.FILE, + range, + } : { + ...fileOptions, + fileKind: FileKind.FOLDER, + icon: !this.themeService.getFileIconTheme().hasFolderIcons ? FolderThemeIcon : undefined + }); + + this.instantiationService.invokeFunction(accessor => hookUpResourceAttachmentInteractions(accessor, this.attachedContextDisposables, widget, resource)); + } else if (attachment.isImage) { - ariaLabel = localize( - "chat.imageAttachment", - "Attached image, {0}", - attachment.name, - ); + ariaLabel = localize('chat.imageAttachment', "Attached image, {0}", attachment.name); - const hoverElement = dom.$("div.chat-attached-context-hover"); - hoverElement.setAttribute("aria-label", ariaLabel); + const hoverElement = dom.$('div.chat-attached-context-hover'); + hoverElement.setAttribute('aria-label', ariaLabel); // Custom label - const pillIcon = dom.$( - "div.chat-attached-context-pill", - {}, - dom.$("span.codicon.codicon-file-media"), - ); - - const textLabel = dom.$( - "span.chat-attached-context-custom-text", - {}, - attachment.name, - ); + const pillIcon = dom.$('div.chat-attached-context-pill', {}, dom.$('span.codicon.codicon-file-media')); + const textLabel = dom.$('span.chat-attached-context-custom-text', {}, attachment.name); widget.appendChild(pillIcon); widget.appendChild(textLabel); let buffer: Uint8Array; - try { if (attachment.value instanceof URI) { - const readFile = await this.fileService.readFile( - attachment.value, - ); + const readFile = await this.fileService.readFile(attachment.value); buffer = readFile.value.buffer; + } else { buffer = attachment.value as Uint8Array; } - await this.createImageElements( - buffer, - widget, - hoverElement, - ); + await this.createImageElements(buffer, widget, hoverElement); } catch (error) { - console.error("Error processing attachment:", error); + console.error('Error processing attachment:', error); + } + + widget.style.position = 'relative'; + if (!this.attachedContextDisposables.isDisposed) { + this.attachedContextDisposables.add(this.hoverService.setupManagedHover(hoverDelegate, widget, hoverElement)); } + } else if (isPasteVariableEntry(attachment)) { + ariaLabel = localize('chat.attachment', "Attached context, {0}", attachment.name); + + const hoverContent: IManagedHoverTooltipMarkdownString = { + markdown: { + value: `\`\`\`${attachment.language}\n${attachment.code}\n\`\`\``, + }, + markdownNotSupportedFallback: attachment.code, + }; - widget.style.position = "relative"; + const classNames = ['file-icon', `${attachment.language}-lang-file-icon`]; + label.setLabel(attachment.fileName, undefined, { extraClasses: classNames }); + widget.appendChild(dom.$('span.attachment-additional-info', {}, `Pasted ${attachment.pastedLines}`)); + + widget.style.position = 'relative'; if (!this.attachedContextDisposables.isDisposed) { - this.attachedContextDisposables.add( - this.hoverService.setupManagedHover( - hoverDelegate, - widget, - hoverElement, - ), - ); + this.attachedContextDisposables.add(this.hoverService.setupManagedHover(hoverDelegate, widget, hoverContent, { trapFocus: true })); } } else { const attachmentLabel = attachment.fullName ?? attachment.name; + const withIcon = attachment.icon?.id ? `$(${attachment.icon.id}) ${attachmentLabel}` : attachmentLabel; + label.setLabel(withIcon, correspondingContentReference?.options?.status?.description); - const withIcon = attachment.icon?.id - ? `$(${attachment.icon.id}) ${attachmentLabel}` - : attachmentLabel; - label.setLabel( - withIcon, - correspondingContentReference?.options?.status?.description, - ); - - ariaLabel = localize( - "chat.attachment3", - "Attached context: {0}.", - attachment.name, - ); + ariaLabel = localize('chat.attachment3', "Attached context: {0}.", attachment.name); } if (isAttachmentPartialOrOmitted) { - widget.classList.add("warning"); + widget.classList.add('warning'); } - const description = - correspondingContentReference?.options?.status?.description; - + const description = correspondingContentReference?.options?.status?.description; if (isAttachmentPartialOrOmitted) { - ariaLabel = `${ariaLabel}${description ? ` ${description}` : ""}`; - - for (const selector of [ - ".monaco-icon-suffix-container", - ".monaco-icon-name-container", - ]) { + ariaLabel = `${ariaLabel}${description ? ` ${description}` : ''}`; + for (const selector of ['.monaco-icon-suffix-container', '.monaco-icon-name-container']) { const element = label.element.querySelector(selector); - if (element) { - element.classList.add("warning"); + element.classList.add('warning'); } } } if (resource) { - widget.style.cursor = "pointer"; - + widget.style.cursor = 'pointer'; if (!this.attachedContextDisposables.isDisposed) { - this.attachedContextDisposables.add( - dom.addDisposableListener( - widget, - dom.EventType.CLICK, - async (e: MouseEvent) => { - dom.EventHelper.stop(e, true); - - if (attachment.isDirectory) { - this.openResource(resource, true); - } else { - this.openResource(resource, false, range); - } - }, - ), - ); + this.attachedContextDisposables.add(dom.addDisposableListener(widget, dom.EventType.CLICK, async (e: MouseEvent) => { + dom.EventHelper.stop(e, true); + if (attachment.isDirectory) { + this.openResource(resource, true); + } else { + this.openResource(resource, false, range); + } + })); } } @@ -333,31 +200,16 @@ export class ChatAttachmentsContentPart extends Disposable { } private openResource(resource: URI, isDirectory: true): void; - private openResource( - resource: URI, - isDirectory: false, - range: IRange | undefined, - ): void; - private openResource( - resource: URI, - isDirectory?: boolean, - range?: IRange, - ): void { + private openResource(resource: URI, isDirectory: false, range: IRange | undefined): void; + private openResource(resource: URI, isDirectory?: boolean, range?: IRange): void { if (isDirectory) { // Reveal Directory in explorer - this.commandService.executeCommand( - revealInSideBarCommand.id, - resource, - ); - + this.commandService.executeCommand(revealInSideBarCommand.id, resource); return; } // Open file in editor - const openTextEditorOptions: ITextEditorOptions | undefined = range - ? { selection: range } - : undefined; - + const openTextEditorOptions: ITextEditorOptions | undefined = range ? { selection: range } : undefined; const options: OpenInternalOptions = { fromUserGesture: true, editorOptions: openTextEditorOptions, @@ -366,31 +218,14 @@ export class ChatAttachmentsContentPart extends Disposable { } // Helper function to create and replace image - private async createImageElements( - buffer: ArrayBuffer | Uint8Array, - widget: HTMLElement, - hoverElement: HTMLElement, - ) { - const blob = new Blob([buffer], { type: "image/png" }); - + private async createImageElements(buffer: ArrayBuffer | Uint8Array, widget: HTMLElement, hoverElement: HTMLElement) { + const blob = new Blob([buffer], { type: 'image/png' }); const url = URL.createObjectURL(blob); + const img = dom.$('img.chat-attached-context-image', { src: url, alt: '' }); + const pillImg = dom.$('img.chat-attached-context-pill-image', { src: url, alt: '' }); + const pill = dom.$('div.chat-attached-context-pill', {}, pillImg); - const img = dom.$("img.chat-attached-context-image", { - src: url, - alt: "", - }); - - const pillImg = dom.$("img.chat-attached-context-pill-image", { - src: url, - alt: "", - }); - - const pill = dom.$("div.chat-attached-context-pill", {}, pillImg); - - const existingPill = widget.querySelector( - ".chat-attached-context-pill", - ); - + const existingPill = widget.querySelector('.chat-attached-context-pill'); if (existingPill) { existingPill.replaceWith(pill); } @@ -399,3 +234,40 @@ export class ChatAttachmentsContentPart extends Disposable { hoverElement.appendChild(img); } } + +export function hookUpResourceAttachmentInteractions(accessor: ServicesAccessor, store: DisposableStore, widget: HTMLElement, resource: URI): void { + const fileService = accessor.get(IFileService); + const languageService = accessor.get(ILanguageService); + const modelService = accessor.get(IModelService); + const contextKeyService = accessor.get(IContextKeyService); + const instantiationService = accessor.get(IInstantiationService); + const contextMenuService = accessor.get(IContextMenuService); + const menuService = accessor.get(IMenuService); + + // Context + const scopedContextKeyService = store.add(contextKeyService.createScoped(widget)); + const resourceContextKey = store.add(new ResourceContextKey(scopedContextKeyService, fileService, languageService, modelService)); + resourceContextKey.set(resource); + + // Drag and drop + widget.draggable = true; + store.add(dom.addDisposableListener(widget, 'dragstart', e => { + instantiationService.invokeFunction(accessor => fillEditorsDragData(accessor, [resource], e)); + e.dataTransfer?.setDragImage(widget, 0, 0); + })); + + // Context menu + store.add(dom.addDisposableListener(widget, dom.EventType.CONTEXT_MENU, domEvent => { + const event = new StandardMouseEvent(dom.getWindow(domEvent), domEvent); + dom.EventHelper.stop(domEvent, true); + + contextMenuService.showContextMenu({ + contextKeyService: scopedContextKeyService, + getAnchor: () => event, + getActions: () => { + const menu = menuService.getMenuActions(MenuId.ChatInputResourceAttachmentContext, scopedContextKeyService, { arg: resource }); + return getFlatContextMenuActions(menu); + }, + }); + })); +} diff --git a/Source/vs/workbench/contrib/chat/browser/chatDragAndDrop.ts b/Source/vs/workbench/contrib/chat/browser/chatDragAndDrop.ts index e4f90c2686b93..16fc765a5ebcb 100644 --- a/Source/vs/workbench/contrib/chat/browser/chatDragAndDrop.ts +++ b/Source/vs/workbench/contrib/chat/browser/chatDragAndDrop.ts @@ -3,43 +3,29 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DataTransfers } from "../../../../base/browser/dnd.js"; -import { $, DragAndDropObserver } from "../../../../base/browser/dom.js"; -import { renderLabelWithIcons } from "../../../../base/browser/ui/iconLabel/iconLabels.js"; -import { coalesce } from "../../../../base/common/arrays.js"; -import { Codicon } from "../../../../base/common/codicons.js"; -import { IDisposable } from "../../../../base/common/lifecycle.js"; -import { Mimes } from "../../../../base/common/mime.js"; -import { basename, joinPath } from "../../../../base/common/resources.js"; -import { URI } from "../../../../base/common/uri.js"; -import { IRange } from "../../../../editor/common/core/range.js"; -import { SymbolKinds } from "../../../../editor/common/languages.js"; -import { localize } from "../../../../nls.js"; -import { - CodeDataTransfers, - containsDragType, - DocumentSymbolTransferData, - extractEditorsDropData, - extractSymbolDropData, - IDraggedResourceEditorInput, -} from "../../../../platform/dnd/browser/dnd.js"; -import { - FileType, - IFileService, - IFileSystemProvider, -} from "../../../../platform/files/common/files.js"; -import { - IThemeService, - Themable, -} from "../../../../platform/theme/common/themeService.js"; -import { EditorInput } from "../../../common/editor/editorInput.js"; -import { - IExtensionService, - isProposedApiEnabled, -} from "../../../services/extensions/common/extensions.js"; -import { IChatRequestVariableEntry } from "../common/chatModel.js"; -import { ChatAttachmentModel } from "./chatAttachmentModel.js"; -import { IChatInputStyles } from "./chatInputPart.js"; +import { DataTransfers } from '../../../../base/browser/dnd.js'; +import { $, DragAndDropObserver } from '../../../../base/browser/dom.js'; +import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; +import { coalesce } from '../../../../base/common/arrays.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { IDisposable } from '../../../../base/common/lifecycle.js'; +import { Mimes } from '../../../../base/common/mime.js'; +import { basename, joinPath } from '../../../../base/common/resources.js'; +import { URI } from '../../../../base/common/uri.js'; +import { IRange } from '../../../../editor/common/core/range.js'; +import { SymbolKinds } from '../../../../editor/common/languages.js'; +import { localize } from '../../../../nls.js'; +import { CodeDataTransfers, containsDragType, DocumentSymbolTransferData, extractEditorsDropData, extractSymbolDropData, IDraggedResourceEditorInput } from '../../../../platform/dnd/browser/dnd.js'; +import { FileType, IFileService, IFileSystemProvider } from '../../../../platform/files/common/files.js'; +import { IThemeService, Themable } from '../../../../platform/theme/common/themeService.js'; +import { isUntitledResourceEditorInput } from '../../../common/editor.js'; +import { EditorInput } from '../../../common/editor/editorInput.js'; +import { IEditorService } from '../../../services/editor/common/editorService.js'; +import { IExtensionService, isProposedApiEnabled } from '../../../services/extensions/common/extensions.js'; +import { UntitledTextEditorInput } from '../../../services/untitled/common/untitledTextEditorInput.js'; +import { IChatRequestVariableEntry } from '../common/chatModel.js'; +import { ChatAttachmentModel } from './chatAttachmentModel.js'; +import { IChatInputStyles } from './chatInputPart.js'; enum ChatDragAndDropType { FILE_INTERNAL, @@ -63,6 +49,7 @@ export class ChatDragAndDrop extends Themable { @IThemeService themeService: IThemeService, @IExtensionService private readonly extensionService: IExtensionService, @IFileService protected readonly fileService: IFileService, + @IEditorService protected readonly editorService: IEditorService, ) { super(themeService); @@ -199,7 +186,7 @@ export class ChatDragAndDrop extends Themable { return ChatDragAndDropType.FILE_EXTERNAL; } else if (containsDragType(e, DataTransfers.INTERNAL_URI_LIST)) { return ChatDragAndDropType.FILE_INTERNAL; - } else if (containsDragType(e, Mimes.uriList)) { + } else if (containsDragType(e, Mimes.uriList, CodeDataTransfers.FILES)) { return ChatDragAndDropType.FOLDER; } @@ -296,9 +283,13 @@ export class ChatDragAndDrop extends Themable { return await this.getEditorAttachContext(editorInput); } - private async getEditorAttachContext( - editor: EditorInput | IDraggedResourceEditorInput, - ): Promise { + private async getEditorAttachContext(editor: EditorInput | IDraggedResourceEditorInput): Promise { + + // untitled editor + if (isUntitledResourceEditorInput(editor)) { + return await this.resolveUntitledAttachContext(editor); + } + if (!editor.resource) { return undefined; } @@ -318,10 +309,27 @@ export class ChatDragAndDrop extends Themable { return getResourceAttachContext(editor.resource, stat.isDirectory); } - private resolveSymbolsAttachContext( - symbols: DocumentSymbolTransferData[], - ): IChatRequestVariableEntry[] { - return symbols.map((symbol) => { + private async resolveUntitledAttachContext(editor: IDraggedResourceEditorInput): Promise { + // If the resource is known, we can use it directly + if (editor.resource) { + return getResourceAttachContext(editor.resource, false); + } + + // Otherwise, we need to check if the contents are already open in another editor + const openUntitledEditors = this.editorService.editors.filter(editor => editor instanceof UntitledTextEditorInput) as UntitledTextEditorInput[]; + for (const canidate of openUntitledEditors) { + const model = await canidate.resolve(); + const contents = model.textEditorModel?.getValue(); + if (contents === editor.contents) { + return getResourceAttachContext(canidate.resource, false); + } + } + + return undefined; + } + + private resolveSymbolsAttachContext(symbols: DocumentSymbolTransferData[]): IChatRequestVariableEntry[] { + return symbols.map(symbol => { const resource = URI.file(symbol.fsPath); return { kind: "symbol", @@ -398,14 +406,9 @@ export class EditsDragAndDrop extends ChatDragAndDrop { @IThemeService themeService: IThemeService, @IExtensionService extensionService: IExtensionService, @IFileService fileService: IFileService, + @IEditorService editorService: IEditorService, ) { - super( - attachmentModel, - styles, - themeService, - extensionService, - fileService, - ); + super(attachmentModel, styles, themeService, extensionService, fileService, editorService); } protected override handleDrop(context: IChatRequestVariableEntry[]): void { diff --git a/Source/vs/workbench/contrib/chat/browser/chatEdinputInputContentProvider.ts b/Source/vs/workbench/contrib/chat/browser/chatEdinputInputContentProvider.ts new file mode 100644 index 0000000000000..cbc084b3b82ad --- /dev/null +++ b/Source/vs/workbench/contrib/chat/browser/chatEdinputInputContentProvider.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { URI } from '../../../../base/common/uri.js'; +import { ILanguageService } from '../../../../editor/common/languages/language.js'; +import { ITextModel } from '../../../../editor/common/model.js'; +import { IModelService } from '../../../../editor/common/services/model.js'; +import { ITextModelContentProvider, ITextModelService } from '../../../../editor/common/services/resolverService.js'; +import { ChatInputPart } from './chatInputPart.js'; + + +export class ChatInputBoxContentProvider extends Disposable implements ITextModelContentProvider { + constructor( + @ITextModelService textModelService: ITextModelService, + @IModelService private readonly modelService: IModelService, + @ILanguageService private readonly languageService: ILanguageService + ) { + super(); + this._register(textModelService.registerTextModelContentProvider(ChatInputPart.INPUT_SCHEME, this)); + } + + async provideTextContent(resource: URI): Promise { + const existing = this.modelService.getModel(resource); + if (existing) { + return existing; + } + return this.modelService.createModel('', this.languageService.createById('chatinput'), resource); + } +} diff --git a/Source/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts b/Source/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts index c2f2ff8d5bbde..491c01c1ba30d 100644 --- a/Source/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts +++ b/Source/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts @@ -3,48 +3,30 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Codicon } from "../../../../../base/common/codicons.js"; -import { KeyCode, KeyMod } from "../../../../../base/common/keyCodes.js"; -import { basename } from "../../../../../base/common/resources.js"; -import { URI } from "../../../../../base/common/uri.js"; -import { isCodeEditor } from "../../../../../editor/browser/editorBrowser.js"; -import { ServicesAccessor } from "../../../../../editor/browser/editorExtensions.js"; -import { localize, localize2 } from "../../../../../nls.js"; -import { - Action2, - MenuId, - registerAction2, -} from "../../../../../platform/actions/common/actions.js"; -import { IConfigurationService } from "../../../../../platform/configuration/common/configuration.js"; -import { ContextKeyExpr } from "../../../../../platform/contextkey/common/contextkey.js"; -import { IDialogService } from "../../../../../platform/dialogs/common/dialogs.js"; -import { EditorActivation } from "../../../../../platform/editor/common/editor.js"; -import { KeybindingWeight } from "../../../../../platform/keybinding/common/keybindingsRegistry.js"; -import { IListService } from "../../../../../platform/list/browser/listService.js"; -import { - GroupsOrder, - IEditorGroupsService, -} from "../../../../services/editor/common/editorGroupsService.js"; -import { IEditorService } from "../../../../services/editor/common/editorService.js"; -import { ChatAgentLocation } from "../../common/chatAgents.js"; -import { ChatContextKeys } from "../../common/chatContextKeys.js"; -import { - applyingChatEditsFailedContextKey, - CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, - chatEditingResourceContextKey, - chatEditingWidgetFileStateContextKey, - decidedChatEditingResourceContextKey, - hasAppliedChatEditsContextKey, - hasUndecidedChatEditingResourceContextKey, - IChatEditingService, - IChatEditingSession, - WorkingSetEntryRemovalReason, - WorkingSetEntryState, -} from "../../common/chatEditingService.js"; -import { IChatService } from "../../common/chatService.js"; -import { isRequestVM, isResponseVM } from "../../common/chatViewModel.js"; -import { CHAT_CATEGORY } from "../actions/chatActions.js"; -import { ChatTreeItem, IChatWidget, IChatWidgetService } from "../chat.js"; +import { Codicon } from '../../../../../base/common/codicons.js'; +import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; +import { basename } from '../../../../../base/common/resources.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { isCodeEditor } from '../../../../../editor/browser/editorBrowser.js'; +import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js'; +import { localize, localize2 } from '../../../../../nls.js'; +import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; +import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js'; +import { EditorActivation } from '../../../../../platform/editor/common/editor.js'; +import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { IListService } from '../../../../../platform/list/browser/listService.js'; +import { GroupsOrder, IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; +import { IEditorService } from '../../../../services/editor/common/editorService.js'; +import { ChatAgentLocation } from '../../common/chatAgents.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; +import { applyingChatEditsFailedContextKey, CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, chatEditingResourceContextKey, chatEditingWidgetFileStateContextKey, decidedChatEditingResourceContextKey, hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, WorkingSetEntryRemovalReason, WorkingSetEntryState } from '../../common/chatEditingService.js'; +import { IChatService } from '../../common/chatService.js'; +import { isRequestVM, isResponseVM } from '../../common/chatViewModel.js'; +import { CHAT_CATEGORY } from '../actions/chatActions.js'; +import { ChatTreeItem, IChatWidget, IChatWidgetService } from '../chat.js'; +import { EditsAttachmentModel } from '../chatAttachmentModel.js'; abstract class WorkingSetAction extends Action2 { run(accessor: ServicesAccessor, ...args: any[]) { @@ -69,296 +51,169 @@ abstract class WorkingSetAction extends Action2 { return; } - return this.runWorkingSetAction( - accessor, - currentEditingSession, - chatWidget, - ...uris, - ); + return this.runWorkingSetAction(accessor, currentEditingSession, chatWidget, ...uris); } - abstract runWorkingSetAction( - accessor: ServicesAccessor, - currentEditingSession: IChatEditingSession, - chatWidget: IChatWidget | undefined, - ...uris: URI[] - ): any; + abstract runWorkingSetAction(accessor: ServicesAccessor, currentEditingSession: IChatEditingSession, chatWidget: IChatWidget | undefined, ...uris: URI[]): any; } -registerAction2( - class AddFileToWorkingSet extends WorkingSetAction { - constructor() { - super({ - id: "chatEditing.addFileToWorkingSet", - title: localize2("addFileToWorkingSet", "Add File"), - icon: Codicon.plus, - menu: [ - { - id: MenuId.ChatEditingWidgetModifiedFilesToolbar, - when: ContextKeyExpr.or( - ContextKeyExpr.equals( - chatEditingWidgetFileStateContextKey.key, - WorkingSetEntryState.Transient, - ), - ContextKeyExpr.equals( - chatEditingWidgetFileStateContextKey.key, - WorkingSetEntryState.Suggested, - ), - ), - order: 0, - group: "navigation", - }, - ], - }); - } +registerAction2(class AddFileToWorkingSet extends WorkingSetAction { + constructor() { + super({ + id: 'chatEditing.addFileToWorkingSet', + title: localize2('addFileToWorkingSet', 'Add File'), + icon: Codicon.plus, + menu: [{ + id: MenuId.ChatEditingWidgetModifiedFilesToolbar, + when: ContextKeyExpr.or(ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Transient), ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Suggested)), + order: 0, + group: 'navigation' + }], + }); + } - async runWorkingSetAction( - _accessor: ServicesAccessor, - currentEditingSession: IChatEditingSession, - _chatWidget: IChatWidget, - ...uris: URI[] - ): Promise { - for (const uri of uris) { - currentEditingSession.addFileToWorkingSet(uri); - } - } - }, -); - -registerAction2( - class RemoveFileFromWorkingSet extends WorkingSetAction { - constructor() { - super({ - id: "chatEditing.removeFileFromWorkingSet", - title: localize2("removeFileFromWorkingSet", "Remove File"), - icon: Codicon.close, - menu: [ - { - id: MenuId.ChatEditingWidgetModifiedFilesToolbar, - // when: ContextKeyExpr.or(ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Attached), ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Suggested), ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Transient)), - order: 5, - group: "navigation", - }, - ], - }); + async runWorkingSetAction(_accessor: ServicesAccessor, currentEditingSession: IChatEditingSession, _chatWidget: IChatWidget, ...uris: URI[]): Promise { + for (const uri of uris) { + currentEditingSession.addFileToWorkingSet(uri); } + } +}); - async runWorkingSetAction( - accessor: ServicesAccessor, - currentEditingSession: IChatEditingSession, - chatWidget: IChatWidget, - ...uris: URI[] - ): Promise { - // Remove from working set - currentEditingSession.remove( - WorkingSetEntryRemovalReason.User, - ...uris, - ); - - // Remove from chat input part - for (const uri of uris) { - chatWidget.attachmentModel.delete(uri.toString()); - } - } - }, -); - -registerAction2( - class OpenFileInDiffAction extends WorkingSetAction { - constructor() { - super({ - id: "chatEditing.openFileInDiff", - title: localize2( - "open.fileInDiff", - "Open Changes in Diff Editor", - ), - icon: Codicon.diffSingle, - menu: [ - { - id: MenuId.ChatEditingWidgetModifiedFilesToolbar, - when: ContextKeyExpr.equals( - chatEditingWidgetFileStateContextKey.key, - WorkingSetEntryState.Modified, - ), - order: 2, - group: "navigation", - }, - ], - }); +registerAction2(class RemoveFileFromWorkingSet extends WorkingSetAction { + constructor() { + super({ + id: 'chatEditing.removeFileFromWorkingSet', + title: localize2('removeFileFromWorkingSet', 'Remove File'), + icon: Codicon.close, + menu: [{ + id: MenuId.ChatEditingWidgetModifiedFilesToolbar, + // when: ContextKeyExpr.or(ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Attached), ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Suggested), ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Transient)), + order: 5, + group: 'navigation' + }], + }); + } + + async runWorkingSetAction(accessor: ServicesAccessor, currentEditingSession: IChatEditingSession, chatWidget: IChatWidget, ...uris: URI[]): Promise { + // Remove from working set + currentEditingSession.remove(WorkingSetEntryRemovalReason.User, ...uris); + + // Remove from chat input part + for (const uri of uris) { + chatWidget.attachmentModel.delete(uri.toString()); } + } +}); - async runWorkingSetAction( - accessor: ServicesAccessor, - currentEditingSession: IChatEditingSession, - _chatWidget: IChatWidget, - ...uris: URI[] - ): Promise { - const editorService = accessor.get(IEditorService); - for (const uri of uris) { - const editedFile = currentEditingSession.entries - .get() - .find((e) => e.modifiedURI.toString() === uri.toString()); - if (editedFile?.state.get() === WorkingSetEntryState.Modified) { - await editorService.openEditor({ - original: { - resource: URI.from(editedFile.originalURI, true), - }, - modified: { - resource: URI.from(editedFile.modifiedURI, true), - }, - }); - } else { - await editorService.openEditor({ resource: uri }); - } +registerAction2(class OpenFileInDiffAction extends WorkingSetAction { + constructor() { + super({ + id: 'chatEditing.openFileInDiff', + title: localize2('open.fileInDiff', 'Open Changes in Diff Editor'), + icon: Codicon.diffSingle, + menu: [{ + id: MenuId.ChatEditingWidgetModifiedFilesToolbar, + when: ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Modified), + order: 2, + group: 'navigation' + }], + }); + } + + async runWorkingSetAction(accessor: ServicesAccessor, currentEditingSession: IChatEditingSession, _chatWidget: IChatWidget, ...uris: URI[]): Promise { + const editorService = accessor.get(IEditorService); + for (const uri of uris) { + const editedFile = currentEditingSession.entries.get().find((e) => e.modifiedURI.toString() === uri.toString()); + if (editedFile?.state.get() === WorkingSetEntryState.Modified) { + await editorService.openEditor({ + original: { resource: URI.from(editedFile.originalURI, true) }, + modified: { resource: URI.from(editedFile.modifiedURI, true) }, + }); + } else { + await editorService.openEditor({ resource: uri }); } } - }, -); - -registerAction2( - class AcceptAction extends WorkingSetAction { - constructor() { - super({ - id: "chatEditing.acceptFile", - title: localize2("accept.file", "Accept"), - icon: Codicon.check, - menu: [ - { - when: ContextKeyExpr.and( - ContextKeyExpr.equals( - "resourceScheme", - CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, - ), - ContextKeyExpr.notIn( - chatEditingResourceContextKey.key, - decidedChatEditingResourceContextKey.key, - ), - ), - id: MenuId.MultiDiffEditorFileToolbar, - order: 0, - group: "navigation", - }, - { - id: MenuId.ChatEditingWidgetModifiedFilesToolbar, - when: ContextKeyExpr.equals( - chatEditingWidgetFileStateContextKey.key, - WorkingSetEntryState.Modified, - ), - order: 0, - group: "navigation", - }, - ], - }); - } + } +}); - async runWorkingSetAction( - accessor: ServicesAccessor, - currentEditingSession: IChatEditingSession, - chatWidget: IChatWidget, - ...uris: URI[] - ): Promise { - await currentEditingSession.accept(...uris); - } - }, -); - -registerAction2( - class DiscardAction extends WorkingSetAction { - constructor() { - super({ - id: "chatEditing.discardFile", - title: localize2("discard.file", "Discard"), - icon: Codicon.discard, - menu: [ - { - when: ContextKeyExpr.and( - ContextKeyExpr.equals( - "resourceScheme", - CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, - ), - ContextKeyExpr.notIn( - chatEditingResourceContextKey.key, - decidedChatEditingResourceContextKey.key, - ), - ), - id: MenuId.MultiDiffEditorFileToolbar, - order: 2, - group: "navigation", - }, - { - id: MenuId.ChatEditingWidgetModifiedFilesToolbar, - when: ContextKeyExpr.equals( - chatEditingWidgetFileStateContextKey.key, - WorkingSetEntryState.Modified, - ), - order: 1, - group: "navigation", - }, - ], - }); - } +registerAction2(class AcceptAction extends WorkingSetAction { + constructor() { + super({ + id: 'chatEditing.acceptFile', + title: localize2('accept.file', 'Accept'), + icon: Codicon.check, + menu: [{ + when: ContextKeyExpr.and(ContextKeyExpr.equals('resourceScheme', CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME), ContextKeyExpr.notIn(chatEditingResourceContextKey.key, decidedChatEditingResourceContextKey.key)), + id: MenuId.MultiDiffEditorFileToolbar, + order: 0, + group: 'navigation', + }, { + id: MenuId.ChatEditingWidgetModifiedFilesToolbar, + when: ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Modified), + order: 0, + group: 'navigation' + }], + }); + } - async runWorkingSetAction( - accessor: ServicesAccessor, - currentEditingSession: IChatEditingSession, - chatWidget: IChatWidget, - ...uris: URI[] - ): Promise { - await currentEditingSession.reject(...uris); - } - }, -); + async runWorkingSetAction(accessor: ServicesAccessor, currentEditingSession: IChatEditingSession, chatWidget: IChatWidget, ...uris: URI[]): Promise { + await currentEditingSession.accept(...uris); + } +}); + +registerAction2(class DiscardAction extends WorkingSetAction { + constructor() { + super({ + id: 'chatEditing.discardFile', + title: localize2('discard.file', 'Discard'), + icon: Codicon.discard, + menu: [{ + when: ContextKeyExpr.and(ContextKeyExpr.equals('resourceScheme', CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME), ContextKeyExpr.notIn(chatEditingResourceContextKey.key, decidedChatEditingResourceContextKey.key)), + id: MenuId.MultiDiffEditorFileToolbar, + order: 2, + group: 'navigation', + }, { + id: MenuId.ChatEditingWidgetModifiedFilesToolbar, + when: ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Modified), + order: 1, + group: 'navigation' + }], + }); + } + + async runWorkingSetAction(accessor: ServicesAccessor, currentEditingSession: IChatEditingSession, chatWidget: IChatWidget, ...uris: URI[]): Promise { + await currentEditingSession.reject(...uris); + } +}); export class ChatEditingAcceptAllAction extends Action2 { + constructor() { super({ - id: "chatEditing.acceptAllFiles", - title: localize("accept", "Accept"), + id: 'chatEditing.acceptAllFiles', + title: localize('accept', 'Accept'), icon: Codicon.check, - tooltip: localize("acceptAllEdits", "Accept All Edits"), - precondition: ContextKeyExpr.and( - ChatContextKeys.requestInProgress.negate(), - hasUndecidedChatEditingResourceContextKey, - ), + tooltip: localize('acceptAllEdits', 'Accept All Edits'), + precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.Enter, - when: ContextKeyExpr.and( - ChatContextKeys.requestInProgress.negate(), - hasUndecidedChatEditingResourceContextKey, - ChatContextKeys.location.isEqualTo( - ChatAgentLocation.EditingSession, - ), - ChatContextKeys.inChatInput, - ), + when: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey, ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), ChatContextKeys.inChatInput), weight: KeybindingWeight.WorkbenchContrib, }, menu: [ { - when: ContextKeyExpr.equals( - "resourceScheme", - CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, - ), + when: ContextKeyExpr.equals('resourceScheme', CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME), id: MenuId.EditorTitle, order: 0, - group: "navigation", + group: 'navigation', }, { id: MenuId.ChatEditingWidgetToolbar, - group: "navigation", + group: 'navigation', order: 0, - when: ContextKeyExpr.and( - applyingChatEditsFailedContextKey.negate(), - ContextKeyExpr.and( - hasUndecidedChatEditingResourceContextKey, - ContextKeyExpr.and( - ChatContextKeys.location.isEqualTo( - ChatAgentLocation.EditingSession, - ), - ), - ), - ), - }, - ], + when: ContextKeyExpr.and(applyingChatEditsFailedContextKey.negate(), ContextKeyExpr.and(hasUndecidedChatEditingResourceContextKey, ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession)))) + } + ] }); } @@ -374,51 +229,30 @@ export class ChatEditingAcceptAllAction extends Action2 { registerAction2(ChatEditingAcceptAllAction); export class ChatEditingDiscardAllAction extends Action2 { + constructor() { super({ - id: "chatEditing.discardAllFiles", - title: localize("discard", "Discard"), + id: 'chatEditing.discardAllFiles', + title: localize('discard', 'Discard'), icon: Codicon.discard, - tooltip: localize("discardAllEdits", "Discard All Edits"), - precondition: ContextKeyExpr.and( - ChatContextKeys.requestInProgress.negate(), - hasUndecidedChatEditingResourceContextKey, - ), + tooltip: localize('discardAllEdits', 'Discard All Edits'), + precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey), menu: [ { - when: ContextKeyExpr.equals( - "resourceScheme", - CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, - ), + when: ContextKeyExpr.equals('resourceScheme', CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME), id: MenuId.EditorTitle, order: 1, - group: "navigation", + group: 'navigation', }, { id: MenuId.ChatEditingWidgetToolbar, - group: "navigation", + group: 'navigation', order: 1, - when: ContextKeyExpr.and( - applyingChatEditsFailedContextKey.negate(), - ContextKeyExpr.and( - ChatContextKeys.location.isEqualTo( - ChatAgentLocation.EditingSession, - ), - hasUndecidedChatEditingResourceContextKey, - ), - ), - }, + when: ContextKeyExpr.and(applyingChatEditsFailedContextKey.negate(), ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), hasUndecidedChatEditingResourceContextKey)) + } ], keybinding: { - when: ContextKeyExpr.and( - ChatContextKeys.requestInProgress.negate(), - hasUndecidedChatEditingResourceContextKey, - ChatContextKeys.location.isEqualTo( - ChatAgentLocation.EditingSession, - ), - ChatContextKeys.inChatInput, - ChatContextKeys.inputHasText.negate(), - ), + when: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey, ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), ChatContextKeys.inChatInput, ChatContextKeys.inputHasText.negate()), weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.Backspace, }, @@ -437,29 +271,12 @@ export class ChatEditingDiscardAllAction extends Action2 { const entries = currentEditingSession.entries.get(); if (entries.length > 0) { const confirmation = await dialogService.confirm({ - title: localize( - "chat.editing.discardAll.confirmation.title", - "Discard all edits?", - ), - message: - entries.length === 1 - ? localize( - "chat.editing.discardAll.confirmation.oneFile", - "This will undo changes made by {0} in {1}. Do you want to proceed?", - "Copilot Edits", - basename(entries[0].modifiedURI), - ) - : localize( - "chat.editing.discardAll.confirmation.manyFiles", - "This will undo changes made by {0} in {1} files. Do you want to proceed?", - "Copilot Edits", - entries.length, - ), - primaryButton: localize( - "chat.editing.discardAll.confirmation.primaryButton", - "Yes", - ), - type: "info", + title: localize('chat.editing.discardAll.confirmation.title', "Discard all edits?"), + message: entries.length === 1 + ? localize('chat.editing.discardAll.confirmation.oneFile', "This will undo changes made by {0} in {1}. Do you want to proceed?", 'Copilot Edits', basename(entries[0].modifiedURI)) + : localize('chat.editing.discardAll.confirmation.manyFiles', "This will undo changes made by {0} in {1} files. Do you want to proceed?", 'Copilot Edits', entries.length), + primaryButton: localize('chat.editing.discardAll.confirmation.primaryButton', "Yes"), + type: 'info' }); if (!confirmation.confirmed) { return; @@ -472,30 +289,23 @@ export class ChatEditingDiscardAllAction extends Action2 { registerAction2(ChatEditingDiscardAllAction); export class ChatEditingRemoveAllFilesAction extends Action2 { - static readonly ID = "chatEditing.clearWorkingSet"; + static readonly ID = 'chatEditing.clearWorkingSet'; constructor() { super({ id: ChatEditingRemoveAllFilesAction.ID, - title: localize("clearWorkingSet", "Clear Working Set"), + title: localize('clearWorkingSet', 'Clear Working Set'), icon: Codicon.clearAll, - tooltip: localize("clearWorkingSet", "Clear Working Set"), - precondition: ContextKeyExpr.and( - ChatContextKeys.requestInProgress.negate(), - ), + tooltip: localize('clearWorkingSet', 'Clear Working Set'), + precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate()), menu: [ { id: MenuId.ChatEditingWidgetToolbar, - group: "navigation", + group: 'navigation', order: 5, - when: ContextKeyExpr.and( - hasAppliedChatEditsContextKey.negate(), - ChatContextKeys.location.isEqualTo( - ChatAgentLocation.EditingSession, - ), - ), - }, - ], + when: ContextKeyExpr.and(hasAppliedChatEditsContextKey.negate(), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession)) + } + ] }); } @@ -506,16 +316,16 @@ export class ChatEditingRemoveAllFilesAction extends Action2 { return; } - const chatWidget = accessor - .get(IChatWidgetService) - .getWidgetBySessionId(currentEditingSession.chatSessionId); + // Remove all files from working set + const chatWidget = accessor.get(IChatWidgetService).getWidgetBySessionId(currentEditingSession.chatSessionId); const uris = [...currentEditingSession.workingSet.keys()]; - currentEditingSession.remove( - WorkingSetEntryRemovalReason.User, - ...uris, - ); - for (const uri of chatWidget?.attachmentModel.attachments ?? []) { - if (uri.isFile && URI.isUri(uri.value)) { + currentEditingSession.remove(WorkingSetEntryRemovalReason.User, ...uris); + + // Remove all file attachments + const attachmentModel = chatWidget?.attachmentModel as EditsAttachmentModel | undefined; + const fileAttachments = attachmentModel ? [...attachmentModel.excludedFileAttachments, ...attachmentModel.fileAttachments] : []; + for (const uri of fileAttachments) { + if (URI.isUri(uri.value)) { chatWidget?.attachmentModel.delete(uri.value.toString()); } } @@ -524,11 +334,8 @@ export class ChatEditingRemoveAllFilesAction extends Action2 { registerAction2(ChatEditingRemoveAllFilesAction); export class ChatEditingShowChangesAction extends Action2 { - static readonly ID = "chatEditing.viewChanges"; - static readonly LABEL = localize( - "chatEditing.viewChanges", - "View All Edits", - ); + static readonly ID = 'chatEditing.viewChanges'; + static readonly LABEL = localize('chatEditing.viewChanges', 'View All Edits'); constructor() { super({ @@ -541,19 +348,10 @@ export class ChatEditingShowChangesAction extends Action2 { menu: [ { id: MenuId.ChatEditingWidgetToolbar, - group: "navigation", + group: 'navigation', order: 4, - when: ContextKeyExpr.and( - applyingChatEditsFailedContextKey.negate(), - ContextKeyExpr.and( - hasAppliedChatEditsContextKey, - hasUndecidedChatEditingResourceContextKey, - ChatContextKeys.location.isEqualTo( - ChatAgentLocation.EditingSession, - ), - ), - ), - }, + when: ContextKeyExpr.and(applyingChatEditsFailedContextKey.negate(), ContextKeyExpr.and(hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession))) + } ], }); } @@ -569,330 +367,202 @@ export class ChatEditingShowChangesAction extends Action2 { } registerAction2(ChatEditingShowChangesAction); -registerAction2( - class AddFilesToWorkingSetAction extends Action2 { - constructor() { - super({ - id: "workbench.action.chat.addSelectedFilesToWorkingSet", - title: localize2( - "workbench.action.chat.addSelectedFilesToWorkingSet.label", - "Add Selected Files to Working Set", - ), - icon: Codicon.attach, - category: CHAT_CATEGORY, - precondition: ChatContextKeys.location.isEqualTo( - ChatAgentLocation.EditingSession, - ), - f1: true, - }); - } +registerAction2(class AddFilesToWorkingSetAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.chat.addSelectedFilesToWorkingSet', + title: localize2('workbench.action.chat.addSelectedFilesToWorkingSet.label', "Add Selected Files to Working Set"), + icon: Codicon.attach, + category: CHAT_CATEGORY, + precondition: ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), + f1: true + }); + } - override async run( - accessor: ServicesAccessor, - ...args: any[] - ): Promise { - const listService = accessor.get(IListService); - const chatEditingService = accessor.get(IChatEditingService); - const editorGroupService = accessor.get(IEditorGroupsService); + override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + const listService = accessor.get(IListService); + const chatEditingService = accessor.get(IChatEditingService); + const editorGroupService = accessor.get(IEditorGroupsService); - const uris: URI[] = []; + const uris: URI[] = []; - for (const group of editorGroupService.getGroups( - GroupsOrder.MOST_RECENTLY_ACTIVE, - )) { - for (const selection of group.selectedEditors) { - if (selection.resource) { - uris.push(selection.resource); - } + for (const group of editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) { + for (const selection of group.selectedEditors) { + if (selection.resource) { + uris.push(selection.resource); } } + } - if (uris.length === 0) { - const selection = listService.lastFocusedList?.getSelection(); - if (selection?.length) { - for (const file of selection) { - if ( - !!file && - typeof file === "object" && - "resource" in file && - URI.isUri(file.resource) - ) { - uris.push(file.resource); - } + if (uris.length === 0) { + const selection = listService.lastFocusedList?.getSelection(); + if (selection?.length) { + for (const file of selection) { + if (!!file && typeof file === 'object' && 'resource' in file && URI.isUri(file.resource)) { + uris.push(file.resource); } } } + } - for (const file of uris) { - chatEditingService?.currentEditingSessionObs - .get() - ?.addFileToWorkingSet(file); - } + for (const file of uris) { + chatEditingService?.currentEditingSessionObs.get()?.addFileToWorkingSet(file); } - }, -); - -registerAction2( - class RemoveAction extends Action2 { - constructor() { - super({ - id: "workbench.action.chat.undoEdits", - title: localize2("chat.undoEdits.label", "Undo Edits"), - f1: false, - category: CHAT_CATEGORY, - icon: Codicon.x, - keybinding: { - primary: KeyCode.Delete, - mac: { - primary: KeyMod.CtrlCmd | KeyCode.Backspace, - }, - when: ContextKeyExpr.and( - ChatContextKeys.location.isEqualTo( - ChatAgentLocation.EditingSession, - ), - ChatContextKeys.inChatSession, - ChatContextKeys.inChatInput.negate(), - ), - weight: KeybindingWeight.WorkbenchContrib, + } +}); + +registerAction2(class RemoveAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.chat.undoEdits', + title: localize2('chat.undoEdits.label', "Undo Edits"), + f1: false, + category: CHAT_CATEGORY, + icon: Codicon.x, + keybinding: { + primary: KeyCode.Delete, + mac: { + primary: KeyMod.CtrlCmd | KeyCode.Backspace, }, - menu: [ - { - id: MenuId.ChatMessageTitle, - group: "navigation", - order: 2, - when: ContextKeyExpr.and( - ChatContextKeys.location.isEqualTo( - ChatAgentLocation.EditingSession, - ), - ChatContextKeys.isRequest, - ), - }, - ], - }); + when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), ChatContextKeys.inChatSession, ChatContextKeys.inChatInput.negate()), + weight: KeybindingWeight.WorkbenchContrib, + }, + menu: [ + { + id: MenuId.ChatMessageTitle, + group: 'navigation', + order: 2, + when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), ChatContextKeys.isRequest) + } + ] + }); + } + + async run(accessor: ServicesAccessor, ...args: any[]) { + let item: ChatTreeItem | undefined = args[0]; + if (!isResponseVM(item) && !isRequestVM(item)) { + const chatWidgetService = accessor.get(IChatWidgetService); + const widget = chatWidgetService.lastFocusedWidget; + item = widget?.getFocus(); } - async run(accessor: ServicesAccessor, ...args: any[]) { - let item: ChatTreeItem | undefined = args[0]; - if (!isResponseVM(item) && !isRequestVM(item)) { - const chatWidgetService = accessor.get(IChatWidgetService); - const widget = chatWidgetService.lastFocusedWidget; - item = widget?.getFocus(); - } + if (!item) { + return; + } - if (!item) { - return; - } + const chatService = accessor.get(IChatService); + const chatModel = chatService.getSession(item.sessionId); + if (chatModel?.initialLocation !== ChatAgentLocation.EditingSession) { + return; + } - const chatService = accessor.get(IChatService); - const chatModel = chatService.getSession(item.sessionId); - if ( - chatModel?.initialLocation !== ChatAgentLocation.EditingSession - ) { - return; - } + const requestId = isRequestVM(item) ? item.id : + isResponseVM(item) ? item.requestId : undefined; - const requestId = isRequestVM(item) - ? item.id - : isResponseVM(item) - ? item.requestId - : undefined; - - if (requestId) { - const configurationService = accessor.get( - IConfigurationService, - ); - const dialogService = accessor.get(IDialogService); - const chatEditingService = accessor.get(IChatEditingService); - const chatRequests = chatModel.getRequests(); - const itemIndex = chatRequests.findIndex( - (request) => request.id === requestId, - ); - const editsToUndo = chatRequests.length - itemIndex; - - const requestsToRemove = chatRequests.slice(itemIndex); - const requestIdsToRemove = new Set( - requestsToRemove.map((request) => request.id), - ); - const entriesModifiedInRequestsToRemove = - chatEditingService.currentEditingSessionObs - .get() - ?.entries.get() - .filter((entry) => - requestIdsToRemove.has( - entry.lastModifyingRequestId, - ), - ) ?? []; - const shouldPrompt = - entriesModifiedInRequestsToRemove.length > 0 && - configurationService.getValue( - "chat.editing.confirmEditRequestRemoval", - ) === true; - - let message: string; - if (editsToUndo === 1) { - if (entriesModifiedInRequestsToRemove.length === 1) { - message = localize( - "chat.removeLast.confirmation.message2", - "This will remove your last request and undo the edits made to {0}. Do you want to proceed?", - basename( - entriesModifiedInRequestsToRemove[0] - .modifiedURI, - ), - ); - } else { - message = localize( - "chat.removeLast.confirmation.multipleEdits.message", - "This will remove your last request and undo edits made to {0} files in your working set. Do you want to proceed?", - entriesModifiedInRequestsToRemove.length, - ); - } + if (requestId) { + const configurationService = accessor.get(IConfigurationService); + const dialogService = accessor.get(IDialogService); + const chatEditingService = accessor.get(IChatEditingService); + const chatRequests = chatModel.getRequests(); + const itemIndex = chatRequests.findIndex(request => request.id === requestId); + const editsToUndo = chatRequests.length - itemIndex; + + const requestsToRemove = chatRequests.slice(itemIndex); + const requestIdsToRemove = new Set(requestsToRemove.map(request => request.id)); + const entriesModifiedInRequestsToRemove = chatEditingService.currentEditingSessionObs.get()?.entries.get().filter((entry) => requestIdsToRemove.has(entry.lastModifyingRequestId)) ?? []; + const shouldPrompt = entriesModifiedInRequestsToRemove.length > 0 && configurationService.getValue('chat.editing.confirmEditRequestRemoval') === true; + + let message: string; + if (editsToUndo === 1) { + if (entriesModifiedInRequestsToRemove.length === 1) { + message = localize('chat.removeLast.confirmation.message2', "This will remove your last request and undo the edits made to {0}. Do you want to proceed?", basename(entriesModifiedInRequestsToRemove[0].modifiedURI)); } else { - if (entriesModifiedInRequestsToRemove.length === 1) { - message = localize( - "chat.remove.confirmation.message2", - "This will remove all subsequent requests and undo edits made to {0}. Do you want to proceed?", - basename( - entriesModifiedInRequestsToRemove[0] - .modifiedURI, - ), - ); - } else { - message = localize( - "chat.remove.confirmation.multipleEdits.message", - "This will remove all subsequent requests and undo edits made to {0} files in your working set. Do you want to proceed?", - entriesModifiedInRequestsToRemove.length, - ); - } - } - - const confirmation = shouldPrompt - ? await dialogService.confirm({ - title: - editsToUndo === 1 - ? localize( - "chat.removeLast.confirmation.title", - "Do you want to undo your last edit?", - ) - : localize( - "chat.remove.confirmation.title", - "Do you want to undo {0} edits?", - editsToUndo, - ), - message: message, - primaryButton: localize( - "chat.remove.confirmation.primaryButton", - "Yes", - ), - checkbox: { - label: localize( - "chat.remove.confirmation.checkbox", - "Don't ask again", - ), - checked: false, - }, - type: "info", - }) - : { confirmed: true }; - - if (!confirmation.confirmed) { - return; + message = localize('chat.removeLast.confirmation.multipleEdits.message', "This will remove your last request and undo edits made to {0} files in your working set. Do you want to proceed?", entriesModifiedInRequestsToRemove.length); } - - if (confirmation.checkboxChecked) { - await configurationService.updateValue( - "chat.editing.confirmEditRequestRemoval", - false, - ); + } else { + if (entriesModifiedInRequestsToRemove.length === 1) { + message = localize('chat.remove.confirmation.message2', "This will remove all subsequent requests and undo edits made to {0}. Do you want to proceed?", basename(entriesModifiedInRequestsToRemove[0].modifiedURI)); + } else { + message = localize('chat.remove.confirmation.multipleEdits.message', "This will remove all subsequent requests and undo edits made to {0} files in your working set. Do you want to proceed?", entriesModifiedInRequestsToRemove.length); } + } - // Restore the snapshot to what it was before the request(s) that we deleted - const snapshotRequestId = chatRequests[itemIndex].id; - await chatEditingService.restoreSnapshot(snapshotRequestId); + const confirmation = shouldPrompt + ? await dialogService.confirm({ + title: editsToUndo === 1 + ? localize('chat.removeLast.confirmation.title', "Do you want to undo your last edit?") + : localize('chat.remove.confirmation.title', "Do you want to undo {0} edits?", editsToUndo), + message: message, + primaryButton: localize('chat.remove.confirmation.primaryButton', "Yes"), + checkbox: { label: localize('chat.remove.confirmation.checkbox', "Don't ask again"), checked: false }, + type: 'info' + }) + : { confirmed: true }; - // Remove the request and all that come after it - for (const request of requestsToRemove) { - await chatService.removeRequest(item.sessionId, request.id); - } + if (!confirmation.confirmed) { + return; } - } - }, -); - -registerAction2( - class OpenWorkingSetHistoryAction extends Action2 { - static readonly id = "chat.openFileSnapshot"; - constructor() { - super({ - id: OpenWorkingSetHistoryAction.id, - title: localize( - "chat.openSnapshot.label", - "Open File Snapshot", - ), - menu: [ - { - id: MenuId.ChatEditingCodeBlockContext, - group: "navigation", - order: 0, - when: ContextKeyExpr.notIn( - ChatContextKeys.itemId.key, - ChatContextKeys.lastItemId.key, - ), - }, - ], - }); - } - override async run( - accessor: ServicesAccessor, - ...args: any[] - ): Promise { - const context: - | { sessionId: string; requestId: string; uri: URI } - | undefined = args[0]; - if (!context?.sessionId) { - return; + if (confirmation.checkboxChecked) { + await configurationService.updateValue('chat.editing.confirmEditRequestRemoval', false); } - const chatService = accessor.get(IChatService); - const chatEditingService = accessor.get(IChatEditingService); - const editorService = accessor.get(IEditorService); + // Restore the snapshot to what it was before the request(s) that we deleted + const snapshotRequestId = chatRequests[itemIndex].id; + await chatEditingService.restoreSnapshot(snapshotRequestId); - const chatModel = chatService.getSession(context.sessionId); - const requests = chatModel?.getRequests(); - if (!requests) { - return; - } - const snapshotRequestIndex = requests?.findIndex( - (v, i) => i > 0 && requests[i - 1]?.id === context.requestId, - ); - if (snapshotRequestIndex < 1) { - return; + // Remove the request and all that come after it + for (const request of requestsToRemove) { + await chatService.removeRequest(item.sessionId, request.id); } - const snapshotRequestId = requests[snapshotRequestIndex]?.id; - if (snapshotRequestId) { - const snapshot = chatEditingService.getSnapshotUri( - snapshotRequestId, - context.uri, - ); - if (snapshot) { - const editor = await editorService.openEditor({ - resource: snapshot, - label: localize( - "chatEditing.snapshot", - "{0} (Snapshot {1})", - basename(context.uri), - snapshotRequestIndex - 1, - ), - options: { - transient: true, - activation: EditorActivation.ACTIVATE, - }, - }); - if (isCodeEditor(editor)) { - editor.updateOptions({ readOnly: true }); - } + } + } +}); + +registerAction2(class OpenWorkingSetHistoryAction extends Action2 { + + static readonly id = 'chat.openFileSnapshot'; + constructor() { + super({ + id: OpenWorkingSetHistoryAction.id, + title: localize('chat.openSnapshot.label', "Open File Snapshot"), + menu: [{ + id: MenuId.ChatEditingCodeBlockContext, + group: 'navigation', + order: 0, + when: ContextKeyExpr.notIn(ChatContextKeys.itemId.key, ChatContextKeys.lastItemId.key), + },] + }); + } + + override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + const context: { sessionId: string; requestId: string; uri: URI } | undefined = args[0]; + if (!context?.sessionId) { + return; + } + + const chatService = accessor.get(IChatService); + const chatEditingService = accessor.get(IChatEditingService); + const editorService = accessor.get(IEditorService); + + const chatModel = chatService.getSession(context.sessionId); + const requests = chatModel?.getRequests(); + if (!requests) { + return; + } + const snapshotRequestIndex = requests?.findIndex((v, i) => i > 0 && requests[i - 1]?.id === context.requestId); + if (snapshotRequestIndex < 1) { + return; + } + const snapshotRequestId = requests[snapshotRequestIndex]?.id; + if (snapshotRequestId) { + const snapshot = chatEditingService.getSnapshotUri(snapshotRequestId, context.uri); + if (snapshot) { + const editor = await editorService.openEditor({ resource: snapshot, label: localize('chatEditing.snapshot', '{0} (Snapshot {1})', basename(context.uri), snapshotRequestIndex - 1), options: { transient: true, activation: EditorActivation.ACTIVATE } }); + if (isCodeEditor(editor)) { + editor.updateOptions({ readOnly: true }); } } } - }, -); + } +}); diff --git a/Source/vs/workbench/contrib/chat/browser/chatEditorActions.ts b/Source/vs/workbench/contrib/chat/browser/chatEditorActions.ts index b168ab29cf832..16aa746bdd36b 100644 --- a/Source/vs/workbench/contrib/chat/browser/chatEditorActions.ts +++ b/Source/vs/workbench/contrib/chat/browser/chatEditorActions.ts @@ -2,53 +2,35 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Codicon } from "../../../../base/common/codicons.js"; -import { KeyCode, KeyMod } from "../../../../base/common/keyCodes.js"; -import { isEqual } from "../../../../base/common/resources.js"; -import { - ICodeEditor, - isCodeEditor, -} from "../../../../editor/browser/editorBrowser.js"; -import { - EditorAction2, - ServicesAccessor, -} from "../../../../editor/browser/editorExtensions.js"; -import { Range } from "../../../../editor/common/core/range.js"; -import { EditorContextKeys } from "../../../../editor/common/editorContextKeys.js"; -import { localize2 } from "../../../../nls.js"; -import { - Action2, - MenuId, - registerAction2, -} from "../../../../platform/actions/common/actions.js"; -import { ContextKeyExpr } from "../../../../platform/contextkey/common/contextkey.js"; -import { KeybindingWeight } from "../../../../platform/keybinding/common/keybindingsRegistry.js"; -import { - ACTIVE_GROUP, - IEditorService, -} from "../../../services/editor/common/editorService.js"; -import { ctxNotebookHasEditorModification } from "../../notebook/browser/contrib/chatEdit/notebookChatEditController.js"; -import { getNotebookEditorFromEditorPane } from "../../notebook/browser/notebookBrowser.js"; -import { ChatContextKeys } from "../common/chatContextKeys.js"; -import { - hasUndecidedChatEditingResourceContextKey, - IChatEditingService, -} from "../common/chatEditingService.js"; -import { CHAT_CATEGORY } from "./actions/chatActions.js"; -import { - ChatEditorController, - ctxHasEditorModification, -} from "./chatEditorController.js"; +import { ICodeEditor, isCodeEditor } from '../../../../editor/browser/editorBrowser.js'; +import { localize2 } from '../../../../nls.js'; +import { EditorAction2, ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; +import { CHAT_CATEGORY } from './actions/chatActions.js'; +import { ChatEditorController, ctxHasEditorModification } from './chatEditorController.js'; +import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; +import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; +import { ACTIVE_GROUP, IEditorService } from '../../../services/editor/common/editorService.js'; +import { hasUndecidedChatEditingResourceContextKey, IChatEditingService } from '../common/chatEditingService.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; +import { isEqual } from '../../../../base/common/resources.js'; +import { Range } from '../../../../editor/common/core/range.js'; +import { getNotebookEditorFromEditorPane } from '../../notebook/browser/notebookBrowser.js'; +import { ctxNotebookHasEditorModification } from '../../notebook/browser/contrib/chatEdit/notebookChatEditController.js'; abstract class NavigateAction extends Action2 { + constructor(readonly next: boolean) { super({ id: next - ? "chatEditor.action.navigateNext" - : "chatEditor.action.navigatePrevious", + ? 'chatEditor.action.navigateNext' + : 'chatEditor.action.navigatePrevious', title: next - ? localize2("next", "Go to Next Chat Edit") - : localize2("prev", "Go to Previous Chat Edit"), + ? localize2('next', 'Go to Next Chat Edit') + : localize2('prev', 'Go to Previous Chat Edit'), category: CHAT_CATEGORY, icon: next ? Codicon.arrowDown : Codicon.arrowUp, keybinding: { @@ -56,42 +38,33 @@ abstract class NavigateAction extends Action2 { ? KeyMod.Alt | KeyCode.F5 : KeyMod.Alt | KeyMod.Shift | KeyCode.F5, weight: KeybindingWeight.EditorContrib, - when: ContextKeyExpr.and( - ContextKeyExpr.or( - ctxHasEditorModification, - ctxNotebookHasEditorModification, - ), - EditorContextKeys.focus, - ), + when: ContextKeyExpr.and(ContextKeyExpr.or(ctxHasEditorModification, ctxNotebookHasEditorModification), EditorContextKeys.focus), }, f1: true, menu: { id: MenuId.ChatEditingEditorContent, - group: "navigate", + group: 'navigate', order: !next ? 2 : 3, - }, + } }); } - override run(accessor: ServicesAccessor) { - const chatEditingService = accessor.get(IChatEditingService); + override async run(accessor: ServicesAccessor) { + const chatEditingService = accessor.get(IChatEditingService); const editorService = accessor.get(IEditorService); const editor = editorService.activeTextEditorControl; - if (!isCodeEditor(editor) || !editor.hasModel()) { return; } const session = chatEditingService.currentEditingSession; - if (!session) { return; } const ctrl = ChatEditorController.get(editor); - if (!ctrl) { return; } @@ -105,18 +78,12 @@ abstract class NavigateAction extends Action2 { } const entries = session.entries.get(); - - const idx = entries.findIndex((e) => - isEqual(e.modifiedURI, editor.getModel().uri), - ); - + const idx = entries.findIndex(e => isEqual(e.modifiedURI, editor.getModel().uri)); if (idx < 0) { return; } - const newIdx = - (idx + (this.next ? 1 : -1) + entries.length) % entries.length; - + const newIdx = (idx + (this.next ? 1 : -1) + entries.length) % entries.length; if (idx === newIdx) { // wrap inside the same file if (this.next) { @@ -128,89 +95,71 @@ abstract class NavigateAction extends Action2 { } const entry = entries[newIdx]; - const change = entry.diffInfo.get().changes.at(this.next ? 0 : -1); - return editorService.openEditor( - { - resource: entry.modifiedURI, - options: { - selection: - change && - Range.fromPositions({ - lineNumber: change.original.startLineNumber, - column: 1, - }), - revealIfOpened: false, - revealIfVisible: false, - }, - }, - ACTIVE_GROUP, - ); + const newEditorPane = await editorService.openEditor({ + resource: entry.modifiedURI, + options: { + selection: change && Range.fromPositions({ lineNumber: change.original.startLineNumber, column: 1 }), + revealIfOpened: false, + revealIfVisible: false, + } + }, ACTIVE_GROUP); + + + const newEditor = newEditorPane?.getControl(); + if (isCodeEditor(newEditor)) { + ChatEditorController.get(newEditor)?.initNavigation(); + } } } abstract class AcceptDiscardAction extends Action2 { - constructor( - id: string, - readonly accept: boolean, - ) { + + constructor(id: string, readonly accept: boolean) { super({ id, title: accept - ? localize2("accept", "Accept Chat Edit") - : localize2("discard", "Discard Chat Edit"), + ? localize2('accept', 'Accept Chat Edit') + : localize2('discard', 'Discard Chat Edit'), shortTitle: accept - ? localize2("accept2", "Accept") - : localize2("discard2", "Discard"), + ? localize2('accept2', 'Accept') + : localize2('discard2', 'Discard'), category: CHAT_CATEGORY, - precondition: ContextKeyExpr.and( - ChatContextKeys.requestInProgress.negate(), - hasUndecidedChatEditingResourceContextKey, - ContextKeyExpr.or( - ctxHasEditorModification, - ctxNotebookHasEditorModification, - ), - ), - icon: accept ? Codicon.check : Codicon.discard, + precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey, ContextKeyExpr.or(ctxHasEditorModification, ctxNotebookHasEditorModification)), + icon: accept + ? Codicon.check + : Codicon.discard, f1: true, keybinding: { when: EditorContextKeys.focus, weight: KeybindingWeight.WorkbenchContrib, primary: accept ? KeyMod.CtrlCmd | KeyCode.Enter - : KeyMod.CtrlCmd | KeyCode.Backspace, + : KeyMod.CtrlCmd | KeyCode.Backspace }, menu: { id: MenuId.ChatEditingEditorContent, - group: "a_resolve", + group: 'a_resolve', order: accept ? 0 : 1, - }, + } }); } override run(accessor: ServicesAccessor) { const chatEditingService = accessor.get(IChatEditingService); - const editorService = accessor.get(IEditorService); - let uri = getNotebookEditorFromEditorPane( - editorService.activeEditorPane, - )?.textModel?.uri; - + let uri = getNotebookEditorFromEditorPane(editorService.activeEditorPane)?.textModel?.uri; if (!uri) { const editor = editorService.activeTextEditorControl; - uri = - isCodeEditor(editor) && editor.hasModel() - ? editor.getModel().uri - : undefined; + uri = isCodeEditor(editor) && editor.hasModel() ? editor.getModel().uri : undefined; } if (!uri) { return; } const session = chatEditingService.getEditingSession(uri); - if (!session) { return; } @@ -224,7 +173,8 @@ abstract class AcceptDiscardAction extends Action2 { } export class AcceptAction extends AcceptDiscardAction { - static readonly ID = "chatEditor.action.accept"; + + static readonly ID = 'chatEditor.action.accept'; constructor() { super(AcceptAction.ID, true); @@ -232,7 +182,8 @@ export class AcceptAction extends AcceptDiscardAction { } export class RejectAction extends AcceptDiscardAction { - static readonly ID = "chatEditor.action.reject"; + + static readonly ID = 'chatEditor.action.reject'; constructor() { super(RejectAction.ID, false); @@ -242,33 +193,26 @@ export class RejectAction extends AcceptDiscardAction { class UndoHunkAction extends EditorAction2 { constructor() { super({ - id: "chatEditor.action.undoHunk", - title: localize2("undo", "Undo this Change"), - shortTitle: localize2("undo2", "Undo"), + id: 'chatEditor.action.undoHunk', + title: localize2('undo', 'Undo this Change'), + shortTitle: localize2('undo2', 'Undo'), category: CHAT_CATEGORY, - precondition: ContextKeyExpr.and( - ChatContextKeys.requestInProgress.negate(), - hasUndecidedChatEditingResourceContextKey, - ), + precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey), icon: Codicon.discard, f1: true, keybinding: { when: EditorContextKeys.focus, weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Backspace, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Backspace }, menu: { id: MenuId.ChatEditingEditorHunk, - order: 1, - }, + order: 1 + } }); } - override runEditorCommand( - _accessor: ServicesAccessor, - editor: ICodeEditor, - ...args: any[] - ) { + override runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) { ChatEditorController.get(editor)?.undoNearestChange(args[0]); } } @@ -276,45 +220,26 @@ class UndoHunkAction extends EditorAction2 { class OpenDiffFromHunkAction extends EditorAction2 { constructor() { super({ - id: "chatEditor.action.diffHunk", - title: localize2("diff", "Open Diff"), + id: 'chatEditor.action.diffHunk', + title: localize2('diff', 'Open Diff'), category: CHAT_CATEGORY, - precondition: ContextKeyExpr.and( - ChatContextKeys.requestInProgress.negate(), - hasUndecidedChatEditingResourceContextKey, - ), + precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey), icon: Codicon.diffSingle, menu: { id: MenuId.ChatEditingEditorHunk, - order: 10, - }, + order: 10 + } }); } - override runEditorCommand( - _accessor: ServicesAccessor, - editor: ICodeEditor, - ...args: any[] - ) { + override runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) { ChatEditorController.get(editor)?.openDiff(args[0]); } } export function registerChatEditorActions() { - registerAction2( - class NextAction extends NavigateAction { - constructor() { - super(true); - } - }, - ); - registerAction2( - class PrevAction extends NavigateAction { - constructor() { - super(false); - } - }, - ); + registerAction2(class NextAction extends NavigateAction { constructor() { super(true); } }); + registerAction2(class PrevAction extends NavigateAction { constructor() { super(false); } }); registerAction2(AcceptAction); registerAction2(RejectAction); registerAction2(UndoHunkAction); diff --git a/Source/vs/workbench/contrib/chat/browser/chatEditorController.ts b/Source/vs/workbench/contrib/chat/browser/chatEditorController.ts index 2ea126d68cb61..7ee423ec280de 100644 --- a/Source/vs/workbench/contrib/chat/browser/chatEditorController.ts +++ b/Source/vs/workbench/contrib/chat/browser/chatEditorController.ts @@ -3,248 +3,141 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import "./media/chatEditorController.css"; - -import { getTotalWidth } from "../../../../base/browser/dom.js"; -import { - binarySearch, - coalesceInPlace, -} from "../../../../base/common/arrays.js"; -import { Event } from "../../../../base/common/event.js"; -import { - Disposable, - DisposableStore, - dispose, - toDisposable, -} from "../../../../base/common/lifecycle.js"; -import { - autorun, - derived, - observableFromEvent, -} from "../../../../base/common/observable.js"; -import { isEqual } from "../../../../base/common/resources.js"; -import { themeColorFromId } from "../../../../base/common/themables.js"; -import { - ICodeEditor, - IOverlayWidget, - IOverlayWidgetPosition, - IOverlayWidgetPositionCoordinates, - IViewZone, - MouseTargetType, -} from "../../../../editor/browser/editorBrowser.js"; -import { - LineSource, - renderLines, - RenderOptions, -} from "../../../../editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js"; -import { - diffAddDecoration, - diffDeleteDecoration, - diffWholeLineAddDecoration, -} from "../../../../editor/browser/widget/diffEditor/registrations.contribution.js"; -import { EditorOption } from "../../../../editor/common/config/editorOptions.js"; -import { - EditOperation, - ISingleEditOperation, -} from "../../../../editor/common/core/editOperation.js"; -import { Position } from "../../../../editor/common/core/position.js"; -import { Range } from "../../../../editor/common/core/range.js"; -import { Selection } from "../../../../editor/common/core/selection.js"; -import { IDocumentDiff } from "../../../../editor/common/diff/documentDiffProvider.js"; -import { - IEditorContribution, - ScrollType, -} from "../../../../editor/common/editorCommon.js"; -import { - IModelDeltaDecoration, - MinimapPosition, - OverviewRulerLane, - TrackedRangeStickiness, -} from "../../../../editor/common/model.js"; -import { ModelDecorationOptions } from "../../../../editor/common/model/textModel.js"; -import { - InlineDecoration, - InlineDecorationType, -} from "../../../../editor/common/viewModel.js"; -import { localize } from "../../../../nls.js"; -import { - HiddenItemStrategy, - MenuWorkbenchToolBar, -} from "../../../../platform/actions/browser/toolbar.js"; -import { MenuId } from "../../../../platform/actions/common/actions.js"; -import { - IContextKey, - IContextKeyService, - RawContextKey, -} from "../../../../platform/contextkey/common/contextkey.js"; -import { IInstantiationService } from "../../../../platform/instantiation/common/instantiation.js"; -import { IEditorService } from "../../../services/editor/common/editorService.js"; -import { - minimapGutterAddedBackground, - minimapGutterDeletedBackground, - minimapGutterModifiedBackground, - overviewRulerAddedForeground, - overviewRulerDeletedForeground, - overviewRulerModifiedForeground, -} from "../../scm/browser/dirtydiffDecorator.js"; -import { - ChatEditingSessionState, - IChatEditingService, - IModifiedFileEntry, - WorkingSetEntryState, -} from "../common/chatEditingService.js"; - -export const ctxHasEditorModification = new RawContextKey( - "chat.hasEditorModifications", - undefined, - localize( - "chat.hasEditorModifications", - "The current editor contains chat modifications", - ), -); - -export class ChatEditorController - extends Disposable - implements IEditorContribution -{ - public static readonly ID = "editor.contrib.chatEditorController"; - - private readonly _decorations = this._editor.createDecorationsCollection(); - private readonly _diffHunksRenderStore = this._register( - new DisposableStore(), - ); +import './media/chatEditorController.css'; +import { getTotalWidth } from '../../../../base/browser/dom.js'; +import { Disposable, DisposableStore, dispose, toDisposable } from '../../../../base/common/lifecycle.js'; +import { autorun, derived, IObservable, observableFromEvent, observableValue } from '../../../../base/common/observable.js'; +import { isEqual } from '../../../../base/common/resources.js'; +import { themeColorFromId } from '../../../../base/common/themables.js'; +import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, IOverlayWidgetPositionCoordinates, IViewZone, MouseTargetType } from '../../../../editor/browser/editorBrowser.js'; +import { LineSource, renderLines, RenderOptions } from '../../../../editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js'; +import { diffAddDecoration, diffDeleteDecoration, diffWholeLineAddDecoration } from '../../../../editor/browser/widget/diffEditor/registrations.contribution.js'; +import { EditorOption } from '../../../../editor/common/config/editorOptions.js'; +import { EditOperation, ISingleEditOperation } from '../../../../editor/common/core/editOperation.js'; +import { Range } from '../../../../editor/common/core/range.js'; +import { IDocumentDiff } from '../../../../editor/common/diff/documentDiffProvider.js'; +import { IEditorContribution, ScrollType } from '../../../../editor/common/editorCommon.js'; +import { IModelDeltaDecoration, MinimapPosition, OverviewRulerLane, TrackedRangeStickiness } from '../../../../editor/common/model.js'; +import { ModelDecorationOptions } from '../../../../editor/common/model/textModel.js'; +import { InlineDecoration, InlineDecorationType } from '../../../../editor/common/viewModel.js'; +import { localize } from '../../../../nls.js'; +import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { minimapGutterAddedBackground, minimapGutterDeletedBackground, minimapGutterModifiedBackground, overviewRulerAddedForeground, overviewRulerDeletedForeground, overviewRulerModifiedForeground } from '../../scm/browser/dirtydiffDecorator.js'; +import { ChatEditingSessionState, IChatEditingService, IModifiedFileEntry, WorkingSetEntryState } from '../common/chatEditingService.js'; +import { Event } from '../../../../base/common/event.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { MenuId } from '../../../../platform/actions/common/actions.js'; +import { IEditorService } from '../../../services/editor/common/editorService.js'; +import { Position } from '../../../../editor/common/core/position.js'; +import { Selection } from '../../../../editor/common/core/selection.js'; +import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; + +export const ctxHasEditorModification = new RawContextKey('chat.hasEditorModifications', undefined, localize('chat.hasEditorModifications', "The current editor contains chat modifications")); + +export class ChatEditorController extends Disposable implements IEditorContribution { + + public static readonly ID = 'editor.contrib.chatEditorController'; + + private static _diffLineDecorationData = ModelDecorationOptions.register({ description: 'diff-line-decoration' }); + + private readonly _diffLineDecorations = this._editor.createDecorationsCollection(); // tracks the line range w/o visuals (used for navigate) + private readonly _diffVisualDecorations = this._editor.createDecorationsCollection(); // tracks the real diff with character level inserts + private readonly _diffHunksRenderStore = this._register(new DisposableStore()); private readonly _diffHunkWidgets: DiffHunkWidget[] = []; private _viewZones: string[] = []; private readonly _ctxHasEditorModification: IContextKey; static get(editor: ICodeEditor): ChatEditorController | null { - const controller = editor.getContribution( - ChatEditorController.ID, - ); - + const controller = editor.getContribution(ChatEditorController.ID); return controller; } + private readonly _currentChange = observableValue(this, undefined); + readonly currentChange: IObservable = this._currentChange; + constructor( private readonly _editor: ICodeEditor, - @IInstantiationService - private readonly _instantiationService: IInstantiationService, - @IChatEditingService - private readonly _chatEditingService: IChatEditingService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IChatEditingService private readonly _chatEditingService: IChatEditingService, @IEditorService private readonly _editorService: IEditorService, @IContextKeyService contextKeyService: IContextKeyService, ) { super(); - this._ctxHasEditorModification = - ctxHasEditorModification.bindTo(contextKeyService); + this._ctxHasEditorModification = ctxHasEditorModification.bindTo(contextKeyService); const configSignal = observableFromEvent( - Event.filter( - this._editor.onDidChangeConfiguration, - (e) => - e.hasChanged(EditorOption.fontInfo) || - e.hasChanged(EditorOption.lineHeight), - ), - (_) => undefined, - ); - - const modelObs = observableFromEvent( - this._editor.onDidChangeModel, - (_) => this._editor.getModel(), + Event.filter(this._editor.onDidChangeConfiguration, e => e.hasChanged(EditorOption.fontInfo) || e.hasChanged(EditorOption.lineHeight)), + _ => undefined ); - this._register( - autorun((r) => { - if (this._editor.getOption(EditorOption.inDiffEditor)) { - this._clearRendering(); - - return; - } - - configSignal.read(r); + const modelObs = observableFromEvent(this._editor.onDidChangeModel, _ => this._editor.getModel()); - const model = modelObs.read(r); + this._register(autorun(r => { - const session = - this._chatEditingService.currentEditingSessionObs.read(r); + if (this._editor.getOption(EditorOption.inDiffEditor)) { + this._clearRendering(); + return; + } - const entry = session?.entries - .read(r) - .find((e) => isEqual(e.modifiedURI, model?.uri)); + configSignal.read(r); - if ( - !entry || - entry.state.read(r) !== WorkingSetEntryState.Modified - ) { - this._clearRendering(); + const model = modelObs.read(r); - return; - } + const session = this._chatEditingService.currentEditingSessionObs.read(r); + const entry = session?.entries.read(r).find(e => isEqual(e.modifiedURI, model?.uri)); - const diff = entry?.diffInfo.read(r); - this._updateWithDiff(entry, diff); - }), - ); + if (!entry || entry.state.read(r) !== WorkingSetEntryState.Modified) { + this._clearRendering(); + return; + } - const shouldBeReadOnly = derived(this, (r) => { - const value = - this._chatEditingService.currentEditingSessionObs.read(r); + const diff = entry?.diffInfo.read(r); + this._updateWithDiff(entry, diff); + this.initNavigation(); + if (this._currentChange.get() === undefined) { + this.revealNext(); + } + })); - if ( - !value || - value.state.read(r) !== ChatEditingSessionState.StreamingEdits - ) { + const shouldBeReadOnly = derived(this, r => { + const value = this._chatEditingService.currentEditingSessionObs.read(r); + if (!value || value.state.read(r) !== ChatEditingSessionState.StreamingEdits) { return false; } - return value.entries - .read(r) - .some((e) => - isEqual(e.modifiedURI, this._editor.getModel()?.uri), - ); + return value.entries.read(r).some(e => isEqual(e.modifiedURI, this._editor.getModel()?.uri)); }); - let actualReadonly: boolean | undefined; - - let actualDeco: "off" | "editable" | "on" | undefined; - this._register( - autorun((r) => { - const value = shouldBeReadOnly.read(r); + let actualReadonly: boolean | undefined; + let actualDeco: 'off' | 'editable' | 'on' | undefined; - if (value) { - actualReadonly ??= this._editor.getOption( - EditorOption.readOnly, - ); - actualDeco ??= this._editor.getOption( - EditorOption.renderValidationDecorations, - ); + this._register(autorun(r => { + const value = shouldBeReadOnly.read(r); + if (value) { + actualReadonly ??= this._editor.getOption(EditorOption.readOnly); + actualDeco ??= this._editor.getOption(EditorOption.renderValidationDecorations); + this._editor.updateOptions({ + readOnly: true, + renderValidationDecorations: 'off' + }); + } else { + if (actualReadonly !== undefined && actualDeco !== undefined) { this._editor.updateOptions({ - readOnly: true, - renderValidationDecorations: "off", + readOnly: actualReadonly, + renderValidationDecorations: actualDeco }); - } else { - if ( - actualReadonly !== undefined && - actualDeco !== undefined - ) { - this._editor.updateOptions({ - readOnly: actualReadonly, - renderValidationDecorations: actualDeco, - }); - actualReadonly = undefined; - actualDeco = undefined; - } + actualReadonly = undefined; + actualDeco = undefined; } - }), - ); + } + })); } override dispose(): void { this._clearRendering(); - super.dispose(); } @@ -256,70 +149,41 @@ export class ChatEditorController }); this._viewZones = []; this._diffHunksRenderStore.clear(); - this._decorations.clear(); + this._diffVisualDecorations.clear(); + this._diffLineDecorations.clear(); this._ctxHasEditorModification.reset(); } - private _updateWithDiff( - entry: IModifiedFileEntry, - diff: IDocumentDiff | null | undefined, - ): void { + private _updateWithDiff(entry: IModifiedFileEntry, diff: IDocumentDiff | null | undefined): void { if (!diff) { this._clearRendering(); - return; } this._ctxHasEditorModification.set(true); - const originalModel = entry.originalModel; const chatDiffAddDecoration = ModelDecorationOptions.createDynamic({ ...diffAddDecoration, + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges + }); + const chatDiffWholeLineAddDecoration = ModelDecorationOptions.createDynamic({ + ...diffWholeLineAddDecoration, stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, }); - - const chatDiffWholeLineAddDecoration = - ModelDecorationOptions.createDynamic({ - ...diffWholeLineAddDecoration, - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - }); - - const createOverviewDecoration = ( - overviewRulerColor: string, - minimapColor: string, - ) => { + const createOverviewDecoration = (overviewRulerColor: string, minimapColor: string) => { return ModelDecorationOptions.createDynamic({ - description: "chat-editing-decoration", - overviewRuler: { - color: themeColorFromId(overviewRulerColor), - position: OverviewRulerLane.Left, - }, - minimap: { - color: themeColorFromId(minimapColor), - position: MinimapPosition.Gutter, - }, + description: 'chat-editing-decoration', + overviewRuler: { color: themeColorFromId(overviewRulerColor), position: OverviewRulerLane.Left }, + minimap: { color: themeColorFromId(minimapColor), position: MinimapPosition.Gutter }, }); }; - - const modifiedDecoration = createOverviewDecoration( - overviewRulerModifiedForeground, - minimapGutterModifiedBackground, - ); - - const addedDecoration = createOverviewDecoration( - overviewRulerAddedForeground, - minimapGutterAddedBackground, - ); - - const deletedDecoration = createOverviewDecoration( - overviewRulerDeletedForeground, - minimapGutterDeletedBackground, - ); + const modifiedDecoration = createOverviewDecoration(overviewRulerModifiedForeground, minimapGutterModifiedBackground); + const addedDecoration = createOverviewDecoration(overviewRulerAddedForeground, minimapGutterAddedBackground); + const deletedDecoration = createOverviewDecoration(overviewRulerDeletedForeground, minimapGutterDeletedBackground); this._diffHunksRenderStore.clear(); this._diffHunkWidgets.length = 0; - const diffHunkDecorations: IModelDeltaDecoration[] = []; this._editor.changeViewZones((viewZoneChangeAccessor) => { @@ -327,273 +191,192 @@ export class ChatEditorController viewZoneChangeAccessor.removeZone(id); } this._viewZones = []; - - const modifiedDecorations: IModelDeltaDecoration[] = []; - - const mightContainNonBasicASCII = - originalModel.mightContainNonBasicASCII(); - + const modifiedVisualDecorations: IModelDeltaDecoration[] = []; + const modifiedLineDecorations: IModelDeltaDecoration[] = []; + const mightContainNonBasicASCII = originalModel.mightContainNonBasicASCII(); const mightContainRTL = originalModel.mightContainRTL(); - const renderOptions = RenderOptions.fromEditor(this._editor); - const editorLineCount = this._editor.getModel()?.getLineCount(); for (const diffEntry of diff.changes) { - const originalRange = diffEntry.original; - originalModel.tokenization.forceTokenization( - Math.max(1, originalRange.endLineNumberExclusive - 1), - ); + const originalRange = diffEntry.original; + originalModel.tokenization.forceTokenization(Math.max(1, originalRange.endLineNumberExclusive - 1)); const source = new LineSource( - originalRange.mapToLineArray((l) => - originalModel.tokenization.getLineTokens(l), - ), + originalRange.mapToLineArray(l => originalModel.tokenization.getLineTokens(l)), [], mightContainNonBasicASCII, mightContainRTL, ); - const decorations: InlineDecoration[] = []; - for (const i of diffEntry.innerChanges || []) { - decorations.push( - new InlineDecoration( - i.originalRange.delta( - -(diffEntry.original.startLineNumber - 1), - ), - diffDeleteDecoration.className!, - InlineDecorationType.Regular, - ), - ); + decorations.push(new InlineDecoration( + i.originalRange.delta(-(diffEntry.original.startLineNumber - 1)), + diffDeleteDecoration.className!, + InlineDecorationType.Regular + )); // If the original range is empty, the start line number is 1 and the new range spans the entire file, don't draw an Added decoration - if ( - !( - i.originalRange.isEmpty() && - i.originalRange.startLineNumber === 1 && - i.modifiedRange.endLineNumber === editorLineCount - ) && - !i.modifiedRange.isEmpty() - ) { - modifiedDecorations.push({ - range: i.modifiedRange, - options: chatDiffAddDecoration, + if (!(i.originalRange.isEmpty() && i.originalRange.startLineNumber === 1 && i.modifiedRange.endLineNumber === editorLineCount) && !i.modifiedRange.isEmpty()) { + modifiedVisualDecorations.push({ + range: i.modifiedRange, options: chatDiffAddDecoration }); } } // Render an added decoration but don't also render a deleted decoration for newly inserted content at the start of the file // Note, this is a workaround for the `LineRange.isEmpty()` in diffEntry.original being `false` for newly inserted content - const isCreatedContent = - decorations.length === 1 && - decorations[0].range.isEmpty() && - diffEntry.original.startLineNumber === 1; - - if ( - !diffEntry.modified.isEmpty && - !( - isCreatedContent && - diffEntry.modified.endLineNumberExclusive - 1 === - editorLineCount - ) - ) { - modifiedDecorations.push({ + const isCreatedContent = decorations.length === 1 && decorations[0].range.isEmpty() && diffEntry.original.startLineNumber === 1; + + if (!diffEntry.modified.isEmpty && !(isCreatedContent && (diffEntry.modified.endLineNumberExclusive - 1) === editorLineCount)) { + modifiedVisualDecorations.push({ range: diffEntry.modified.toInclusiveRange()!, - options: chatDiffWholeLineAddDecoration, + options: chatDiffWholeLineAddDecoration }); } if (diffEntry.original.isEmpty) { // insertion - modifiedDecorations.push({ + modifiedVisualDecorations.push({ range: diffEntry.modified.toInclusiveRange()!, - options: addedDecoration, + options: addedDecoration }); } else if (diffEntry.modified.isEmpty) { // deletion - modifiedDecorations.push({ - range: new Range( - diffEntry.modified.startLineNumber - 1, - 1, - diffEntry.modified.startLineNumber, - 1, - ), - options: deletedDecoration, + modifiedVisualDecorations.push({ + range: new Range(diffEntry.modified.startLineNumber - 1, 1, diffEntry.modified.startLineNumber, 1), + options: deletedDecoration }); } else { // modification - modifiedDecorations.push({ + modifiedVisualDecorations.push({ range: diffEntry.modified.toInclusiveRange()!, - options: modifiedDecoration, + options: modifiedDecoration }); } - const domNode = document.createElement("div"); - - domNode.className = - "chat-editing-original-zone view-lines line-delete monaco-mouse-cursor-text"; - - const result = renderLines( - source, - renderOptions, - decorations, - domNode, - ); + const domNode = document.createElement('div'); + domNode.className = 'chat-editing-original-zone view-lines line-delete monaco-mouse-cursor-text'; + const result = renderLines(source, renderOptions, decorations, domNode); if (!isCreatedContent) { const viewZoneData: IViewZone = { afterLineNumber: diffEntry.modified.startLineNumber - 1, heightInLines: result.heightInLines, domNode, - ordinal: 50000 + 2, // more than https://github.com/microsoft/vscode/blob/bf52a5cfb2c75a7327c9adeaefbddc06d529dcad/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts#L42 + ordinal: 50000 + 2 // more than https://github.com/microsoft/vscode/blob/bf52a5cfb2c75a7327c9adeaefbddc06d529dcad/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts#L42 }; - this._viewZones.push( - viewZoneChangeAccessor.addZone(viewZoneData), - ); + this._viewZones.push(viewZoneChangeAccessor.addZone(viewZoneData)); } // Add content widget for each diff change const undoEdits: ISingleEditOperation[] = []; - for (const c of diffEntry.innerChanges ?? []) { - const oldText = originalModel.getValueInRange( - c.originalRange, - ); - undoEdits.push( - EditOperation.replace(c.modifiedRange, oldText), - ); + const oldText = originalModel.getValueInRange(c.originalRange); + undoEdits.push(EditOperation.replace(c.modifiedRange, oldText)); } - const widget = this._instantiationService.createInstance( - DiffHunkWidget, - entry, - undoEdits, - this._editor.getModel()!.getVersionId(), - this._editor, - isCreatedContent ? 0 : result.heightInLines, - ); + const widget = this._instantiationService.createInstance(DiffHunkWidget, entry, undoEdits, this._editor.getModel()!.getVersionId(), this._editor, isCreatedContent ? 0 : result.heightInLines); widget.layout(diffEntry.modified.startLineNumber); this._diffHunkWidgets.push(widget); diffHunkDecorations.push({ - range: - diffEntry.modified.toInclusiveRange() ?? - new Range( - diffEntry.modified.startLineNumber, - 1, - diffEntry.modified.startLineNumber, - Number.MAX_SAFE_INTEGER, - ), + range: diffEntry.modified.toInclusiveRange() ?? new Range(diffEntry.modified.startLineNumber, 1, diffEntry.modified.startLineNumber, Number.MAX_SAFE_INTEGER), options: { - description: "diff-hunk-widget", - stickiness: - TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges, - }, + description: 'diff-hunk-widget', + stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges + } + }); + + // Add line decorations for diff navigation + modifiedLineDecorations.push({ + range: diffEntry.modified.toInclusiveRange() ?? new Range(diffEntry.modified.startLineNumber, 1, diffEntry.modified.startLineNumber, Number.MAX_SAFE_INTEGER), + options: ChatEditorController._diffLineDecorationData }); } - this._decorations.set(modifiedDecorations); + this._diffVisualDecorations.set(modifiedVisualDecorations); + this._diffLineDecorations.set(modifiedLineDecorations); }); - const diffHunkDecoCollection = - this._editor.createDecorationsCollection(diffHunkDecorations); + const diffHunkDecoCollection = this._editor.createDecorationsCollection(diffHunkDecorations); - this._diffHunksRenderStore.add( - toDisposable(() => { - dispose(this._diffHunkWidgets); - this._diffHunkWidgets.length = 0; - diffHunkDecoCollection.clear(); - }), - ); + this._diffHunksRenderStore.add(toDisposable(() => { + dispose(this._diffHunkWidgets); + this._diffHunkWidgets.length = 0; + diffHunkDecoCollection.clear(); + })); - const positionObs = observableFromEvent( - this._editor.onDidChangeCursorPosition, - (_) => this._editor.getPosition(), - ); - const activeWidgetIdx = derived((r) => { - const position = positionObs.read(r); + const positionObs = observableFromEvent(this._editor.onDidChangeCursorPosition, _ => this._editor.getPosition()); + + const activeWidgetIdx = derived(r => { + const position = positionObs.read(r); if (!position) { return -1; } - const idx = diffHunkDecoCollection - .getRanges() - .findIndex((r) => r.containsPosition(position)); - + const idx = diffHunkDecoCollection.getRanges().findIndex(r => r.containsPosition(position)); return idx; }); - const toggleWidget = (activeWidget: DiffHunkWidget | undefined) => { const positionIdx = activeWidgetIdx.get(); - for (let i = 0; i < this._diffHunkWidgets.length; i++) { const widget = this._diffHunkWidgets[i]; widget.toggle(widget === activeWidget || i === positionIdx); } }; - this._diffHunksRenderStore.add( - autorun((r) => { - // reveal when cursor inside - const idx = activeWidgetIdx.read(r); + this._diffHunksRenderStore.add(autorun(r => { + // reveal when cursor inside + const idx = activeWidgetIdx.read(r); + const widget = this._diffHunkWidgets[idx]; + toggleWidget(widget); + })); - const widget = this._diffHunkWidgets[idx]; + + this._diffHunksRenderStore.add(this._editor.onMouseMove(e => { + + // reveal when hovering over + if (e.target.type === MouseTargetType.OVERLAY_WIDGET) { + const id = e.target.detail; + const widget = this._diffHunkWidgets.find(w => w.getId() === id); toggleWidget(widget); - }), - ); - this._diffHunksRenderStore.add( - this._editor.onMouseMove((e) => { - // reveal when hovering over - if (e.target.type === MouseTargetType.OVERLAY_WIDGET) { - const id = e.target.detail; - - const widget = this._diffHunkWidgets.find( - (w) => w.getId() === id, - ); - toggleWidget(widget); - } else if ( - e.target.type === MouseTargetType.CONTENT_VIEW_ZONE - ) { - const zone = e.target.detail; - - const idx = this._viewZones.findIndex( - (id) => id === zone.viewZoneId, - ); - toggleWidget(this._diffHunkWidgets[idx]); - } else if (e.target.position) { - const { position } = e.target; - - const idx = diffHunkDecoCollection - .getRanges() - .findIndex((r) => r.containsPosition(position)); - toggleWidget(this._diffHunkWidgets[idx]); - } else { - toggleWidget(undefined); - } - }), - ); + } else if (e.target.type === MouseTargetType.CONTENT_VIEW_ZONE) { + const zone = e.target.detail; + const idx = this._viewZones.findIndex(id => id === zone.viewZoneId); + toggleWidget(this._diffHunkWidgets[idx]); - this._diffHunksRenderStore.add( - Event.any( - this._editor.onDidScrollChange, - this._editor.onDidLayoutChange, - )(() => { - for (let i = 0; i < this._diffHunkWidgets.length; i++) { - const widget = this._diffHunkWidgets[i]; + } else if (e.target.position) { + const { position } = e.target; + const idx = diffHunkDecoCollection.getRanges().findIndex(r => r.containsPosition(position)); + toggleWidget(this._diffHunkWidgets[idx]); - const range = diffHunkDecoCollection.getRange(i); + } else { + toggleWidget(undefined); + } + })); - if (range) { - widget.layout(range?.startLineNumber); - } else { - widget.dispose(); - } + this._diffHunksRenderStore.add(Event.any(this._editor.onDidScrollChange, this._editor.onDidLayoutChange)(() => { + for (let i = 0; i < this._diffHunkWidgets.length; i++) { + const widget = this._diffHunkWidgets[i]; + const range = diffHunkDecoCollection.getRange(i); + if (range) { + widget.layout(range?.startLineNumber); + } else { + widget.dispose(); } - }), - ); + } + })); + } + + initNavigation(): void { + const position = this._editor.getPosition(); + const range = position && this._diffLineDecorations.getRanges().find(r => r.containsPosition(position)); + if (range) { + this._currentChange.set(position, undefined); + } } revealNext(strict = false): boolean { @@ -606,56 +389,28 @@ export class ChatEditorController private _reveal(next: boolean, strict: boolean): boolean { const position = this._editor.getPosition(); - if (!position) { return false; } - const decorations: (Range | undefined)[] = this._decorations + const decorations = this._diffLineDecorations .getRanges() .sort((a, b) => Range.compareRangesUsingStarts(a, b)); - // TODO@jrieken this is slow and should be done smarter, e.g being able to read - // only whole range decorations because the goal is to go from change to change, skipping - // over word level changes - for (let i = 0; i < decorations.length; i++) { - const decoration = decorations[i]; - - for (let j = 0; j < decorations.length; j++) { - if ( - i !== j && - decoration && - decorations[j]?.containsRange(decoration) - ) { - decorations[i] = undefined; - - break; - } - } - } - - coalesceInPlace(decorations); - if (decorations.length === 0) { return false; } - let idx = binarySearch( - decorations, - Range.fromPositions(position), - Range.compareRangesUsingStarts, - ); - - if (idx < 0) { - idx = ~idx; - } - - let target: number; - - if (decorations[idx]?.containsPosition(position)) { - target = idx + (next ? 1 : -1); - } else { - target = next ? idx : idx - 1; + let target: number = -1; + for (let i = 0; i < decorations.length; i++) { + const range = decorations[i]; + if (range.containsPosition(position)) { + target = i + (next ? 1 : -1); + break; + } else if (Position.isBefore(position, range.getStartPosition())) { + target = next ? i : i - 1; + break; + } } if (strict && (target < 0 || target >= decorations.length)) { @@ -664,11 +419,13 @@ export class ChatEditorController target = (target + decorations.length) % decorations.length; - const targetPosition = decorations[target].getStartPosition(); + const targetPosition = next ? decorations[target].getStartPosition() : decorations[target].getEndPosition(); + + this._currentChange.set(targetPosition, undefined); + this._editor.setPosition(targetPosition); this._editor.revealPositionInCenter(targetPosition, ScrollType.Smooth); this._editor.focus(); - return true; } @@ -676,22 +433,14 @@ export class ChatEditorController if (!this._editor.hasModel()) { return; } - const lineRelativeTop = - this._editor.getTopForLineNumber( - this._editor.getPosition().lineNumber, - ) - this._editor.getScrollTop(); - + const lineRelativeTop = this._editor.getTopForLineNumber(this._editor.getPosition().lineNumber) - this._editor.getScrollTop(); let closestDistance = Number.MAX_VALUE; if (!(closestWidget instanceof DiffHunkWidget)) { for (const widget of this._diffHunkWidgets) { - const widgetTop = (< - IOverlayWidgetPositionCoordinates | undefined - >widget.getPosition()?.preference)?.top; - + const widgetTop = (widget.getPosition()?.preference)?.top; if (widgetTop !== undefined) { const distance = Math.abs(widgetTop - lineRelativeTop); - if (distance < closestDistance) { closestDistance = distance; closestWidget = widget; @@ -709,22 +458,14 @@ export class ChatEditorController if (!this._editor.hasModel()) { return; } - const lineRelativeTop = - this._editor.getTopForLineNumber( - this._editor.getPosition().lineNumber, - ) - this._editor.getScrollTop(); - + const lineRelativeTop = this._editor.getTopForLineNumber(this._editor.getPosition().lineNumber) - this._editor.getScrollTop(); let closestDistance = Number.MAX_VALUE; if (!(widget instanceof DiffHunkWidget)) { for (const candidate of this._diffHunkWidgets) { - const widgetTop = (< - IOverlayWidgetPositionCoordinates | undefined - >candidate.getPosition()?.preference)?.top; - + const widgetTop = (candidate.getPosition()?.preference)?.top; if (widgetTop !== undefined) { const distance = Math.abs(widgetTop - lineRelativeTop); - if (distance < closestDistance) { closestDistance = distance; widget = candidate; @@ -734,27 +475,17 @@ export class ChatEditorController } if (widget instanceof DiffHunkWidget) { - const lineNumber = widget.getStartLineNumber(); - - const position = lineNumber - ? new Position(lineNumber, 1) - : undefined; + const lineNumber = widget.getStartLineNumber(); + const position = lineNumber ? new Position(lineNumber, 1) : undefined; let selection = this._editor.getSelection(); - if (position && !selection.containsPosition(position)) { selection = Selection.fromPositions(position); } const diffEditor = await this._editorService.openEditor({ - original: { - resource: widget.entry.originalURI, - options: { selection: undefined }, - }, - modified: { - resource: widget.entry.modifiedURI, - options: { selection }, - }, + original: { resource: widget.entry.originalURI, options: { selection: undefined } }, + modified: { resource: widget.entry.modifiedURI, options: { selection } }, }); // this is needed, passing the selection doesn't seem to work @@ -764,6 +495,7 @@ export class ChatEditorController } class DiffHunkWidget implements IOverlayWidget { + private static _idPool = 0; private readonly _id: string = `diff-change-widget-${DiffHunkWidget._idPool++}`; @@ -772,6 +504,7 @@ class DiffHunkWidget implements IOverlayWidget { private _position: IOverlayWidgetPosition | undefined; private _lastStartLineNumber: number | undefined; + constructor( readonly entry: IModifiedFileEntry, private readonly _undoEdits: ISingleEditOperation[], @@ -780,23 +513,18 @@ class DiffHunkWidget implements IOverlayWidget { private readonly _lineDelta: number, @IInstantiationService instaService: IInstantiationService, ) { - this._domNode = document.createElement("div"); - this._domNode.className = "chat-diff-change-content-widget"; - - const toolbar = instaService.createInstance( - MenuWorkbenchToolBar, - this._domNode, - MenuId.ChatEditingEditorHunk, - { - telemetrySource: "chatEditingEditorHunk", - hiddenItemStrategy: HiddenItemStrategy.NoHide, - toolbarOptions: { primaryGroup: () => true }, - menuOptions: { - renderShortTitle: true, - arg: this, - }, + this._domNode = document.createElement('div'); + this._domNode.className = 'chat-diff-change-content-widget'; + + const toolbar = instaService.createInstance(MenuWorkbenchToolBar, this._domNode, MenuId.ChatEditingEditorHunk, { + telemetrySource: 'chatEditingEditorHunk', + hiddenItemStrategy: HiddenItemStrategy.NoHide, + toolbarOptions: { primaryGroup: () => true, }, + menuOptions: { + renderShortTitle: true, + arg: this, }, - ); + }); this._store.add(toolbar); this._editor.addOverlayWidget(this); @@ -812,25 +540,17 @@ class DiffHunkWidget implements IOverlayWidget { } layout(startLineNumber: number): void { - const lineHeight = this._editor.getOption(EditorOption.lineHeight); - - const { contentLeft, contentWidth, verticalScrollbarWidth } = - this._editor.getLayoutInfo(); + const lineHeight = this._editor.getOption(EditorOption.lineHeight); + const { contentLeft, contentWidth, verticalScrollbarWidth } = this._editor.getLayoutInfo(); const scrollTop = this._editor.getScrollTop(); this._position = { stackOridinal: 1, preference: { - top: - this._editor.getTopForLineNumber(startLineNumber) - - scrollTop - - lineHeight * this._lineDelta, - left: - contentLeft + - contentWidth - - (2 * verticalScrollbarWidth + getTotalWidth(this._domNode)), - }, + top: this._editor.getTopForLineNumber(startLineNumber) - scrollTop - (lineHeight * this._lineDelta), + left: contentLeft + contentWidth - (2 * verticalScrollbarWidth + getTotalWidth(this._domNode)) + } }; this._editor.layoutOverlayWidget(this); @@ -838,8 +558,7 @@ class DiffHunkWidget implements IOverlayWidget { } toggle(show: boolean) { - this._domNode.classList.toggle("hover", show); - + this._domNode.classList.toggle('hover', show); if (this._lastStartLineNumber) { this.layout(this._lastStartLineNumber); } @@ -861,7 +580,7 @@ class DiffHunkWidget implements IOverlayWidget { undo() { if (this._versionId === this._editor.getModel()?.getVersionId()) { - this._editor.executeEdits("chatEdits.undo", this._undoEdits); + this._editor.executeEdits('chatEdits.undo', this._undoEdits); } } } diff --git a/Source/vs/workbench/contrib/chat/browser/chatEditorOverlay.ts b/Source/vs/workbench/contrib/chat/browser/chatEditorOverlay.ts index f8576421bfdbc..fd58d52bb5f87 100644 --- a/Source/vs/workbench/contrib/chat/browser/chatEditorOverlay.ts +++ b/Source/vs/workbench/contrib/chat/browser/chatEditorOverlay.ts @@ -3,70 +3,34 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import "./media/chatEditorOverlay.css"; - -import { - getWindow, - reset, - scheduleAtNextAnimationFrame, -} from "../../../../base/browser/dom.js"; -import { ActionViewItem } from "../../../../base/browser/ui/actionbar/actionViewItems.js"; -import { renderIcon } from "../../../../base/browser/ui/iconLabel/iconLabels.js"; -import { IActionRunner } from "../../../../base/common/actions.js"; -import { Codicon } from "../../../../base/common/codicons.js"; -import { - DisposableStore, - MutableDisposable, -} from "../../../../base/common/lifecycle.js"; -import { - autorun, - IReader, - ISettableObservable, - ITransaction, - observableFromEvent, - observableSignal, - observableValue, - transaction, -} from "../../../../base/common/observable.js"; -import { isEqual } from "../../../../base/common/resources.js"; -import { ThemeIcon } from "../../../../base/common/themables.js"; -import { assertType } from "../../../../base/common/types.js"; -import { - ICodeEditor, - IOverlayWidget, - IOverlayWidgetPosition, - OverlayWidgetPositionPreference, -} from "../../../../editor/browser/editorBrowser.js"; -import { EditorOption } from "../../../../editor/common/config/editorOptions.js"; -import { Range } from "../../../../editor/common/core/range.js"; -import { IEditorContribution } from "../../../../editor/common/editorCommon.js"; -import { localize } from "../../../../nls.js"; -import { - HiddenItemStrategy, - MenuWorkbenchToolBar, - WorkbenchToolBar, -} from "../../../../platform/actions/browser/toolbar.js"; -import { - MenuId, - MenuRegistry, -} from "../../../../platform/actions/common/actions.js"; -import { ContextKeyExpr } from "../../../../platform/contextkey/common/contextkey.js"; -import { IInstantiationService } from "../../../../platform/instantiation/common/instantiation.js"; -import { - ACTIVE_GROUP, - IEditorService, -} from "../../../services/editor/common/editorService.js"; -import { ctxNotebookHasEditorModification } from "../../notebook/browser/contrib/chatEdit/notebookChatEditController.js"; -import { - ChatEditingSessionState, - IChatEditingService, - IChatEditingSession, - IModifiedFileEntry, - WorkingSetEntryState, -} from "../common/chatEditingService.js"; -import { AcceptAction, RejectAction } from "./chatEditorActions.js"; +import './media/chatEditorOverlay.css'; +import { DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js'; +import { autorun, IReader, ISettableObservable, ITransaction, observableFromEvent, observableSignal, observableValue, transaction } from '../../../../base/common/observable.js'; +import { isEqual } from '../../../../base/common/resources.js'; +import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from '../../../../editor/browser/editorBrowser.js'; +import { IEditorContribution } from '../../../../editor/common/editorCommon.js'; +import { HiddenItemStrategy, MenuWorkbenchToolBar, WorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { ChatEditingSessionState, IChatEditingService, IChatEditingSession, IModifiedFileEntry, WorkingSetEntryState } from '../common/chatEditingService.js'; +import { MenuId, MenuRegistry } from '../../../../platform/actions/common/actions.js'; +import { ActionViewItem } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; +import { ACTIVE_GROUP, IEditorService } from '../../../services/editor/common/editorService.js'; +import { Range } from '../../../../editor/common/core/range.js'; +import { IActionRunner } from '../../../../base/common/actions.js'; +import { getWindow, reset, scheduleAtNextAnimationFrame } from '../../../../base/browser/dom.js'; +import { EditorOption } from '../../../../editor/common/config/editorOptions.js'; +import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { assertType } from '../../../../base/common/types.js'; +import { localize } from '../../../../nls.js'; +import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; +import { ctxNotebookHasEditorModification } from '../../notebook/browser/contrib/chatEdit/notebookChatEditController.js'; +import { AcceptAction, RejectAction } from './chatEditorActions.js'; +import { ChatEditorController } from './chatEditorController.js'; class ChatEditorOverlayWidget implements IOverlayWidget { + readonly allowEditorOverflow = false; private readonly _domNode: HTMLElement; @@ -76,207 +40,124 @@ class ChatEditorOverlayWidget implements IOverlayWidget { private _isAdded: boolean = false; private readonly _showStore = new DisposableStore(); - private readonly _entry = observableValue< - { entry: IModifiedFileEntry; next: IModifiedFileEntry } | undefined - >(this, undefined); + private readonly _entry = observableValue<{ entry: IModifiedFileEntry; next: IModifiedFileEntry } | undefined>(this, undefined); - private readonly _navigationBearings = observableValue<{ - changeCount: number; - activeIdx: number; - entriesCount: number; - }>(this, { changeCount: -1, activeIdx: -1, entriesCount: -1 }); + private readonly _navigationBearings = observableValue<{ changeCount: number; activeIdx: number; entriesCount: number }>(this, { changeCount: -1, activeIdx: -1, entriesCount: -1 }); constructor( private readonly _editor: ICodeEditor, @IEditorService editorService: IEditorService, @IInstantiationService instaService: IInstantiationService, ) { - this._domNode = document.createElement("div"); - this._domNode.classList.add("chat-editor-overlay-widget"); + this._domNode = document.createElement('div'); + this._domNode.classList.add('chat-editor-overlay-widget'); - this._progressNode = document.createElement("div"); - this._progressNode.classList.add("chat-editor-overlay-progress"); + this._progressNode = document.createElement('div'); + this._progressNode.classList.add('chat-editor-overlay-progress'); this._domNode.appendChild(this._progressNode); - const toolbarNode = document.createElement("div"); - toolbarNode.classList.add("chat-editor-overlay-toolbar"); + const toolbarNode = document.createElement('div'); + toolbarNode.classList.add('chat-editor-overlay-toolbar'); this._domNode.appendChild(toolbarNode); - this._toolbar = instaService.createInstance( - MenuWorkbenchToolBar, - toolbarNode, - MenuId.ChatEditingEditorContent, - { - telemetrySource: "chatEditor.overlayToolbar", - hiddenItemStrategy: HiddenItemStrategy.Ignore, - toolbarOptions: { - primaryGroup: () => true, - useSeparatorsInPrimaryActions: true, - }, - menuOptions: { renderShortTitle: true }, - actionViewItemProvider: (action, options) => { - const that = this; - - if (action.id === navigationBearingFakeActionId) { - return new (class extends ActionViewItem { - constructor() { - super(undefined, action, { - ...options, - icon: false, - label: true, - keybindingNotRenderedWithLabel: true, - }); - } + this._toolbar = instaService.createInstance(MenuWorkbenchToolBar, toolbarNode, MenuId.ChatEditingEditorContent, { + telemetrySource: 'chatEditor.overlayToolbar', + hiddenItemStrategy: HiddenItemStrategy.Ignore, + toolbarOptions: { + primaryGroup: () => true, + useSeparatorsInPrimaryActions: true + }, + menuOptions: { renderShortTitle: true }, + actionViewItemProvider: (action, options) => { + const that = this; - override render(container: HTMLElement) { - super.render(container); - - container.classList.add("label-item"); - - this._store.add( - autorun((r) => { - assertType(this.label); - - const { changeCount, activeIdx } = - that._navigationBearings.read(r); - - const n = - activeIdx === -1 - ? "?" - : `${activeIdx + 1}`; - - const m = - changeCount === -1 - ? "?" - : `${changeCount}`; - this.label.innerText = localize( - "nOfM", - "{0} of {1}", - n, - m, - ); - - this.updateTooltip(); - }), - ); - } + if (action.id === navigationBearingFakeActionId) { + return new class extends ActionViewItem { - protected override getTooltip(): - | string - | undefined { - const { changeCount, entriesCount } = - that._navigationBearings.get(); - - if (changeCount === -1 || entriesCount === -1) { - return undefined; - } else if ( - changeCount === 1 && - entriesCount === 1 - ) { - return localize( - "tooltip_11", - "1 change in 1 file", - ); - } else if (changeCount === 1) { - return localize( - "tooltip_1n", - "1 change in {0} files", - entriesCount, - ); - } else if (entriesCount === 1) { - return localize( - "tooltip_n1", - "{0} changes in 1 file", - changeCount, - ); - } else { - return localize( - "tooltip_nm", - "{0} changes in {1} files", - changeCount, - entriesCount, - ); - } - } - })(); - } + constructor() { + super(undefined, action, { ...options, icon: false, label: true, keybindingNotRenderedWithLabel: true }); + } - if ( - action.id === AcceptAction.ID || - action.id === RejectAction.ID - ) { - return new (class extends ActionViewItem { - private readonly _reveal = this._store.add( - new MutableDisposable(), - ); - - constructor() { - super(undefined, action, { - ...options, - icon: false, - label: true, - keybindingNotRenderedWithLabel: true, - }); - } - override set actionRunner( - actionRunner: IActionRunner, - ) { - super.actionRunner = actionRunner; - - const store = new DisposableStore(); - - store.add( - actionRunner.onWillRun((_e) => { - that._editor.focus(); - }), - ); - - store.add( - actionRunner.onDidRun((e) => { - if (e.action !== this.action) { - return; - } - const d = that._entry.get(); - - if (!d || d.entry === d.next) { - return; - } - const change = d.next.diffInfo - .get() - .changes.at(0); - - return editorService.openEditor( - { - resource: d.next.modifiedURI, - options: { - selection: - change && - Range.fromPositions({ - lineNumber: - change.original - .startLineNumber, - column: 1, - }), - revealIfOpened: false, - revealIfVisible: false, - }, - }, - ACTIVE_GROUP, - ); - }), - ); - - this._reveal.value = store; - } - override get actionRunner(): IActionRunner { - return super.actionRunner; + override render(container: HTMLElement) { + super.render(container); + + container.classList.add('label-item'); + + this._store.add(autorun(r => { + assertType(this.label); + + const { changeCount, activeIdx } = that._navigationBearings.read(r); + const n = activeIdx === -1 ? '?' : `${activeIdx + 1}`; + const m = changeCount === -1 ? '?' : `${changeCount}`; + this.label.innerText = localize('nOfM', "{0} of {1}", n, m); + + this.updateTooltip(); + })); + } + + protected override getTooltip(): string | undefined { + const { changeCount, entriesCount } = that._navigationBearings.get(); + if (changeCount === -1 || entriesCount === -1) { + return undefined; + } else if (changeCount === 1 && entriesCount === 1) { + return localize('tooltip_11', "1 change in 1 file"); + } else if (changeCount === 1) { + return localize('tooltip_1n', "1 change in {0} files", entriesCount); + } else if (entriesCount === 1) { + return localize('tooltip_n1', "{0} changes in 1 file", changeCount); + } else { + return localize('tooltip_nm', "{0} changes in {1} files", changeCount, entriesCount); } - })(); - } - return undefined; - }, - }, - ); + } + }; + } + + if (action.id === AcceptAction.ID || action.id === RejectAction.ID) { + return new class extends ActionViewItem { + + private readonly _reveal = this._store.add(new MutableDisposable()); + + constructor() { + super(undefined, action, { ...options, icon: false, label: true, keybindingNotRenderedWithLabel: true }); + } + override set actionRunner(actionRunner: IActionRunner) { + super.actionRunner = actionRunner; + + const store = new DisposableStore(); + + store.add(actionRunner.onWillRun(_e => { + that._editor.focus(); + })); + + store.add(actionRunner.onDidRun(e => { + if (e.action !== this.action) { + return; + } + const d = that._entry.get(); + if (!d || d.entry === d.next) { + return; + } + const change = d.next.diffInfo.get().changes.at(0); + return editorService.openEditor({ + resource: d.next.modifiedURI, + options: { + selection: change && Range.fromPositions({ lineNumber: change.original.startLineNumber, column: 1 }), + revealIfOpened: false, + revealIfVisible: false, + } + }, ACTIVE_GROUP); + })); + + this._reveal.value = store; + } + override get actionRunner(): IActionRunner { + return super.actionRunner; + } + }; + } + return undefined; + } + }); } dispose() { @@ -286,7 +167,7 @@ class ChatEditorOverlayWidget implements IOverlayWidget { } getId(): string { - return "chatEditorOverlayWidget"; + return 'chatEditorOverlayWidget'; } getDomNode(): HTMLElement { @@ -294,100 +175,64 @@ class ChatEditorOverlayWidget implements IOverlayWidget { } getPosition(): IOverlayWidgetPosition | null { - return { - preference: OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER, - }; + return { preference: OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER }; } - show( - session: IChatEditingSession, - activeEntry: IModifiedFileEntry, - next: IModifiedFileEntry, - ) { + show(session: IChatEditingSession, activeEntry: IModifiedFileEntry, next: IModifiedFileEntry) { + this._showStore.clear(); this._entry.set({ entry: activeEntry, next }, undefined); - this._showStore.add( - autorun((r) => { - const busy = activeEntry.isCurrentlyBeingModified.read(r); - this._domNode.classList.toggle("busy", busy); - }), - ); + this._showStore.add(autorun(r => { + const busy = activeEntry.isCurrentlyBeingModified.read(r); + this._domNode.classList.toggle('busy', busy); + })); const slickRatio = ObservableAnimatedValue.const(0); - let t = Date.now(); - this._showStore.add( - autorun((r) => { - const value = activeEntry.rewriteRatio.read(r); - - slickRatio.changeAnimation((prev) => { - const result = new AnimatedValue( - prev.getValue(), - value, - Date.now() - t, - ); - t = Date.now(); - - return result; - }, undefined); - - const value2 = slickRatio.getValue(r); - reset( - this._progressNode, - value === 0 - ? renderIcon(ThemeIcon.modify(Codicon.loading, "spin")) - : `${Math.round(value2 * 100)}%`, - ); - }), - ); - - const editorPositionObs = observableFromEvent( - this._editor.onDidChangeCursorPosition, - () => this._editor.getPosition(), - ); - - this._showStore.add( - autorun((r) => { - const position = editorPositionObs.read(r); - - if (!position) { - return; - } + this._showStore.add(autorun(r => { + const value = activeEntry.rewriteRatio.read(r); + + slickRatio.changeAnimation(prev => { + const result = new AnimatedValue(prev.getValue(), value, Date.now() - t); + t = Date.now(); + return result; + }, undefined); + + const value2 = slickRatio.getValue(r); + reset(this._progressNode, value === 0 + ? renderIcon(ThemeIcon.modify(Codicon.loading, 'spin')) + : `${Math.round(value2 * 100)}%` + ); + })); - const entries = session.entries.read(r); + this._showStore.add(autorun(r => { - let changes = 0; + const position = ChatEditorController.get(this._editor)?.currentChange.read(r); + const entries = session.entries.read(r); - let activeIdx = -1; + let changes = 0; + let activeIdx = -1; + for (const entry of entries) { + const diffInfo = entry.diffInfo.read(r); - for (const entry of entries) { - const diffInfo = entry.diffInfo.read(r); + if (activeIdx !== -1 || entry !== activeEntry) { + // just add up + changes += diffInfo.changes.length; - if (activeIdx !== -1 || entry !== activeEntry) { - // just add up - changes += diffInfo.changes.length; - } else { - for (const change of diffInfo.changes) { - if (change.modified.includes(position.lineNumber)) { - activeIdx = changes; - } - changes += 1; + } else { + for (const change of diffInfo.changes) { + if (position && change.modified.includes(position.lineNumber)) { + activeIdx = changes; } + changes += 1; } } + } - this._navigationBearings.set( - { - changeCount: changes, - activeIdx, - entriesCount: entries.length, - }, - undefined, - ); - }), - ); + this._navigationBearings.set({ changeCount: changes, activeIdx, entriesCount: entries.length }, undefined); + })); if (!this._isAdded) { this._editor.addOverlayWidget(this); @@ -396,12 +241,10 @@ class ChatEditorOverlayWidget implements IOverlayWidget { } hide() { - transaction((tx) => { + + transaction(tx => { this._entry.set(undefined, tx); - this._navigationBearings.set( - { changeCount: -1, activeIdx: -1, entriesCount: -1 }, - tx, - ); + this._navigationBearings.set({ changeCount: -1, activeIdx: -1, entriesCount: -1 }, tx); }); if (this._isAdded) { @@ -412,19 +255,20 @@ class ChatEditorOverlayWidget implements IOverlayWidget { } } -export const navigationBearingFakeActionId = "chatEditor.navigation.bearings"; +export const navigationBearingFakeActionId = 'chatEditor.navigation.bearings'; MenuRegistry.appendMenuItem(MenuId.ChatEditingEditorContent, { command: { id: navigationBearingFakeActionId, - title: localize("label", "Navigation Status"), + title: localize('label', "Navigation Status"), precondition: ContextKeyExpr.false(), }, when: ctxNotebookHasEditorModification.negate(), - group: "navigate", - order: -1, + group: 'navigate', + order: -1 }); + export class ObservableAnimatedValue { public static const(value: number): ObservableAnimatedValue { return new ObservableAnimatedValue(AnimatedValue.const(value)); @@ -432,7 +276,9 @@ export class ObservableAnimatedValue { private readonly _value: ISettableObservable; - constructor(initialValue: AnimatedValue) { + constructor( + initialValue: AnimatedValue, + ) { this._value = observableValue(this, initialValue); } @@ -440,17 +286,13 @@ export class ObservableAnimatedValue { this._value.set(value, tx); } - changeAnimation( - fn: (prev: AnimatedValue) => AnimatedValue, - tx: ITransaction | undefined, - ): void { + changeAnimation(fn: (prev: AnimatedValue) => AnimatedValue, tx: ITransaction | undefined): void { const value = fn(this._value.get()); this._value.set(value, tx); } getValue(reader: IReader | undefined): number { const value = this._value.read(reader); - if (!value.isFinished()) { Scheduler.instance.invalidateOnNextAnimationFrame(reader); } @@ -467,7 +309,6 @@ class Scheduler { invalidateOnNextAnimationFrame(reader: IReader | undefined): void { this._signal.read(reader); - if (!this._isScheduled) { this._isScheduled = true; scheduleAtNextAnimationFrame(getWindow(undefined), () => { @@ -479,6 +320,7 @@ class Scheduler { } export class AnimatedValue { + static const(value: number): AnimatedValue { return new AnimatedValue(value, value, 0); } @@ -501,42 +343,29 @@ export class AnimatedValue { getValue(): number { const timePassed = Date.now() - this.startTimeMs; - if (timePassed >= this.durationMs) { return this.endValue; } - const value = easeOutExpo( - timePassed, - this.startValue, - this.endValue - this.startValue, - this.durationMs, - ); - + const value = easeOutExpo(timePassed, this.startValue, this.endValue - this.startValue, this.durationMs); return value; } } -function easeOutExpo( - passedTime: number, - start: number, - length: number, - totalDuration: number, -): number { +function easeOutExpo(passedTime: number, start: number, length: number, totalDuration: number): number { return passedTime === totalDuration ? start + length - : length * (-Math.pow(2, (-10 * passedTime) / totalDuration) + 1) + - start; + : length * (-Math.pow(2, -10 * passedTime / totalDuration) + 1) + start; } + export class ChatEditorOverlayController implements IEditorContribution { - static readonly ID = "editor.contrib.chatOverlayController"; + + static readonly ID = 'editor.contrib.chatOverlayController'; private readonly _store = new DisposableStore(); static get(editor: ICodeEditor) { - return editor.getContribution( - ChatEditorOverlayController.ID, - ); + return editor.getContribution(ChatEditorOverlayController.ID); } constructor( @@ -544,73 +373,44 @@ export class ChatEditorOverlayController implements IEditorContribution { @IChatEditingService chatEditingService: IChatEditingService, @IInstantiationService instaService: IInstantiationService, ) { - const modelObs = observableFromEvent( - this._editor.onDidChangeModel, - () => this._editor.getModel(), - ); - - const widget = instaService.createInstance( - ChatEditorOverlayWidget, - this._editor, - ); + const modelObs = observableFromEvent(this._editor.onDidChangeModel, () => this._editor.getModel()); + const widget = this._store.add(instaService.createInstance(ChatEditorOverlayWidget, this._editor)); if (this._editor.getOption(EditorOption.inDiffEditor)) { return; } - this._store.add( - autorun((r) => { - const model = modelObs.read(r); - - const session = - chatEditingService.currentEditingSessionObs.read(r); - - if (!session || !model) { - widget.hide(); - - return; - } - - const state = session.state.read(r); - - if (state === ChatEditingSessionState.Disposed) { - widget.hide(); - - return; - } - - const entries = session.entries.read(r); - - const idx = entries.findIndex((e) => - isEqual(e.modifiedURI, model.uri), - ); - - if (idx < 0) { - widget.hide(); - - return; - } - - const isModifyingOrModified = entries.some( - (e) => - e.state.read(r) === WorkingSetEntryState.Modified || - e.isCurrentlyBeingModified.read(r), - ); - - if (!isModifyingOrModified) { - widget.hide(); - - return; - } - - const entry = entries[idx]; - widget.show( - session, - entry, - entries[(idx + 1) % entries.length], - ); - }), - ); + this._store.add(autorun(r => { + const model = modelObs.read(r); + const session = chatEditingService.currentEditingSessionObs.read(r); + if (!session || !model) { + widget.hide(); + return; + } + + const state = session.state.read(r); + if (state === ChatEditingSessionState.Disposed) { + widget.hide(); + return; + } + + const entries = session.entries.read(r); + const idx = entries.findIndex(e => isEqual(e.modifiedURI, model.uri)); + if (idx < 0) { + widget.hide(); + return; + } + + const isModifyingOrModified = entries.some(e => e.state.read(r) === WorkingSetEntryState.Modified || e.isCurrentlyBeingModified.read(r)); + if (!isModifyingOrModified) { + widget.hide(); + return; + } + + const entry = entries[idx]; + widget.show(session, entry, entries[(idx + 1) % entries.length]); + + })); } dispose() { diff --git a/Source/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts b/Source/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts index b9922ed2bbfee..6e99218fcfbb2 100644 --- a/Source/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts +++ b/Source/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts @@ -3,89 +3,63 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as dom from "../../../../base/browser/dom.js"; -import { StandardMouseEvent } from "../../../../base/browser/mouseEvent.js"; -import { getDefaultHoverDelegate } from "../../../../base/browser/ui/hover/hoverDelegateFactory.js"; -import { KeyCode, KeyMod } from "../../../../base/common/keyCodes.js"; -import { Lazy } from "../../../../base/common/lazy.js"; -import { Disposable } from "../../../../base/common/lifecycle.js"; -import { URI } from "../../../../base/common/uri.js"; -import { generateUuid } from "../../../../base/common/uuid.js"; -import { ICodeEditorService } from "../../../../editor/browser/services/codeEditorService.js"; -import { IRange } from "../../../../editor/common/core/range.js"; -import { EditorContextKeys } from "../../../../editor/common/editorContextKeys.js"; -import { LanguageFeatureRegistry } from "../../../../editor/common/languageFeatureRegistry.js"; -import { Location, SymbolKinds } from "../../../../editor/common/languages.js"; -import { ILanguageService } from "../../../../editor/common/languages/language.js"; -import { getIconClasses } from "../../../../editor/common/services/getIconClasses.js"; -import { ILanguageFeaturesService } from "../../../../editor/common/services/languageFeatures.js"; -import { IModelService } from "../../../../editor/common/services/model.js"; -import { ITextModelService } from "../../../../editor/common/services/resolverService.js"; -import { DefinitionAction } from "../../../../editor/contrib/gotoSymbol/browser/goToCommands.js"; -import * as nls from "../../../../nls.js"; -import { localize } from "../../../../nls.js"; -import { getFlatContextMenuActions } from "../../../../platform/actions/browser/menuEntryActionViewItem.js"; -import { - Action2, - IMenuService, - MenuId, - registerAction2, -} from "../../../../platform/actions/common/actions.js"; -import { IClipboardService } from "../../../../platform/clipboard/common/clipboardService.js"; -import { ICommandService } from "../../../../platform/commands/common/commands.js"; -import { - IContextKey, - IContextKeyService, - RawContextKey, -} from "../../../../platform/contextkey/common/contextkey.js"; -import { IContextMenuService } from "../../../../platform/contextview/browser/contextView.js"; -import { ITextResourceEditorInput } from "../../../../platform/editor/common/editor.js"; -import { - FileKind, - IFileService, -} from "../../../../platform/files/common/files.js"; -import { IHoverService } from "../../../../platform/hover/browser/hover.js"; -import { - IInstantiationService, - ServicesAccessor, -} from "../../../../platform/instantiation/common/instantiation.js"; -import { KeybindingWeight } from "../../../../platform/keybinding/common/keybindingsRegistry.js"; -import { ILabelService } from "../../../../platform/label/common/label.js"; -import { ITelemetryService } from "../../../../platform/telemetry/common/telemetry.js"; -import { fillEditorsDragData } from "../../../browser/dnd.js"; -import { ResourceContextKey } from "../../../common/contextkeys.js"; -import { - IEditorService, - SIDE_GROUP, -} from "../../../services/editor/common/editorService.js"; -import { ExplorerFolderContext } from "../../files/common/files.js"; -import { IWorkspaceSymbol } from "../../search/common/search.js"; -import { IChatContentInlineReference } from "../common/chatService.js"; -import { IChatVariablesService } from "../common/chatVariables.js"; -import { IChatWidgetService } from "./chat.js"; -import { IChatMarkdownAnchorService } from "./chatContentParts/chatMarkdownAnchorService.js"; - -const chatResourceContextKey = new RawContextKey( - "chatAnchorResource", - undefined, - { - type: "URI", - description: localize( - "resource", - "The full value of the chat anchor resource, including scheme and path", - ), - }, -); +import * as dom from '../../../../base/browser/dom.js'; +import { StandardMouseEvent } from '../../../../base/browser/mouseEvent.js'; +import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; +import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; +import { Lazy } from '../../../../base/common/lazy.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { URI } from '../../../../base/common/uri.js'; +import { generateUuid } from '../../../../base/common/uuid.js'; +import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; +import { IRange } from '../../../../editor/common/core/range.js'; +import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; +import { LanguageFeatureRegistry } from '../../../../editor/common/languageFeatureRegistry.js'; +import { Location, SymbolKinds } from '../../../../editor/common/languages.js'; +import { ILanguageService } from '../../../../editor/common/languages/language.js'; +import { getIconClasses } from '../../../../editor/common/services/getIconClasses.js'; +import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js'; +import { IModelService } from '../../../../editor/common/services/model.js'; +import { ITextModelService } from '../../../../editor/common/services/resolverService.js'; +import { DefinitionAction } from '../../../../editor/contrib/gotoSymbol/browser/goToCommands.js'; +import * as nls from '../../../../nls.js'; +import { localize } from '../../../../nls.js'; +import { getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { Action2, IMenuService, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; +import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; +import { fillInSymbolsDragData, IResourceStat } from '../../../../platform/dnd/browser/dnd.js'; +import { ITextResourceEditorInput } from '../../../../platform/editor/common/editor.js'; +import { FileKind, IFileService } from '../../../../platform/files/common/files.js'; +import { IHoverService } from '../../../../platform/hover/browser/hover.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { ILabelService } from '../../../../platform/label/common/label.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { fillEditorsDragData } from '../../../browser/dnd.js'; +import { ResourceContextKey } from '../../../common/contextkeys.js'; +import { IEditorService, SIDE_GROUP } from '../../../services/editor/common/editorService.js'; +import { ExplorerFolderContext } from '../../files/common/files.js'; +import { IWorkspaceSymbol } from '../../search/common/search.js'; +import { IChatContentInlineReference } from '../common/chatService.js'; +import { IChatVariablesService } from '../common/chatVariables.js'; +import { IChatWidgetService } from './chat.js'; +import { IChatMarkdownAnchorService } from './chatContentParts/chatMarkdownAnchorService.js'; + +const chatResourceContextKey = new RawContextKey('chatAnchorResource', undefined, { type: 'URI', description: localize('resource', "The full value of the chat anchor resource, including scheme and path") }); type ContentRefData = - | { readonly kind: "symbol"; readonly symbol: IWorkspaceSymbol } + | { readonly kind: 'symbol'; readonly symbol: IWorkspaceSymbol } | { - readonly kind?: undefined; - readonly uri: URI; - readonly range?: IRange; - }; + readonly kind?: undefined; + readonly uri: URI; + readonly range?: IRange; + }; export class InlineAnchorWidget extends Disposable { - public static readonly className = "chat-inline-anchor-widget"; + + public static readonly className = 'chat-inline-anchor-widget'; private readonly _chatResourceContext: IContextKey; @@ -102,8 +76,7 @@ export class InlineAnchorWidget extends Disposable { @IHoverService hoverService: IHoverService, @IInstantiationService instantiationService: IInstantiationService, @ILabelService labelService: ILabelService, - @ILanguageFeaturesService - languageFeaturesService: ILanguageFeaturesService, + @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, @ILanguageService languageService: ILanguageService, @IMenuService menuService: IMenuService, @IModelService modelService: IModelService, @@ -114,98 +87,50 @@ export class InlineAnchorWidget extends Disposable { // TODO: Make sure we handle updates from an inlineReference being `resolved` late - this.data = - "uri" in inlineReference.inlineReference - ? inlineReference.inlineReference - : "name" in inlineReference.inlineReference - ? { - kind: "symbol", - symbol: inlineReference.inlineReference, - } - : { uri: inlineReference.inlineReference }; - - const contextKeyService = this._register( - originalContextKeyService.createScoped(element), - ); - this._chatResourceContext = - chatResourceContextKey.bindTo(contextKeyService); + this.data = 'uri' in inlineReference.inlineReference + ? inlineReference.inlineReference + : 'name' in inlineReference.inlineReference + ? { kind: 'symbol', symbol: inlineReference.inlineReference } + : { uri: inlineReference.inlineReference }; + + const contextKeyService = this._register(originalContextKeyService.createScoped(element)); + this._chatResourceContext = chatResourceContextKey.bindTo(contextKeyService); const anchorId = new Lazy(generateUuid); - element.classList.add(InlineAnchorWidget.className, "show-file-icons"); + element.classList.add(InlineAnchorWidget.className, 'show-file-icons'); let iconText: string; - let iconClasses: string[]; let location: { readonly uri: URI; readonly range?: IRange }; - let contextMenuId: MenuId; - - let contextMenuArg: - | URI - | { readonly uri: URI; readonly range?: IRange }; + let contextMenuArg: URI | { readonly uri: URI; readonly range?: IRange }; let updateContextKeys: (() => Promise) | undefined; - - if (this.data.kind === "symbol") { + if (this.data.kind === 'symbol') { location = this.data.symbol.location; contextMenuId = MenuId.ChatInlineSymbolAnchorContext; contextMenuArg = location; iconText = this.data.symbol.name; - iconClasses = [ - "codicon", - ...getIconClasses( - modelService, - languageService, - undefined, - undefined, - SymbolKinds.toIcon(this.data.symbol.kind), - ), - ]; + iconClasses = ['codicon', ...getIconClasses(modelService, languageService, undefined, undefined, SymbolKinds.toIcon(this.data.symbol.kind))]; - const providerContexts: ReadonlyArray< - [IContextKey, LanguageFeatureRegistry] - > = [ - [ - EditorContextKeys.hasDefinitionProvider.bindTo( - contextKeyService, - ), - languageFeaturesService.definitionProvider, - ], - [ - EditorContextKeys.hasReferenceProvider.bindTo( - contextKeyService, - ), - languageFeaturesService.referenceProvider, - ], - [ - EditorContextKeys.hasImplementationProvider.bindTo( - contextKeyService, - ), - languageFeaturesService.implementationProvider, - ], - [ - EditorContextKeys.hasTypeDefinitionProvider.bindTo( - contextKeyService, - ), - languageFeaturesService.typeDefinitionProvider, - ], + const providerContexts: ReadonlyArray<[IContextKey, LanguageFeatureRegistry]> = [ + [EditorContextKeys.hasDefinitionProvider.bindTo(contextKeyService), languageFeaturesService.definitionProvider], + [EditorContextKeys.hasReferenceProvider.bindTo(contextKeyService), languageFeaturesService.referenceProvider], + [EditorContextKeys.hasImplementationProvider.bindTo(contextKeyService), languageFeaturesService.implementationProvider], + [EditorContextKeys.hasTypeDefinitionProvider.bindTo(contextKeyService), languageFeaturesService.typeDefinitionProvider], ]; updateContextKeys = async () => { - const modelRef = await textModelService.createModelReference( - location.uri, - ); - + const modelRef = await textModelService.createModelReference(location.uri); try { if (this._isDisposed) { return; } const model = modelRef.object.textEditorModel; - for (const [contextKey, registry] of providerContexts) { contextKey.set(registry.has(model)); } @@ -214,172 +139,114 @@ export class InlineAnchorWidget extends Disposable { } }; - this._register( - dom.addDisposableListener(element, "click", () => { - telemetryService.publicLog2< - { - anchorId: string; - }, - { - anchorId: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "Unique identifier for the current anchor."; - }; - owner: "mjbvz"; - comment: "Provides insight into the usage of Chat features."; - } - >("chat.inlineAnchor.openSymbol", { - anchorId: anchorId.value, - }); - }), - ); + this._register(dom.addDisposableListener(element, 'click', () => { + telemetryService.publicLog2<{ + anchorId: string; + }, { + anchorId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Unique identifier for the current anchor.' }; + owner: 'mjbvz'; + comment: 'Provides insight into the usage of Chat features.'; + }>('chat.inlineAnchor.openSymbol', { + anchorId: anchorId.value + }); + })); } else { location = this.data; contextMenuId = MenuId.ChatInlineResourceAnchorContext; contextMenuArg = location.uri; const label = labelService.getUriBasenameLabel(location.uri); - iconText = - location.range && this.data.kind !== "symbol" - ? `${label}#${location.range.startLineNumber}-${location.range.endLineNumber}` - : label; - - const fileKind = location.uri.path.endsWith("/") - ? FileKind.FOLDER - : FileKind.FILE; - iconClasses = getIconClasses( - modelService, - languageService, - location.uri, - fileKind, - ); - - const isFolderContext = - ExplorerFolderContext.bindTo(contextKeyService); - fileService - .stat(location.uri) - .then((stat) => { + iconText = location.range && this.data.kind !== 'symbol' ? + `${label}#${location.range.startLineNumber}-${location.range.endLineNumber}` : + label; + + const fileKind = location.uri.path.endsWith('/') ? FileKind.FOLDER : FileKind.FILE; + iconClasses = getIconClasses(modelService, languageService, location.uri, fileKind); + + const isFolderContext = ExplorerFolderContext.bindTo(contextKeyService); + fileService.stat(location.uri) + .then(stat => { isFolderContext.set(stat.isDirectory); }) - .catch(() => {}); - - this._register( - dom.addDisposableListener(element, "click", () => { - telemetryService.publicLog2< - { - anchorId: string; - }, - { - anchorId: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "Unique identifier for the current anchor."; - }; - owner: "mjbvz"; - comment: "Provides insight into the usage of Chat features."; - } - >("chat.inlineAnchor.openResource", { - anchorId: anchorId.value, - }); - }), - ); + .catch(() => { }); + + this._register(dom.addDisposableListener(element, 'click', () => { + telemetryService.publicLog2<{ + anchorId: string; + }, { + anchorId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Unique identifier for the current anchor.' }; + owner: 'mjbvz'; + comment: 'Provides insight into the usage of Chat features.'; + }>('chat.inlineAnchor.openResource', { + anchorId: anchorId.value + }); + })); } - const resourceContextKey = this._register( - new ResourceContextKey( - contextKeyService, - fileService, - languageService, - modelService, - ), - ); + const resourceContextKey = this._register(new ResourceContextKey(contextKeyService, fileService, languageService, modelService)); resourceContextKey.set(location.uri); this._chatResourceContext.set(location.uri.toString()); - const iconEl = dom.$("span.icon"); + const iconEl = dom.$('span.icon'); iconEl.classList.add(...iconClasses); - element.replaceChildren(iconEl, dom.$("span.icon-label", {}, iconText)); - - const fragment = location.range - ? `${location.range.startLineNumber},${location.range.startColumn}` - : ""; - element.setAttribute( - "data-href", - (fragment - ? location.uri.with({ fragment }) - : location.uri - ).toString(), - ); + element.replaceChildren(iconEl, dom.$('span.icon-label', {}, iconText)); + + const fragment = location.range ? `${location.range.startLineNumber},${location.range.startColumn}` : ''; + element.setAttribute('data-href', (fragment ? location.uri.with({ fragment }) : location.uri).toString()); // Context menu - this._register( - dom.addDisposableListener( - element, - dom.EventType.CONTEXT_MENU, - async (domEvent) => { - const event = new StandardMouseEvent( - dom.getWindow(domEvent), - domEvent, - ); - - dom.EventHelper.stop(domEvent, true); - - try { - await updateContextKeys?.(); - } catch (e) { - console.error(e); - } + this._register(dom.addDisposableListener(element, dom.EventType.CONTEXT_MENU, async domEvent => { + const event = new StandardMouseEvent(dom.getWindow(domEvent), domEvent); + dom.EventHelper.stop(domEvent, true); + + try { + await updateContextKeys?.(); + } catch (e) { + console.error(e); + } - if (this._isDisposed) { - return; - } + if (this._isDisposed) { + return; + } - contextMenuService.showContextMenu({ - contextKeyService, - getAnchor: () => event, - getActions: () => { - const menu = menuService.getMenuActions( - contextMenuId, - contextKeyService, - { arg: contextMenuArg }, - ); - - return getFlatContextMenuActions(menu); - }, - }); + contextMenuService.showContextMenu({ + contextKeyService, + getAnchor: () => event, + getActions: () => { + const menu = menuService.getMenuActions(contextMenuId, contextKeyService, { arg: contextMenuArg }); + return getFlatContextMenuActions(menu); }, - ), - ); + }); + })); // Hover - const relativeLabel = labelService.getUriLabel(location.uri, { - relative: true, - }); - this._register( - hoverService.setupManagedHover( - getDefaultHoverDelegate("element"), - element, - relativeLabel, - ), - ); + const relativeLabel = labelService.getUriLabel(location.uri, { relative: true }); + this._register(hoverService.setupManagedHover(getDefaultHoverDelegate('element'), element, relativeLabel)); // Drag and drop element.draggable = true; - this._register( - dom.addDisposableListener(element, "dragstart", (e) => { - instantiationService.invokeFunction((accessor) => - fillEditorsDragData(accessor, [location.uri], e), - ); - - e.dataTransfer?.setDragImage(element, 0, 0); - }), - ); + this._register(dom.addDisposableListener(element, 'dragstart', e => { + const stat: IResourceStat = { + resource: location.uri, + selection: location.range, + }; + instantiationService.invokeFunction(accessor => fillEditorsDragData(accessor, [stat], e)); + + if (this.data.kind === 'symbol') { + fillInSymbolsDragData([{ + name: this.data.symbol.name, + fsPath: this.data.symbol.location.uri.fsPath, + range: this.data.symbol.location.range, + kind: this.data.symbol.kind + }], e); + } + + e.dataTransfer?.setDragImage(element, 0, 0); + })); } override dispose(): void { this._isDisposed = true; - super.dispose(); } @@ -390,235 +257,169 @@ export class InlineAnchorWidget extends Disposable { //#region Resource context menu -registerAction2( - class AddFileToChatAction extends Action2 { - static readonly id = "chat.inlineResourceAnchor.addFileToChat"; - - constructor() { - super({ - id: AddFileToChatAction.id, - title: nls.localize2( - "actions.attach.label", - "Add File to Chat", - ), - menu: [ - { - id: MenuId.ChatInlineResourceAnchorContext, - group: "chat", - order: 1, - when: ExplorerFolderContext.negate(), - }, - ], - }); - } +registerAction2(class AddFileToChatAction extends Action2 { - override async run( - accessor: ServicesAccessor, - resource: URI, - ): Promise { - const chatWidgetService = accessor.get(IChatWidgetService); + static readonly id = 'chat.inlineResourceAnchor.addFileToChat'; - const variablesService = accessor.get(IChatVariablesService); + constructor() { + super({ + id: AddFileToChatAction.id, + title: nls.localize2('actions.attach.label', "Add File to Chat"), + menu: [{ + id: MenuId.ChatInlineResourceAnchorContext, + group: 'chat', + order: 1, + when: ExplorerFolderContext.negate(), + }] + }); + } - const widget = chatWidgetService.lastFocusedWidget; + override async run(accessor: ServicesAccessor, resource: URI): Promise { + const chatWidgetService = accessor.get(IChatWidgetService); + const variablesService = accessor.get(IChatVariablesService); - if (!widget) { - return; - } - - variablesService.attachContext("file", resource, widget.location); + const widget = chatWidgetService.lastFocusedWidget; + if (!widget) { + return; } - }, -); + + variablesService.attachContext('file', resource, widget.location); + } +}); //#endregion //#region Resource keybindings -registerAction2( - class CopyResourceAction extends Action2 { - static readonly id = "chat.inlineResourceAnchor.copyResource"; - - constructor() { - super({ - id: CopyResourceAction.id, - title: nls.localize2("actions.copy.label", "Copy"), - f1: false, - precondition: chatResourceContextKey, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyCode.KeyC, - }, - }); - } - - override async run(accessor: ServicesAccessor): Promise { - const chatWidgetService = accessor.get(IChatMarkdownAnchorService); - - const clipboardService = accessor.get(IClipboardService); +registerAction2(class CopyResourceAction extends Action2 { - const anchor = chatWidgetService.lastFocusedAnchor; + static readonly id = 'chat.inlineResourceAnchor.copyResource'; - if (!anchor) { - return; + constructor() { + super({ + id: CopyResourceAction.id, + title: nls.localize2('actions.copy.label', "Copy"), + f1: false, + precondition: chatResourceContextKey, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyCode.KeyC, } + }); + } - // TODO: we should also write out the standard mime types so that external programs can use them - // like how `fillEditorsDragData` works but without having an event to work with. - const resource = - anchor.data.kind === "symbol" - ? anchor.data.symbol.location.uri - : anchor.data.uri; - clipboardService.writeResources([resource]); - } - }, -); - -registerAction2( - class OpenToSideResourceAction extends Action2 { - static readonly id = "chat.inlineResourceAnchor.openToSide"; - - constructor() { - super({ - id: OpenToSideResourceAction.id, - title: nls.localize2( - "actions.openToSide.label", - "Open to the Side", - ), - f1: false, - precondition: chatResourceContextKey, - keybinding: { - weight: KeybindingWeight.ExternalExtension + 2, - primary: KeyMod.CtrlCmd | KeyCode.Enter, - mac: { - primary: KeyMod.WinCtrl | KeyCode.Enter, - }, - }, - menu: [ - { - id: MenuId.ChatInlineSymbolAnchorContext, - group: "navigation", - order: 1, - }, - ], - }); + override async run(accessor: ServicesAccessor): Promise { + const chatWidgetService = accessor.get(IChatMarkdownAnchorService); + const clipboardService = accessor.get(IClipboardService); + + const anchor = chatWidgetService.lastFocusedAnchor; + if (!anchor) { + return; } - override async run(accessor: ServicesAccessor): Promise { - const chatWidgetService = accessor.get(IChatMarkdownAnchorService); + // TODO: we should also write out the standard mime types so that external programs can use them + // like how `fillEditorsDragData` works but without having an event to work with. + const resource = anchor.data.kind === 'symbol' ? anchor.data.symbol.location.uri : anchor.data.uri; + clipboardService.writeResources([resource]); + } +}); + +registerAction2(class OpenToSideResourceAction extends Action2 { + + static readonly id = 'chat.inlineResourceAnchor.openToSide'; + + constructor() { + super({ + id: OpenToSideResourceAction.id, + title: nls.localize2('actions.openToSide.label', "Open to the Side"), + f1: false, + precondition: chatResourceContextKey, + keybinding: { + weight: KeybindingWeight.ExternalExtension + 2, + primary: KeyMod.CtrlCmd | KeyCode.Enter, + mac: { + primary: KeyMod.WinCtrl | KeyCode.Enter + }, + }, + menu: [{ + id: MenuId.ChatInlineSymbolAnchorContext, + group: 'navigation', + order: 1 + }] + }); + } - const editorService = accessor.get(IEditorService); + override async run(accessor: ServicesAccessor): Promise { + const chatWidgetService = accessor.get(IChatMarkdownAnchorService); + const editorService = accessor.get(IEditorService); - const anchor = chatWidgetService.lastFocusedAnchor; + const anchor = chatWidgetService.lastFocusedAnchor; + if (!anchor) { + return; + } - if (!anchor) { - return; + const input: ITextResourceEditorInput = anchor.data.kind === 'symbol' + ? { + resource: anchor.data.symbol.location.uri, options: { + selection: { + startColumn: anchor.data.symbol.location.range.startColumn, + startLineNumber: anchor.data.symbol.location.range.startLineNumber, + } + } } + : { resource: anchor.data.uri }; - const input: ITextResourceEditorInput = - anchor.data.kind === "symbol" - ? { - resource: anchor.data.symbol.location.uri, - options: { - selection: { - startColumn: - anchor.data.symbol.location.range - .startColumn, - startLineNumber: - anchor.data.symbol.location.range - .startLineNumber, - }, - }, - } - : { resource: anchor.data.uri }; - - await editorService.openEditors([input], SIDE_GROUP); - } - }, -); + await editorService.openEditors([input], SIDE_GROUP); + } +}); //#endregion //#region Symbol context menu -registerAction2( - class GoToDefinitionAction extends Action2 { - static readonly id = "chat.inlineSymbolAnchor.goToDefinition"; - - constructor() { - super({ - id: GoToDefinitionAction.id, - title: { - ...nls.localize2( - "actions.goToDecl.label", - "Go to Definition", - ), - mnemonicTitle: nls.localize( - { - key: "miGotoDefinition", - comment: ["&& denotes a mnemonic"], - }, - "Go to &&Definition", - ), - }, - menu: [ - { - id: MenuId.ChatInlineSymbolAnchorContext, - group: "4_symbol_nav", - order: 1.1, - when: EditorContextKeys.hasDefinitionProvider, - }, - ], - }); - } +registerAction2(class GoToDefinitionAction extends Action2 { - override async run( - accessor: ServicesAccessor, - location: Location, - ): Promise { - const editorService = accessor.get(ICodeEditorService); + static readonly id = 'chat.inlineSymbolAnchor.goToDefinition'; + + constructor() { + super({ + id: GoToDefinitionAction.id, + title: { + ...nls.localize2('actions.goToDecl.label', "Go to Definition"), + mnemonicTitle: nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition"), + }, + menu: [{ + id: MenuId.ChatInlineSymbolAnchorContext, + group: '4_symbol_nav', + order: 1.1, + when: EditorContextKeys.hasDefinitionProvider, + }] + }); + } - await openEditorWithSelection(editorService, location); + override async run(accessor: ServicesAccessor, location: Location): Promise { + const editorService = accessor.get(ICodeEditorService); - const action = new DefinitionAction( - { openToSide: false, openInPeek: false, muteMessage: true }, - { - title: { value: "", original: "" }, - id: "", - precondition: undefined, - }, - ); + await openEditorWithSelection(editorService, location); - return action.run(accessor); + const action = new DefinitionAction({ openToSide: false, openInPeek: false, muteMessage: true }, { title: { value: '', original: '' }, id: '', precondition: undefined }); + return action.run(accessor); + } +}); + +async function openEditorWithSelection(editorService: ICodeEditorService, location: Location) { + await editorService.openCodeEditor({ + resource: location.uri, options: { + selection: { + startColumn: location.range.startColumn, + startLineNumber: location.range.startLineNumber, + } } - }, -); - -async function openEditorWithSelection( - editorService: ICodeEditorService, - location: Location, -) { - await editorService.openCodeEditor( - { - resource: location.uri, - options: { - selection: { - startColumn: location.range.startColumn, - startLineNumber: location.range.startLineNumber, - }, - }, - }, - null, - ); + }, null); } -async function runGoToCommand( - accessor: ServicesAccessor, - command: string, - location: Location, -) { +async function runGoToCommand(accessor: ServicesAccessor, command: string, location: Location) { const editorService = accessor.get(ICodeEditorService); - const commandService = accessor.get(ICommandService); await openEditorWithSelection(editorService, location); @@ -626,136 +427,79 @@ async function runGoToCommand( return commandService.executeCommand(command); } -registerAction2( - class GoToTypeDefinitionsAction extends Action2 { - static readonly id = "chat.inlineSymbolAnchor.goToTypeDefinitions"; - - constructor() { - super({ - id: GoToTypeDefinitionsAction.id, - title: { - ...nls.localize2( - "goToTypeDefinitions.label", - "Go to Type Definitions", - ), - mnemonicTitle: nls.localize( - { - key: "miGotoTypeDefinition", - comment: ["&& denotes a mnemonic"], - }, - "Go to &&Type Definitions", - ), - }, - menu: [ - { - id: MenuId.ChatInlineSymbolAnchorContext, - group: "4_symbol_nav", - order: 1.1, - when: EditorContextKeys.hasTypeDefinitionProvider, - }, - ], - }); - } +registerAction2(class GoToTypeDefinitionsAction extends Action2 { - override async run( - accessor: ServicesAccessor, - location: Location, - ): Promise { - return runGoToCommand( - accessor, - "editor.action.goToTypeDefinition", - location, - ); - } - }, -); - -registerAction2( - class GoToImplementations extends Action2 { - static readonly id = "chat.inlineSymbolAnchor.goToImplementations"; - - constructor() { - super({ - id: GoToImplementations.id, - title: { - ...nls.localize2( - "goToImplementations.label", - "Go to Implementations", - ), - mnemonicTitle: nls.localize( - { - key: "miGotoImplementations", - comment: ["&& denotes a mnemonic"], - }, - "Go to &&Implementations", - ), - }, - menu: [ - { - id: MenuId.ChatInlineSymbolAnchorContext, - group: "4_symbol_nav", - order: 1.2, - when: EditorContextKeys.hasImplementationProvider, - }, - ], - }); - } + static readonly id = 'chat.inlineSymbolAnchor.goToTypeDefinitions'; - override async run( - accessor: ServicesAccessor, - location: Location, - ): Promise { - return runGoToCommand( - accessor, - "editor.action.goToImplementation", - location, - ); - } - }, -); - -registerAction2( - class GoToReferencesAction extends Action2 { - static readonly id = "chat.inlineSymbolAnchor.goToReferences"; - - constructor() { - super({ - id: GoToReferencesAction.id, - title: { - ...nls.localize2( - "goToReferences.label", - "Go to References", - ), - mnemonicTitle: nls.localize( - { - key: "miGotoReference", - comment: ["&& denotes a mnemonic"], - }, - "Go to &&References", - ), - }, - menu: [ - { - id: MenuId.ChatInlineSymbolAnchorContext, - group: "4_symbol_nav", - order: 1.3, - when: EditorContextKeys.hasReferenceProvider, - }, - ], - }); - } + constructor() { + super({ + id: GoToTypeDefinitionsAction.id, + title: { + ...nls.localize2('goToTypeDefinitions.label', "Go to Type Definitions"), + mnemonicTitle: nls.localize({ key: 'miGotoTypeDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Type Definitions"), + }, + menu: [{ + id: MenuId.ChatInlineSymbolAnchorContext, + group: '4_symbol_nav', + order: 1.1, + when: EditorContextKeys.hasTypeDefinitionProvider, + },] + }); + } - override async run( - accessor: ServicesAccessor, - location: Location, - ): Promise { - return runGoToCommand( - accessor, - "editor.action.goToReferences", - location, - ); - } - }, -); + override async run(accessor: ServicesAccessor, location: Location): Promise { + return runGoToCommand(accessor, 'editor.action.goToTypeDefinition', location); + } +}); + +registerAction2(class GoToImplementations extends Action2 { + + static readonly id = 'chat.inlineSymbolAnchor.goToImplementations'; + + constructor() { + super({ + id: GoToImplementations.id, + title: { + ...nls.localize2('goToImplementations.label', "Go to Implementations"), + mnemonicTitle: nls.localize({ key: 'miGotoImplementations', comment: ['&& denotes a mnemonic'] }, "Go to &&Implementations"), + }, + menu: [{ + id: MenuId.ChatInlineSymbolAnchorContext, + group: '4_symbol_nav', + order: 1.2, + when: EditorContextKeys.hasImplementationProvider, + },] + }); + } + + override async run(accessor: ServicesAccessor, location: Location): Promise { + return runGoToCommand(accessor, 'editor.action.goToImplementation', location); + } +}); + +registerAction2(class GoToReferencesAction extends Action2 { + + static readonly id = 'chat.inlineSymbolAnchor.goToReferences'; + + constructor() { + super({ + id: GoToReferencesAction.id, + title: { + ...nls.localize2('goToReferences.label', "Go to References"), + mnemonicTitle: nls.localize({ key: 'miGotoReference', comment: ['&& denotes a mnemonic'] }, "Go to &&References"), + }, + menu: [{ + id: MenuId.ChatInlineSymbolAnchorContext, + group: '4_symbol_nav', + order: 1.3, + when: EditorContextKeys.hasReferenceProvider, + },] + }); + } + + override async run(accessor: ServicesAccessor, location: Location): Promise { + return runGoToCommand(accessor, 'editor.action.goToReferences', location); + } +}); //#endregion diff --git a/Source/vs/workbench/contrib/chat/browser/chatInputPart.ts b/Source/vs/workbench/contrib/chat/browser/chatInputPart.ts index c598ba6e5af7e..f438650c42f08 100644 --- a/Source/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/Source/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -3,170 +3,98 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as dom from "../../../../base/browser/dom.js"; -import { addDisposableListener } from "../../../../base/browser/dom.js"; -import { DEFAULT_FONT_FAMILY } from "../../../../base/browser/fonts.js"; -import { IHistoryNavigationWidget } from "../../../../base/browser/history.js"; -import { StandardKeyboardEvent } from "../../../../base/browser/keyboardEvent.js"; -import { StandardMouseEvent } from "../../../../base/browser/mouseEvent.js"; -import * as aria from "../../../../base/browser/ui/aria/aria.js"; -import { Button } from "../../../../base/browser/ui/button/button.js"; -import { IHoverDelegate } from "../../../../base/browser/ui/hover/hoverDelegate.js"; -import { getBaseLayerHoverDelegate } from "../../../../base/browser/ui/hover/hoverDelegate2.js"; -import { createInstantHoverDelegate } from "../../../../base/browser/ui/hover/hoverDelegateFactory.js"; -import { HoverPosition } from "../../../../base/browser/ui/hover/hoverWidget.js"; -import { renderLabelWithIcons } from "../../../../base/browser/ui/iconLabel/iconLabels.js"; -import { ProgressBar } from "../../../../base/browser/ui/progressbar/progressbar.js"; -import { IAction } from "../../../../base/common/actions.js"; -import { coalesce } from "../../../../base/common/arrays.js"; -import { Promises } from "../../../../base/common/async.js"; -import { Codicon } from "../../../../base/common/codicons.js"; -import { Emitter, Event } from "../../../../base/common/event.js"; -import { HistoryNavigator2 } from "../../../../base/common/history.js"; -import { KeyCode } from "../../../../base/common/keyCodes.js"; -import { - Disposable, - DisposableStore, - IDisposable, - MutableDisposable, -} from "../../../../base/common/lifecycle.js"; -import { ResourceSet } from "../../../../base/common/map.js"; -import { basename, dirname } from "../../../../base/common/path.js"; -import { isMacintosh } from "../../../../base/common/platform.js"; -import { URI } from "../../../../base/common/uri.js"; -import { IEditorConstructionOptions } from "../../../../editor/browser/config/editorConfiguration.js"; -import { EditorExtensionsRegistry } from "../../../../editor/browser/editorExtensions.js"; -import { CodeEditorWidget } from "../../../../editor/browser/widget/codeEditor/codeEditorWidget.js"; -import { EditorOptions } from "../../../../editor/common/config/editorOptions.js"; -import { IDimension } from "../../../../editor/common/core/dimension.js"; -import { IPosition } from "../../../../editor/common/core/position.js"; -import { IRange, Range } from "../../../../editor/common/core/range.js"; -import { ILanguageService } from "../../../../editor/common/languages/language.js"; -import { ITextModel } from "../../../../editor/common/model.js"; -import { IModelService } from "../../../../editor/common/services/model.js"; -import { CopyPasteController } from "../../../../editor/contrib/dropOrPasteInto/browser/copyPasteController.js"; -import { ContentHoverController } from "../../../../editor/contrib/hover/browser/contentHoverController.js"; -import { GlyphHoverController } from "../../../../editor/contrib/hover/browser/glyphHoverController.js"; -import { SuggestController } from "../../../../editor/contrib/suggest/browser/suggestController.js"; -import { localize } from "../../../../nls.js"; -import { IAccessibilityService } from "../../../../platform/accessibility/common/accessibility.js"; -import { MenuWorkbenchButtonBar } from "../../../../platform/actions/browser/buttonbar.js"; -import { - DropdownWithPrimaryActionViewItem, - IDropdownWithPrimaryActionViewItemOptions, -} from "../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js"; -import { - getFlatActionBarActions, - getFlatContextMenuActions, - IMenuEntryActionViewItemOptions, - MenuEntryActionViewItem, -} from "../../../../platform/actions/browser/menuEntryActionViewItem.js"; -import { - HiddenItemStrategy, - MenuWorkbenchToolBar, -} from "../../../../platform/actions/browser/toolbar.js"; -import { - IMenuService, - MenuId, - MenuItemAction, -} from "../../../../platform/actions/common/actions.js"; -import { ICommandService } from "../../../../platform/commands/common/commands.js"; -import { IConfigurationService } from "../../../../platform/configuration/common/configuration.js"; -import { - IContextKey, - IContextKeyService, -} from "../../../../platform/contextkey/common/contextkey.js"; -import { IContextMenuService } from "../../../../platform/contextview/browser/contextView.js"; -import { ITextEditorOptions } from "../../../../platform/editor/common/editor.js"; -import { - FileKind, - IFileService, -} from "../../../../platform/files/common/files.js"; -import { registerAndCreateHistoryNavigationContext } from "../../../../platform/history/browser/contextScopedHistoryWidget.js"; -import { IHoverService } from "../../../../platform/hover/browser/hover.js"; -import { IInstantiationService } from "../../../../platform/instantiation/common/instantiation.js"; -import { ServiceCollection } from "../../../../platform/instantiation/common/serviceCollection.js"; -import { IKeybindingService } from "../../../../platform/keybinding/common/keybinding.js"; -import { WorkbenchList } from "../../../../platform/list/browser/listService.js"; -import { ILogService } from "../../../../platform/log/common/log.js"; -import { INotificationService } from "../../../../platform/notification/common/notification.js"; -import { - IOpenerService, - type OpenInternalOptions, -} from "../../../../platform/opener/common/opener.js"; -import { - IStorageService, - StorageScope, - StorageTarget, -} from "../../../../platform/storage/common/storage.js"; -import { - FolderThemeIcon, - IThemeService, -} from "../../../../platform/theme/common/themeService.js"; -import { fillEditorsDragData } from "../../../browser/dnd.js"; -import { IFileLabelOptions, ResourceLabels } from "../../../browser/labels.js"; -import { ResourceContextKey } from "../../../common/contextkeys.js"; -import { - ACTIVE_GROUP, - IEditorService, - SIDE_GROUP, -} from "../../../services/editor/common/editorService.js"; -import { AccessibilityVerbositySettingId } from "../../accessibility/browser/accessibilityConfiguration.js"; -import { AccessibilityCommandId } from "../../accessibility/common/accessibilityCommands.js"; -import { - getSimpleCodeEditorWidgetOptions, - getSimpleEditorOptions, - setupSimpleEditorSelectionStyling, -} from "../../codeEditor/browser/simpleEditorOptions.js"; -import { revealInSideBarCommand } from "../../files/browser/fileActions.contribution.js"; -import { ChatAgentLocation, IChatAgentService } from "../common/chatAgents.js"; -import { ChatContextKeys } from "../common/chatContextKeys.js"; -import { - ChatEditingSessionState, - IChatEditingService, - IChatEditingSession, - WorkingSetEntryState, -} from "../common/chatEditingService.js"; -import { IChatRequestVariableEntry } from "../common/chatModel.js"; -import { ChatRequestDynamicVariablePart } from "../common/chatParserTypes.js"; -import { IChatFollowup } from "../common/chatService.js"; -import { IChatResponseViewModel } from "../common/chatViewModel.js"; -import { - IChatHistoryEntry, - IChatInputState, - IChatWidgetHistoryService, -} from "../common/chatWidgetHistoryService.js"; -import { - ILanguageModelChatMetadata, - ILanguageModelsService, -} from "../common/languageModels.js"; -import { - CancelAction, - ChatModelPickerActionId, - ChatSubmitAction, - ChatSubmitSecondaryAgentAction, - IChatExecuteActionContext, -} from "./actions/chatExecuteActions.js"; -import { ImplicitContextAttachmentWidget } from "./attachments/implicitContextAttachment.js"; -import { IChatWidget } from "./chat.js"; -import { - ChatAttachmentModel, - EditsAttachmentModel, -} from "./chatAttachmentModel.js"; -import { IDisposableReference } from "./chatContentParts/chatCollections.js"; -import { - CollapsibleListPool, - IChatCollapsibleListItem, -} from "./chatContentParts/chatReferencesContentPart.js"; -import { ChatDragAndDrop, EditsDragAndDrop } from "./chatDragAndDrop.js"; -import { - ChatEditingRemoveAllFilesAction, - ChatEditingShowChangesAction, -} from "./chatEditing/chatEditingActions.js"; -import { ChatEditingSaveAllAction } from "./chatEditorSaving.js"; -import { ChatFollowups } from "./chatFollowups.js"; -import { IChatViewState } from "./chatWidget.js"; -import { ChatImplicitContext } from "./contrib/chatImplicitContext.js"; +import * as dom from '../../../../base/browser/dom.js'; +import { addDisposableListener } from '../../../../base/browser/dom.js'; +import { DEFAULT_FONT_FAMILY } from '../../../../base/browser/fonts.js'; +import { IHistoryNavigationWidget } from '../../../../base/browser/history.js'; +import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; +import * as aria from '../../../../base/browser/ui/aria/aria.js'; +import { Button } from '../../../../base/browser/ui/button/button.js'; +import { IManagedHoverTooltipMarkdownString } from '../../../../base/browser/ui/hover/hover.js'; +import { IHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegate.js'; +import { getBaseLayerHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegate2.js'; +import { createInstantHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; +import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js'; +import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; +import { ProgressBar } from '../../../../base/browser/ui/progressbar/progressbar.js'; +import { IAction } from '../../../../base/common/actions.js'; +import { coalesce } from '../../../../base/common/arrays.js'; +import { Promises } from '../../../../base/common/async.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; +import { HistoryNavigator2 } from '../../../../base/common/history.js'; +import { KeyCode } from '../../../../base/common/keyCodes.js'; +import { Disposable, DisposableStore, IDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; +import { ResourceSet } from '../../../../base/common/map.js'; +import { basename, dirname } from '../../../../base/common/path.js'; +import { isMacintosh } from '../../../../base/common/platform.js'; +import { URI } from '../../../../base/common/uri.js'; +import { IEditorConstructionOptions } from '../../../../editor/browser/config/editorConfiguration.js'; +import { EditorExtensionsRegistry } from '../../../../editor/browser/editorExtensions.js'; +import { CodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/codeEditorWidget.js'; +import { EditorOptions } from '../../../../editor/common/config/editorOptions.js'; +import { IDimension } from '../../../../editor/common/core/dimension.js'; +import { IPosition } from '../../../../editor/common/core/position.js'; +import { IRange, Range } from '../../../../editor/common/core/range.js'; +import { ITextModel } from '../../../../editor/common/model.js'; +import { IModelService } from '../../../../editor/common/services/model.js'; +import { ITextModelService } from '../../../../editor/common/services/resolverService.js'; +import { CopyPasteController } from '../../../../editor/contrib/dropOrPasteInto/browser/copyPasteController.js'; +import { ContentHoverController } from '../../../../editor/contrib/hover/browser/contentHoverController.js'; +import { GlyphHoverController } from '../../../../editor/contrib/hover/browser/glyphHoverController.js'; +import { SuggestController } from '../../../../editor/contrib/suggest/browser/suggestController.js'; +import { localize } from '../../../../nls.js'; +import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; +import { MenuWorkbenchButtonBar } from '../../../../platform/actions/browser/buttonbar.js'; +import { DropdownWithPrimaryActionViewItem, IDropdownWithPrimaryActionViewItemOptions } from '../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js'; +import { getFlatActionBarActions, IMenuEntryActionViewItemOptions, MenuEntryActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; +import { IMenuService, MenuId, MenuItemAction } from '../../../../platform/actions/common/actions.js'; +import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; +import { ITextEditorOptions } from '../../../../platform/editor/common/editor.js'; +import { FileKind, IFileService } from '../../../../platform/files/common/files.js'; +import { registerAndCreateHistoryNavigationContext } from '../../../../platform/history/browser/contextScopedHistoryWidget.js'; +import { IHoverService } from '../../../../platform/hover/browser/hover.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; +import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { WorkbenchList } from '../../../../platform/list/browser/listService.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; +import { INotificationService } from '../../../../platform/notification/common/notification.js'; +import { IOpenerService, type OpenInternalOptions } from '../../../../platform/opener/common/opener.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; +import { FolderThemeIcon, IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { IFileLabelOptions, ResourceLabels } from '../../../browser/labels.js'; +import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from '../../../services/editor/common/editorService.js'; +import { AccessibilityVerbositySettingId } from '../../accessibility/browser/accessibilityConfiguration.js'; +import { AccessibilityCommandId } from '../../accessibility/common/accessibilityCommands.js'; +import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions, setupSimpleEditorSelectionStyling } from '../../codeEditor/browser/simpleEditorOptions.js'; +import { revealInSideBarCommand } from '../../files/browser/fileActions.contribution.js'; +import { ChatAgentLocation, IChatAgentService } from '../common/chatAgents.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; +import { ChatEditingSessionState, IChatEditingService, IChatEditingSession, WorkingSetEntryState } from '../common/chatEditingService.js'; +import { IChatRequestVariableEntry, isPasteVariableEntry } from '../common/chatModel.js'; +import { ChatRequestDynamicVariablePart } from '../common/chatParserTypes.js'; +import { IChatFollowup } from '../common/chatService.js'; +import { IChatResponseViewModel } from '../common/chatViewModel.js'; +import { IChatHistoryEntry, IChatInputState, IChatWidgetHistoryService } from '../common/chatWidgetHistoryService.js'; +import { ILanguageModelChatMetadata, ILanguageModelsService } from '../common/languageModels.js'; +import { CancelAction, ChatModelPickerActionId, ChatSubmitAction, ChatSubmitSecondaryAgentAction, IChatExecuteActionContext } from './actions/chatExecuteActions.js'; +import { ImplicitContextAttachmentWidget } from './attachments/implicitContextAttachment.js'; +import { IChatWidget } from './chat.js'; +import { ChatAttachmentModel, EditsAttachmentModel } from './chatAttachmentModel.js'; +import { hookUpResourceAttachmentInteractions } from './chatContentParts/chatAttachmentsContentPart.js'; +import { IDisposableReference } from './chatContentParts/chatCollections.js'; +import { CollapsibleListPool, IChatCollapsibleListItem } from './chatContentParts/chatReferencesContentPart.js'; +import { ChatDragAndDrop, EditsDragAndDrop } from './chatDragAndDrop.js'; +import { ChatEditingRemoveAllFilesAction, ChatEditingShowChangesAction } from './chatEditing/chatEditingActions.js'; +import { ChatEditingSaveAllAction } from './chatEditorSaving.js'; +import { ChatFollowups } from './chatFollowups.js'; +import { IChatViewState } from './chatWidget.js'; +import { ChatImplicitContext } from './contrib/chatImplicitContext.js'; const $ = dom.$; @@ -180,7 +108,7 @@ export interface IChatInputStyles { interface IChatInputPartOptions { renderFollowups: boolean; - renderStyle?: "compact"; + renderStyle?: 'compact'; menus: { executeToolbar: MenuId; inputSideToolbar?: MenuId; @@ -190,11 +118,8 @@ interface IChatInputPartOptions { enableImplicitContext?: boolean; } -export class ChatInputPart - extends Disposable - implements IHistoryNavigationWidget -{ - static readonly INPUT_SCHEME = "chatSessionInput"; +export class ChatInputPart extends Disposable implements IHistoryNavigationWidget { + static readonly INPUT_SCHEME = 'chatSessionInput'; private static _counter = 0; private _onDidLoadInputState = this._register(new Emitter()); @@ -209,20 +134,10 @@ export class ChatInputPart private _onDidBlur = this._register(new Emitter()); readonly onDidBlur = this._onDidBlur.event; - private _onDidChangeContext = this._register( - new Emitter<{ - removed?: IChatRequestVariableEntry[]; - added?: IChatRequestVariableEntry[]; - }>(), - ); + private _onDidChangeContext = this._register(new Emitter<{ removed?: IChatRequestVariableEntry[]; added?: IChatRequestVariableEntry[] }>()); readonly onDidChangeContext = this._onDidChangeContext.event; - private _onDidAcceptFollowup = this._register( - new Emitter<{ - followup: IChatFollowup; - response: IChatResponseViewModel | undefined; - }>(), - ); + private _onDidAcceptFollowup = this._register(new Emitter<{ followup: IChatFollowup; response: IChatResponseViewModel | undefined }>()); readonly onDidAcceptFollowup = this._onDidAcceptFollowup.event; private readonly _attachmentModel: ChatAttachmentModel; @@ -248,13 +163,8 @@ export class ChatInputPart private _hasFileAttachmentContextKey: IContextKey; - private readonly _onDidChangeVisibility = this._register( - new Emitter(), - ); - private readonly _contextResourceLabels = - this.instantiationService.createInstance(ResourceLabels, { - onDidChangeVisibility: this._onDidChangeVisibility.event, - }); + private readonly _onDidChangeVisibility = this._register(new Emitter()); + private readonly _contextResourceLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this._onDidChangeVisibility.event }); private readonly inputEditorMaxHeight: number; private inputEditorHeight = 0; @@ -263,14 +173,10 @@ export class ChatInputPart private inputSideToolbarContainer?: HTMLElement; private followupsContainer!: HTMLElement; - private readonly followupsDisposables = this._register( - new DisposableStore(), - ); + private readonly followupsDisposables = this._register(new DisposableStore()); private attachedContextContainer!: HTMLElement; - private readonly attachedContextDisposables = this._register( - new MutableDisposable(), - ); + private readonly attachedContextDisposables = this._register(new MutableDisposable()); private chatEditingSessionWidgetContainer!: HTMLElement; @@ -304,12 +210,8 @@ export class ChatInputPart private chatCursorAtTop: IContextKey; private inputEditorHasFocus: IContextKey; - private readonly _waitForPersistedLanguageModel = this._register( - new MutableDisposable(), - ); - private _onDidChangeCurrentLanguageModel = this._register( - new Emitter(), - ); + private readonly _waitForPersistedLanguageModel = this._register(new MutableDisposable()); + private _onDidChangeCurrentLanguageModel = this._register(new Emitter()); private _currentLanguageModel: string | undefined; get currentLanguageModel() { return this._currentLanguageModel; @@ -319,30 +221,20 @@ export class ChatInputPart private cachedExecuteToolbarWidth: number | undefined; private cachedInputToolbarWidth: number | undefined; - readonly inputUri = URI.parse( - `${ChatInputPart.INPUT_SCHEME}:input-${ChatInputPart._counter++}`, - ); - - private readonly _chatEditsActionsDisposables = this._register( - new DisposableStore(), - ); - private readonly _chatEditsDisposables = this._register( - new DisposableStore(), - ); - private readonly _chatEditsFileLimitHover = this._register( - new MutableDisposable(), - ); + readonly inputUri = URI.parse(`${ChatInputPart.INPUT_SCHEME}:input-${ChatInputPart._counter++}`); + + private readonly _chatEditsActionsDisposables = this._register(new DisposableStore()); + private readonly _chatEditsDisposables = this._register(new DisposableStore()); + private readonly _chatEditsFileLimitHover = this._register(new MutableDisposable()); private _chatEditsProgress: ProgressBar | undefined; private _chatEditsListPool: CollapsibleListPool; - private _chatEditList: - | IDisposableReference> - | undefined; + private _chatEditList: IDisposableReference> | undefined; get selectedElements(): URI[] { const edits = []; const editsList = this._chatEditList?.object; const selectedElements = editsList?.getSelectedElements() ?? []; for (const element of selectedElements) { - if (element.kind === "reference" && URI.isUri(element.reference)) { + if (element.kind === 'reference' && URI.isUri(element.reference)) { edits.push(element.reference); } } @@ -370,60 +262,33 @@ export class ChatInputPart private readonly options: IChatInputPartOptions, styles: IChatInputStyles, getContribsInputState: () => any, - @IChatWidgetHistoryService - private readonly historyService: IChatWidgetHistoryService, + @IChatWidgetHistoryService private readonly historyService: IChatWidgetHistoryService, @IModelService private readonly modelService: IModelService, - @IInstantiationService - private readonly instantiationService: IInstantiationService, - @IContextKeyService - private readonly contextKeyService: IContextKeyService, - @IContextMenuService - private readonly contextMenuService: IContextMenuService, - @IConfigurationService - private readonly configurationService: IConfigurationService, - @IKeybindingService - private readonly keybindingService: IKeybindingService, - @IAccessibilityService - private readonly accessibilityService: IAccessibilityService, - @ILanguageModelsService - private readonly languageModelsService: ILanguageModelsService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IKeybindingService private readonly keybindingService: IKeybindingService, + @IAccessibilityService private readonly accessibilityService: IAccessibilityService, + @ILanguageModelsService private readonly languageModelsService: ILanguageModelsService, @ILogService private readonly logService: ILogService, @IHoverService private readonly hoverService: IHoverService, @IFileService private readonly fileService: IFileService, @ICommandService private readonly commandService: ICommandService, @IEditorService private readonly editorService: IEditorService, @IOpenerService private readonly openerService: IOpenerService, - @IChatEditingService - private readonly chatEditingService: IChatEditingService, - @IMenuService private readonly menuService: IMenuService, - @ILanguageService private readonly languageService: ILanguageService, + @IChatEditingService private readonly chatEditingService: IChatEditingService, @IThemeService private readonly themeService: IThemeService, + @ITextModelService private readonly textModelResolverService: ITextModelService, @IStorageService private readonly storageService: IStorageService, ) { super(); if (this.location === ChatAgentLocation.EditingSession) { - this._attachmentModel = this._register( - this.instantiationService.createInstance(EditsAttachmentModel), - ); - this.dnd = this._register( - this.instantiationService.createInstance( - EditsDragAndDrop, - this.attachmentModel, - styles, - ), - ); + this._attachmentModel = this._register(this.instantiationService.createInstance(EditsAttachmentModel)); + this.dnd = this._register(this.instantiationService.createInstance(EditsDragAndDrop, this.attachmentModel, styles)); } else { - this._attachmentModel = this._register( - this.instantiationService.createInstance(ChatAttachmentModel), - ); - this.dnd = this._register( - this.instantiationService.createInstance( - ChatDragAndDrop, - this.attachmentModel, - styles, - ), - ); + this._attachmentModel = this._register(this.instantiationService.createInstance(ChatAttachmentModel)); + this.dnd = this._register(this.instantiationService.createInstance(ChatDragAndDrop, this.attachmentModel, styles)); } this.getInputState = (): IChatInputState => { @@ -432,52 +297,24 @@ export class ChatInputPart chatContextAttachments: this._attachmentModel.attachments, }; }; - this.inputEditorMaxHeight = - this.options.renderStyle === "compact" - ? INPUT_EDITOR_MAX_HEIGHT / 3 - : INPUT_EDITOR_MAX_HEIGHT; - - this.inputEditorHasText = - ChatContextKeys.inputHasText.bindTo(contextKeyService); - this.chatCursorAtTop = - ChatContextKeys.inputCursorAtTop.bindTo(contextKeyService); - this.inputEditorHasFocus = - ChatContextKeys.inputHasFocus.bindTo(contextKeyService); + this.inputEditorMaxHeight = this.options.renderStyle === 'compact' ? INPUT_EDITOR_MAX_HEIGHT / 3 : INPUT_EDITOR_MAX_HEIGHT; + + this.inputEditorHasText = ChatContextKeys.inputHasText.bindTo(contextKeyService); + this.chatCursorAtTop = ChatContextKeys.inputCursorAtTop.bindTo(contextKeyService); + this.inputEditorHasFocus = ChatContextKeys.inputHasFocus.bindTo(contextKeyService); this.history = this.loadHistory(); - this._register( - this.historyService.onDidClearHistory( - () => - (this.history = new HistoryNavigator2( - [{ text: "" }], - 50, - historyKeyFn, - )), - ), - ); - - this._register( - this.configurationService.onDidChangeConfiguration((e) => { - if ( - e.affectsConfiguration(AccessibilityVerbositySettingId.Chat) - ) { - this.inputEditor.updateOptions({ - ariaLabel: this._getAriaLabel(), - }); - } - }), - ); + this._register(this.historyService.onDidClearHistory(() => this.history = new HistoryNavigator2([{ text: '' }], 50, historyKeyFn))); - this._chatEditsListPool = this._register( - this.instantiationService.createInstance( - CollapsibleListPool, - this._onDidChangeVisibility.event, - MenuId.ChatEditingWidgetModifiedFilesToolbar, - ), - ); + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(AccessibilityVerbositySettingId.Chat)) { + this.inputEditor.updateOptions({ ariaLabel: this._getAriaLabel() }); + } + })); - this._hasFileAttachmentContextKey = - ChatContextKeys.hasFileAttachments.bindTo(contextKeyService); + this._chatEditsListPool = this._register(this.instantiationService.createInstance(CollapsibleListPool, this._onDidChangeVisibility.event, MenuId.ChatEditingWidgetModifiedFilesToolbar)); + + this._hasFileAttachmentContextKey = ChatContextKeys.hasFileAttachments.bindTo(contextKeyService); this.initSelectedModel(); } @@ -487,62 +324,35 @@ export class ChatInputPart } private initSelectedModel() { - const persistedSelection = this.storageService.get( - this.getSelectedModelStorageKey(), - StorageScope.APPLICATION, - ); + const persistedSelection = this.storageService.get(this.getSelectedModelStorageKey(), StorageScope.APPLICATION); if (persistedSelection) { - const model = - this.languageModelsService.lookupLanguageModel( - persistedSelection, - ); + const model = this.languageModelsService.lookupLanguageModel(persistedSelection); if (model) { this._currentLanguageModel = persistedSelection; - this._onDidChangeCurrentLanguageModel.fire( - this._currentLanguageModel, - ); + this._onDidChangeCurrentLanguageModel.fire(this._currentLanguageModel); } else { - this._waitForPersistedLanguageModel.value = - this.languageModelsService.onDidChangeLanguageModels( - (e) => { - const persistedModel = e.added?.find( - (m) => m.identifier === persistedSelection, - ); - if (persistedModel) { - this._waitForPersistedLanguageModel.clear(); - - if (persistedModel.metadata.isUserSelectable) { - this._currentLanguageModel = - persistedSelection; - this._onDidChangeCurrentLanguageModel.fire( - this._currentLanguageModel!, - ); - } - } - }, - ); + this._waitForPersistedLanguageModel.value = this.languageModelsService.onDidChangeLanguageModels(e => { + const persistedModel = e.added?.find(m => m.identifier === persistedSelection); + if (persistedModel) { + this._waitForPersistedLanguageModel.clear(); + + if (persistedModel.metadata.isUserSelectable) { + this._currentLanguageModel = persistedSelection; + this._onDidChangeCurrentLanguageModel.fire(this._currentLanguageModel!); + } + } + }); } } } private setCurrentLanguageModelToDefault() { - const defaultLanguageModel = this.languageModelsService - .getLanguageModelIds() - .find( - (id) => - this.languageModelsService.lookupLanguageModel(id) - ?.isDefault, - ); - const hasUserSelectableLanguageModels = this.languageModelsService - .getLanguageModelIds() - .find((id) => { - const model = - this.languageModelsService.lookupLanguageModel(id); - return model?.isUserSelectable && !model.isDefault; - }); - this._currentLanguageModel = hasUserSelectableLanguageModels - ? defaultLanguageModel - : undefined; + const defaultLanguageModel = this.languageModelsService.getLanguageModelIds().find(id => this.languageModelsService.lookupLanguageModel(id)?.isDefault); + const hasUserSelectableLanguageModels = this.languageModelsService.getLanguageModelIds().find(id => { + const model = this.languageModelsService.lookupLanguageModel(id); + return model?.isUserSelectable && !model.isDefault; + }); + this._currentLanguageModel = hasUserSelectableLanguageModels ? defaultLanguageModel : undefined; } private setCurrentLanguageModelByUser(modelId: string) { @@ -551,56 +361,35 @@ export class ChatInputPart // The user changed the language model, so we don't wait for the persisted option to be registered this._waitForPersistedLanguageModel.clear(); if (this.cachedDimensions) { - this.layout( - this.cachedDimensions.height, - this.cachedDimensions.width, - ); + this.layout(this.cachedDimensions.height, this.cachedDimensions.width); } - this.storageService.store( - this.getSelectedModelStorageKey(), - modelId, - StorageScope.APPLICATION, - StorageTarget.USER, - ); + this.storageService.store(this.getSelectedModelStorageKey(), modelId, StorageScope.APPLICATION, StorageTarget.USER); } private loadHistory(): HistoryNavigator2 { const history = this.historyService.getHistory(this.location); if (history.length === 0) { - history.push({ text: "" }); + history.push({ text: '' }); } return new HistoryNavigator2(history, 50, historyKeyFn); } private _getAriaLabel(): string { - const verbose = this.configurationService.getValue( - AccessibilityVerbositySettingId.Chat, - ); + const verbose = this.configurationService.getValue(AccessibilityVerbositySettingId.Chat); if (verbose) { - const kbLabel = this.keybindingService - .lookupKeybinding(AccessibilityCommandId.OpenAccessibilityHelp) - ?.getLabel(); - return kbLabel - ? localize( - "actions.chat.accessibiltyHelp", - "Chat Input, Type to ask questions or type / for topics, press enter to send out the request. Use {0} for Chat Accessibility Help.", - kbLabel, - ) - : localize( - "chatInput.accessibilityHelpNoKb", - "Chat Input, Type code here and press Enter to run. Use the Chat Accessibility Help command for more information.", - ); + const kbLabel = this.keybindingService.lookupKeybinding(AccessibilityCommandId.OpenAccessibilityHelp)?.getLabel(); + return kbLabel ? localize('actions.chat.accessibiltyHelp', "Chat Input, Type to ask questions or type / for topics, press enter to send out the request. Use {0} for Chat Accessibility Help.", kbLabel) : localize('chatInput.accessibilityHelpNoKb', "Chat Input, Type code here and press Enter to run. Use the Chat Accessibility Help command for more information."); } - return localize("chatInput", "Chat Input"); + return localize('chatInput', "Chat Input"); } initForNewChatModel(state: IChatViewState): void { this.history = this.loadHistory(); this.history.add({ text: state.inputValue ?? this.history.current().text, - state: state.inputState ?? this.getInputState(), + state: state.inputState ?? this.getInputState() }); const attachments = state.inputState?.chatContextAttachments ?? []; this._attachmentModel.clearAndSetContext(...attachments); @@ -611,13 +400,8 @@ export class ChatInputPart } logInputHistory(): void { - const historyStr = [...this.history] - .map((entry) => JSON.stringify(entry)) - .join("\n"); - this.logService.info( - `[${this.location}] Chat input history:`, - historyStr, - ); + const historyStr = [...this.history].map(entry => JSON.stringify(entry)).join('\n'); + this.logService.info(`[${this.location}] Chat input history:`, historyStr); } setVisible(visible: boolean): void { @@ -633,12 +417,7 @@ export class ChatInputPart if (this.history.isAtEnd()) { this.saveCurrentValue(inputState); } else { - if ( - !this.history.has({ - text: this._inputEditor.getValue(), - state: inputState, - }) - ) { + if (!this.history.has({ text: this._inputEditor.getValue(), state: inputState })) { this.saveCurrentValue(inputState); this.history.resetCursor(); } @@ -652,12 +431,7 @@ export class ChatInputPart if (this.history.isAtEnd()) { return; } else { - if ( - !this.history.has({ - text: this._inputEditor.getValue(), - state: inputState, - }) - ) { + if (!this.history.has({ text: this._inputEditor.getValue(), state: inputState })) { this.saveCurrentValue(inputState); this.history.resetCursor(); } @@ -667,12 +441,10 @@ export class ChatInputPart } private navigateHistory(previous: boolean): void { - const historyEntry = previous - ? this.history.previous() - : this.history.next(); + const historyEntry = previous ? + this.history.previous() : this.history.next(); - const historyAttachments = - historyEntry.state?.chatContextAttachments ?? []; + const historyAttachments = historyEntry.state?.chatContextAttachments ?? []; this._attachmentModel.clearAndSetContext(...historyAttachments); aria.status(historyEntry.text); @@ -686,22 +458,15 @@ export class ChatInputPart } if (previous) { - const endOfFirstViewLine = - this._inputEditor._getViewModel()?.getLineLength(1) ?? 1; + const endOfFirstViewLine = this._inputEditor._getViewModel()?.getLineLength(1) ?? 1; const endOfFirstModelLine = model.getLineLength(1); if (endOfFirstViewLine === endOfFirstModelLine) { // Not wrapped - set cursor to the end of the first line - this._inputEditor.setPosition({ - lineNumber: 1, - column: endOfFirstViewLine + 1, - }); + this._inputEditor.setPosition({ lineNumber: 1, column: endOfFirstViewLine + 1 }); } else { // Wrapped - set cursor one char short of the end of the first view line. // If it's after the next character, the cursor shows on the second line. - this._inputEditor.setPosition({ - lineNumber: 1, - column: endOfFirstViewLine, - }); + this._inputEditor.setPosition({ lineNumber: 1, column: endOfFirstViewLine }); } } else { this._inputEditor.setPosition(getLastPosition(model)); @@ -711,10 +476,7 @@ export class ChatInputPart setValue(value: string, transient: boolean): void { this.inputEditor.setValue(value); // always leave cursor at the end - this.inputEditor.setPosition({ - lineNumber: 1, - column: value.length + 1, - }); + this.inputEditor.setPosition({ lineNumber: 1, column: value.length + 1 }); if (!transient) { this.saveCurrentValue(this.getInputState()); @@ -722,10 +484,7 @@ export class ChatInputPart } private saveCurrentValue(inputState: any): void { - const newEntry = { - text: this._inputEditor.getValue(), - state: inputState, - }; + const newEntry = { text: this._inputEditor.getValue(), state: inputState }; this.history.replaceLast(newEntry); } @@ -744,25 +503,19 @@ export class ChatInputPart async acceptInput(isUserQuery?: boolean): Promise { if (isUserQuery) { const userQuery = this._inputEditor.getValue(); - const entry: IChatHistoryEntry = { - text: userQuery, - state: this.getInputState(), - }; + const entry: IChatHistoryEntry = { text: userQuery, state: this.getInputState() }; this.history.replaceLast(entry); - this.history.add({ text: "" }); + this.history.add({ text: '' }); } // Clear attached context, fire event to clear input state, and clear the input editor this.attachmentModel.clear(); this._onDidLoadInputState.fire({}); - if ( - this.accessibilityService.isScreenReaderOptimized() && - isMacintosh - ) { + if (this.accessibilityService.isScreenReaderOptimized() && isMacintosh) { this._acceptInputForVoiceover(); } else { this._inputEditor.focus(); - this._inputEditor.setValue(""); + this._inputEditor.setValue(''); } } @@ -774,407 +527,221 @@ export class ChatInputPart // Remove the input editor from the DOM temporarily to prevent VoiceOver // from reading the cleared text (the request) to the user. domNode.remove(); - this._inputEditor.setValue(""); + this._inputEditor.setValue(''); this._inputEditorElement.appendChild(domNode); this._inputEditor.focus(); } private _handleAttachedContextChange() { - this._hasFileAttachmentContextKey.set( - Boolean(this._attachmentModel.attachments.find((a) => a.isFile)), - ); + this._hasFileAttachmentContextKey.set(Boolean(this._attachmentModel.attachments.find(a => a.isFile))); this.renderAttachedContext(); } render(container: HTMLElement, initialValue: string, widget: IChatWidget) { let elements; - if (this.options.renderStyle === "compact") { - elements = dom.h(".interactive-input-part", [ - dom.h(".interactive-input-and-edit-session", [ - dom.h( - ".chat-editing-session@chatEditingSessionWidgetContainer", - ), - dom.h( - ".interactive-input-and-side-toolbar@inputAndSideToolbar", - [ - dom.h(".chat-input-container@inputContainer", [ - dom.h(".chat-editor-container@editorContainer"), - dom.h(".chat-input-toolbars@inputToolbars"), - ]), - ], - ), - dom.h(".chat-attached-context@attachedContextContainer"), - dom.h(".interactive-input-followups@followupsContainer"), - ]), + if (this.options.renderStyle === 'compact') { + elements = dom.h('.interactive-input-part', [ + dom.h('.interactive-input-and-edit-session', [ + dom.h('.chat-editing-session@chatEditingSessionWidgetContainer'), + dom.h('.interactive-input-and-side-toolbar@inputAndSideToolbar', [ + dom.h('.chat-input-container@inputContainer', [ + dom.h('.chat-editor-container@editorContainer'), + dom.h('.chat-input-toolbars@inputToolbars'), + ]), + ]), + dom.h('.chat-attached-context@attachedContextContainer'), + dom.h('.interactive-input-followups@followupsContainer'), + ]) ]); } else { - elements = dom.h(".interactive-input-part", [ - dom.h(".interactive-input-followups@followupsContainer"), - dom.h( - ".chat-editing-session@chatEditingSessionWidgetContainer", - ), - dom.h( - ".interactive-input-and-side-toolbar@inputAndSideToolbar", - [ - dom.h(".chat-input-container@inputContainer", [ - dom.h(".chat-editor-container@editorContainer"), - dom.h( - ".chat-attached-context@attachedContextContainer", - ), - dom.h(".chat-input-toolbars@inputToolbars"), - ]), - ], - ), + elements = dom.h('.interactive-input-part', [ + dom.h('.interactive-input-followups@followupsContainer'), + dom.h('.chat-editing-session@chatEditingSessionWidgetContainer'), + dom.h('.interactive-input-and-side-toolbar@inputAndSideToolbar', [ + dom.h('.chat-input-container@inputContainer', [ + dom.h('.chat-editor-container@editorContainer'), + dom.h('.chat-attached-context@attachedContextContainer'), + dom.h('.chat-input-toolbars@inputToolbars'), + ]), + ]), ]); } this.container = elements.root; container.append(this.container); - this.container.classList.toggle( - "compact", - this.options.renderStyle === "compact", - ); + this.container.classList.toggle('compact', this.options.renderStyle === 'compact'); this.followupsContainer = elements.followupsContainer; const inputAndSideToolbar = elements.inputAndSideToolbar; // The chat input and toolbar to the right const inputContainer = elements.inputContainer; // The chat editor, attachments, and toolbars const editorContainer = elements.editorContainer; this.attachedContextContainer = elements.attachedContextContainer; const toolbarsContainer = elements.inputToolbars; - this.chatEditingSessionWidgetContainer = - elements.chatEditingSessionWidgetContainer; + this.chatEditingSessionWidgetContainer = elements.chatEditingSessionWidgetContainer; this.renderAttachedContext(); if (this.options.enableImplicitContext) { this._implicitContext = this._register(new ChatImplicitContext()); - this._register( - this._implicitContext.onDidChangeValue(() => - this._handleAttachedContextChange(), - ), - ); + this._register(this._implicitContext.onDidChangeValue(() => this._handleAttachedContextChange())); } - this._register( - this._attachmentModel.onDidChangeContext(() => - this._handleAttachedContextChange(), - ), - ); + this._register(this._attachmentModel.onDidChangeContext(() => this._handleAttachedContextChange())); this.renderChatEditingSessionState(null, widget); this.dnd.addOverlay(container, container); - const inputScopedContextKeyService = this._register( - this.contextKeyService.createScoped(inputContainer), - ); - ChatContextKeys.inChatInput - .bindTo(inputScopedContextKeyService) - .set(true); - const scopedInstantiationService = this._register( - this.instantiationService.createChild( - new ServiceCollection([ - IContextKeyService, - inputScopedContextKeyService, - ]), - ), - ); - - const { - historyNavigationBackwardsEnablement, - historyNavigationForwardsEnablement, - } = this._register( - registerAndCreateHistoryNavigationContext( - inputScopedContextKeyService, - this, - ), - ); - this.historyNavigationBackwardsEnablement = - historyNavigationBackwardsEnablement; - this.historyNavigationForewardsEnablement = - historyNavigationForwardsEnablement; - - const options: IEditorConstructionOptions = getSimpleEditorOptions( - this.configurationService, - ); - options.overflowWidgetsDomNode = - this.options.editorOverflowWidgetsDomNode; + const inputScopedContextKeyService = this._register(this.contextKeyService.createScoped(inputContainer)); + ChatContextKeys.inChatInput.bindTo(inputScopedContextKeyService).set(true); + const scopedInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, inputScopedContextKeyService]))); + + const { historyNavigationBackwardsEnablement, historyNavigationForwardsEnablement } = this._register(registerAndCreateHistoryNavigationContext(inputScopedContextKeyService, this)); + this.historyNavigationBackwardsEnablement = historyNavigationBackwardsEnablement; + this.historyNavigationForewardsEnablement = historyNavigationForwardsEnablement; + + const options: IEditorConstructionOptions = getSimpleEditorOptions(this.configurationService); + options.overflowWidgetsDomNode = this.options.editorOverflowWidgetsDomNode; options.pasteAs = EditorOptions.pasteAs.defaultValue; options.readOnly = false; options.ariaLabel = this._getAriaLabel(); options.fontFamily = DEFAULT_FONT_FAMILY; options.fontSize = 13; options.lineHeight = 20; - options.padding = - this.options.renderStyle === "compact" - ? { top: 2, bottom: 2 } - : { top: 8, bottom: 8 }; + options.padding = this.options.renderStyle === 'compact' ? { top: 2, bottom: 2 } : { top: 8, bottom: 8 }; options.cursorWidth = 1; - options.wrappingStrategy = "advanced"; + options.wrappingStrategy = 'advanced'; options.bracketPairColorization = { enabled: false }; options.suggest = { showIcons: false, showSnippets: false, showWords: true, showStatusBar: false, - insertMode: "replace", - }; - options.scrollbar = { - ...(options.scrollbar ?? {}), - vertical: "hidden", + insertMode: 'replace', }; + options.scrollbar = { ...(options.scrollbar ?? {}), vertical: 'hidden' }; options.stickyScroll = { enabled: false }; - this._inputEditorElement = dom.append( - editorContainer!, - $(chatInputEditorContainerSelector), - ); + this._inputEditorElement = dom.append(editorContainer!, $(chatInputEditorContainerSelector)); const editorOptions = getSimpleCodeEditorWidgetOptions(); - editorOptions.contributions?.push( - ...EditorExtensionsRegistry.getSomeEditorContributions([ - ContentHoverController.ID, - GlyphHoverController.ID, - CopyPasteController.ID, - ]), - ); - this._inputEditor = this._register( - scopedInstantiationService.createInstance( - CodeEditorWidget, - this._inputEditorElement, - options, - editorOptions, - ), - ); + editorOptions.contributions?.push(...EditorExtensionsRegistry.getSomeEditorContributions([ContentHoverController.ID, GlyphHoverController.ID, CopyPasteController.ID])); + this._inputEditor = this._register(scopedInstantiationService.createInstance(CodeEditorWidget, this._inputEditorElement, options, editorOptions)); SuggestController.get(this._inputEditor)?.forceRenderingAbove(); - this._register( - this._inputEditor.onDidChangeModelContent(() => { - const currentHeight = Math.min( - this._inputEditor.getContentHeight(), - this.inputEditorMaxHeight, - ); - if (currentHeight !== this.inputEditorHeight) { - this.inputEditorHeight = currentHeight; - this._onDidChangeHeight.fire(); - } + this._register(this._inputEditor.onDidChangeModelContent(() => { + const currentHeight = Math.min(this._inputEditor.getContentHeight(), this.inputEditorMaxHeight); + if (currentHeight !== this.inputEditorHeight) { + this.inputEditorHeight = currentHeight; + this._onDidChangeHeight.fire(); + } - const model = this._inputEditor.getModel(); - const inputHasText = - !!model && model.getValue().trim().length > 0; - this.inputEditorHasText.set(inputHasText); - }), - ); - this._register( - this._inputEditor.onDidContentSizeChange((e) => { - if (e.contentHeightChanged) { - this.inputEditorHeight = e.contentHeight; - this._onDidChangeHeight.fire(); - } - }), - ); - this._register( - this._inputEditor.onDidFocusEditorText(() => { - this.inputEditorHasFocus.set(true); - this._onDidFocus.fire(); - inputContainer.classList.toggle("focused", true); - }), - ); - this._register( - this._inputEditor.onDidBlurEditorText(() => { - this.inputEditorHasFocus.set(false); - inputContainer.classList.toggle("focused", false); - - this._onDidBlur.fire(); - }), - ); + const model = this._inputEditor.getModel(); + const inputHasText = !!model && model.getValue().trim().length > 0; + this.inputEditorHasText.set(inputHasText); + })); + this._register(this._inputEditor.onDidContentSizeChange(e => { + if (e.contentHeightChanged) { + this.inputEditorHeight = e.contentHeight; + this._onDidChangeHeight.fire(); + } + })); + this._register(this._inputEditor.onDidFocusEditorText(() => { + this.inputEditorHasFocus.set(true); + this._onDidFocus.fire(); + inputContainer.classList.toggle('focused', true); + })); + this._register(this._inputEditor.onDidBlurEditorText(() => { + this.inputEditorHasFocus.set(false); + inputContainer.classList.toggle('focused', false); + + this._onDidBlur.fire(); + })); const hoverDelegate = this._register(createInstantHoverDelegate()); - this._register( - dom.addStandardDisposableListener( - toolbarsContainer, - dom.EventType.CLICK, - (e) => this.inputEditor.focus(), - ), - ); - this.inputActionsToolbar = this._register( - this.instantiationService.createInstance( - MenuWorkbenchToolBar, - toolbarsContainer, - MenuId.ChatInput, - { - telemetrySource: this.options.menus.telemetrySource, - menuOptions: { shouldForwardArgs: true }, - hiddenItemStrategy: HiddenItemStrategy.Ignore, - hoverDelegate, - }, - ), - ); - this.inputActionsToolbar.context = { - widget, - } satisfies IChatExecuteActionContext; - this._register( - this.inputActionsToolbar.onDidChangeMenuItems(() => { - if ( - this.cachedDimensions && - typeof this.cachedInputToolbarWidth === "number" && - this.cachedInputToolbarWidth !== - this.inputActionsToolbar.getItemsWidth() - ) { - this.layout( - this.cachedDimensions.height, - this.cachedDimensions.width, - ); + this._register(dom.addStandardDisposableListener(toolbarsContainer, dom.EventType.CLICK, e => this.inputEditor.focus())); + this.inputActionsToolbar = this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, toolbarsContainer, MenuId.ChatInput, { + telemetrySource: this.options.menus.telemetrySource, + menuOptions: { shouldForwardArgs: true }, + hiddenItemStrategy: HiddenItemStrategy.Ignore, + hoverDelegate + })); + this.inputActionsToolbar.context = { widget } satisfies IChatExecuteActionContext; + this._register(this.inputActionsToolbar.onDidChangeMenuItems(() => { + if (this.cachedDimensions && typeof this.cachedInputToolbarWidth === 'number' && this.cachedInputToolbarWidth !== this.inputActionsToolbar.getItemsWidth()) { + this.layout(this.cachedDimensions.height, this.cachedDimensions.width); + } + })); + this.executeToolbar = this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, toolbarsContainer, this.options.menus.executeToolbar, { + telemetrySource: this.options.menus.telemetrySource, + menuOptions: { + shouldForwardArgs: true + }, + hoverDelegate, + hiddenItemStrategy: HiddenItemStrategy.Ignore, // keep it lean when hiding items and avoid a "..." overflow menu + actionViewItemProvider: (action, options) => { + if (this.location === ChatAgentLocation.Panel || this.location === ChatAgentLocation.Editor) { + if ((action.id === ChatSubmitAction.ID || action.id === CancelAction.ID) && action instanceof MenuItemAction) { + const dropdownAction = this.instantiationService.createInstance(MenuItemAction, { id: 'chat.moreExecuteActions', title: localize('notebook.moreExecuteActionsLabel', "More..."), icon: Codicon.chevronDown }, undefined, undefined, undefined, undefined); + return this.instantiationService.createInstance(ChatSubmitDropdownActionItem, action, dropdownAction, options); + } } - }), - ); - this.executeToolbar = this._register( - this.instantiationService.createInstance( - MenuWorkbenchToolBar, - toolbarsContainer, - this.options.menus.executeToolbar, - { - telemetrySource: this.options.menus.telemetrySource, - menuOptions: { - shouldForwardArgs: true, - }, - hoverDelegate, - hiddenItemStrategy: HiddenItemStrategy.Ignore, // keep it lean when hiding items and avoid a "..." overflow menu - actionViewItemProvider: (action, options) => { - if ( - this.location === ChatAgentLocation.Panel || - this.location === ChatAgentLocation.Editor - ) { - if ( - (action.id === ChatSubmitAction.ID || - action.id === CancelAction.ID) && - action instanceof MenuItemAction - ) { - const dropdownAction = - this.instantiationService.createInstance( - MenuItemAction, - { - id: "chat.moreExecuteActions", - title: localize( - "notebook.moreExecuteActionsLabel", - "More...", - ), - icon: Codicon.chevronDown, - }, - undefined, - undefined, - undefined, - undefined, - ); - return this.instantiationService.createInstance( - ChatSubmitDropdownActionItem, - action, - dropdownAction, - options, - ); - } - } - if ( - action.id === ChatModelPickerActionId && - action instanceof MenuItemAction - ) { - if (!this._currentLanguageModel) { - this.setCurrentLanguageModelToDefault(); - } + if (action.id === ChatModelPickerActionId && action instanceof MenuItemAction) { + if (!this._currentLanguageModel) { + this.setCurrentLanguageModelToDefault(); + } - if (this._currentLanguageModel) { - const itemDelegate: ModelPickerDelegate = { - onDidChangeModel: - this._onDidChangeCurrentLanguageModel - .event, - setModel: (modelId: string) => { - this.setCurrentLanguageModelByUser( - modelId, - ); - }, - }; - return this.instantiationService.createInstance( - ModelPickerActionViewItem, - action, - this._currentLanguageModel, - itemDelegate, - { - hoverDelegate: options.hoverDelegate, - keybinding: - options.keybinding ?? undefined, - }, - ); + if (this._currentLanguageModel) { + const itemDelegate: ModelPickerDelegate = { + onDidChangeModel: this._onDidChangeCurrentLanguageModel.event, + setModel: (modelId: string) => { + this.setCurrentLanguageModelByUser(modelId); } - } - - return undefined; - }, - }, - ), - ); - this.executeToolbar.getElement().classList.add("chat-execute-toolbar"); - this.executeToolbar.context = { - widget, - } satisfies IChatExecuteActionContext; - this._register( - this.executeToolbar.onDidChangeMenuItems(() => { - if ( - this.cachedDimensions && - typeof this.cachedExecuteToolbarWidth === "number" && - this.cachedExecuteToolbarWidth !== - this.executeToolbar.getItemsWidth() - ) { - this.layout( - this.cachedDimensions.height, - this.cachedDimensions.width, - ); + }; + return this.instantiationService.createInstance(ModelPickerActionViewItem, action, this._currentLanguageModel, itemDelegate, { hoverDelegate: options.hoverDelegate, keybinding: options.keybinding ?? undefined }); + } } - }), - ); + + return undefined; + } + })); + this.executeToolbar.getElement().classList.add('chat-execute-toolbar'); + this.executeToolbar.context = { widget } satisfies IChatExecuteActionContext; + this._register(this.executeToolbar.onDidChangeMenuItems(() => { + if (this.cachedDimensions && typeof this.cachedExecuteToolbarWidth === 'number' && this.cachedExecuteToolbarWidth !== this.executeToolbar.getItemsWidth()) { + this.layout(this.cachedDimensions.height, this.cachedDimensions.width); + } + })); if (this.options.menus.inputSideToolbar) { - const toolbarSide = this._register( - this.instantiationService.createInstance( - MenuWorkbenchToolBar, - inputAndSideToolbar, - this.options.menus.inputSideToolbar, - { - telemetrySource: this.options.menus.telemetrySource, - menuOptions: { - shouldForwardArgs: true, - }, - hoverDelegate, - }, - ), - ); + const toolbarSide = this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, inputAndSideToolbar, this.options.menus.inputSideToolbar, { + telemetrySource: this.options.menus.telemetrySource, + menuOptions: { + shouldForwardArgs: true + }, + hoverDelegate + })); this.inputSideToolbarContainer = toolbarSide.getElement(); - toolbarSide.getElement().classList.add("chat-side-toolbar"); - toolbarSide.context = { - widget, - } satisfies IChatExecuteActionContext; + toolbarSide.getElement().classList.add('chat-side-toolbar'); + toolbarSide.context = { widget } satisfies IChatExecuteActionContext; } let inputModel = this.modelService.getModel(this.inputUri); if (!inputModel) { - inputModel = this.modelService.createModel( - "", - null, - this.inputUri, - true, - ); - this._register(inputModel); + inputModel = this.modelService.createModel('', null, this.inputUri, true); } - this.inputModel = inputModel; - this.inputModel.updateOptions({ - bracketColorizationOptions: { - enabled: false, - independentColorPoolPerBracketType: false, - }, + this.textModelResolverService.createModelReference(this.inputUri).then(ref => { + // make sure to hold a reference so that the model doesn't get disposed by the text model service + if (this._store.isDisposed) { + ref.dispose(); + return; + } + this._register(ref); }); + + this.inputModel = inputModel; + this.inputModel.updateOptions({ bracketColorizationOptions: { enabled: false, independentColorPoolPerBracketType: false } }); this._inputEditor.setModel(this.inputModel); if (initialValue) { this.inputModel.setValue(initialValue); const lineNumber = this.inputModel.getLineCount(); - this._inputEditor.setPosition({ - lineNumber, - column: this.inputModel.getLineMaxColumn(lineNumber), - }); + this._inputEditor.setPosition({ lineNumber, column: this.inputModel.getLineMaxColumn(lineNumber) }); } const onDidChangeCursorPosition = () => { @@ -1188,29 +755,18 @@ export class ChatInputPart return; } - const atTop = - position.lineNumber === 1 && - position.column - 1 <= - (this._inputEditor._getViewModel()?.getLineLength(1) ?? 0); + const atTop = position.lineNumber === 1 && position.column - 1 <= (this._inputEditor._getViewModel()?.getLineLength(1) ?? 0); this.chatCursorAtTop.set(atTop); this.historyNavigationBackwardsEnablement.set(atTop); - this.historyNavigationForewardsEnablement.set( - position.equals(getLastPosition(model)), - ); + this.historyNavigationForewardsEnablement.set(position.equals(getLastPosition(model))); }; - this._register( - this._inputEditor.onDidChangeCursorPosition((e) => - onDidChangeCursorPosition(), - ), - ); + this._register(this._inputEditor.onDidChangeCursorPosition(e => onDidChangeCursorPosition())); onDidChangeCursorPosition(); - this._register( - this.themeService.onDidFileIconThemeChange(() => { - this.renderAttachedContext(); - }), - ); + this._register(this.themeService.onDidFileIconThemeChange(() => { + this.renderAttachedContext(); + })); } private async renderAttachedContext() { @@ -1221,229 +777,109 @@ export class ChatInputPart dom.clearNode(container); const hoverDelegate = store.add(createInstantHoverDelegate()); - const attachments = - this.location === ChatAgentLocation.EditingSession - ? // Render as attachments anything that isn't a file, but still render specific ranges in a file - [...this.attachmentModel.attachments.entries()].filter( - ([_, attachment]) => - !attachment.isFile || - (attachment.isFile && - typeof attachment.value === "object" && - !!attachment.value && - "range" in attachment.value), - ) - : [...this.attachmentModel.attachments.entries()]; - dom.setVisibility( - Boolean(attachments.length) || Boolean(this.implicitContext?.value), - this.attachedContextContainer, - ); + const attachments = this.location === ChatAgentLocation.EditingSession + // Render as attachments anything that isn't a file, but still render specific ranges in a file + ? [...this.attachmentModel.attachments.entries()].filter(([_, attachment]) => !attachment.isFile || attachment.isFile && typeof attachment.value === 'object' && !!attachment.value && 'range' in attachment.value) + : [...this.attachmentModel.attachments.entries()]; + dom.setVisibility(Boolean(attachments.length) || Boolean(this.implicitContext?.value), this.attachedContextContainer); if (!attachments.length) { this._indexOfLastAttachedContextDeletedWithKeyboard = -1; } if (this.implicitContext?.value) { - const implicitPart = store.add( - this.instantiationService.createInstance( - ImplicitContextAttachmentWidget, - this.implicitContext, - this._contextResourceLabels, - ), - ); + const implicitPart = store.add(this.instantiationService.createInstance(ImplicitContextAttachmentWidget, this.implicitContext, this._contextResourceLabels)); container.appendChild(implicitPart.domNode); } const attachmentInitPromises: Promise[] = []; for (const [index, attachment] of attachments) { - const widget = dom.append( - container, - $(".chat-attached-context-attachment.show-file-icons"), - ); - const label = this._contextResourceLabels.create(widget, { - supportIcons: true, - hoverDelegate, - hoverTargetOverride: widget, - }); + const widget = dom.append(container, $('.chat-attached-context-attachment.show-file-icons')); + const label = this._contextResourceLabels.create(widget, { supportIcons: true, hoverDelegate, hoverTargetOverride: widget }); let ariaLabel: string | undefined; - const resource = URI.isUri(attachment.value) - ? attachment.value - : attachment.value && - typeof attachment.value === "object" && - "uri" in attachment.value && - URI.isUri(attachment.value.uri) - ? attachment.value.uri - : undefined; - const range = - attachment.value && - typeof attachment.value === "object" && - "range" in attachment.value && - Range.isIRange(attachment.value.range) - ? attachment.value.range - : undefined; + const resource = URI.isUri(attachment.value) ? attachment.value : attachment.value && typeof attachment.value === 'object' && 'uri' in attachment.value && URI.isUri(attachment.value.uri) ? attachment.value.uri : undefined; + const range = attachment.value && typeof attachment.value === 'object' && 'range' in attachment.value && Range.isIRange(attachment.value.range) ? attachment.value.range : undefined; if (resource && (attachment.isFile || attachment.isDirectory)) { const fileBasename = basename(resource.path); const fileDirname = dirname(resource.path); const friendlyName = `${fileBasename} ${fileDirname}`; - ariaLabel = range - ? localize( - "chat.fileAttachmentWithRange", - "Attached file, {0}, line {1} to line {2}", - friendlyName, - range.startLineNumber, - range.endLineNumber, - ) - : localize( - "chat.fileAttachment", - "Attached file, {0}", - friendlyName, - ); + ariaLabel = range ? localize('chat.fileAttachmentWithRange', "Attached file, {0}, line {1} to line {2}", friendlyName, range.startLineNumber, range.endLineNumber) : localize('chat.fileAttachment', "Attached file, {0}", friendlyName); const fileOptions: IFileLabelOptions = { hidePath: true }; - label.setFile( - resource, - attachment.isFile - ? { - ...fileOptions, - fileKind: FileKind.FILE, - range, - } - : { - ...fileOptions, - fileKind: FileKind.FOLDER, - icon: !this.themeService.getFileIconTheme() - .hasFolderIcons - ? FolderThemeIcon - : undefined, - }, - ); - - const scopedContextKeyService = store.add( - this.contextKeyService.createScoped(widget), - ); - const resourceContextKey = store.add( - new ResourceContextKey( - scopedContextKeyService, - this.fileService, - this.languageService, - this.modelService, - ), - ); - resourceContextKey.set(resource); - - this.attachButtonAndDisposables( - widget, - index, - attachment, - hoverDelegate, - { - contextMenuArg: resource, - contextKeyService: scopedContextKeyService, - contextMenuId: - MenuId.ChatInputResourceAttachmentContext, - }, - ); - - // Drag and drop - widget.draggable = true; - this._register( - dom.addDisposableListener(widget, "dragstart", (e) => { - this.instantiationService.invokeFunction((accessor) => - fillEditorsDragData(accessor, [resource], e), - ); - e.dataTransfer?.setDragImage(widget, 0, 0); - }), - ); + label.setFile(resource, attachment.isFile ? { + ...fileOptions, + fileKind: FileKind.FILE, + range, + } : { + ...fileOptions, + fileKind: FileKind.FOLDER, + icon: !this.themeService.getFileIconTheme().hasFolderIcons ? FolderThemeIcon : undefined + }); + + this.attachButtonAndDisposables(widget, index, attachment, hoverDelegate); + this.instantiationService.invokeFunction(accessor => hookUpResourceAttachmentInteractions(accessor, store, widget, resource)); + } else if (attachment.isImage) { - ariaLabel = localize( - "chat.imageAttachment", - "Attached image, {0}", - attachment.name, - ); + ariaLabel = localize('chat.imageAttachment', "Attached image, {0}", attachment.name); - const hoverElement = dom.$("div.chat-attached-context-hover"); - hoverElement.setAttribute("aria-label", ariaLabel); + const hoverElement = dom.$('div.chat-attached-context-hover'); + hoverElement.setAttribute('aria-label', ariaLabel); // Custom label - const pillIcon = dom.$( - "div.chat-attached-context-pill", - {}, - dom.$("span.codicon.codicon-file-media"), - ); - const textLabel = dom.$( - "span.chat-attached-context-custom-text", - {}, - attachment.name, - ); + const pillIcon = dom.$('div.chat-attached-context-pill', {}, dom.$('span.codicon.codicon-file-media')); + const textLabel = dom.$('span.chat-attached-context-custom-text', {}, attachment.name); widget.appendChild(pillIcon); widget.appendChild(textLabel); - attachmentInitPromises.push( - Promises.withAsyncBody(async (resolve) => { - let buffer: Uint8Array; - try { - this.attachButtonAndDisposables( - widget, - index, - attachment, - hoverDelegate, - ); - if (attachment.value instanceof URI) { - const readFile = - await this.fileService.readFile( - attachment.value, - ); - if (store.isDisposed) { - return; - } - buffer = readFile.value.buffer; - } else { - buffer = attachment.value as Uint8Array; + attachmentInitPromises.push(Promises.withAsyncBody(async (resolve) => { + let buffer: Uint8Array; + try { + this.attachButtonAndDisposables(widget, index, attachment, hoverDelegate); + if (attachment.value instanceof URI) { + const readFile = await this.fileService.readFile(attachment.value); + if (store.isDisposed) { + return; } - this.createImageElements( - buffer, - widget, - hoverElement, - ); - } catch (error) { - console.error( - "Error processing attachment:", - error, - ); + buffer = readFile.value.buffer; + } else { + buffer = attachment.value as Uint8Array; } + this.createImageElements(buffer, widget, hoverElement); + } catch (error) { + console.error('Error processing attachment:', error); + } + + widget.style.position = 'relative'; + store.add(this.hoverService.setupManagedHover(hoverDelegate, widget, hoverElement, { trapFocus: false })); + resolve(); + })); + } else if (isPasteVariableEntry(attachment)) { + ariaLabel = localize('chat.attachment', "Attached context, {0}", attachment.name); - widget.style.position = "relative"; - store.add( - this.hoverService.setupManagedHover( - hoverDelegate, - widget, - hoverElement, - { trapFocus: false }, - ), - ); - resolve(); - }), - ); + const hoverContent: IManagedHoverTooltipMarkdownString = { + markdown: { + value: `\`\`\`${attachment.language}\n${attachment.code}\n\`\`\``, + }, + markdownNotSupportedFallback: attachment.code, + }; + + const classNames = ['file-icon', `${attachment.language}-lang-file-icon`]; + label.setLabel(attachment.fileName, undefined, { extraClasses: classNames }); + widget.appendChild(dom.$('span.attachment-additional-info', {}, `Pasted ${attachment.pastedLines}`)); + + widget.style.position = 'relative'; + store.add(this.hoverService.setupManagedHover(hoverDelegate, widget, hoverContent, { trapFocus: true })); + this.attachButtonAndDisposables(widget, index, attachment, hoverDelegate); } else { const attachmentLabel = attachment.fullName ?? attachment.name; - const withIcon = attachment.icon?.id - ? `$(${attachment.icon.id}) ${attachmentLabel}` - : attachmentLabel; + const withIcon = attachment.icon?.id ? `$(${attachment.icon.id}) ${attachmentLabel}` : attachmentLabel; label.setLabel(withIcon, undefined); - ariaLabel = localize( - "chat.attachment", - "Attached context, {0}", - attachment.name, - ); - - this.attachButtonAndDisposables( - widget, - index, - attachment, - hoverDelegate, - ); + ariaLabel = localize('chat.attachment', "Attached context, {0}", attachment.name); + + this.attachButtonAndDisposables(widget, index, attachment, hoverDelegate); } await Promise.all(attachmentInitPromises); @@ -1452,42 +888,27 @@ export class ChatInputPart } if (resource) { - widget.style.cursor = "pointer"; - store.add( - dom.addDisposableListener( - widget, - dom.EventType.CLICK, - (e: MouseEvent) => { - dom.EventHelper.stop(e, true); - if (attachment.isDirectory) { - this.openResource(resource, true); - } else { - this.openResource(resource, false, range); - } - }, - ), - ); - - store.add( - dom.addDisposableListener( - widget, - dom.EventType.KEY_DOWN, - (e: KeyboardEvent) => { - const event = new StandardKeyboardEvent(e); - if ( - event.equals(KeyCode.Enter) || - event.equals(KeyCode.Space) - ) { - dom.EventHelper.stop(e, true); - if (attachment.isDirectory) { - this.openResource(resource, true); - } else { - this.openResource(resource, false, range); - } - } - }, - ), - ); + widget.style.cursor = 'pointer'; + store.add(dom.addDisposableListener(widget, dom.EventType.CLICK, (e: MouseEvent) => { + dom.EventHelper.stop(e, true); + if (attachment.isDirectory) { + this.openResource(resource, true); + } else { + this.openResource(resource, false, range); + } + })); + + store.add(dom.addDisposableListener(widget, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { + const event = new StandardKeyboardEvent(e); + if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { + dom.EventHelper.stop(e, true); + if (attachment.isDirectory) { + this.openResource(resource, true); + } else { + this.openResource(resource, false, range); + } + } + })); } widget.tabIndex = 0; @@ -1500,29 +921,16 @@ export class ChatInputPart } private openResource(resource: URI, isDirectory: true): void; - private openResource( - resource: URI, - isDirectory: false, - range: IRange | undefined, - ): void; - private openResource( - resource: URI, - isDirectory?: boolean, - range?: IRange, - ): void { + private openResource(resource: URI, isDirectory: false, range: IRange | undefined): void; + private openResource(resource: URI, isDirectory?: boolean, range?: IRange): void { if (isDirectory) { // Reveal Directory in explorer - this.commandService.executeCommand( - revealInSideBarCommand.id, - resource, - ); + this.commandService.executeCommand(revealInSideBarCommand.id, resource); return; } // Open file in editor - const openTextEditorOptions: ITextEditorOptions | undefined = range - ? { selection: range } - : undefined; + const openTextEditorOptions: ITextEditorOptions | undefined = range ? { selection: range } : undefined; const options: OpenInternalOptions = { fromUserGesture: true, editorOptions: openTextEditorOptions, @@ -1530,17 +938,7 @@ export class ChatInputPart this.openerService.open(resource, options); } - private attachButtonAndDisposables( - widget: HTMLElement, - index: number, - attachment: IChatRequestVariableEntry, - hoverDelegate: IHoverDelegate, - contextMenuOpts?: { - contextMenuId: MenuId; - contextKeyService: IContextKeyService; - contextMenuArg: unknown; - }, - ) { + private attachButtonAndDisposables(widget: HTMLElement, index: number, attachment: IChatRequestVariableEntry, hoverDelegate: IHoverDelegate) { const store = this.attachedContextDisposables.value; if (!store) { return; @@ -1549,106 +947,48 @@ export class ChatInputPart const clearButton = new Button(widget, { supportIcons: true, hoverDelegate, - title: localize( - "chat.attachment.clearButton", - "Remove from context", - ), + title: localize('chat.attachment.clearButton', "Remove from context"), }); // If this item is rendering in place of the last attached context item, focus the clear button so the user can continue deleting attached context items with the keyboard - if ( - index === - Math.min( - this._indexOfLastAttachedContextDeletedWithKeyboard, - this.attachmentModel.size - 1, - ) - ) { + if (index === Math.min(this._indexOfLastAttachedContextDeletedWithKeyboard, this.attachmentModel.size - 1)) { clearButton.focus(); } store.add(clearButton); clearButton.icon = Codicon.close; - store.add( - Event.once(clearButton.onDidClick)((e) => { - this._attachmentModel.delete(attachment.id); + store.add(Event.once(clearButton.onDidClick)((e) => { + this._attachmentModel.delete(attachment.id); - // Set focus to the next attached context item if deletion was triggered by a keystroke (vs a mouse click) - if (dom.isKeyboardEvent(e)) { - const event = new StandardKeyboardEvent(e); - if ( - event.equals(KeyCode.Enter) || - event.equals(KeyCode.Space) - ) { - this._indexOfLastAttachedContextDeletedWithKeyboard = - index; - } + // Set focus to the next attached context item if deletion was triggered by a keystroke (vs a mouse click) + if (dom.isKeyboardEvent(e)) { + const event = new StandardKeyboardEvent(e); + if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { + this._indexOfLastAttachedContextDeletedWithKeyboard = index; } + } - if (this._attachmentModel.size === 0) { - this.focus(); - } + if (this._attachmentModel.size === 0) { + this.focus(); + } - this._onDidChangeContext.fire({ removed: [attachment] }); - }), - ); - - // Context menu - if (contextMenuOpts) { - store.add( - dom.addDisposableListener( - widget, - dom.EventType.CONTEXT_MENU, - async (domEvent) => { - const event = new StandardMouseEvent( - dom.getWindow(domEvent), - domEvent, - ); - dom.EventHelper.stop(domEvent, true); - - this.contextMenuService.showContextMenu({ - contextKeyService: - contextMenuOpts.contextKeyService, - getAnchor: () => event, - getActions: () => { - const menu = this.menuService.getMenuActions( - contextMenuOpts.contextMenuId, - contextMenuOpts.contextKeyService, - { arg: contextMenuOpts.contextMenuArg }, - ); - return getFlatContextMenuActions(menu); - }, - }); - }, - ), - ); - } + this._onDidChangeContext.fire({ removed: [attachment] }); + })); } // Helper function to create and replace image - private createImageElements( - buffer: ArrayBuffer | Uint8Array, - widget: HTMLElement, - hoverElement: HTMLElement, - ) { - const blob = new Blob([buffer], { type: "image/png" }); + private createImageElements(buffer: ArrayBuffer | Uint8Array, widget: HTMLElement, hoverElement: HTMLElement) { + const blob = new Blob([buffer], { type: 'image/png' }); const url = URL.createObjectURL(blob); - const pillImg = dom.$("img.chat-attached-context-pill-image", { - src: url, - alt: "", - }); - const pill = dom.$("div.chat-attached-context-pill", {}, pillImg); + const pillImg = dom.$('img.chat-attached-context-pill-image', { src: url, alt: '' }); + const pill = dom.$('div.chat-attached-context-pill', {}, pillImg); - const existingPill = widget.querySelector( - ".chat-attached-context-pill", - ); + const existingPill = widget.querySelector('.chat-attached-context-pill'); if (existingPill) { existingPill.replaceWith(pill); } - const hoverImage = dom.$("img.chat-attached-context-image", { - src: url, - alt: "", - }); + const hoverImage = dom.$('img.chat-attached-context-image', { src: url, alt: '' }); // Update hover image hoverElement.appendChild(hoverImage); @@ -1658,14 +998,8 @@ export class ChatInputPart }; } - async renderChatEditingSessionState( - chatEditingSession: IChatEditingSession | null, - chatWidget?: IChatWidget, - ) { - dom.setVisibility( - Boolean(chatEditingSession), - this.chatEditingSessionWidgetContainer, - ); + async renderChatEditingSessionState(chatEditingSession: IChatEditingSession | null, chatWidget?: IChatWidget) { + dom.setVisibility(Boolean(chatEditingSession), this.chatEditingSessionWidgetContainer); if (!chatEditingSession) { dom.clearNode(this.chatEditingSessionWidgetContainer); @@ -1677,44 +1011,27 @@ export class ChatInputPart } const currentChatEditingState = chatEditingSession.state.get(); - if ( - this._chatEditList && - !chatWidget?.viewModel?.requestInProgress && - (currentChatEditingState === ChatEditingSessionState.Idle || - currentChatEditingState === ChatEditingSessionState.Initial) - ) { + if (this._chatEditList && !chatWidget?.viewModel?.requestInProgress && (currentChatEditingState === ChatEditingSessionState.Idle || currentChatEditingState === ChatEditingSessionState.Initial)) { this._chatEditsProgress?.stop(); } // Summary of number of files changed - const innerContainer = - (this.chatEditingSessionWidgetContainer.querySelector( - ".chat-editing-session-container.show-file-icons", - ) as HTMLElement) ?? - dom.append( - this.chatEditingSessionWidgetContainer, - $(".chat-editing-session-container.show-file-icons"), - ); + const innerContainer = this.chatEditingSessionWidgetContainer.querySelector('.chat-editing-session-container.show-file-icons') as HTMLElement ?? dom.append(this.chatEditingSessionWidgetContainer, $('.chat-editing-session-container.show-file-icons')); const seenEntries = new ResourceSet(); - let entries: IChatCollapsibleListItem[] = - chatEditingSession?.entries.get().map((entry) => { - seenEntries.add(entry.modifiedURI); - return { - reference: entry.modifiedURI, - state: entry.state.get(), - kind: "reference", - }; - }) ?? []; - for (const attachment of this.attachmentModel.attachments) { - if ( - attachment.isFile && - URI.isUri(attachment.value) && - !seenEntries.has(attachment.value) - ) { + let entries: IChatCollapsibleListItem[] = chatEditingSession?.entries.get().map((entry) => { + seenEntries.add(entry.modifiedURI); + return { + reference: entry.modifiedURI, + state: entry.state.get(), + kind: 'reference', + }; + }) ?? []; + for (const attachment of (this.attachmentModel as EditsAttachmentModel).fileAttachments) { + if (URI.isUri(attachment.value) && !seenEntries.has(attachment.value)) { entries.unshift({ reference: attachment.value, state: WorkingSetEntryState.Attached, - kind: "reference", + kind: 'reference', }); seenEntries.add(attachment.value); } @@ -1725,102 +1042,59 @@ export class ChatInputPart reference: file, state: state.state, description: state.description, - kind: "reference", + kind: 'reference', }); seenEntries.add(file); } } // Factor file variables that are part of the user query into the working set for (const part of chatWidget?.parsedInput.parts ?? []) { - if ( - part instanceof ChatRequestDynamicVariablePart && - part.isFile && - URI.isUri(part.data) && - !seenEntries.has(part.data) - ) { + if (part instanceof ChatRequestDynamicVariablePart && part.isFile && URI.isUri(part.data) && !seenEntries.has(part.data)) { entries.unshift({ reference: part.data, state: WorkingSetEntryState.Attached, - kind: "reference", + kind: 'reference', }); } } const excludedEntries: IChatCollapsibleListItem[] = []; - for (const excludedAttachment of ( - this.attachmentModel as EditsAttachmentModel - ).excludedFileAttachments) { - if ( - excludedAttachment.isFile && - URI.isUri(excludedAttachment.value) && - !seenEntries.has(excludedAttachment.value) - ) { + for (const excludedAttachment of (this.attachmentModel as EditsAttachmentModel).excludedFileAttachments) { + if (excludedAttachment.isFile && URI.isUri(excludedAttachment.value) && !seenEntries.has(excludedAttachment.value)) { excludedEntries.push({ reference: excludedAttachment.value, state: WorkingSetEntryState.Attached, - kind: "reference", + kind: 'reference', excluded: true, - title: localize( - "chatEditingSession.excludedFile", - "The Working Set file limit has ben reached. {0} is excluded from the Woking Set. Remove other files to make space for {0}.", - basename(excludedAttachment.value.path), - ), + title: localize('chatEditingSession.excludedFile', 'The Working Set file limit has ben reached. {0} is excluded from the Woking Set. Remove other files to make space for {0}.', basename(excludedAttachment.value.path)) }); seenEntries.add(excludedAttachment.value); } } entries.sort((a, b) => { - if (a.kind === "reference" && b.kind === "reference") { - if ( - a.state === b.state || - a.state === undefined || - b.state === undefined - ) { - return a.reference - .toString() - .localeCompare(b.reference.toString()); + if (a.kind === 'reference' && b.kind === 'reference') { + if (a.state === b.state || a.state === undefined || b.state === undefined) { + return a.reference.toString().localeCompare(b.reference.toString()); } return a.state - b.state; } return 0; }); - let remainingFileEntriesBudget = - this.chatEditingService.editingSessionFileLimit; - const overviewRegion = - (innerContainer.querySelector( - ".chat-editing-session-overview", - ) as HTMLElement) ?? - dom.append(innerContainer, $(".chat-editing-session-overview")); - const overviewTitle = - (overviewRegion.querySelector( - ".working-set-title", - ) as HTMLElement) ?? - dom.append(overviewRegion, $(".working-set-title")); - const overviewWorkingSet = - overviewTitle.querySelector("span") ?? - dom.append(overviewTitle, $("span")); - const overviewFileCount = - overviewTitle.querySelector("span.working-set-count") ?? - dom.append(overviewTitle, $("span.working-set-count")); - - overviewWorkingSet.textContent = localize( - "chatEditingSession.workingSet", - "Working Set", - ); + let remainingFileEntriesBudget = this.chatEditingService.editingSessionFileLimit; + const overviewRegion = innerContainer.querySelector('.chat-editing-session-overview') as HTMLElement ?? dom.append(innerContainer, $('.chat-editing-session-overview')); + const overviewTitle = overviewRegion.querySelector('.working-set-title') as HTMLElement ?? dom.append(overviewRegion, $('.working-set-title')); + const overviewWorkingSet = overviewTitle.querySelector('span') ?? dom.append(overviewTitle, $('span')); + const overviewFileCount = overviewTitle.querySelector('span.working-set-count') ?? dom.append(overviewTitle, $('span.working-set-count')); + + overviewWorkingSet.textContent = localize('chatEditingSession.workingSet', 'Working Set'); // Record the number of entries that the user wanted to add to the working set - this._attemptedWorkingSetEntriesCount = - entries.length + excludedEntries.length; + this._attemptedWorkingSetEntriesCount = entries.length + excludedEntries.length; let suggestedFilesInWorkingSetCount = 0; - overviewFileCount.textContent = ""; + overviewFileCount.textContent = ''; if (entries.length === 1) { - overviewFileCount.textContent = - " " + localize("chatEditingSession.oneFile", "(1 file)"); - suggestedFilesInWorkingSetCount = - entries[0].kind === "reference" && - entries[0].state === WorkingSetEntryState.Suggested - ? 1 - : 0; + overviewFileCount.textContent = ' ' + localize('chatEditingSession.oneFile', '(1 file)'); + suggestedFilesInWorkingSetCount = entries[0].kind === 'reference' && entries[0].state === WorkingSetEntryState.Suggested ? 1 : 0; } else if (entries.length >= remainingFileEntriesBudget) { // The user tried to attach too many files, we have to drop anything after the limit const entriesToPreserve: IChatCollapsibleListItem[] = []; @@ -1828,18 +1102,14 @@ export class ChatInputPart const suggestedFiles: IChatCollapsibleListItem[] = []; for (let i = 0; i < entries.length; i += 1) { const entry = entries[i]; - if (entry.kind !== "reference" || !URI.isUri(entry.reference)) { + if (entry.kind !== 'reference' || !URI.isUri(entry.reference)) { continue; } const currentEntryUri = entry.reference; if (entry.state === WorkingSetEntryState.Suggested) { // Keep track of suggested files for now, they should not take precedence over newly added files suggestedFiles.push(entry); - } else if ( - this._combinedChatEditWorkingSetEntries.find( - (e) => e.toString() === currentEntryUri?.toString(), - ) - ) { + } else if (this._combinedChatEditWorkingSetEntries.find((e) => e.toString() === currentEntryUri?.toString())) { // If this entry was here earlier and is still here, we should prioritize preserving it // so that nothing existing gets evicted if (remainingFileEntriesBudget > 0) { @@ -1851,93 +1121,39 @@ export class ChatInputPart } } - const newEntriesThatFit = - remainingFileEntriesBudget > 0 - ? newEntries.slice(0, remainingFileEntriesBudget) - : []; + const newEntriesThatFit = remainingFileEntriesBudget > 0 ? newEntries.slice(0, remainingFileEntriesBudget) : []; remainingFileEntriesBudget -= newEntriesThatFit.length; - const suggestedFilesThatFit = - remainingFileEntriesBudget > 0 - ? suggestedFiles.slice(0, remainingFileEntriesBudget) - : []; + const suggestedFilesThatFit = remainingFileEntriesBudget > 0 ? suggestedFiles.slice(0, remainingFileEntriesBudget) : []; // Intentional: to make bad suggestions less annoying, // here we don't count the suggested files against the budget, // so that the Add Files button remains enabled and the user can easily // override the suggestions with their own manual file selections - entries = [ - ...entriesToPreserve, - ...newEntriesThatFit, - ...suggestedFilesThatFit, - ]; + entries = [...entriesToPreserve, ...newEntriesThatFit, ...suggestedFilesThatFit]; suggestedFilesInWorkingSetCount = suggestedFilesThatFit.length; } else { - suggestedFilesInWorkingSetCount = entries.filter( - (e) => - e.kind === "reference" && - e.state === WorkingSetEntryState.Suggested, - ).length; + suggestedFilesInWorkingSetCount = entries.filter(e => e.kind === 'reference' && e.state === WorkingSetEntryState.Suggested).length; } if (excludedEntries.length > 0) { - overviewFileCount.textContent = - " " + - localize( - "chatEditingSession.excludedFiles", - "({0}/{1} files)", - this.chatEditingService.editingSessionFileLimit + - excludedEntries.length, - this.chatEditingService.editingSessionFileLimit, - ); + overviewFileCount.textContent = ' ' + localize('chatEditingSession.excludedFiles', '({0}/{1} files)', this.chatEditingService.editingSessionFileLimit + excludedEntries.length, this.chatEditingService.editingSessionFileLimit); } else if (entries.length > 1) { const fileCount = entries.length - suggestedFilesInWorkingSetCount; - overviewFileCount.textContent = - " " + - (fileCount === 1 - ? localize("chatEditingSession.oneFile", "(1 file)") - : localize( - "chatEditingSession.manyFiles", - "({0} files)", - fileCount, - )); + overviewFileCount.textContent = ' ' + (fileCount === 1 ? localize('chatEditingSession.oneFile', '(1 file)') : localize('chatEditingSession.manyFiles', '({0} files)', fileCount)); } const fileLimitReached = remainingFileEntriesBudget <= 0; - overviewFileCount.classList.toggle( - "file-limit-reached", - fileLimitReached, - ); + overviewFileCount.classList.toggle('file-limit-reached', fileLimitReached); if (fileLimitReached) { - let title = localize( - "chatEditingSession.fileLimitReached", - "You have reached the maximum number of files that can be added to the working set.", - ); - title += - excludedEntries.length === 1 - ? " " + - localize( - "chatEditingSession.excludedOneFile", - "1 file is excluded from the Working Set.", - ) - : ""; - title += - excludedEntries.length > 1 - ? " " + - localize( - "chatEditingSession.excludedSomeFiles", - "{0} files are excluded from the Working Set.", - excludedEntries.length, - ) - : ""; - - this._chatEditsFileLimitHover.value = - getBaseLayerHoverDelegate().setupDelayedHover( - overviewFileCount as HTMLElement, - { - content: title, - appearance: { showPointer: true, compact: true }, - position: { hoverPosition: HoverPosition.ABOVE }, - }, - ); + let title = localize('chatEditingSession.fileLimitReached', 'You have reached the maximum number of files that can be added to the working set.'); + title += excludedEntries.length === 1 ? ' ' + localize('chatEditingSession.excludedOneFile', '1 file is excluded from the Working Set.') : ''; + title += excludedEntries.length > 1 ? ' ' + localize('chatEditingSession.excludedSomeFiles', '{0} files are excluded from the Working Set.', excludedEntries.length) : ''; + + this._chatEditsFileLimitHover.value = getBaseLayerHoverDelegate().setupDelayedHover(overviewFileCount as HTMLElement, + { + content: title, + appearance: { showPointer: true, compact: true }, + position: { hoverPosition: HoverPosition.ABOVE } + }); } else { this._chatEditsFileLimitHover.clear(); } @@ -1946,177 +1162,92 @@ export class ChatInputPart this._chatEditsActionsDisposables.clear(); // Chat editing session actions - const actionsContainer = - (overviewRegion.querySelector( - ".chat-editing-session-actions", - ) as HTMLElement) ?? - dom.append(overviewRegion, $(".chat-editing-session-actions")); - - this._chatEditsActionsDisposables.add( - this.instantiationService.createInstance( - MenuWorkbenchButtonBar, - actionsContainer, - MenuId.ChatEditingWidgetToolbar, - { - telemetrySource: this.options.menus.telemetrySource, - menuOptions: { - arg: { sessionId: chatEditingSession.chatSessionId }, - }, - buttonConfigProvider: (action) => { - if ( - action.id === ChatEditingShowChangesAction.ID || - action.id === ChatEditingSaveAllAction.ID || - action.id === ChatEditingRemoveAllFilesAction.ID - ) { - return { - showIcon: true, - showLabel: false, - isSecondary: true, - }; - } - return undefined; - }, - }, - ), - ); + const actionsContainer = overviewRegion.querySelector('.chat-editing-session-actions') as HTMLElement ?? dom.append(overviewRegion, $('.chat-editing-session-actions')); + + this._chatEditsActionsDisposables.add(this.instantiationService.createInstance(MenuWorkbenchButtonBar, actionsContainer, MenuId.ChatEditingWidgetToolbar, { + telemetrySource: this.options.menus.telemetrySource, + menuOptions: { + arg: { sessionId: chatEditingSession.chatSessionId }, + }, + buttonConfigProvider: (action) => { + if (action.id === ChatEditingShowChangesAction.ID || action.id === ChatEditingSaveAllAction.ID || action.id === ChatEditingRemoveAllFilesAction.ID) { + return { showIcon: true, showLabel: false, isSecondary: true }; + } + return undefined; + } + })); if (!chatEditingSession) { return; } - if ( - currentChatEditingState === - ChatEditingSessionState.StreamingEdits || - chatWidget?.viewModel?.requestInProgress - ) { + if (currentChatEditingState === ChatEditingSessionState.StreamingEdits || chatWidget?.viewModel?.requestInProgress) { this._chatEditsProgress ??= new ProgressBar(innerContainer); this._chatEditsProgress?.infinite().show(500); } // Working set - const workingSetContainer = - (innerContainer.querySelector( - ".chat-editing-session-list", - ) as HTMLElement) ?? - dom.append(innerContainer, $(".chat-editing-session-list")); + const workingSetContainer = innerContainer.querySelector('.chat-editing-session-list') as HTMLElement ?? dom.append(innerContainer, $('.chat-editing-session-list')); if (!this._chatEditList) { this._chatEditList = this._chatEditsListPool.get(); const list = this._chatEditList.object; this._chatEditsDisposables.add(this._chatEditList); - this._chatEditsDisposables.add( - list.onDidFocus(() => { - this._onDidFocus.fire(); - }), - ); - this._chatEditsDisposables.add( - list.onDidOpen((e) => { - if ( - e.element?.kind === "reference" && - URI.isUri(e.element.reference) - ) { - const modifiedFileUri = e.element.reference; - - const entry = chatEditingSession.entries - .get() - .find( - (entry) => - entry.modifiedURI.toString() === - modifiedFileUri.toString(), - ); - const diffInfo = entry?.diffInfo.get(); - const range = diffInfo?.changes - .at(0) - ?.modified.toExclusiveRange(); - - this.editorService.openEditor( - { - resource: modifiedFileUri, - options: { - ...e.editorOptions, - selection: range, - }, - }, - e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP, - ); - } - }), - ); - this._chatEditsDisposables.add( - addDisposableListener( - list.getHTMLElement(), - "click", - (e) => { - if (!this.hasFocus()) { - this._onDidFocus.fire(); + this._chatEditsDisposables.add(list.onDidFocus(() => { + this._onDidFocus.fire(); + })); + this._chatEditsDisposables.add(list.onDidOpen((e) => { + if (e.element?.kind === 'reference' && URI.isUri(e.element.reference)) { + const modifiedFileUri = e.element.reference; + + const entry = chatEditingSession.entries.get().find(entry => entry.modifiedURI.toString() === modifiedFileUri.toString()); + const diffInfo = entry?.diffInfo.get(); + const range = diffInfo?.changes.at(0)?.modified.toExclusiveRange(); + + this.editorService.openEditor({ + resource: modifiedFileUri, + options: { + ...e.editorOptions, + selection: range, } - }, - true, - ), - ); + }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + } + })); + this._chatEditsDisposables.add(addDisposableListener(list.getHTMLElement(), 'click', e => { + if (!this.hasFocus()) { + this._onDidFocus.fire(); + } + }, true)); dom.append(workingSetContainer, list.getHTMLElement()); dom.append(innerContainer, workingSetContainer); } const maxItemsShown = 6; - const itemsShown = Math.min( - entries.length + excludedEntries.length, - maxItemsShown, - ); + const itemsShown = Math.min(entries.length + excludedEntries.length, maxItemsShown); const height = itemsShown * 22; const list = this._chatEditList.object; list.layout(height); list.getHTMLElement().style.height = `${height}px`; list.splice(0, list.length, entries); list.splice(entries.length, 0, excludedEntries); - this._combinedChatEditWorkingSetEntries = coalesce( - entries.map((e) => - e.kind === "reference" && URI.isUri(e.reference) - ? e.reference - : undefined, - ), - ); - - const addFilesElement = - (innerContainer.querySelector( - ".chat-editing-session-toolbar-actions", - ) as HTMLElement) ?? - dom.append( - innerContainer, - $(".chat-editing-session-toolbar-actions"), - ); - - const button = this._chatEditsActionsDisposables.add( - new Button(addFilesElement, { - supportIcons: true, - secondary: true, - }), - ); + this._combinedChatEditWorkingSetEntries = coalesce(entries.map((e) => e.kind === 'reference' && URI.isUri(e.reference) ? e.reference : undefined)); + + const addFilesElement = innerContainer.querySelector('.chat-editing-session-toolbar-actions') as HTMLElement ?? dom.append(innerContainer, $('.chat-editing-session-toolbar-actions')); + + const button = this._chatEditsActionsDisposables.add(new Button(addFilesElement, { + supportIcons: true, + secondary: true + })); // Disable the button if the entries that are not suggested exceed the budget button.enabled = remainingFileEntriesBudget > 0; - button.label = localize("chatAddFiles", "{0} Add Files...", "$(add)"); - button.setTitle( - button.enabled - ? localize("addFiles.label", "Add files to your working set") - : localize( - "chatEditingSession.fileLimitReached", - "You have reached the maximum number of files that can be added to the working set.", - ), - ); - this._chatEditsActionsDisposables.add( - button.onDidClick(() => { - this.commandService.executeCommand( - "workbench.action.chat.editing.attachFiles", - { widget: chatWidget }, - ); - }), - ); + button.label = localize('chatAddFiles', '{0} Add Files...', '$(add)'); + button.setTitle(button.enabled ? localize('addFiles.label', 'Add files to your working set') : localize('chatEditingSession.fileLimitReached', 'You have reached the maximum number of files that can be added to the working set.')); + this._chatEditsActionsDisposables.add(button.onDidClick(() => { + this.commandService.executeCommand('workbench.action.chat.editing.attachFiles', { widget: chatWidget }); + })); dom.append(addFilesElement, button.element); } - async renderFollowups( - items: IChatFollowup[] | undefined, - response: IChatResponseViewModel | undefined, - ): Promise { + async renderFollowups(items: IChatFollowup[] | undefined, response: IChatResponseViewModel | undefined): Promise { if (!this.options.renderFollowups) { return; } @@ -2124,35 +1255,14 @@ export class ChatInputPart dom.clearNode(this.followupsContainer); if (items && items.length > 0) { - this.followupsDisposables.add( - this.instantiationService.createInstance< - typeof ChatFollowups, - ChatFollowups - >( - ChatFollowups, - this.followupsContainer, - items, - this.location, - undefined, - (followup) => - this._onDidAcceptFollowup.fire({ followup, response }), - ), - ); + this.followupsDisposables.add(this.instantiationService.createInstance, ChatFollowups>(ChatFollowups, this.followupsContainer, items, this.location, undefined, followup => this._onDidAcceptFollowup.fire({ followup, response }))); } this._onDidChangeHeight.fire(); } get contentHeight(): number { const data = this.getLayoutData(); - return ( - data.followupsHeight + - data.inputPartEditorHeight + - data.inputPartVerticalPadding + - data.inputEditorBorder + - data.attachmentsHeight + - data.toolbarsHeight + - data.chatEditingStateHeight - ); + return data.followupsHeight + data.inputPartEditorHeight + data.inputPartVerticalPadding + data.inputEditorBorder + data.attachmentsHeight + data.toolbarsHeight + data.chatEditingStateHeight; } layout(height: number, width: number) { @@ -2164,45 +1274,18 @@ export class ChatInputPart private previousInputEditorDimension: IDimension | undefined; private _layout(height: number, width: number, allowRecurse = true): void { const data = this.getLayoutData(); - const inputEditorHeight = Math.min( - data.inputPartEditorHeight, - height - - data.followupsHeight - - data.attachmentsHeight - - data.inputPartVerticalPadding - - data.toolbarsHeight, - ); + const inputEditorHeight = Math.min(data.inputPartEditorHeight, height - data.followupsHeight - data.attachmentsHeight - data.inputPartVerticalPadding - data.toolbarsHeight); const followupsWidth = width - data.inputPartHorizontalPadding; this.followupsContainer.style.width = `${followupsWidth}px`; - this._inputPartHeight = - data.inputPartVerticalPadding + - data.followupsHeight + - inputEditorHeight + - data.inputEditorBorder + - data.attachmentsHeight + - data.toolbarsHeight + - data.chatEditingStateHeight; + this._inputPartHeight = data.inputPartVerticalPadding + data.followupsHeight + inputEditorHeight + data.inputEditorBorder + data.attachmentsHeight + data.toolbarsHeight + data.chatEditingStateHeight; this._followupsHeight = data.followupsHeight; const initialEditorScrollWidth = this._inputEditor.getScrollWidth(); - const newEditorWidth = - width - - data.inputPartHorizontalPadding - - data.editorBorder - - data.inputPartHorizontalPaddingInside - - data.toolbarsWidth - - data.sideToolbarWidth; - const newDimension = { - width: newEditorWidth, - height: inputEditorHeight, - }; - if ( - !this.previousInputEditorDimension || - this.previousInputEditorDimension.width !== newDimension.width || - this.previousInputEditorDimension.height !== newDimension.height - ) { + const newEditorWidth = width - data.inputPartHorizontalPadding - data.editorBorder - data.inputPartHorizontalPaddingInside - data.toolbarsWidth - data.sideToolbarWidth; + const newDimension = { width: newEditorWidth, height: inputEditorHeight }; + if (!this.previousInputEditorDimension || (this.previousInputEditorDimension.width !== newDimension.width || this.previousInputEditorDimension.height !== newDimension.height)) { // This layout call has side-effects that are hard to understand. eg if we are calling this inside a onDidChangeContent handler, this can trigger the next onDidChangeContent handler // to be invoked, and we have a lot of these on this editor. Only doing a layout this when the editor size has actually changed makes it much easier to follow. this._inputEditor.layout(newDimension); @@ -2216,42 +1299,23 @@ export class ChatInputPart } private getLayoutData() { - const executeToolbarWidth = (this.cachedExecuteToolbarWidth = - this.executeToolbar.getItemsWidth()); - const inputToolbarWidth = (this.cachedInputToolbarWidth = - this.inputActionsToolbar.getItemsWidth()); - const executeToolbarPadding = - (this.executeToolbar.getItemsLength() - 1) * 4; - const inputToolbarPadding = this.inputActionsToolbar.getItemsLength() - ? (this.inputActionsToolbar.getItemsLength() - 1) * 4 - : 0; + const executeToolbarWidth = this.cachedExecuteToolbarWidth = this.executeToolbar.getItemsWidth(); + const inputToolbarWidth = this.cachedInputToolbarWidth = this.inputActionsToolbar.getItemsWidth(); + const executeToolbarPadding = (this.executeToolbar.getItemsLength() - 1) * 4; + const inputToolbarPadding = this.inputActionsToolbar.getItemsLength() ? (this.inputActionsToolbar.getItemsLength() - 1) * 4 : 0; return { inputEditorBorder: 2, followupsHeight: this.followupsContainer.offsetHeight, - inputPartEditorHeight: Math.min( - this._inputEditor.getContentHeight(), - this.inputEditorMaxHeight, - ), - inputPartHorizontalPadding: - this.options.renderStyle === "compact" ? 16 : 32, - inputPartVerticalPadding: - this.options.renderStyle === "compact" ? 12 : 28, + inputPartEditorHeight: Math.min(this._inputEditor.getContentHeight(), this.inputEditorMaxHeight), + inputPartHorizontalPadding: this.options.renderStyle === 'compact' ? 16 : 32, + inputPartVerticalPadding: this.options.renderStyle === 'compact' ? 12 : 28, attachmentsHeight: this.attachedContextContainer.offsetHeight, editorBorder: 2, inputPartHorizontalPaddingInside: 12, - toolbarsWidth: - this.options.renderStyle === "compact" - ? executeToolbarWidth + - executeToolbarPadding + - inputToolbarWidth + - inputToolbarPadding - : 0, - toolbarsHeight: this.options.renderStyle === "compact" ? 0 : 22, - chatEditingStateHeight: - this.chatEditingSessionWidgetContainer.offsetHeight, - sideToolbarWidth: this.inputSideToolbarContainer - ? dom.getTotalWidth(this.inputSideToolbarContainer) + 4 /*gap*/ - : 0, + toolbarsWidth: this.options.renderStyle === 'compact' ? executeToolbarWidth + executeToolbarPadding + inputToolbarWidth + inputToolbarPadding : 0, + toolbarsHeight: this.options.renderStyle === 'compact' ? 0 : 22, + chatEditingStateHeight: this.chatEditingSessionWidgetContainer.offsetHeight, + sideToolbarWidth: this.inputSideToolbarContainer ? dom.getTotalWidth(this.inputSideToolbarContainer) + 4 /*gap*/ : 0, }; } @@ -2269,10 +1333,7 @@ export class ChatInputPart const historyKeyFn = (entry: IChatHistoryEntry) => JSON.stringify(entry); function getLastPosition(model: ITextModel): IPosition { - return { - lineNumber: model.getLineCount(), - column: model.getLineLength(model.getLineCount()) + 1, - }; + return { lineNumber: model.getLineCount(), column: model.getLineLength(model.getLineCount()) + 1 }; } // This does seems like a lot just to customize an item with dropdown. This whole class exists just because we need an @@ -2289,45 +1350,31 @@ class ChatSubmitDropdownActionItem extends DropdownWithPrimaryActionViewItem { @IKeybindingService keybindingService: IKeybindingService, @INotificationService notificationService: INotificationService, @IThemeService themeService: IThemeService, - @IAccessibilityService accessibilityService: IAccessibilityService, + @IAccessibilityService accessibilityService: IAccessibilityService ) { super( action, dropdownAction, [], - "", + '', { ...options, - getKeyBinding: (action: IAction) => - keybindingService.lookupKeybinding( - action.id, - contextKeyService, - ), + getKeyBinding: (action: IAction) => keybindingService.lookupKeybinding(action.id, contextKeyService) }, contextMenuService, keybindingService, notificationService, contextKeyService, themeService, - accessibilityService, - ); - const menu = menuService.createMenu( - MenuId.ChatExecuteSecondary, - contextKeyService, - ); + accessibilityService); + const menu = menuService.createMenu(MenuId.ChatExecuteSecondary, contextKeyService); const setActions = () => { - const secondary = getFlatActionBarActions( - menu.getActions({ shouldForwardArgs: true }), - ); + const secondary = getFlatActionBarActions(menu.getActions({ shouldForwardArgs: true })); const secondaryAgent = chatAgentService.getSecondaryAgent(); if (secondaryAgent) { - secondary.forEach((a) => { + secondary.forEach(a => { if (a.id === ChatSubmitSecondaryAgentAction.ID) { - a.label = localize( - "chat.submitToSecondaryAgent", - "Send to @{0}", - secondaryAgent.name, - ); + a.label = localize('chat.submitToSecondaryAgent', "Send to @{0}", secondaryAgent.name); } return a; @@ -2357,27 +1404,15 @@ class ModelPickerActionViewItem extends MenuEntryActionViewItem { @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, @IContextMenuService contextMenuService: IContextMenuService, - @ILanguageModelsService - private readonly _languageModelsService: ILanguageModelsService, - @IAccessibilityService _accessibilityService: IAccessibilityService, + @ILanguageModelsService private readonly _languageModelsService: ILanguageModelsService, + @IAccessibilityService _accessibilityService: IAccessibilityService ) { - super( - action, - options, - keybindingService, - notificationService, - contextKeyService, - themeService, - contextMenuService, - _accessibilityService, - ); - - this._register( - delegate.onDidChangeModel((modelId) => { - this.currentLanguageModel = modelId; - this.updateLabel(); - }), - ); + super(action, options, keybindingService, notificationService, contextKeyService, themeService, contextMenuService, _accessibilityService); + + this._register(delegate.onDidChangeModel(modelId => { + this.currentLanguageModel = modelId; + this.updateLabel(); + })); } override async onClick(event: MouseEvent): Promise { @@ -2386,46 +1421,32 @@ class ModelPickerActionViewItem extends MenuEntryActionViewItem { override render(container: HTMLElement): void { super.render(container); - container.classList.add("chat-modelPicker-item"); + container.classList.add('chat-modelPicker-item'); // TODO@roblourens this should be a DropdownMenuActionViewItem, but we can't customize how it's rendered yet. - this._register( - dom.addDisposableListener(container, dom.EventType.KEY_UP, (e) => { - const event = new StandardKeyboardEvent(e); - if ( - event.equals(KeyCode.Enter) || - event.equals(KeyCode.Space) - ) { - this._openContextMenu(); - } - }), - ); + this._register(dom.addDisposableListener(container, dom.EventType.KEY_UP, e => { + const event = new StandardKeyboardEvent(e); + if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { + this._openContextMenu(); + } + })); } protected override updateLabel(): void { if (this.label) { - const model = this._languageModelsService.lookupLanguageModel( - this.currentLanguageModel, - ); + const model = this._languageModelsService.lookupLanguageModel(this.currentLanguageModel); if (model) { - dom.reset( - this.label, - dom.$("span.chat-model-label", undefined, model.name), - ...renderLabelWithIcons(`$(chevron-down)`), - ); + dom.reset(this.label, dom.$('span.chat-model-label', undefined, model.name), ...renderLabelWithIcons(`$(chevron-down)`)); } } } private _openContextMenu() { - const setLanguageModelAction = ( - id: string, - modelMetadata: ILanguageModelChatMetadata, - ): IAction => { + const setLanguageModelAction = (id: string, modelMetadata: ILanguageModelChatMetadata): IAction => { return { id, label: modelMetadata.name, - tooltip: "", + tooltip: '', class: undefined, enabled: true, checked: id === this.currentLanguageModel, @@ -2433,29 +1454,20 @@ class ModelPickerActionViewItem extends MenuEntryActionViewItem { this.currentLanguageModel = id; this.updateLabel(); this.delegate.setModel(id); - }, + } }; }; - const models = this._languageModelsService - .getLanguageModelIds() - .map((modelId) => ({ - id: modelId, - model: this._languageModelsService.lookupLanguageModel( - modelId, - )!, - })) - .filter((entry) => entry.model?.isUserSelectable); + const models = this._languageModelsService.getLanguageModelIds() + .map(modelId => ({ id: modelId, model: this._languageModelsService.lookupLanguageModel(modelId)! })) + .filter(entry => entry.model?.isUserSelectable); models.sort((a, b) => a.model.name.localeCompare(b.model.name)); this._contextMenuService.showContextMenu({ getAnchor: () => this.element!, - getActions: () => - models.map((entry) => - setLanguageModelAction(entry.id, entry.model), - ), + getActions: () => models.map(entry => setLanguageModelAction(entry.id, entry.model)), }); } } -const chatInputEditorContainerSelector = ".interactive-input-editor"; +const chatInputEditorContainerSelector = '.interactive-input-editor'; setupSimpleEditorSelectionStyling(chatInputEditorContainerSelector); diff --git a/Source/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts b/Source/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts new file mode 100644 index 0000000000000..a7845edbca55a --- /dev/null +++ b/Source/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts @@ -0,0 +1,427 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { coalesce, isNonEmptyArray } from '../../../../base/common/arrays.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { toErrorMessage } from '../../../../base/common/errorMessage.js'; +import { Event } from '../../../../base/common/event.js'; +import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; +import { Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; +import * as strings from '../../../../base/common/strings.js'; +import { localize, localize2 } from '../../../../nls.js'; +import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js'; +import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; +import { IProductService } from '../../../../platform/product/common/productService.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { ViewPaneContainer } from '../../../browser/parts/views/viewPaneContainer.js'; +import { IWorkbenchContribution } from '../../../common/contributions.js'; +import { IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer, ViewContainerLocation, Extensions as ViewExtensions } from '../../../common/views.js'; +import { isProposedApiEnabled } from '../../../services/extensions/common/extensions.js'; +import * as extensionsRegistry from '../../../services/extensions/common/extensionsRegistry.js'; +import { showExtensionsWithIdsCommandId } from '../../extensions/browser/extensionsActions.js'; +import { IExtension, IExtensionsWorkbenchService } from '../../extensions/common/extensions.js'; +import { ChatAgentLocation, IChatAgentData, IChatAgentService } from '../common/chatAgents.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; +import { IRawChatParticipantContribution } from '../common/chatParticipantContribTypes.js'; +import { ChatViewId } from './chat.js'; +import { CHAT_EDITING_SIDEBAR_PANEL_ID, CHAT_SIDEBAR_PANEL_ID, ChatViewPane } from './chatViewPane.js'; + +const chatParticipantExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'chatParticipants', + jsonSchema: { + description: localize('vscode.extension.contributes.chatParticipant', 'Contributes a chat participant'), + type: 'array', + items: { + additionalProperties: false, + type: 'object', + defaultSnippets: [{ body: { name: '', description: '' } }], + required: ['name', 'id'], + properties: { + id: { + description: localize('chatParticipantId', "A unique id for this chat participant."), + type: 'string' + }, + name: { + description: localize('chatParticipantName', "User-facing name for this chat participant. The user will use '@' with this name to invoke the participant. Name must not contain whitespace."), + type: 'string', + pattern: '^[\\w-]+$' + }, + fullName: { + markdownDescription: localize('chatParticipantFullName', "The full name of this chat participant, which is shown as the label for responses coming from this participant. If not provided, {0} is used.", '`name`'), + type: 'string' + }, + description: { + description: localize('chatParticipantDescription', "A description of this chat participant, shown in the UI."), + type: 'string' + }, + isSticky: { + description: localize('chatCommandSticky', "Whether invoking the command puts the chat into a persistent mode, where the command is automatically added to the chat input for the next message."), + type: 'boolean' + }, + sampleRequest: { + description: localize('chatSampleRequest', "When the user clicks this participant in `/help`, this text will be submitted to the participant."), + type: 'string' + }, + when: { + description: localize('chatParticipantWhen', "A condition which must be true to enable this participant."), + type: 'string' + }, + disambiguation: { + description: localize('chatParticipantDisambiguation', "Metadata to help with automatically routing user questions to this chat participant."), + type: 'array', + items: { + additionalProperties: false, + type: 'object', + defaultSnippets: [{ body: { category: '', description: '', examples: [] } }], + required: ['category', 'description', 'examples'], + properties: { + category: { + markdownDescription: localize('chatParticipantDisambiguationCategory', "A detailed name for this category, e.g. `workspace_questions` or `web_questions`."), + type: 'string' + }, + description: { + description: localize('chatParticipantDisambiguationDescription', "A detailed description of the kinds of questions that are suitable for this chat participant."), + type: 'string' + }, + examples: { + description: localize('chatParticipantDisambiguationExamples', "A list of representative example questions that are suitable for this chat participant."), + type: 'array' + }, + } + } + }, + commands: { + markdownDescription: localize('chatCommandsDescription', "Commands available for this chat participant, which the user can invoke with a `/`."), + type: 'array', + items: { + additionalProperties: false, + type: 'object', + defaultSnippets: [{ body: { name: '', description: '' } }], + required: ['name'], + properties: { + name: { + description: localize('chatCommand', "A short name by which this command is referred to in the UI, e.g. `fix` or * `explain` for commands that fix an issue or explain code. The name should be unique among the commands provided by this participant."), + type: 'string' + }, + description: { + description: localize('chatCommandDescription', "A description of this command."), + type: 'string' + }, + when: { + description: localize('chatCommandWhen', "A condition which must be true to enable this command."), + type: 'string' + }, + sampleRequest: { + description: localize('chatCommandSampleRequest', "When the user clicks this command in `/help`, this text will be submitted to the participant."), + type: 'string' + }, + isSticky: { + description: localize('chatCommandSticky', "Whether invoking the command puts the chat into a persistent mode, where the command is automatically added to the chat input for the next message."), + type: 'boolean' + }, + disambiguation: { + description: localize('chatCommandDisambiguation', "Metadata to help with automatically routing user questions to this chat command."), + type: 'array', + items: { + additionalProperties: false, + type: 'object', + defaultSnippets: [{ body: { category: '', description: '', examples: [] } }], + required: ['category', 'description', 'examples'], + properties: { + category: { + markdownDescription: localize('chatCommandDisambiguationCategory', "A detailed name for this category, e.g. `workspace_questions` or `web_questions`."), + type: 'string' + }, + description: { + description: localize('chatCommandDisambiguationDescription', "A detailed description of the kinds of questions that are suitable for this chat command."), + type: 'string' + }, + examples: { + description: localize('chatCommandDisambiguationExamples', "A list of representative example questions that are suitable for this chat command."), + type: 'array' + }, + } + } + } + } + } + }, + } + } + }, + activationEventsGenerator: (contributions: IRawChatParticipantContribution[], result: { push(item: string): void }) => { + for (const contrib of contributions) { + result.push(`onChatParticipant:${contrib.id}`); + } + }, +}); + +export class ChatExtensionPointHandler implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.chatExtensionPointHandler'; + + private _viewContainer: ViewContainer; + private _participantRegistrationDisposables = new DisposableMap(); + + constructor( + @IChatAgentService private readonly _chatAgentService: IChatAgentService, + @ILogService private readonly logService: ILogService + ) { + this._viewContainer = this.registerViewContainer(); + this.registerDefaultParticipantView(); + this.registerChatEditingView(); + this.handleAndRegisterChatExtensions(); + } + + private handleAndRegisterChatExtensions(): void { + chatParticipantExtensionPoint.setHandler((extensions, delta) => { + for (const extension of delta.added) { + for (const providerDescriptor of extension.value) { + if (!providerDescriptor.name?.match(/^[\w-]+$/)) { + this.logService.error(`Extension '${extension.description.identifier.value}' CANNOT register participant with invalid name: ${providerDescriptor.name}. Name must match /^[\\w-]+$/.`); + continue; + } + + if (providerDescriptor.fullName && strings.AmbiguousCharacters.getInstance(new Set()).containsAmbiguousCharacter(providerDescriptor.fullName)) { + this.logService.error(`Extension '${extension.description.identifier.value}' CANNOT register participant with fullName that contains ambiguous characters: ${providerDescriptor.fullName}.`); + continue; + } + + // Spaces are allowed but considered "invisible" + if (providerDescriptor.fullName && strings.InvisibleCharacters.containsInvisibleCharacter(providerDescriptor.fullName.replace(/ /g, ''))) { + this.logService.error(`Extension '${extension.description.identifier.value}' CANNOT register participant with fullName that contains invisible characters: ${providerDescriptor.fullName}.`); + continue; + } + + if (providerDescriptor.isDefault && !isProposedApiEnabled(extension.description, 'defaultChatParticipant')) { + this.logService.error(`Extension '${extension.description.identifier.value}' CANNOT use API proposal: defaultChatParticipant.`); + continue; + } + + if ((providerDescriptor.defaultImplicitVariables || providerDescriptor.locations) && !isProposedApiEnabled(extension.description, 'chatParticipantAdditions')) { + this.logService.error(`Extension '${extension.description.identifier.value}' CANNOT use API proposal: chatParticipantAdditions.`); + continue; + } + + if (!providerDescriptor.id || !providerDescriptor.name) { + this.logService.error(`Extension '${extension.description.identifier.value}' CANNOT register participant without both id and name.`); + continue; + } + + const participantsDisambiguation: { + category: string; + description: string; + examples: string[]; + }[] = []; + + if (providerDescriptor.disambiguation?.length) { + participantsDisambiguation.push(...providerDescriptor.disambiguation.map((d) => ({ + ...d, category: d.category ?? d.categoryName + }))); + } + + try { + const store = new DisposableStore(); + store.add(this._chatAgentService.registerAgent( + providerDescriptor.id, + { + extensionId: extension.description.identifier, + publisherDisplayName: extension.description.publisherDisplayName ?? extension.description.publisher, // May not be present in OSS + extensionPublisherId: extension.description.publisher, + extensionDisplayName: extension.description.displayName ?? extension.description.name, + id: providerDescriptor.id, + description: providerDescriptor.description, + when: providerDescriptor.when, + metadata: { + isSticky: providerDescriptor.isSticky, + sampleRequest: providerDescriptor.sampleRequest, + }, + name: providerDescriptor.name, + fullName: providerDescriptor.fullName, + isDefault: providerDescriptor.isDefault, + locations: isNonEmptyArray(providerDescriptor.locations) ? + providerDescriptor.locations.map(ChatAgentLocation.fromRaw) : + [ChatAgentLocation.Panel], + slashCommands: providerDescriptor.commands ?? [], + disambiguation: coalesce(participantsDisambiguation.flat()), + } satisfies IChatAgentData)); + + this._participantRegistrationDisposables.set( + getParticipantKey(extension.description.identifier, providerDescriptor.id), + store + ); + } catch (e) { + this.logService.error(`Failed to register participant ${providerDescriptor.id}: ${toErrorMessage(e, true)}`); + } + } + } + + for (const extension of delta.removed) { + for (const providerDescriptor of extension.value) { + this._participantRegistrationDisposables.deleteAndDispose(getParticipantKey(extension.description.identifier, providerDescriptor.id)); + } + } + }); + } + + private registerViewContainer(): ViewContainer { + // Register View Container + const title = localize2('chat.viewContainer.label', "Chat"); + const icon = Codicon.commentDiscussion; + const viewContainerId = CHAT_SIDEBAR_PANEL_ID; + const viewContainer: ViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ + id: viewContainerId, + title, + icon, + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [viewContainerId, { mergeViewWithContainerWhenSingleView: true }]), + storageId: viewContainerId, + hideIfEmpty: true, + order: 100, + }, ViewContainerLocation.AuxiliaryBar, { doNotRegisterOpenCommand: true }); + + return viewContainer; + } + + private registerDefaultParticipantView(): IDisposable { + // Register View. Name must be hardcoded because we want to show it even when the extension fails to load due to an API version incompatibility. + const name = 'GitHub Copilot'; + const viewDescriptor: IViewDescriptor[] = [{ + id: ChatViewId, + containerIcon: this._viewContainer.icon, + containerTitle: this._viewContainer.title.value, + singleViewPaneContainerTitle: this._viewContainer.title.value, + name: { value: name, original: name }, + canToggleVisibility: false, + canMoveView: true, + openCommandActionDescriptor: { + id: CHAT_SIDEBAR_PANEL_ID, + title: this._viewContainer.title, + mnemonicTitle: localize({ key: 'miToggleChat', comment: ['&& denotes a mnemonic'] }, "&&Chat"), + keybindings: { + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyI, + mac: { + primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KeyI + } + }, + order: 1 + }, + ctorDescriptor: new SyncDescriptor(ChatViewPane, [{ location: ChatAgentLocation.Panel }]), + when: ContextKeyExpr.or( + ChatContextKeys.Setup.triggered, + ChatContextKeys.Setup.installed, + ChatContextKeys.panelParticipantRegistered, + ChatContextKeys.extensionInvalid + ) + }]; + Registry.as(ViewExtensions.ViewsRegistry).registerViews(viewDescriptor, this._viewContainer); + + return toDisposable(() => { + Registry.as(ViewExtensions.ViewsRegistry).deregisterViews(viewDescriptor, this._viewContainer); + }); + } + + private registerChatEditingView(): IDisposable { + const title = localize2('chatEditing.viewContainer.label', "Copilot Edits"); + const icon = Codicon.editSession; + const viewContainerId = CHAT_EDITING_SIDEBAR_PANEL_ID; + const viewContainer: ViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ + id: viewContainerId, + title, + icon, + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [viewContainerId, { mergeViewWithContainerWhenSingleView: true }]), + storageId: viewContainerId, + hideIfEmpty: true, + order: 101, + }, ViewContainerLocation.AuxiliaryBar, { doNotRegisterOpenCommand: true }); + + const id = 'workbench.panel.chat.view.edits'; + const viewDescriptor: IViewDescriptor[] = [{ + id, + containerIcon: viewContainer.icon, + containerTitle: title.value, + singleViewPaneContainerTitle: title.value, + name: { value: title.value, original: title.value }, + canToggleVisibility: false, + canMoveView: true, + openCommandActionDescriptor: { + id: viewContainerId, + title, + mnemonicTitle: localize({ key: 'miToggleEdits', comment: ['&& denotes a mnemonic'] }, "Copilot Ed&&its"), + keybindings: { + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyI, + linux: { + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.KeyI + } + }, + order: 2 + }, + ctorDescriptor: new SyncDescriptor(ChatViewPane, [{ location: ChatAgentLocation.EditingSession }]), + when: ContextKeyExpr.or( + ChatContextKeys.Setup.installed, + ChatContextKeys.editingParticipantRegistered + ) + }]; + Registry.as(ViewExtensions.ViewsRegistry).registerViews(viewDescriptor, viewContainer); + + return toDisposable(() => { + Registry.as(ViewExtensions.ViewContainersRegistry).deregisterViewContainer(viewContainer); + Registry.as(ViewExtensions.ViewsRegistry).deregisterViews(viewDescriptor, viewContainer); + }); + } +} + +function getParticipantKey(extensionId: ExtensionIdentifier, participantName: string): string { + return `${extensionId.value}_${participantName}`; +} + +export class ChatCompatibilityNotifier extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.chatCompatNotifier'; + + private registeredWelcomeView = false; + + constructor( + @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, + @IContextKeyService contextKeyService: IContextKeyService, + @IProductService private readonly productService: IProductService, + ) { + super(); + + // It may be better to have some generic UI for this, for any extension that is incompatible, + // but this is only enabled for Copilot Chat now and it needs to be obvious. + const isInvalid = ChatContextKeys.extensionInvalid.bindTo(contextKeyService); + this._register(Event.runAndSubscribe( + extensionsWorkbenchService.onDidChangeExtensionsNotification, + () => { + const notification = extensionsWorkbenchService.getExtensionsNotification(); + const chatExtension = notification?.extensions.find(ext => ExtensionIdentifier.equals(ext.identifier.id, this.productService.defaultChatAgent?.chatExtensionId)); + if (chatExtension) { + isInvalid.set(true); + this.registerWelcomeView(chatExtension); + } else { + isInvalid.set(false); + } + } + )); + } + + private registerWelcomeView(chatExtension: IExtension) { + if (this.registeredWelcomeView) { + return; + } + + this.registeredWelcomeView = true; + const showExtensionLabel = localize('showExtension', "Show Extension"); + const mainMessage = localize('chatFailErrorMessage', "Chat failed to load because the installed version of the {0} extension is not compatible with this version of {1}. Please ensure that the {2} extension is up to date.", this.productService.defaultChatAgent?.chatName, this.productService.nameLong, this.productService.defaultChatAgent?.chatName); + const commandButton = `[${showExtensionLabel}](command:${showExtensionsWithIdsCommandId}?${encodeURIComponent(JSON.stringify([[this.productService.defaultChatAgent?.chatExtensionId]]))})`; + const versionMessage = `${this.productService.defaultChatAgent?.chatName} version: ${chatExtension.version}`; + const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); + this._register(viewsRegistry.registerViewWelcomeContent(ChatViewId, { + content: [mainMessage, commandButton, versionMessage].join('\n\n'), + when: ChatContextKeys.extensionInvalid, + })); + } +} diff --git a/Source/vs/workbench/contrib/chat/browser/chatPasteProviders.ts b/Source/vs/workbench/contrib/chat/browser/chatPasteProviders.ts index 86ab46fd7ff7d..62350a6cb09d3 100644 --- a/Source/vs/workbench/contrib/chat/browser/chatPasteProviders.ts +++ b/Source/vs/workbench/contrib/chat/browser/chatPasteProviders.ts @@ -3,77 +3,60 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken } from "../../../../base/common/cancellation.js"; -import { Codicon } from "../../../../base/common/codicons.js"; -import { - IDataTransferItem, - IReadonlyVSDataTransfer, -} from "../../../../base/common/dataTransfer.js"; -import { HierarchicalKind } from "../../../../base/common/hierarchicalKind.js"; -import { Disposable } from "../../../../base/common/lifecycle.js"; -import { IRange } from "../../../../editor/common/core/range.js"; -import { - DocumentPasteContext, - DocumentPasteEditProvider, - DocumentPasteEditsSession, -} from "../../../../editor/common/languages.js"; -import { ITextModel } from "../../../../editor/common/model.js"; -import { ILanguageFeaturesService } from "../../../../editor/common/services/languageFeatures.js"; -import { localize } from "../../../../nls.js"; -import { - IExtensionService, - isProposedApiEnabled, -} from "../../../services/extensions/common/extensions.js"; -import { IChatRequestVariableEntry } from "../common/chatModel.js"; -import { IChatWidgetService } from "./chat.js"; -import { ChatInputPart } from "./chatInputPart.js"; +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { createStringDataTransferItem, IDataTransferItem, IReadonlyVSDataTransfer, VSDataTransfer } from '../../../../base/common/dataTransfer.js'; +import { HierarchicalKind } from '../../../../base/common/hierarchicalKind.js'; +import { IRange } from '../../../../editor/common/core/range.js'; +import { DocumentPasteContext, DocumentPasteEditProvider, DocumentPasteEditsSession } from '../../../../editor/common/languages.js'; +import { ITextModel } from '../../../../editor/common/model.js'; +import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { ChatInputPart } from './chatInputPart.js'; +import { IChatWidgetService } from './chat.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { localize } from '../../../../nls.js'; +import { IChatRequestVariableEntry } from '../common/chatModel.js'; +import { IExtensionService, isProposedApiEnabled } from '../../../services/extensions/common/extensions.js'; +import { Mimes } from '../../../../base/common/mime.js'; +import { URI } from '../../../../base/common/uri.js'; + +const COPY_MIME_TYPES = 'application/vnd.code.additional-editor-data'; export class PasteImageProvider implements DocumentPasteEditProvider { - public readonly kind = new HierarchicalKind("image"); - public readonly copyMimeTypes = ["image/*"]; + + public readonly kind = new HierarchicalKind('chat.attach.image'); public readonly providedPasteEditKinds = [this.kind]; - public readonly pasteMimeTypes = ["image/*"]; + + public readonly copyMimeTypes = []; + public readonly pasteMimeTypes = ['image/*']; constructor( private readonly chatWidgetService: IChatWidgetService, private readonly extensionService: IExtensionService, - ) {} - - async provideDocumentPasteEdits( - _model: ITextModel, - _ranges: readonly IRange[], - dataTransfer: IReadonlyVSDataTransfer, - context: DocumentPasteContext, - token: CancellationToken, - ): Promise { - if ( - !this.extensionService.extensions.some((ext) => - isProposedApiEnabled(ext, "chatReferenceBinaryData"), - ) - ) { + ) { } + + async provideDocumentPasteEdits(model: ITextModel, ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, context: DocumentPasteContext, token: CancellationToken): Promise { + if (!this.extensionService.extensions.some(ext => isProposedApiEnabled(ext, 'chatReferenceBinaryData'))) { return; } const supportedMimeTypes = [ - "image/png", - "image/jpeg", - "image/jpg", - "image/bmp", - "image/gif", - "image/tiff", + 'image/png', + 'image/jpeg', + 'image/jpg', + 'image/bmp', + 'image/gif', + 'image/tiff' ]; let mimeType: string | undefined; - let imageItem: IDataTransferItem | undefined; // Find the first matching image type in the dataTransfer for (const type of supportedMimeTypes) { imageItem = dataTransfer.get(type); - if (imageItem) { mimeType = type; - break; } } @@ -82,39 +65,24 @@ export class PasteImageProvider implements DocumentPasteEditProvider { return; } const currClipboard = await imageItem.asFile()?.data(); - if (token.isCancellationRequested || !currClipboard) { return; } - const widget = this.chatWidgetService.getWidgetByInputUri(_model.uri); - + const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); if (!widget) { return; } const attachedVariables = widget.attachmentModel.attachments; - - const displayName = localize("pastedImageName", "Pasted Image"); - + const displayName = localize('pastedImageName', 'Pasted Image'); let tempDisplayName = displayName; - for ( - let appendValue = 2; - attachedVariables.some( - (attachment) => attachment.name === tempDisplayName, - ); - appendValue++ - ) { + for (let appendValue = 2; attachedVariables.some(attachment => attachment.name === tempDisplayName); appendValue++) { tempDisplayName = `${displayName} ${appendValue}`; } - const imageContext = await getImageAttachContext( - currClipboard, - mimeType, - token, - tempDisplayName, - ); + const imageContext = await getImageAttachContext(currClipboard, mimeType, token, tempDisplayName); if (token.isCancellationRequested || !imageContext) { return; @@ -122,25 +90,16 @@ export class PasteImageProvider implements DocumentPasteEditProvider { // Make sure to attach only new contexts const currentContextIds = widget.attachmentModel.getAttachmentIDs(); - if (currentContextIds.has(imageContext.id)) { return; } - widget.attachmentModel.addContext(imageContext); - - return; + return getCustomPaste(model, imageContext, mimeType, this.kind, localize('pastedImageAttachment', 'Pasted Image Attachment'), this.chatWidgetService); } } -async function getImageAttachContext( - data: Uint8Array, - mimeType: string, - token: CancellationToken, - displayName: string, -): Promise { +async function getImageAttachContext(data: Uint8Array, mimeType: string, token: CancellationToken, displayName: string): Promise { const imageHash = await imageToHash(data); - if (token.isCancellationRequested) { return undefined; } @@ -152,17 +111,14 @@ async function getImageAttachContext( isImage: true, icon: Codicon.fileMedia, isDynamic: true, - isFile: false, - mimeType, + mimeType }; } export async function imageToHash(data: Uint8Array): Promise { - const hashBuffer = await crypto.subtle.digest("SHA-256", data); - + const hashBuffer = await crypto.subtle.digest('SHA-256', data); const hashArray = Array.from(new Uint8Array(hashBuffer)); - - return hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); + return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); } export function isImage(array: Uint8Array): boolean { @@ -172,35 +128,147 @@ export function isImage(array: Uint8Array): boolean { // Magic numbers (identification bytes) for various image formats const identifier: { [key: string]: number[] } = { - png: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], - jpeg: [0xff, 0xd8, 0xff], - bmp: [0x42, 0x4d], + png: [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A], + jpeg: [0xFF, 0xD8, 0xFF], + bmp: [0x42, 0x4D], gif: [0x47, 0x49, 0x46, 0x38], - tiff: [0x49, 0x49, 0x2a, 0x00], + tiff: [0x49, 0x49, 0x2A, 0x00] }; return Object.values(identifier).some((signature) => - signature.every((byte, index) => array[index] === byte), + signature.every((byte, index) => array[index] === byte) ); } +export class CopyTextProvider implements DocumentPasteEditProvider { + public readonly providedPasteEditKinds = []; + public readonly copyMimeTypes = [COPY_MIME_TYPES]; + public readonly pasteMimeTypes = []; + + async prepareDocumentPaste(model: ITextModel, ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise { + if (model.uri.scheme === ChatInputPart.INPUT_SCHEME) { + return; + } + const customDataTransfer = new VSDataTransfer(); + const rangesString = JSON.stringify({ ranges: ranges[0], uri: model.uri.toString() }); + customDataTransfer.append(COPY_MIME_TYPES, createStringDataTransferItem(rangesString)); + return customDataTransfer; + } +} + +export class PasteTextProvider implements DocumentPasteEditProvider { + + public readonly kind = new HierarchicalKind('chat.attach.text'); + public readonly providedPasteEditKinds = [this.kind]; + + public readonly copyMimeTypes = []; + public readonly pasteMimeTypes = [COPY_MIME_TYPES]; + + constructor( + private readonly chatWidgetService: IChatWidgetService + ) { } + + async provideDocumentPasteEdits(model: ITextModel, ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, context: DocumentPasteContext, token: CancellationToken): Promise { + if (model.uri.scheme !== ChatInputPart.INPUT_SCHEME) { + return; + } + const text = dataTransfer.get(Mimes.text); + const editorData = dataTransfer.get('vscode-editor-data'); + const additionalEditorData = dataTransfer.get(COPY_MIME_TYPES); + + if (!editorData || !text || !additionalEditorData) { + return; + } + + const textdata = await text.asString(); + const metadata = JSON.parse(await editorData.asString()); + const additionalData = JSON.parse(await additionalEditorData.asString()); + + const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); + if (!widget) { + return; + } + + const copiedContext = await getCopiedContext(textdata, additionalData.uri, metadata.mode, additionalData.ranges); + + if (token.isCancellationRequested || !copiedContext) { + return; + } + + const currentContextIds = widget.attachmentModel.getAttachmentIDs(); + if (currentContextIds.has(copiedContext.id)) { + return; + } + + return getCustomPaste(model, copiedContext, Mimes.text, this.kind, localize('pastedCodeAttachment', 'Pasted Code Attachment'), this.chatWidgetService); + } +} + +async function getCopiedContext(code: string, file: string, language: string, ranges: IRange): Promise { + const fileName = file.split('/').pop() || 'unknown file'; + const start = ranges.startLineNumber; + const end = ranges.endLineNumber; + const resultText = `Copied Selection of Code: \n\n\n From the file: ${fileName} From lines ${start} to ${end} \n \`\`\`${code}\`\`\``; + const pastedLines = start === end ? localize('pastedAttachment.oneLine', '1 line') : localize('pastedAttachment.multipleLines', '{0} lines', end + 1 - start); + return { + kind: 'paste', + value: resultText, + id: `${fileName}${start}${end}${ranges.startColumn}${ranges.endColumn}`, + name: `${fileName} ${pastedLines}`, + icon: Codicon.code, + isDynamic: true, + pastedLines, + language, + fileName, + code, + references: [{ + reference: URI.parse(file), + kind: 'reference' + }] + }; +} + +async function getCustomPaste(model: ITextModel, context: IChatRequestVariableEntry, handledMimeType: string, kind: HierarchicalKind, title: string, chatWidgetService: IChatWidgetService): Promise { + const customEdit = { + resource: model.uri, + variable: context, + undo: () => { + const widget = chatWidgetService.getWidgetByInputUri(model.uri); + if (!widget) { + throw new Error('No widget found for undo'); + } + widget.attachmentModel.delete(context.id); + }, + redo: () => { + const widget = chatWidgetService.getWidgetByInputUri(model.uri); + if (!widget) { + throw new Error('No widget found for redo'); + } + widget.attachmentModel.addContext(context); + }, + metadata: { needsConfirmation: false, label: context.name } + }; + + return { + edits: [{ + insertText: '', title, kind, handledMimeType, + additionalEdit: { + edits: [customEdit], + } + }], + dispose() { }, + }; +} + export class ChatPasteProvidersFeature extends Disposable { constructor( - @ILanguageFeaturesService - languageFeaturesService: ILanguageFeaturesService, + @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, @IChatWidgetService chatWidgetService: IChatWidgetService, - @IExtensionService extensionService: IExtensionService, + @IExtensionService extensionService: IExtensionService ) { super(); - this._register( - languageFeaturesService.documentPasteEditProvider.register( - { - scheme: ChatInputPart.INPUT_SCHEME, - pattern: "*", - hasAccessToAllModels: true, - }, - new PasteImageProvider(chatWidgetService, extensionService), - ), - ); + this._register(languageFeaturesService.documentPasteEditProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, pattern: '*', hasAccessToAllModels: true }, new PasteImageProvider(chatWidgetService, extensionService))); + this._register(languageFeaturesService.documentPasteEditProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, pattern: '*', hasAccessToAllModels: true }, new PasteTextProvider(chatWidgetService))); + this._register(languageFeaturesService.documentPasteEditProvider.register('*', new CopyTextProvider())); } } diff --git a/Source/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts b/Source/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts index 76717bbf1bf60..503c46cbd9a55 100644 --- a/Source/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts +++ b/Source/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts @@ -3,147 +3,95 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import "./media/chatViewSetup.css"; - -import { - $, - addDisposableListener, - EventType, - getActiveElement, - setVisibility, -} from "../../../../base/browser/dom.js"; -import { Button } from "../../../../base/browser/ui/button/button.js"; -import { Checkbox } from "../../../../base/browser/ui/toggle/toggle.js"; -import { - CancellationToken, - CancellationTokenSource, -} from "../../../../base/common/cancellation.js"; -import { Codicon } from "../../../../base/common/codicons.js"; -import { isCancellationError } from "../../../../base/common/errors.js"; -import { Emitter, Event } from "../../../../base/common/event.js"; -import { MarkdownString } from "../../../../base/common/htmlContent.js"; -import { Disposable, toDisposable } from "../../../../base/common/lifecycle.js"; -import { IRequestContext } from "../../../../base/parts/request/common/request.js"; -import { ServicesAccessor } from "../../../../editor/browser/editorExtensions.js"; -import { MarkdownRenderer } from "../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js"; -import { localize, localize2 } from "../../../../nls.js"; -import { - Action2, - MenuId, - registerAction2, -} from "../../../../platform/actions/common/actions.js"; -import { IConfigurationService } from "../../../../platform/configuration/common/configuration.js"; -import { - ContextKeyExpr, - IContextKeyService, -} from "../../../../platform/contextkey/common/contextkey.js"; -import { IDialogService } from "../../../../platform/dialogs/common/dialogs.js"; -import { IExtensionManagementService } from "../../../../platform/extensionManagement/common/extensionManagement.js"; -import { ExtensionIdentifier } from "../../../../platform/extensions/common/extensions.js"; -import { IInstantiationService } from "../../../../platform/instantiation/common/instantiation.js"; -import { ILogService, LogLevel } from "../../../../platform/log/common/log.js"; -import product from "../../../../platform/product/common/product.js"; -import { IProductService } from "../../../../platform/product/common/productService.js"; -import { Registry } from "../../../../platform/registry/common/platform.js"; -import { - asText, - IRequestService, -} from "../../../../platform/request/common/request.js"; -import { - IStorageService, - StorageScope, - StorageTarget, -} from "../../../../platform/storage/common/storage.js"; -import { - ITelemetryService, - TelemetryLevel, -} from "../../../../platform/telemetry/common/telemetry.js"; -import { - defaultButtonStyles, - defaultCheckboxStyles, -} from "../../../../platform/theme/browser/defaultStyles.js"; -import { IWorkspaceContextService } from "../../../../platform/workspace/common/workspace.js"; -import { - IWorkbenchContribution, - registerWorkbenchContribution2, - WorkbenchPhase, -} from "../../../common/contributions.js"; -import { - IViewDescriptorService, - ViewContainerLocation, -} from "../../../common/views.js"; -import { - AuthenticationSession, - IAuthenticationService, -} from "../../../services/authentication/common/authentication.js"; -import { IExtensionService } from "../../../services/extensions/common/extensions.js"; -import { - IWorkbenchLayoutService, - Parts, -} from "../../../services/layout/browser/layoutService.js"; -import { IViewsService } from "../../../services/views/common/viewsService.js"; -import { IExtensionsWorkbenchService } from "../../extensions/common/extensions.js"; -import { ChatContextKeys } from "../common/chatContextKeys.js"; -import { ChatViewId, showChatView } from "./chat.js"; -import { - ChatViewsWelcomeExtensions, - IChatViewsWelcomeContributionRegistry, -} from "./viewsWelcome/chatViewsWelcome.js"; +import './media/chatViewSetup.css'; +import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { ITelemetryService, TelemetryLevel } from '../../../../platform/telemetry/common/telemetry.js'; +import { AuthenticationSession, IAuthenticationService } from '../../../services/authentication/common/authentication.js'; +import { IProductService } from '../../../../platform/product/common/productService.js'; +import { IExtensionManagementService } from '../../../../platform/extensionManagement/common/extensionManagement.js'; +import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js'; +import { IExtensionService } from '../../../services/extensions/common/extensions.js'; +import { IRequestService, asText } from '../../../../platform/request/common/request.js'; +import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; +import { IRequestContext } from '../../../../base/parts/request/common/request.js'; +import { isCancellationError } from '../../../../base/common/errors.js'; +import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; +import { localize, localize2 } from '../../../../nls.js'; +import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { IWorkbenchLayoutService, Parts } from '../../../services/layout/browser/layoutService.js'; +import { IViewsService } from '../../../services/views/common/viewsService.js'; +import { IExtensionsWorkbenchService } from '../../extensions/common/extensions.js'; +import { showChatView, ChatViewId } from './chat.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; +import product from '../../../../platform/product/common/product.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { IChatViewsWelcomeContributionRegistry, ChatViewsWelcomeExtensions } from './viewsWelcome/chatViewsWelcome.js'; +import { IViewDescriptorService, ViewContainerLocation } from '../../../common/views.js'; +import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; +import { $, addDisposableListener, EventType, getActiveElement, setVisibility } from '../../../../base/browser/dom.js'; +import { ILogService, LogLevel } from '../../../../platform/log/common/log.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; +import { Checkbox } from '../../../../base/browser/ui/toggle/toggle.js'; +import { defaultButtonStyles, defaultCheckboxStyles } from '../../../../platform/theme/browser/defaultStyles.js'; +import { Button } from '../../../../base/browser/ui/button/button.js'; +import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { MarkdownString } from '../../../../base/common/htmlContent.js'; +import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js'; +import { Barrier, timeout } from '../../../../base/common/async.js'; +import { IChatAgentService } from '../common/chatAgents.js'; +import { IActivityService, ProgressBadge } from '../../../services/activity/common/activity.js'; +import { CHAT_SIDEBAR_PANEL_ID } from './chatViewPane.js'; const defaultChat = { - extensionId: product.defaultChatAgent?.extensionId ?? "", - name: product.defaultChatAgent?.name ?? "", - icon: Codicon[ - (product.defaultChatAgent?.icon as keyof typeof Codicon) ?? - "commentDiscussion" - ], - chatWelcomeTitle: product.defaultChatAgent?.chatWelcomeTitle ?? "", - documentationUrl: product.defaultChatAgent?.documentationUrl ?? "", - privacyStatementUrl: product.defaultChatAgent?.privacyStatementUrl ?? "", - collectionDocumentationUrl: - product.defaultChatAgent?.collectionDocumentationUrl ?? "", - skusDocumentationUrl: product.defaultChatAgent?.skusDocumentationUrl ?? "", - providerId: product.defaultChatAgent?.providerId ?? "", - providerName: product.defaultChatAgent?.providerName ?? "", + extensionId: product.defaultChatAgent?.extensionId ?? '', + chatExtensionId: product.defaultChatAgent?.chatExtensionId ?? '', + name: product.defaultChatAgent?.name ?? '', + icon: Codicon[product.defaultChatAgent?.icon as keyof typeof Codicon ?? 'commentDiscussion'], + chatWelcomeTitle: product.defaultChatAgent?.chatWelcomeTitle ?? '', + documentationUrl: product.defaultChatAgent?.documentationUrl ?? '', + privacyStatementUrl: product.defaultChatAgent?.privacyStatementUrl ?? '', + skusDocumentationUrl: product.defaultChatAgent?.skusDocumentationUrl ?? '', + providerId: product.defaultChatAgent?.providerId ?? '', + providerName: product.defaultChatAgent?.providerName ?? '', providerScopes: product.defaultChatAgent?.providerScopes ?? [[]], - entitlementUrl: product.defaultChatAgent?.entitlementUrl ?? "", - entitlementChatEnabled: - product.defaultChatAgent?.entitlementChatEnabled ?? "", - entitlementSkuLimitedUrl: - product.defaultChatAgent?.entitlementSkuLimitedUrl ?? "", - entitlementSkuLimitedEnabled: - product.defaultChatAgent?.entitlementSkuLimitedEnabled ?? "", + entitlementUrl: product.defaultChatAgent?.entitlementUrl ?? '', + entitlementChatEnabled: product.defaultChatAgent?.entitlementChatEnabled ?? '', + entitlementSignupLimitedUrl: product.defaultChatAgent?.entitlementSignupLimitedUrl ?? '', + entitlementCanSignupLimited: product.defaultChatAgent?.entitlementCanSignupLimited ?? '', + entitlementSkuType: product.defaultChatAgent?.entitlementSkuType ?? '', + entitlementSkuTypeLimited: product.defaultChatAgent?.entitlementSkuTypeLimited ?? '', + entitlementSkuTypeLimitedName: product.defaultChatAgent?.entitlementSkuTypeLimitedName ?? '' }; enum ChatEntitlement { /** Signed out */ Unknown = 1, - /** Not yet resolved */ + /** Signed in but not yet resolved if Sign-up possible */ Unresolved, /** Signed in and entitled to Sign-up */ Available, /** Signed in but not entitled to Sign-up */ - Unavailable, + Unavailable } //#region Contribution -class ChatSetupContribution - extends Disposable - implements IWorkbenchContribution -{ - private readonly chatSetupState = - this.instantiationService.createInstance(ChatSetupState); - private readonly entitlementsResolver = this._register( - this.instantiationService.createInstance(ChatSetupEntitlementResolver), - ); +class ChatSetupContribution extends Disposable implements IWorkbenchContribution { + + private readonly chatSetupContextKeys = this.instantiationService.createInstance(ChatSetupContextKeys); + private readonly entitlementsResolver = this._register(this.instantiationService.createInstance(ChatSetupEntitlementResolver, this.chatSetupContextKeys)); constructor( @IProductService private readonly productService: IProductService, - @IExtensionManagementService - private readonly extensionManagementService: IExtensionManagementService, - @IInstantiationService - private readonly instantiationService: IInstantiationService, + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IInstantiationService private readonly instantiationService: IInstantiationService, @IExtensionService private readonly extensionService: IExtensionService, ) { super(); @@ -158,61 +106,38 @@ class ChatSetupContribution } private registerChatWelcome(): void { - Registry.as( - ChatViewsWelcomeExtensions.ChatViewsWelcomeRegistry, - ).register({ + Registry.as(ChatViewsWelcomeExtensions.ChatViewsWelcomeRegistry).register({ title: defaultChat.chatWelcomeTitle, when: ContextKeyExpr.and( ChatContextKeys.Setup.triggered, - ChatContextKeys.Setup.installed.negate(), + ChatContextKeys.Setup.installed.negate() )!, icon: defaultChat.icon, - content: () => - ChatSetupWelcomeContent.getInstance( - this.instantiationService, - this.entitlementsResolver, - ).element, + content: () => ChatSetupWelcomeContent.getInstance(this.instantiationService, this.entitlementsResolver, this.chatSetupContextKeys).element, }); } private async checkExtensionInstallation(): Promise { - this._register( - this.extensionService.onDidChangeExtensions((result) => { - for (const extension of result.removed) { - if ( - ExtensionIdentifier.equals( - defaultChat.extensionId, - extension.identifier, - ) - ) { - this.chatSetupState.update({ chatInstalled: false }); - break; - } + this._register(this.extensionService.onDidChangeExtensions(result => { + for (const extension of result.removed) { + if (ExtensionIdentifier.equals(defaultChat.extensionId, extension.identifier)) { + this.chatSetupContextKeys.update({ chatInstalled: false }); + break; } + } - for (const extension of result.added) { - if ( - ExtensionIdentifier.equals( - defaultChat.extensionId, - extension.identifier, - ) - ) { - this.chatSetupState.update({ chatInstalled: true }); - break; - } + for (const extension of result.added) { + if (ExtensionIdentifier.equals(defaultChat.extensionId, extension.identifier)) { + this.chatSetupContextKeys.update({ chatInstalled: true }); + break; } - }), - ); + } + })); const extensions = await this.extensionManagementService.getInstalled(); - const chatInstalled = !!extensions.find((value) => - ExtensionIdentifier.equals( - value.identifier.id, - defaultChat.extensionId, - ), - ); - this.chatSetupState.update({ chatInstalled }); + const chatInstalled = !!extensions.find(value => ExtensionIdentifier.equals(value.identifier.id, defaultChat.extensionId)); + this.chatSetupContextKeys.update({ chatInstalled }); } } @@ -221,263 +146,172 @@ class ChatSetupContribution //#region Entitlements Resolver type ChatSetupEntitlementClassification = { - entitled: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "Flag indicating if the user is chat setup entitled"; - }; - entitlement: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "Flag indicating the chat entitlement state"; - }; - owner: "bpasero"; - comment: "Reporting chat setup entitlements"; + entitlement: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Flag indicating the chat entitlement state' }; + entitled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Flag indicating if the user is chat setup entitled' }; + limited: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Flag indicating if the user is chat setup limited' }; + owner: 'bpasero'; + comment: 'Reporting chat setup entitlements'; }; type ChatSetupEntitlementEvent = { - entitled: boolean; entitlement: ChatEntitlement; + entitled: boolean; + limited: boolean; }; class ChatSetupEntitlementResolver extends Disposable { + private _entitlement = ChatEntitlement.Unknown; - get entitlement() { - return this._entitlement; - } + get entitlement() { return this._entitlement; } - private readonly _onDidChangeEntitlement = this._register( - new Emitter(), - ); + private readonly _onDidChangeEntitlement = this._register(new Emitter()); readonly onDidChangeEntitlement = this._onDidChangeEntitlement.event; - private readonly chatSetupEntitledContextKey = - ChatContextKeys.Setup.entitled.bindTo(this.contextKeyService); - + private pendingResolveCts = new CancellationTokenSource(); private resolvedEntitlement: ChatEntitlement | undefined = undefined; constructor( - @IContextKeyService - private readonly contextKeyService: IContextKeyService, + private readonly chatSetupContextKeys: ChatSetupContextKeys, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IAuthenticationService - private readonly authenticationService: IAuthenticationService, - @IInstantiationService - private readonly instantiationService: IInstantiationService, + @IAuthenticationService private readonly authenticationService: IAuthenticationService, + @IInstantiationService private readonly instantiationService: IInstantiationService, @ILogService private readonly logService: ILogService, ) { super(); - this.registerEntitlementListeners(); - this.registerAuthListeners(); + this.registerListeners(); - this.handleDeclaredAuthProviders(); + this.resolve(); } - private registerEntitlementListeners(): void { - this._register( - this.authenticationService.onDidChangeSessions((e) => { - if (e.providerId === defaultChat.providerId) { - if (e.event.added?.length) { - this.resolveEntitlement(e.event.added.at(0)); - } else if (e.event.removed?.length) { - this.resolvedEntitlement = undefined; - this.update(this.toEntitlement(false)); - } - } - }), - ); - - this._register( - this.authenticationService.onDidRegisterAuthenticationProvider( - async (e) => { - if (e.id === defaultChat.providerId) { - this.resolveEntitlement( - ( - await this.authenticationService.getSessions( - e.id, - ) - ).at(0), - ); - } - }, - ), - ); - } + private registerListeners(): void { + this._register(this.authenticationService.onDidChangeDeclaredProviders(() => this.resolve())); - private registerAuthListeners(): void { - this._register( - this.authenticationService.onDidChangeDeclaredProviders(() => - this.handleDeclaredAuthProviders(), - ), - ); - this._register( - this.authenticationService.onDidRegisterAuthenticationProvider(() => - this.handleDeclaredAuthProviders(), - ), - ); - - this._register( - this.authenticationService.onDidChangeSessions( - async ({ providerId }) => { - if (providerId === defaultChat.providerId) { - this.update( - this.toEntitlement( - await this.hasProviderSessions(), - ), - ); - } - }, - ), - ); + this._register(this.authenticationService.onDidChangeSessions(e => { + if (e.providerId === defaultChat.providerId) { + this.resolve(); + } + })); + + this._register(this.authenticationService.onDidRegisterAuthenticationProvider(e => { + if (e.id === defaultChat.providerId) { + this.resolve(); + } + })); + + this._register(this.authenticationService.onDidUnregisterAuthenticationProvider(e => { + if (e.id === defaultChat.providerId) { + this.resolve(); + } + })); } - private toEntitlement( - hasSession: boolean, - skuLimitedAvailable?: boolean, - ): ChatEntitlement { - if (!hasSession) { - return ChatEntitlement.Unknown; - } + private async resolve(): Promise { + this.pendingResolveCts.dispose(true); + const cts = this.pendingResolveCts = new CancellationTokenSource(); - if (typeof this.resolvedEntitlement !== "undefined") { - return this.resolvedEntitlement; + const session = await this.findMatchingProviderSession(cts.token); + if (cts.token.isCancellationRequested) { + return; } - if (typeof skuLimitedAvailable === "boolean") { - return skuLimitedAvailable - ? ChatEntitlement.Available - : ChatEntitlement.Unavailable; + // Immediately signal whether we have a session or not + if (session) { + this.update(this.resolvedEntitlement ?? ChatEntitlement.Unresolved); + } else { + this.resolvedEntitlement = undefined; // reset resolved entitlement when there is no session + this.update(ChatEntitlement.Unknown); } - return ChatEntitlement.Unresolved; + if (session && typeof this.resolvedEntitlement === 'undefined') { + // Afterwards resolve entitlement with a network request + // but only unless it was not already resolved before. + await this.resolveEntitlement(session, cts.token); + } } - private async handleDeclaredAuthProviders(): Promise { - if ( - this.authenticationService.declaredProviders.find( - (provider) => provider.id === defaultChat.providerId, - ) - ) { - this.update(this.toEntitlement(await this.hasProviderSessions())); + private async findMatchingProviderSession(token: CancellationToken): Promise { + const sessions = await this.authenticationService.getSessions(defaultChat.providerId); + if (token.isCancellationRequested) { + return undefined; } - } - private async hasProviderSessions(): Promise { - const sessions = await this.authenticationService.getSessions( - defaultChat.providerId, - ); for (const session of sessions) { for (const scopes of defaultChat.providerScopes) { if (this.scopesMatch(session.scopes, scopes)) { - return true; + return session; } } } - return false; + return undefined; } - private scopesMatch( - scopes: ReadonlyArray, - expectedScopes: string[], - ): boolean { - return ( - scopes.length === expectedScopes.length && - expectedScopes.every((scope) => scopes.includes(scope)) - ); + private scopesMatch(scopes: ReadonlyArray, expectedScopes: string[]): boolean { + return scopes.length === expectedScopes.length && expectedScopes.every(scope => scopes.includes(scope)); } - private async resolveEntitlement( - session: AuthenticationSession | undefined, - ): Promise { - if (!session) { - return; + private async resolveEntitlement(session: AuthenticationSession, token: CancellationToken): Promise { + const entitlement = await this.doResolveEntitlement(session, token); + if (typeof entitlement === 'number' && !token.isCancellationRequested) { + this.resolvedEntitlement = entitlement; + this.update(entitlement); } - this.update(await this.doResolveEntitlement(session)); + return entitlement; } - private async doResolveEntitlement( - session: AuthenticationSession, - ): Promise { - if (this.resolvedEntitlement) { - return this.resolvedEntitlement; + private async doResolveEntitlement(session: AuthenticationSession, token: CancellationToken): Promise { + if (token.isCancellationRequested) { + return undefined; + } + + const response = await this.instantiationService.invokeFunction(accessor => ChatSetupRequestHelper.request(accessor, defaultChat.entitlementUrl, 'GET', undefined, session, token)); + if (token.isCancellationRequested) { + return undefined; } - const cts = new CancellationTokenSource(); - this._register(toDisposable(() => cts.dispose(true))); - - const response = await this.instantiationService.invokeFunction( - (accessor) => - ChatSetupRequestHelper.request( - accessor, - defaultChat.entitlementUrl, - "GET", - undefined, - session, - cts.token, - ), - ); if (!response) { - this.logService.trace("[chat setup] entitlement: no response"); + this.logService.trace('[chat setup] entitlement: no response'); return ChatEntitlement.Unresolved; } if (response.res.statusCode && response.res.statusCode !== 200) { - this.logService.trace( - `[chat setup] entitlement: unexpected status code ${response.res.statusCode}`, - ); + this.logService.trace(`[chat setup] entitlement: unexpected status code ${response.res.statusCode}`); return ChatEntitlement.Unresolved; } - const result = await asText(response); - if (!result) { - this.logService.trace( - "[chat setup] entitlement: response has no content", - ); + const responseText = await asText(response); + if (token.isCancellationRequested) { + return undefined; + } + + if (!responseText) { + this.logService.trace('[chat setup] entitlement: response has no content'); return ChatEntitlement.Unresolved; } let parsedResult: any; try { - parsedResult = JSON.parse(result); - this.logService.trace( - `[chat setup] entitlement: parsed result is ${JSON.stringify(parsedResult)}`, - ); + parsedResult = JSON.parse(responseText); + this.logService.trace(`[chat setup] entitlement: parsed result is ${JSON.stringify(parsedResult)}`); } catch (err) { - this.logService.trace( - `[chat setup] entitlement: error parsing response (${err})`, - ); + this.logService.trace(`[chat setup] entitlement: error parsing response (${err})`); return ChatEntitlement.Unresolved; } - const entitled = Boolean( - parsedResult[defaultChat.entitlementChatEnabled], - ); - this.chatSetupEntitledContextKey.set(entitled); - - const skuLimitedAvailable = Boolean( - parsedResult[defaultChat.entitlementSkuLimitedEnabled], - ); - this.resolvedEntitlement = this.toEntitlement( - entitled, - skuLimitedAvailable, - ); - - this.logService.trace( - `[chat setup] entitlement: resolved to ${this.resolvedEntitlement}`, - ); - - this.telemetryService.publicLog2< - ChatSetupEntitlementEvent, - ChatSetupEntitlementClassification - >("chatInstallEntitlement", { - entitled, - entitlement: this.resolvedEntitlement, - }); + const result = { + entitlement: Boolean(parsedResult[defaultChat.entitlementCanSignupLimited]) ? ChatEntitlement.Available : ChatEntitlement.Unavailable, + entitled: Boolean(parsedResult[defaultChat.entitlementChatEnabled]), + limited: Boolean(parsedResult[defaultChat.entitlementSkuType] === defaultChat.entitlementSkuTypeLimited) + }; - return this.resolvedEntitlement; + this.chatSetupContextKeys.update({ entitled: result.entitled, limited: result.limited }); + + this.logService.trace(`[chat setup] entitlement: resolved to ${result.entitlement}, entitled: ${result.entitled}, limited: ${result.limited}`); + this.telemetryService.publicLog2('chatInstallEntitlement', result); + + return result.entitlement; } private update(newEntitlement: ChatEntitlement): void { @@ -487,6 +321,16 @@ class ChatSetupEntitlementResolver extends Disposable { this._onDidChangeEntitlement.fire(this._entitlement); } } + + async forceResolveEntitlement(session: AuthenticationSession): Promise { + return this.resolveEntitlement(session, CancellationToken.None); + } + + override dispose(): void { + this.pendingResolveCts.dispose(true); + + super.dispose(); + } } //#endregion @@ -494,373 +338,283 @@ class ChatSetupEntitlementResolver extends Disposable { //#region Setup Rendering type InstallChatClassification = { - owner: "bpasero"; - comment: "Provides insight into chat installation."; - installResult: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "Whether the extension was installed successfully, cancelled or failed to install."; - }; - signedIn: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "Whether the user did sign in prior to installing the extension."; - }; + owner: 'bpasero'; + comment: 'Provides insight into chat installation.'; + installResult: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the extension was installed successfully, cancelled or failed to install.' }; + signedIn: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user did sign in prior to installing the extension.' }; }; type InstallChatEvent = { - installResult: - | "installed" - | "cancelled" - | "failedInstall" - | "failedNotSignedIn"; + installResult: 'installed' | 'cancelled' | 'failedInstall' | 'failedNotSignedIn'; signedIn: boolean; }; -interface IChatSetupWelcomeContentOptions { - readonly entitlement: ChatEntitlement; - readonly onDidChangeEntitlement: Event; +enum ChatSetupStep { + Initial = 1, + SigningIn, + Installing } -class ChatSetupWelcomeContent extends Disposable { - private static INSTANCE: ChatSetupWelcomeContent | undefined; - static getInstance( - instantiationService: IInstantiationService, - options: IChatSetupWelcomeContentOptions, - ): ChatSetupWelcomeContent { - if (!ChatSetupWelcomeContent.INSTANCE) { - ChatSetupWelcomeContent.INSTANCE = - instantiationService.createInstance( - ChatSetupWelcomeContent, - options, - ); - } +class ChatSetupController extends Disposable { - return ChatSetupWelcomeContent.INSTANCE; + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange = this._onDidChange.event; + + private _step = ChatSetupStep.Initial; + get step(): ChatSetupStep { + return this._step; } - readonly element = $(".chat-setup-view"); + get entitlement(): ChatEntitlement { + return this.entitlementResolver.entitlement; + } + + get canSignUpLimited(): boolean { + return this.entitlement === ChatEntitlement.Available || // user can sign up for limited + this.entitlement === ChatEntitlement.Unresolved; // user unresolved, play safe and allow + } constructor( - private readonly options: IChatSetupWelcomeContentOptions, + private readonly entitlementResolver: ChatSetupEntitlementResolver, + private readonly chatSetupContextKeys: ChatSetupContextKeys, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IAuthenticationService - private readonly authenticationService: IAuthenticationService, - @IInstantiationService - private readonly instantiationService: IInstantiationService, + @IAuthenticationService private readonly authenticationService: IAuthenticationService, + @IInstantiationService private readonly instantiationService: IInstantiationService, @IViewsService private readonly viewsService: IViewsService, - @IExtensionsWorkbenchService - private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IProductService private readonly productService: IProductService, @ILogService private readonly logService: ILogService, + @IProgressService private readonly progressService: IProgressService, + @IChatAgentService private readonly chatAgentService: IChatAgentService, + @IActivityService private readonly activityService: IActivityService ) { super(); - this.create(); + this.registerListeners(); } - private create(): void { - const markdown = this._register( - this.instantiationService.createInstance(MarkdownRenderer, {}), - ); - - // Header - this.element.appendChild($("p")).textContent = localize( - "setupHeader", - "{0} is your AI pair programmer.", - defaultChat.name, - ); - - // SKU Limited Sign-up - const skuHeader = localize( - { key: "skuHeader", comment: ['{Locked="]({0})"}'] }, - "Setup will sign you up to {0} [limited access]({1}).", - defaultChat.name, - defaultChat.skusDocumentationUrl, - ); - const skuHeaderElement = this.element - .appendChild($("p")) - .appendChild( - this._register( - markdown.render( - new MarkdownString(skuHeader, { isTrusted: true }), - ), - ).element, - ); - - const telemetryLabel = localize( - "telemetryLabel", - "Allow {0} to use my data, including Prompts, Suggestions, and Code Snippets, for product improvements", - defaultChat.providerName, - ); - const { container: telemetryContainer, checkbox: telemetryCheckbox } = - this.createCheckBox( - telemetryLabel, - this.telemetryService.telemetryLevel === TelemetryLevel.NONE - ? false - : false, - ); - - const detectionLabel = localize( - "detectionLabel", - "Allow suggestions matching public code", - ); - const { container: detectionContainer, checkbox: detectionCheckbox } = - this.createCheckBox(detectionLabel, true); - - // Setup Button - let setupRunning = false; - - const buttonRow = this.element.appendChild($("p")); - - const button = this._register( - new Button(buttonRow, { - ...defaultButtonStyles, - supportIcons: true, - }), - ); - this.updateControls( - button, - [telemetryCheckbox, detectionCheckbox], - false, - ); - - this._register( - button.onDidClick(async () => { - setupRunning = true; - this.updateControls( - button, - [telemetryCheckbox, detectionCheckbox], - setupRunning, - ); - - try { - await this.setup( - telemetryCheckbox?.checked, - detectionCheckbox?.checked, - ); - } finally { - setupRunning = false; - } - - this.updateControls( - button, - [telemetryCheckbox, detectionCheckbox], - setupRunning, - ); - }), - ); - - // Footer - const footer = localize( - { key: "privacyFooter", comment: ['{Locked="]({0})"}'] }, - "By proceeding you agree to our [privacy statement]({0}). You can [learn more]({1}) about {2}.", - defaultChat.privacyStatementUrl, - defaultChat.documentationUrl, - defaultChat.name, - ); - this.element - .appendChild($("p")) - .appendChild( - this._register( - markdown.render( - new MarkdownString(footer, { isTrusted: true }), - ), - ).element, - ); - - // Update based on entilement changes - this._register( - this.options.onDidChangeEntitlement(() => { - if (setupRunning) { - return; // do not change when setup running - } - setVisibility( - this.options.entitlement !== ChatEntitlement.Unavailable, - skuHeaderElement, - telemetryContainer, - detectionContainer, - ); - this.updateControls( - button, - [telemetryCheckbox, detectionCheckbox], - setupRunning, - ); - }), - ); + private registerListeners(): void { + this._register(this.entitlementResolver.onDidChangeEntitlement(() => this._onDidChange.fire())); } - private createCheckBox( - label: string, - checked: boolean, - ): { container: HTMLElement; checkbox: Checkbox } { - const container = this.element.appendChild($("p")); - const checkbox = this._register( - new Checkbox(label, checked, defaultCheckboxStyles), - ); - container.appendChild(checkbox.domNode); - - const telemetryCheckboxLabelContainer = container.appendChild($("div")); - telemetryCheckboxLabelContainer.textContent = label; - this._register( - addDisposableListener( - telemetryCheckboxLabelContainer, - EventType.CLICK, - () => { - if (checkbox?.enabled) { - checkbox.checked = !checkbox.checked; - } - }, - ), - ); + setStep(step: ChatSetupStep): void { + if (this._step === step) { + return; + } - return { container, checkbox }; + this._step = step; + this._onDidChange.fire(); } - private updateControls( - button: Button, - checkboxes: Checkbox[], - setupRunning: boolean, - ): void { - if (setupRunning) { - button.enabled = false; - button.label = localize( - "setupChatInstalling", - "$(loading~spin) Completing Setup...", - ); - - for (const checkbox of checkboxes) { - checkbox.disable(); - } - } else { - button.enabled = true; - button.label = - this.options.entitlement === ChatEntitlement.Unknown - ? localize("signInAndSetup", "Sign in and Complete Setup") - : localize("setup", "Complete Setup"); - - for (const checkbox of checkboxes) { - checkbox.enable(); - } + async setup(enableTelemetry: boolean): Promise { + const title = localize('setupChatProgress', "Getting {0} ready...", defaultChat.name); + const badge = this.activityService.showViewContainerActivity(CHAT_SIDEBAR_PANEL_ID, { + badge: new ProgressBadge(() => title), + priority: 100 + }); + + try { + await this.progressService.withProgress({ + location: ProgressLocation.Window, + command: ChatSetupTriggerAction.ID, + title, + }, () => this.doSetup(enableTelemetry)); + } finally { + badge.dispose(); } } - private async setup( - enableTelemetry: boolean | undefined, - enableDetection: boolean | undefined, - ): Promise { - let session: AuthenticationSession | undefined; - if (this.options.entitlement === ChatEntitlement.Unknown) { - session = await this.signIn(); - if (!session) { - return false; // user cancelled + private async doSetup(enableTelemetry: boolean): Promise { + try { + let session: AuthenticationSession | undefined; + + // Entitlement Unknown: we need to sign-in user + if (this.entitlement === ChatEntitlement.Unknown) { + this.setStep(ChatSetupStep.SigningIn); + session = await this.signIn(); + if (!session) { + return; // user cancelled + } + + const entitlement = await this.entitlementResolver.forceResolveEntitlement(session); + if (entitlement !== ChatEntitlement.Unavailable) { + return; // we cannot proceed with automated install because user needs to sign-up in a second step + } } - } - return this.install(session, enableTelemetry, enableDetection); + // Entitlement known: proceed with installation + this.setStep(ChatSetupStep.Installing); + await this.install(session, enableTelemetry); + } finally { + this.setStep(ChatSetupStep.Initial); + } } private async signIn(): Promise { let session: AuthenticationSession | undefined; try { showChatView(this.viewsService); - session = await this.authenticationService.createSession( - defaultChat.providerId, - defaultChat.providerScopes[0], - ); + session = await this.authenticationService.createSession(defaultChat.providerId, defaultChat.providerScopes[0]); } catch (error) { // noop } if (!session) { - this.telemetryService.publicLog2< - InstallChatEvent, - InstallChatClassification - >("commandCenter.chatInstall", { - installResult: "failedNotSignedIn", - signedIn: false, - }); + this.telemetryService.publicLog2('commandCenter.chatInstall', { installResult: 'failedNotSignedIn', signedIn: false }); } return session; } - private async install( - session: AuthenticationSession | undefined, - enableTelemetry: boolean | undefined, - enableDetection: boolean | undefined, - ): Promise { + private async install(session: AuthenticationSession | undefined, enableTelemetry: boolean): Promise { const signedIn = !!session; const activeElement = getActiveElement(); - let installResult: "installed" | "cancelled" | "failedInstall"; + let installResult: 'installed' | 'cancelled' | 'failedInstall'; try { showChatView(this.viewsService); - if (this.options.entitlement !== ChatEntitlement.Unavailable) { + if (this.canSignUpLimited) { const body = { - public_code_suggestions: enableDetection - ? "enabled" - : "disabled", - restricted_telemetry: enableTelemetry - ? "enabled" - : "disabled", + restricted_telemetry: enableTelemetry ? 'enabled' : 'disabled' }; - this.logService.trace( - `[chat setup] install: signing up to limited SKU with ${JSON.stringify(body)}`, - ); - - const response = await this.instantiationService.invokeFunction( - (accessor) => - ChatSetupRequestHelper.request( - accessor, - defaultChat.entitlementSkuLimitedUrl, - "POST", - body, - session, - CancellationToken.None, - ), - ); + this.logService.trace(`[chat setup] install: signing up to limited SKU with ${JSON.stringify(body)}`); + + const response = await this.instantiationService.invokeFunction(accessor => ChatSetupRequestHelper.request(accessor, defaultChat.entitlementSignupLimitedUrl, 'POST', body, session, CancellationToken.None)); if (response && this.logService.getLevel() === LogLevel.Trace) { - this.logService.trace( - `[chat setup] install: response from signing up to limited SKU ${JSON.stringify(await asText(response))}`, - ); + this.logService.trace(`[chat setup] install: response from signing up to limited SKU ${JSON.stringify(await asText(response))}`); } } else { - this.logService.trace( - "[chat setup] install: not signing up to limited SKU", - ); + this.logService.trace('[chat setup] install: not signing up to limited SKU'); } - await this.extensionsWorkbenchService.install( - defaultChat.extensionId, - { - enable: true, - isMachineScoped: false, - installPreReleaseVersion: - this.productService.quality !== "stable", - }, - ChatViewId, - ); - - installResult = "installed"; - } catch (error) { - this.logService.trace(`[chat setup] install: error ${error}`); + this.chatSetupContextKeys.suspend(); // reduces flicker - installResult = isCancellationError(error) - ? "cancelled" - : "failedInstall"; + await this.extensionsWorkbenchService.install(defaultChat.extensionId, { + enable: true, + isMachineScoped: false, + installPreReleaseVersion: this.productService.quality !== 'stable' + }, ChatViewId); + + installResult = 'installed'; + } catch (error) { + this.logService.error(`[chat setup] install: error ${error}`); + + installResult = isCancellationError(error) ? 'cancelled' : 'failedInstall'; + } finally { + await Promise.race([ + timeout(5000), // helps prevent flicker with sign-in welcome view + Event.toPromise(this.chatAgentService.onDidChangeAgents) // https://github.com/microsoft/vscode-copilot/issues/9274 + ]).finally(() => this.chatSetupContextKeys.resume()); } - this.telemetryService.publicLog2< - InstallChatEvent, - InstallChatClassification - >("commandCenter.chatInstall", { installResult, signedIn }); + this.telemetryService.publicLog2('commandCenter.chatInstall', { installResult, signedIn }); if (activeElement === getActiveElement()) { (await showChatView(this.viewsService))?.focusInput(); } + } +} + +class ChatSetupWelcomeContent extends Disposable { + + private static INSTANCE: ChatSetupWelcomeContent | undefined; + static getInstance(instantiationService: IInstantiationService, entitlementResolver: ChatSetupEntitlementResolver, chatSetupContextKeys: ChatSetupContextKeys): ChatSetupWelcomeContent { + if (!ChatSetupWelcomeContent.INSTANCE) { + ChatSetupWelcomeContent.INSTANCE = instantiationService.createInstance(ChatSetupWelcomeContent, entitlementResolver, chatSetupContextKeys); + } + + return ChatSetupWelcomeContent.INSTANCE; + } + + readonly element = $('.chat-setup-view'); + + private readonly controller: ChatSetupController; + + constructor( + entitlementResolver: ChatSetupEntitlementResolver, + chatSetupContextKeys: ChatSetupContextKeys, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { + super(); + + this.controller = this._register(instantiationService.createInstance(ChatSetupController, entitlementResolver, chatSetupContextKeys)); + + this.create(); + } + + private create(): void { + const markdown = this._register(this.instantiationService.createInstance(MarkdownRenderer, {})); + + // Header + const header = localize({ key: 'setupHeader', comment: ['{Locked="]({0})"}'] }, "[{0}]({1}) is your AI pair programmer that helps you with code suggestions, answers your questions, and more.", defaultChat.name, defaultChat.documentationUrl); + this.element.appendChild($('p')).appendChild(this._register(markdown.render(new MarkdownString(header, { isTrusted: true }))).element); + + const limitedSkuHeader = localize({ key: 'limitedSkuHeader', comment: ['{Locked="]({0})"}'] }, "Enable powerful AI features for free with the [{0}]({1}) plan.", defaultChat.entitlementSkuTypeLimitedName, defaultChat.skusDocumentationUrl); + const limitedSkuHeaderElement = this.element.appendChild($('p')).appendChild(this._register(markdown.render(new MarkdownString(limitedSkuHeader, { isTrusted: true }))).element); + + // Limited SKU Sign-up + const telemetryLabel = localize('telemetryLabel', "Allow {0} to use my data, including prompts, suggestions, and code snippets, for product improvements", defaultChat.providerName); + const { container: telemetryContainer, checkbox: telemetryCheckbox } = this.createCheckBox(telemetryLabel, this.telemetryService.telemetryLevel === TelemetryLevel.NONE ? false : true, markdown); + + // Setup Button + const buttonRow = this.element.appendChild($('p')); + const button = this._register(new Button(buttonRow, { ...defaultButtonStyles, supportIcons: true })); + this._register(button.onDidClick(() => this.controller.setup(telemetryCheckbox.checked))); + + // Footer + const footer = localize({ key: 'privacyFooter', comment: ['{Locked="]({0})"}'] }, "By proceeding you agree to our [privacy statement]({0}).", defaultChat.privacyStatementUrl); + this.element.appendChild($('p')).appendChild(this._register(markdown.render(new MarkdownString(footer, { isTrusted: true }))).element); + + // Update based on model state + this._register(Event.runAndSubscribe(this.controller.onDidChange, () => this.update(limitedSkuHeaderElement, [telemetryContainer], [telemetryCheckbox], button))); + } + + private createCheckBox(label: string, checked: boolean, markdown: MarkdownRenderer): { container: HTMLElement; checkbox: Checkbox } { + const container = this.element.appendChild($('p.checkbox-container')); + const checkbox = this._register(new Checkbox(label, checked, defaultCheckboxStyles)); + container.appendChild(checkbox.domNode); - return installResult === "installed"; + const checkboxLabel = container.appendChild(this._register(markdown.render(new MarkdownString(label, { isTrusted: true, supportThemeIcons: true }), { inline: true, className: 'checkbox-label' })).element); + this._register(addDisposableListener(checkboxLabel, EventType.CLICK, e => { + if (checkbox?.enabled && (e.target as HTMLElement).tagName !== 'A') { + checkbox.checked = !checkbox.checked; + checkbox.focus(); + } + })); + + return { container, checkbox }; + } + + private update(limitedSkuHeaderElement: HTMLElement, limitedCheckboxContainers: HTMLElement[], limitedCheckboxes: Checkbox[], button: Button): void { + switch (this.controller.step) { + case ChatSetupStep.Initial: + setVisibility(this.controller.canSignUpLimited || this.controller.entitlement === ChatEntitlement.Unknown, limitedSkuHeaderElement); + setVisibility(this.controller.canSignUpLimited, ...limitedCheckboxContainers); + + for (const checkbox of limitedCheckboxes) { + checkbox.enable(); + } + + button.enabled = true; + button.label = this.controller.canSignUpLimited ? + localize('startSetupLimited', "Start Using {0}", defaultChat.entitlementSkuTypeLimitedName) : this.controller.entitlement === ChatEntitlement.Unknown ? + localize('signInToStartSetup', "Sign in to Start") : + localize('startSetupLimited', "Start Using {0}", defaultChat.name); + break; + case ChatSetupStep.SigningIn: + case ChatSetupStep.Installing: + for (const checkbox of limitedCheckboxes) { + checkbox.disable(); + } + + button.enabled = false; + button.label = this.controller.step === ChatSetupStep.SigningIn ? + localize('setupChatSigningIn', "$(loading~spin) Signing in to {0}...", defaultChat.providerName) : + localize('setupChatInstalling', "$(loading~spin) Getting {0} ready...", defaultChat.name); + + break; + } } } @@ -869,60 +623,31 @@ class ChatSetupWelcomeContent extends Disposable { //#region Helpers class ChatSetupRequestHelper { - static async request( - accessor: ServicesAccessor, - url: string, - type: "GET", - body: undefined, - session: AuthenticationSession | undefined, - token: CancellationToken, - ): Promise; - static async request( - accessor: ServicesAccessor, - url: string, - type: "POST", - body: object, - session: AuthenticationSession | undefined, - token: CancellationToken, - ): Promise; - static async request( - accessor: ServicesAccessor, - url: string, - type: "GET" | "POST", - body: object | undefined, - session: AuthenticationSession | undefined, - token: CancellationToken, - ): Promise { + + static async request(accessor: ServicesAccessor, url: string, type: 'GET', body: undefined, session: AuthenticationSession | undefined, token: CancellationToken): Promise; + static async request(accessor: ServicesAccessor, url: string, type: 'POST', body: object, session: AuthenticationSession | undefined, token: CancellationToken): Promise; + static async request(accessor: ServicesAccessor, url: string, type: 'GET' | 'POST', body: object | undefined, session: AuthenticationSession | undefined, token: CancellationToken): Promise { const requestService = accessor.get(IRequestService); const logService = accessor.get(ILogService); const authenticationService = accessor.get(IAuthenticationService); try { if (!session) { - session = ( - await authenticationService.getSessions( - defaultChat.providerId, - ) - ).at(0); + session = (await authenticationService.getSessions(defaultChat.providerId)).at(0); } if (!session) { - throw new Error( - "ChatSetupRequestHelper: No session found for provider", - ); + throw new Error('ChatSetupRequestHelper: No session found for provider'); } - return await requestService.request( - { - type, - url, - data: type === "POST" ? JSON.stringify(body) : undefined, - headers: { - "Authorization": `Bearer ${session.accessToken}`, - }, - }, - token, - ); + return await requestService.request({ + type, + url, + data: type === 'POST' ? JSON.stringify(body) : undefined, + headers: { + 'Authorization': `Bearer ${session.accessToken}` + } + }, token); } catch (error) { logService.error(`[chat setup] request: error ${error}`); @@ -931,92 +656,85 @@ class ChatSetupRequestHelper { } } -class ChatSetupState { - private static readonly CHAT_SETUP_TRIGGERD = "chat.setupTriggered"; - private static readonly CHAT_EXTENSION_INSTALLED = - "chat.extensionInstalled"; +class ChatSetupContextKeys { + + private static readonly CHAT_SETUP_TRIGGERD = 'chat.setupTriggered'; + private static readonly CHAT_EXTENSION_INSTALLED = 'chat.extensionInstalled'; + + private readonly chatSetupEntitledContextKey = ChatContextKeys.Setup.entitled.bindTo(this.contextKeyService); + private readonly chatSetupLimitedContextKey = ChatContextKeys.Setup.limited.bindTo(this.contextKeyService); + private readonly chatSetupTriggeredContext = ChatContextKeys.Setup.triggered.bindTo(this.contextKeyService); + private readonly chatSetupInstalledContext = ChatContextKeys.Setup.installed.bindTo(this.contextKeyService); - private readonly chatSetupTriggeredContext = - ChatContextKeys.Setup.triggered.bindTo(this.contextKeyService); - private readonly chatSetupInstalledContext = - ChatContextKeys.Setup.installed.bindTo(this.contextKeyService); + private chatSetupEntitled = false; + private chatSetupLimited = false; + + private contextKeyUpdateBarrier: Barrier | undefined = undefined; constructor( - @IContextKeyService - private readonly contextKeyService: IContextKeyService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, @IStorageService private readonly storageService: IStorageService, - @IWorkspaceContextService - private readonly workspaceContextService: IWorkspaceContextService, + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, ) { this.updateContext(); } - update(context: { triggered: boolean }): void; - update(context: { chatInstalled?: boolean }): void; - update(context: { triggered?: boolean; chatInstalled?: boolean }): void { - if (typeof context.chatInstalled === "boolean") { - this.storageService.store( - ChatSetupState.CHAT_EXTENSION_INSTALLED, - context.chatInstalled, - StorageScope.PROFILE, - StorageTarget.MACHINE, - ); - this.storageService.store( - ChatSetupState.CHAT_SETUP_TRIGGERD, - true, - StorageScope.PROFILE, - StorageTarget.MACHINE, - ); // allows to fallback to setup view if the extension is uninstalled + update(context: { chatInstalled: boolean }): Promise; + update(context: { triggered: boolean }): Promise; + update(context: { entitled: boolean; limited: boolean }): Promise; + update(context: { triggered?: boolean; chatInstalled?: boolean; entitled?: boolean; limited?: boolean }): Promise { + if (typeof context.chatInstalled === 'boolean') { + this.storageService.store(ChatSetupContextKeys.CHAT_EXTENSION_INSTALLED, context.chatInstalled, StorageScope.PROFILE, StorageTarget.MACHINE); + if (context.chatInstalled) { + this.storageService.store(ChatSetupContextKeys.CHAT_SETUP_TRIGGERD, true, StorageScope.PROFILE, StorageTarget.MACHINE); // allows to fallback to setup view if the extension is uninstalled + } } - if (typeof context.triggered === "boolean") { + if (typeof context.triggered === 'boolean') { if (context.triggered) { - this.storageService.store( - ChatSetupState.CHAT_SETUP_TRIGGERD, - true, - StorageScope.PROFILE, - StorageTarget.MACHINE, - ); + this.storageService.store(ChatSetupContextKeys.CHAT_SETUP_TRIGGERD, true, StorageScope.PROFILE, StorageTarget.MACHINE); } else { - this.storageService.remove( - ChatSetupState.CHAT_SETUP_TRIGGERD, - StorageScope.PROFILE, - ); + this.storageService.remove(ChatSetupContextKeys.CHAT_SETUP_TRIGGERD, StorageScope.PROFILE); } } - this.updateContext(); + if (typeof context.entitled === 'boolean') { + this.chatSetupEntitled = context.entitled; + } + + if (typeof context.limited === 'boolean') { + this.chatSetupLimited = context.limited; + } + + return this.updateContext(); } - private updateContext(): void { - const chatSetupTriggered = this.storageService.getBoolean( - ChatSetupState.CHAT_SETUP_TRIGGERD, - StorageScope.PROFILE, - false, - ); - const chatInstalled = this.storageService.getBoolean( - ChatSetupState.CHAT_EXTENSION_INSTALLED, - StorageScope.PROFILE, - false, - ); + private async updateContext(): Promise { + await this.contextKeyUpdateBarrier?.wait(); + + const chatSetupTriggered = this.storageService.getBoolean(ChatSetupContextKeys.CHAT_SETUP_TRIGGERD, StorageScope.PROFILE, false); + const chatInstalled = this.storageService.getBoolean(ChatSetupContextKeys.CHAT_EXTENSION_INSTALLED, StorageScope.PROFILE, false); const showChatSetup = chatSetupTriggered && !chatInstalled; if (showChatSetup) { // this is ugly but fixes flicker from a previous chat install - this.storageService.remove( - "chat.welcomeMessageContent.panel", - StorageScope.APPLICATION, - ); - this.storageService.remove( - "interactive.sessions", - this.workspaceContextService.getWorkspace().folders.length - ? StorageScope.WORKSPACE - : StorageScope.APPLICATION, - ); + this.storageService.remove('chat.welcomeMessageContent.panel', StorageScope.APPLICATION); + this.storageService.remove('interactive.sessions', this.workspaceContextService.getWorkspace().folders.length ? StorageScope.WORKSPACE : StorageScope.APPLICATION); } this.chatSetupTriggeredContext.set(showChatSetup); this.chatSetupInstalledContext.set(chatInstalled); + this.chatSetupEntitledContextKey.set(this.chatSetupEntitled); + this.chatSetupLimitedContextKey.set(this.chatSetupLimited); + } + + suspend(): void { + this.contextKeyUpdateBarrier = new Barrier(); + } + + resume(): void { + this.contextKeyUpdateBarrier?.open(); + this.contextKeyUpdateBarrier = undefined; } } @@ -1025,60 +743,61 @@ class ChatSetupState { //#region Actions class ChatSetupTriggerAction extends Action2 { - static readonly ID = "workbench.action.chat.triggerSetup"; - static readonly TITLE = localize2( - "triggerChatSetup", - "Setup {0}...", - defaultChat.name, - ); + + static readonly ID = 'workbench.action.chat.triggerSetup'; + static readonly TITLE = localize2('triggerChatSetup', "Use AI features with {0}...", defaultChat.name); constructor() { super({ id: ChatSetupTriggerAction.ID, title: ChatSetupTriggerAction.TITLE, f1: true, - precondition: ChatContextKeys.Setup.installed.negate(), + precondition: ContextKeyExpr.and( + ChatContextKeys.Setup.installed.negate(), + ContextKeyExpr.has('config.chat.experimental.offerSetup') + ), menu: { id: MenuId.ChatCommandCenter, - group: "a_first", + group: 'a_first', order: 1, - when: ChatContextKeys.Setup.installed.negate(), - }, + when: ChatContextKeys.Setup.installed.negate() + } }); } override async run(accessor: ServicesAccessor): Promise { const viewsService = accessor.get(IViewsService); const instantiationService = accessor.get(IInstantiationService); + const configurationService = accessor.get(IConfigurationService); - instantiationService - .createInstance(ChatSetupState) - .update({ triggered: true }); + await instantiationService.createInstance(ChatSetupContextKeys).update({ triggered: true }); showChatView(viewsService); + + configurationService.updateValue('chat.commandCenter.enabled', true); } } class ChatSetupHideAction extends Action2 { - static readonly ID = "workbench.action.chat.hideSetup"; - static readonly TITLE = localize2( - "hideChatSetup", - "Hide {0}", - defaultChat.name, - ); + + static readonly ID = 'workbench.action.chat.hideSetup'; + static readonly TITLE = localize2('hideChatSetup', "Hide {0}", defaultChat.name); constructor() { super({ id: ChatSetupHideAction.ID, title: ChatSetupHideAction.TITLE, f1: true, - precondition: ChatContextKeys.Setup.installed.negate(), + precondition: ContextKeyExpr.and( + ChatContextKeys.Setup.installed.negate(), + ContextKeyExpr.has('config.chat.experimental.offerSetup') + ), menu: { id: MenuId.ChatCommandCenter, - group: "a_first", + group: 'z_end', order: 2, - when: ChatContextKeys.Setup.installed.negate(), - }, + when: ChatContextKeys.Setup.installed.negate() + } }); } @@ -1090,20 +809,9 @@ class ChatSetupHideAction extends Action2 { const dialogService = accessor.get(IDialogService); const { confirmed } = await dialogService.confirm({ - message: localize( - "hideChatSetupConfirm", - "Are you sure you want to hide {0}?", - defaultChat.name, - ), - detail: localize( - "hideChatSetupDetail", - "You can restore chat controls from the 'chat.commandCenter.enabled' setting.", - ), - primaryButton: localize( - "hideChatSetup", - "Hide {0}", - defaultChat.name, - ), + message: localize('hideChatSetupConfirm', "Are you sure you want to hide {0}?", defaultChat.name), + detail: localize('hideChatSetupDetail', "You can restore it by running the '{0}' command.", ChatSetupTriggerAction.TITLE.value), + primaryButton: localize('hideChatSetupButton', "Hide {0}", defaultChat.name) }); if (!confirmed) { @@ -1112,24 +820,16 @@ class ChatSetupHideAction extends Action2 { const location = viewsDescriptorService.getViewLocationById(ChatViewId); - instantiationService - .createInstance(ChatSetupState) - .update({ triggered: false }); + await instantiationService.createInstance(ChatSetupContextKeys).update({ triggered: false }); if (location === ViewContainerLocation.AuxiliaryBar) { - const activeContainers = viewsDescriptorService - .getViewContainersByLocation(location) - .filter( - (container) => - viewsDescriptorService.getViewContainerModel(container) - .activeViewDescriptors.length > 0, - ); + const activeContainers = viewsDescriptorService.getViewContainersByLocation(location).filter(container => viewsDescriptorService.getViewContainerModel(container).activeViewDescriptors.length > 0); if (activeContainers.length === 0) { layoutService.setPartHidden(true, Parts.AUXILIARYBAR_PART); // hide if there are no views in the secondary sidebar } } - configurationService.updateValue("chat.commandCenter.enabled", false); + configurationService.updateValue('chat.commandCenter.enabled', false); } } @@ -1138,8 +838,4 @@ class ChatSetupHideAction extends Action2 { registerAction2(ChatSetupTriggerAction); registerAction2(ChatSetupHideAction); -registerWorkbenchContribution2( - "workbench.chat.setup", - ChatSetupContribution, - WorkbenchPhase.BlockRestore, -); +registerWorkbenchContribution2('workbench.chat.setup', ChatSetupContribution, WorkbenchPhase.BlockRestore); diff --git a/Source/vs/workbench/contrib/chat/browser/chatWidget.ts b/Source/vs/workbench/contrib/chat/browser/chatWidget.ts index 295bfcb3bcd1c..e7792dc1d9a9f 100644 --- a/Source/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/Source/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -3,127 +3,58 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as dom from "../../../../base/browser/dom.js"; -import { Button } from "../../../../base/browser/ui/button/button.js"; -import { - ITreeContextMenuEvent, - ITreeElement, -} from "../../../../base/browser/ui/tree/tree.js"; -import { disposableTimeout, timeout } from "../../../../base/common/async.js"; -import { Codicon } from "../../../../base/common/codicons.js"; -import { toErrorMessage } from "../../../../base/common/errorMessage.js"; -import { Emitter, Event } from "../../../../base/common/event.js"; -import { MarkdownString } from "../../../../base/common/htmlContent.js"; -import { - combinedDisposable, - Disposable, - DisposableStore, - IDisposable, - MutableDisposable, - toDisposable, -} from "../../../../base/common/lifecycle.js"; -import { ResourceSet } from "../../../../base/common/map.js"; -import { Schemas } from "../../../../base/common/network.js"; -import { extUri, isEqual } from "../../../../base/common/resources.js"; -import { isDefined } from "../../../../base/common/types.js"; -import { URI } from "../../../../base/common/uri.js"; -import { ICodeEditor } from "../../../../editor/browser/editorBrowser.js"; -import { ICodeEditorService } from "../../../../editor/browser/services/codeEditorService.js"; -import { localize } from "../../../../nls.js"; -import { MenuId } from "../../../../platform/actions/common/actions.js"; -import { - IContextKey, - IContextKeyService, -} from "../../../../platform/contextkey/common/contextkey.js"; -import { IContextMenuService } from "../../../../platform/contextview/browser/contextView.js"; -import { ITextResourceEditorInput } from "../../../../platform/editor/common/editor.js"; -import { IInstantiationService } from "../../../../platform/instantiation/common/instantiation.js"; -import { ServiceCollection } from "../../../../platform/instantiation/common/serviceCollection.js"; -import { WorkbenchObjectTree } from "../../../../platform/list/browser/listService.js"; -import { ILogService } from "../../../../platform/log/common/log.js"; -import { - IStorageService, - StorageScope, - StorageTarget, -} from "../../../../platform/storage/common/storage.js"; -import { ITelemetryService } from "../../../../platform/telemetry/common/telemetry.js"; -import { - buttonSecondaryBackground, - buttonSecondaryForeground, - buttonSecondaryHoverBackground, -} from "../../../../platform/theme/common/colorRegistry.js"; -import { asCssVariable } from "../../../../platform/theme/common/colorUtils.js"; -import { IThemeService } from "../../../../platform/theme/common/themeService.js"; -import { - ChatAgentLocation, - IChatAgentCommand, - IChatAgentData, - IChatAgentService, - IChatWelcomeMessageContent, - isChatWelcomeMessageContent, -} from "../common/chatAgents.js"; -import { ChatContextKeys } from "../common/chatContextKeys.js"; -import { - IChatEditingService, - IChatEditingSession, - WorkingSetEntryRemovalReason, - WorkingSetEntryState, -} from "../common/chatEditingService.js"; -import { - IChatModel, - IChatRequestVariableEntry, - IChatResponseModel, -} from "../common/chatModel.js"; -import { - chatAgentLeader, - ChatRequestAgentPart, - chatSubcommandLeader, - formatChatQuestion, - IParsedChatRequest, -} from "../common/chatParserTypes.js"; -import { ChatRequestParser } from "../common/chatRequestParser.js"; -import { - IChatFollowup, - IChatLocationData, - IChatSendRequestOptions, - IChatService, -} from "../common/chatService.js"; -import { IChatSlashCommandService } from "../common/chatSlashCommands.js"; -import { - ChatViewModel, - IChatResponseViewModel, - isRequestVM, - isResponseVM, -} from "../common/chatViewModel.js"; -import { IChatInputState } from "../common/chatWidgetHistoryService.js"; -import { CodeBlockModelCollection } from "../common/codeBlockModelCollection.js"; -import { - ChatTreeItem, - IChatAcceptInputOptions, - IChatAccessibilityService, - IChatCodeBlockInfo, - IChatFileTreeInfo, - IChatListItemRendererOptions, - IChatWidget, - IChatWidgetService, - IChatWidgetViewContext, - IChatWidgetViewOptions, -} from "./chat.js"; -import { ChatAccessibilityProvider } from "./chatAccessibilityProvider.js"; -import { ChatAttachmentModel } from "./chatAttachmentModel.js"; -import { ChatInputPart, IChatInputStyles } from "./chatInputPart.js"; -import { - ChatListDelegate, - ChatListItemRenderer, - IChatRendererDelegate, -} from "./chatListRenderer.js"; -import { ChatEditorOptions } from "./chatOptions.js"; - -import "./media/chat.css"; -import "./media/chatAgentHover.css"; -import "./media/chatViewWelcome.css"; - -import { ChatViewWelcomePart } from "./viewsWelcome/chatViewWelcomeController.js"; +import * as dom from '../../../../base/browser/dom.js'; +import { Button } from '../../../../base/browser/ui/button/button.js'; +import { ITreeContextMenuEvent, ITreeElement } from '../../../../base/browser/ui/tree/tree.js'; +import { disposableTimeout, timeout } from '../../../../base/common/async.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { toErrorMessage } from '../../../../base/common/errorMessage.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; +import { FuzzyScore } from '../../../../base/common/filters.js'; +import { MarkdownString } from '../../../../base/common/htmlContent.js'; +import { Disposable, DisposableStore, IDisposable, MutableDisposable, combinedDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; +import { ResourceSet } from '../../../../base/common/map.js'; +import { Schemas } from '../../../../base/common/network.js'; +import { extUri, isEqual } from '../../../../base/common/resources.js'; +import { isDefined } from '../../../../base/common/types.js'; +import { URI } from '../../../../base/common/uri.js'; +import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; +import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; +import { localize } from '../../../../nls.js'; +import { MenuId } from '../../../../platform/actions/common/actions.js'; +import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; +import { ITextResourceEditorInput } from '../../../../platform/editor/common/editor.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; +import { WorkbenchObjectTree } from '../../../../platform/list/browser/listService.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { buttonSecondaryBackground, buttonSecondaryForeground, buttonSecondaryHoverBackground } from '../../../../platform/theme/common/colorRegistry.js'; +import { asCssVariable } from '../../../../platform/theme/common/colorUtils.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { ChatAgentLocation, IChatAgentCommand, IChatAgentData, IChatAgentService, IChatWelcomeMessageContent, isChatWelcomeMessageContent } from '../common/chatAgents.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; +import { IChatEditingService, IChatEditingSession, WorkingSetEntryRemovalReason, WorkingSetEntryState } from '../common/chatEditingService.js'; +import { IChatModel, IChatRequestVariableEntry, IChatResponseModel } from '../common/chatModel.js'; +import { ChatRequestAgentPart, IParsedChatRequest, chatAgentLeader, chatSubcommandLeader, formatChatQuestion } from '../common/chatParserTypes.js'; +import { ChatRequestParser } from '../common/chatRequestParser.js'; +import { IChatFollowup, IChatLocationData, IChatSendRequestOptions, IChatService } from '../common/chatService.js'; +import { IChatSlashCommandService } from '../common/chatSlashCommands.js'; +import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM } from '../common/chatViewModel.js'; +import { IChatInputState } from '../common/chatWidgetHistoryService.js'; +import { CodeBlockModelCollection } from '../common/codeBlockModelCollection.js'; +import { ChatTreeItem, IChatAcceptInputOptions, IChatAccessibilityService, IChatCodeBlockInfo, IChatFileTreeInfo, IChatListItemRendererOptions, IChatWidget, IChatWidgetService, IChatWidgetViewContext, IChatWidgetViewOptions } from './chat.js'; +import { ChatAccessibilityProvider } from './chatAccessibilityProvider.js'; +import { ChatAttachmentModel } from './chatAttachmentModel.js'; +import { ChatInputPart, IChatInputStyles } from './chatInputPart.js'; +import { ChatListDelegate, ChatListItemRenderer, IChatRendererDelegate } from './chatListRenderer.js'; +import { ChatEditorOptions } from './chatOptions.js'; +import './media/chat.css'; +import './media/chatAgentHover.css'; +import './media/chatViewWelcome.css'; +import { ChatViewWelcomePart } from './viewsWelcome/chatViewWelcomeController.js'; const $ = dom.$; @@ -157,34 +88,18 @@ export interface IChatWidgetLocationOptions { } export function isQuickChat(widget: IChatWidget): boolean { - return ( - "viewContext" in widget && - "isQuickChat" in widget.viewContext && - Boolean(widget.viewContext.isQuickChat) - ); + return 'viewContext' in widget && 'isQuickChat' in widget.viewContext && Boolean(widget.viewContext.isQuickChat); } -const PersistWelcomeMessageContentKey = "chat.welcomeMessageContent"; +const PersistWelcomeMessageContentKey = 'chat.welcomeMessageContent'; export class ChatWidget extends Disposable implements IChatWidget { - public static readonly CONTRIBS: { - new (...args: [IChatWidget, ...any]): IChatWidgetContrib; - }[] = []; - - private readonly _onDidSubmitAgent = this._register( - new Emitter<{ - agent: IChatAgentData; - slashCommand?: IChatAgentCommand; - }>(), - ); + public static readonly CONTRIBS: { new(...args: [IChatWidget, ...any]): IChatWidgetContrib }[] = []; + + private readonly _onDidSubmitAgent = this._register(new Emitter<{ agent: IChatAgentData; slashCommand?: IChatAgentCommand }>()); public readonly onDidSubmitAgent = this._onDidSubmitAgent.event; - private _onDidChangeAgent = this._register( - new Emitter<{ - agent: IChatAgentData; - slashCommand?: IChatAgentCommand; - }>(), - ); + private _onDidChangeAgent = this._register(new Emitter<{ agent: IChatAgentData; slashCommand?: IChatAgentCommand }>()); readonly onDidChangeAgent = this._onDidChangeAgent.event; private _onDidFocus = this._register(new Emitter()); @@ -209,19 +124,17 @@ export class ChatWidget extends Disposable implements IChatWidget { readonly onDidChangeParsedInput = this._onDidChangeParsedInput.event; private readonly _onWillMaybeChangeHeight = new Emitter(); - readonly onWillMaybeChangeHeight: Event = - this._onWillMaybeChangeHeight.event; + readonly onWillMaybeChangeHeight: Event = this._onWillMaybeChangeHeight.event; private _onDidChangeHeight = this._register(new Emitter()); readonly onDidChangeHeight = this._onDidChangeHeight.event; private readonly _onDidChangeContentHeight = new Emitter(); - readonly onDidChangeContentHeight: Event = - this._onDidChangeContentHeight.event; + readonly onDidChangeContentHeight: Event = this._onDidChangeContentHeight.event; private contribs: ReadonlyArray = []; - private tree!: WorkbenchObjectTree; + private tree!: WorkbenchObjectTree; private renderer!: ChatListItemRenderer; private readonly _codeBlockModelCollection: CodeBlockModelCollection; private lastItem: ChatTreeItem | undefined; @@ -252,9 +165,7 @@ export class ChatWidget extends Disposable implements IChatWidget { */ private scrollLock = true; - private readonly viewModelDisposables = this._register( - new DisposableStore(), - ); + private readonly viewModelDisposables = this._register(new DisposableStore()); private _viewModel: ChatViewModel | undefined; private set viewModel(viewModel: ChatViewModel | undefined) { if (this._viewModel === viewModel) { @@ -279,17 +190,10 @@ export class ChatWidget extends Disposable implements IChatWidget { get parsedInput() { if (this.parsedChatRequest === undefined) { if (!this.viewModel) { - return { text: "", parts: [] }; + return { text: '', parts: [] }; } - this.parsedChatRequest = this.instantiationService - .createInstance(ChatRequestParser) - .parseChatRequest( - this.viewModel!.sessionId, - this.getInput(), - this.location, - { selectedAgent: this._lastSelectedAgent }, - ); + this.parsedChatRequest = this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(this.viewModel!.sessionId, this.getInput(), this.location, { selectedAgent: this._lastSelectedAgent }); } return this.parsedChatRequest; @@ -312,24 +216,17 @@ export class ChatWidget extends Disposable implements IChatWidget { private readonly viewOptions: IChatWidgetViewOptions, private readonly styles: IChatWidgetStyles, @ICodeEditorService codeEditorService: ICodeEditorService, - @IContextKeyService - private readonly contextKeyService: IContextKeyService, - @IInstantiationService - private readonly instantiationService: IInstantiationService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IInstantiationService private readonly instantiationService: IInstantiationService, @IChatService private readonly chatService: IChatService, @IChatAgentService private readonly chatAgentService: IChatAgentService, - @IChatWidgetService - private readonly chatWidgetService: IChatWidgetService, - @IContextMenuService - private readonly contextMenuService: IContextMenuService, - @IChatAccessibilityService - private readonly chatAccessibilityService: IChatAccessibilityService, + @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, + @IContextMenuService private readonly contextMenuService: IContextMenuService, + @IChatAccessibilityService private readonly chatAccessibilityService: IChatAccessibilityService, @ILogService private readonly logService: ILogService, @IThemeService private readonly themeService: IThemeService, - @IChatSlashCommandService - private readonly chatSlashCommandService: IChatSlashCommandService, - @IChatEditingService - private readonly chatEditingService: IChatEditingService, + @IChatSlashCommandService private readonly chatSlashCommandService: IChatSlashCommandService, + @IChatEditingService private readonly chatEditingService: IChatEditingService, @IStorageService private readonly storageService: IStorageService, @ITelemetryService private readonly telemetryService: ITelemetryService, ) { @@ -337,186 +234,130 @@ export class ChatWidget extends Disposable implements IChatWidget { this.viewContext = _viewContext ?? {}; - if (typeof location === "object") { + if (typeof location === 'object') { this._location = location; } else { this._location = { location }; } ChatContextKeys.inChatSession.bindTo(contextKeyService).set(true); - ChatContextKeys.location - .bindTo(contextKeyService) - .set(this._location.location); - ChatContextKeys.inQuickChat - .bindTo(contextKeyService) - .set(isQuickChat(this)); - this.agentInInput = - ChatContextKeys.inputHasAgent.bindTo(contextKeyService); - this.requestInProgress = - ChatContextKeys.requestInProgress.bindTo(contextKeyService); - - this._codeBlockModelCollection = this._register( - instantiationService.createInstance(CodeBlockModelCollection), - ); + ChatContextKeys.location.bindTo(contextKeyService).set(this._location.location); + ChatContextKeys.inQuickChat.bindTo(contextKeyService).set(isQuickChat(this)); + this.agentInInput = ChatContextKeys.inputHasAgent.bindTo(contextKeyService); + this.requestInProgress = ChatContextKeys.requestInProgress.bindTo(contextKeyService); - const chatEditingSessionDisposables = this._register( - new DisposableStore(), - ); + this._codeBlockModelCollection = this._register(instantiationService.createInstance(CodeBlockModelCollection)); - this._register( - this.chatEditingService.onDidCreateEditingSession((session) => { - if (session.chatSessionId !== this.viewModel?.sessionId) { - // this chat editing session is for a different chat widget - return; - } - // make sure to clean up anything related to the prev session (if any) + const chatEditingSessionDisposables = this._register(new DisposableStore()); + + this._register(this.chatEditingService.onDidCreateEditingSession((session) => { + if (session.chatSessionId !== this.viewModel?.sessionId) { + // this chat editing session is for a different chat widget + return; + } + // make sure to clean up anything related to the prev session (if any) + chatEditingSessionDisposables.clear(); + this.renderChatEditingSessionState(null); // this is necessary to make sure we dispose previous buttons, etc. + + chatEditingSessionDisposables.add(session.onDidChange(() => { + this.renderChatEditingSessionState(session); + })); + chatEditingSessionDisposables.add(session.onDidDispose(() => { chatEditingSessionDisposables.clear(); - this.renderChatEditingSessionState(null); // this is necessary to make sure we dispose previous buttons, etc. - - chatEditingSessionDisposables.add( - session.onDidChange(() => { - this.renderChatEditingSessionState(session); - }), - ); - chatEditingSessionDisposables.add( - session.onDidDispose(() => { - chatEditingSessionDisposables.clear(); - this.renderChatEditingSessionState(null); - }), - ); - chatEditingSessionDisposables.add( - this.onDidChangeParsedInput(() => { - this.renderChatEditingSessionState(session); - }), - ); - chatEditingSessionDisposables.add( - this.inputEditor.onDidChangeModelContent(() => { - if (this.getInput() === "") { - this.refreshParsedInput(); - this.renderChatEditingSessionState(session); - } - }), - ); + this.renderChatEditingSessionState(null); + })); + chatEditingSessionDisposables.add(this.onDidChangeParsedInput(() => { this.renderChatEditingSessionState(session); - }), - ); + })); + chatEditingSessionDisposables.add(this.inputEditor.onDidChangeModelContent(() => { + if (this.getInput() === '') { + this.refreshParsedInput(); + this.renderChatEditingSessionState(session); + } + })); + this.renderChatEditingSessionState(session); + })); if (this._location.location === ChatAgentLocation.EditingSession) { let currentEditSession: IChatEditingSession | undefined = undefined; - this._register( - this.onDidChangeViewModel(async () => { - const sessionId = this._viewModel?.sessionId; - if (sessionId) { - if (sessionId !== currentEditSession?.chatSessionId) { - currentEditSession = - await this.chatEditingService.startOrContinueEditingSession( - sessionId, - ); - } - } else { - if (currentEditSession) { - const session = currentEditSession; - currentEditSession = undefined; - await session.stop(); - } + this._register(this.onDidChangeViewModel(async () => { + const sessionId = this._viewModel?.sessionId; + if (sessionId) { + if (sessionId !== currentEditSession?.chatSessionId) { + currentEditSession = await this.chatEditingService.startOrContinueEditingSession(sessionId); + } + } else { + if (currentEditSession) { + const session = currentEditSession; + currentEditSession = undefined; + await session.stop(); } - }), - ); + } + })); } - this._register( - codeEditorService.registerCodeEditorOpenHandler( - async ( - input: ITextResourceEditorInput, - _source: ICodeEditor | null, - _sideBySide?: boolean, - ): Promise => { - const resource = input.resource; - if (resource.scheme !== Schemas.vscodeChatCodeBlock) { - return null; - } + this._register(codeEditorService.registerCodeEditorOpenHandler(async (input: ITextResourceEditorInput, _source: ICodeEditor | null, _sideBySide?: boolean): Promise => { + const resource = input.resource; + if (resource.scheme !== Schemas.vscodeChatCodeBlock) { + return null; + } - const responseId = resource.path.split("/").at(1); - if (!responseId) { - return null; - } + const responseId = resource.path.split('/').at(1); + if (!responseId) { + return null; + } - const item = this.viewModel - ?.getItems() - .find((item) => item.id === responseId); - if (!item) { - return null; - } + const item = this.viewModel?.getItems().find(item => item.id === responseId); + if (!item) { + return null; + } - // TODO: needs to reveal the chat view - - this.reveal(item); - - await timeout(0); // wait for list to actually render - - for (const codeBlockPart of this.renderer.editorsInUse()) { - if (extUri.isEqual(codeBlockPart.uri, resource, true)) { - const editor = codeBlockPart.editor; - - let relativeTop = 0; - const editorDomNode = editor.getDomNode(); - if (editorDomNode) { - const row = dom.findParentWithClass( - editorDomNode, - "monaco-list-row", - ); - if (row) { - relativeTop = - dom.getTopLeftOffset(editorDomNode) - .top - - dom.getTopLeftOffset(row).top; - } - } + // TODO: needs to reveal the chat view - if (input.options?.selection) { - const editorSelectionTopOffset = - editor.getTopForPosition( - input.options.selection.startLineNumber, - input.options.selection.startColumn, - ); - relativeTop += editorSelectionTopOffset; - - editor.focus(); - editor.setSelection({ - startLineNumber: - input.options.selection.startLineNumber, - startColumn: - input.options.selection.startColumn, - endLineNumber: - input.options.selection.endLineNumber ?? - input.options.selection.startLineNumber, - endColumn: - input.options.selection.endColumn ?? - input.options.selection.startColumn, - }); - } + this.reveal(item); + + await timeout(0); // wait for list to actually render - this.reveal(item, relativeTop); + for (const codeBlockPart of this.renderer.editorsInUse()) { + if (extUri.isEqual(codeBlockPart.uri, resource, true)) { + const editor = codeBlockPart.editor; - return editor; + let relativeTop = 0; + const editorDomNode = editor.getDomNode(); + if (editorDomNode) { + const row = dom.findParentWithClass(editorDomNode, 'monaco-list-row'); + if (row) { + relativeTop = dom.getTopLeftOffset(editorDomNode).top - dom.getTopLeftOffset(row).top; } } - return null; - }, - ), - ); - const loadedWelcomeContent = storageService.getObject( - `${PersistWelcomeMessageContentKey}.${this.location}`, - StorageScope.APPLICATION, - ); + if (input.options?.selection) { + const editorSelectionTopOffset = editor.getTopForPosition(input.options.selection.startLineNumber, input.options.selection.startColumn); + relativeTop += editorSelectionTopOffset; + + editor.focus(); + editor.setSelection({ + startLineNumber: input.options.selection.startLineNumber, + startColumn: input.options.selection.startColumn, + endLineNumber: input.options.selection.endLineNumber ?? input.options.selection.startLineNumber, + endColumn: input.options.selection.endColumn ?? input.options.selection.startColumn + }); + } + + this.reveal(item, relativeTop); + + return editor; + } + } + return null; + })); + + const loadedWelcomeContent = storageService.getObject(`${PersistWelcomeMessageContentKey}.${this.location}`, StorageScope.APPLICATION); if (isChatWelcomeMessageContent(loadedWelcomeContent)) { this.persistedWelcomeMessage = loadedWelcomeContent; } - this._register( - this.onDidChangeParsedInput(() => this.updateChatInputContext()), - ); + this._register(this.onDidChangeParsedInput(() => this.updateChatInputContext())); } private _lastSelectedAgent: IChatAgentData | undefined; @@ -555,72 +396,40 @@ export class ChatWidget extends Disposable implements IChatWidget { } render(parent: HTMLElement): void { - const viewId = - "viewId" in this.viewContext ? this.viewContext.viewId : undefined; - this.editorOptions = this._register( - this.instantiationService.createInstance( - ChatEditorOptions, - viewId, - this.styles.listForeground, - this.styles.inputEditorBackground, - this.styles.resultEditorBackground, - ), - ); + const viewId = 'viewId' in this.viewContext ? this.viewContext.viewId : undefined; + this.editorOptions = this._register(this.instantiationService.createInstance(ChatEditorOptions, viewId, this.styles.listForeground, this.styles.inputEditorBackground, this.styles.resultEditorBackground)); const renderInputOnTop = this.viewOptions.renderInputOnTop ?? false; - const renderFollowups = - this.viewOptions.renderFollowups ?? !renderInputOnTop; + const renderFollowups = this.viewOptions.renderFollowups ?? !renderInputOnTop; const renderStyle = this.viewOptions.renderStyle; - this.container = dom.append(parent, $(".interactive-session")); - this.welcomeMessageContainer = dom.append( - this.container, - $(".chat-welcome-view-container", { style: "display: none" }), - ); + this.container = dom.append(parent, $('.interactive-session')); + this.welcomeMessageContainer = dom.append(this.container, $('.chat-welcome-view-container', { style: 'display: none' })); this.renderWelcomeViewContentIfNeeded(); if (renderInputOnTop) { this.createInput(this.container, { renderFollowups, renderStyle }); - this.listContainer = dom.append( - this.container, - $(`.interactive-list`), - ); + this.listContainer = dom.append(this.container, $(`.interactive-list`)); } else { - this.listContainer = dom.append( - this.container, - $(`.interactive-list`), - ); + this.listContainer = dom.append(this.container, $(`.interactive-list`)); this.createInput(this.container, { renderFollowups, renderStyle }); } - this.createList(this.listContainer, { - ...this.viewOptions.rendererOptions, - renderStyle, - }); + this.createList(this.listContainer, { ...this.viewOptions.rendererOptions, renderStyle }); - const scrollDownButton = this._register( - new Button(this.listContainer, { - supportIcons: true, - buttonBackground: asCssVariable(buttonSecondaryBackground), - buttonForeground: asCssVariable(buttonSecondaryForeground), - buttonHoverBackground: asCssVariable( - buttonSecondaryHoverBackground, - ), - }), - ); - scrollDownButton.element.classList.add("chat-scroll-down"); + const scrollDownButton = this._register(new Button(this.listContainer, { + supportIcons: true, + buttonBackground: asCssVariable(buttonSecondaryBackground), + buttonForeground: asCssVariable(buttonSecondaryForeground), + buttonHoverBackground: asCssVariable(buttonSecondaryHoverBackground), + })); + scrollDownButton.element.classList.add('chat-scroll-down'); scrollDownButton.label = `$(${Codicon.chevronDown.id})`; - scrollDownButton.setTitle( - localize("scrollDownButtonLabel", "Scroll down"), - ); - this._register( - scrollDownButton.onDidClick(() => { - this.scrollLock = true; - this.scrollToEnd(); - }), - ); + scrollDownButton.setTitle(localize('scrollDownButtonLabel', "Scroll down")); + this._register(scrollDownButton.onDidClick(() => { + this.scrollLock = true; + this.scrollToEnd(); + })); - this._register( - this.editorOptions.onDidChange(() => this.onDidStyleChange()), - ); + this._register(this.editorOptions.onDidChange(() => this.onDidStyleChange())); this.onDidStyleChange(); // Do initial render @@ -629,37 +438,27 @@ export class ChatWidget extends Disposable implements IChatWidget { this.scrollToEnd(); } - this.contribs = ChatWidget.CONTRIBS.map((contrib) => { + this.contribs = ChatWidget.CONTRIBS.map(contrib => { try { - return this._register( - this.instantiationService.createInstance(contrib, this), - ); + return this._register(this.instantiationService.createInstance(contrib, this)); } catch (err) { - this.logService.error( - "Failed to instantiate chat widget contrib", - toErrorMessage(err), - ); + this.logService.error('Failed to instantiate chat widget contrib', toErrorMessage(err)); return undefined; } }).filter(isDefined); - this._register( - (this.chatWidgetService as ChatWidgetService).register(this), - ); + this._register((this.chatWidgetService as ChatWidgetService).register(this)); } private scrollToEnd() { if (this.lastItem) { - const offset = Math.max( - this.lastItem.currentRenderedHeight ?? 0, - 1e6, - ); + const offset = Math.max(this.lastItem.currentRenderedHeight ?? 0, 1e6); this.tree.reveal(this.lastItem, offset); } } getContrib(id: string): T | undefined { - return this.contribs.find((c) => c.id === id) as T; + return this.contribs.find(c => c.id === id) as T; } focusInput(): void { @@ -674,21 +473,11 @@ export class ChatWidget extends Disposable implements IChatWidget { if (!this.viewModel) { return; } - this.parsedChatRequest = this.instantiationService - .createInstance(ChatRequestParser) - .parseChatRequest( - this.viewModel.sessionId, - this.getInput(), - this.location, - { selectedAgent: this._lastSelectedAgent }, - ); + this.parsedChatRequest = this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(this.viewModel.sessionId, this.getInput(), this.location, { selectedAgent: this._lastSelectedAgent }); this._onDidChangeParsedInput.fire(); } - getSibling( - item: ChatTreeItem, - type: "next" | "previous", - ): ChatTreeItem | undefined { + getSibling(item: ChatTreeItem, type: 'next' | 'previous'): ChatTreeItem | undefined { if (!isResponseVM(item)) { return; } @@ -696,13 +485,12 @@ export class ChatWidget extends Disposable implements IChatWidget { if (!items) { return; } - const responseItems = items.filter((i) => isResponseVM(i)); + const responseItems = items.filter(i => isResponseVM(i)); const targetIndex = responseItems.indexOf(item); if (targetIndex === undefined) { return; } - const indexToFocus = - type === "next" ? targetIndex + 1 : targetIndex - 1; + const indexToFocus = type === 'next' ? targetIndex + 1 : targetIndex - 1; if (indexToFocus < 0 || indexToFocus > responseItems.length - 1) { return; } @@ -718,15 +506,14 @@ export class ChatWidget extends Disposable implements IChatWidget { private onDidChangeItems(skipDynamicLayout?: boolean) { if (this.tree && this._visible) { - const treeItems = (this.viewModel?.getItems() ?? []).map( - (item): ITreeElement => { + const treeItems = (this.viewModel?.getItems() ?? []) + .map((item): ITreeElement => { return { element: item, collapsed: false, - collapsible: false, + collapsible: false }; - }, - ); + }); this.renderWelcomeViewContentIfNeeded(); @@ -735,27 +522,21 @@ export class ChatWidget extends Disposable implements IChatWidget { this.tree.setChildren(null, treeItems, { diffIdentityProvider: { getId: (element) => { - return ( - element.dataId + + return element.dataId + // Ensure re-rendering an element once slash commands are loaded, so the colorization can be applied. - `${isRequestVM(element) /* && !!this.lastSlashCommands ? '_scLoaded' : '' */}` + + `${(isRequestVM(element)) /* && !!this.lastSlashCommands ? '_scLoaded' : '' */}` + // If a response is in the process of progressive rendering, we need to ensure that it will // be re-rendered so progressive rendering is restarted, even if the model wasn't updated. - `${isResponseVM(element) && element.renderData ? `_${this.visibleChangeCount}` : ""}` + + `${isResponseVM(element) && element.renderData ? `_${this.visibleChangeCount}` : ''}` + // Re-render once content references are loaded - (isResponseVM(element) - ? `_${element.contentReferences.length}` - : "") + + (isResponseVM(element) ? `_${element.contentReferences.length}` : '') + // Re-render if element becomes hidden due to undo/redo - `_${element.isHidden ? "1" : "0"}` + + `_${element.isHidden ? '1' : '0'}` + // Rerender request if we got new content references in the response // since this may change how we render the corresponding attachments in the request - (isRequestVM(element) && element.contentReferences - ? `_${element.contentReferences?.length}` - : "") - ); + (isRequestVM(element) && element.contentReferences ? `_${element.contentReferences?.length}` : ''); }, - }, + } }); if (!skipDynamicLayout && this._dynamicMessageLayoutData) { @@ -764,19 +545,10 @@ export class ChatWidget extends Disposable implements IChatWidget { this.lastItem = treeItems[treeItems.length - 1]?.element; if (this.lastItem) { - ChatContextKeys.lastItemId - .bindTo(this.contextKeyService) - .set([this.lastItem.id]); + ChatContextKeys.lastItemId.bindTo(this.contextKeyService).set([this.lastItem.id]); } - if ( - this.lastItem && - isResponseVM(this.lastItem) && - this.lastItem.isComplete - ) { - this.renderFollowups( - this.lastItem.replyFollowups, - this.lastItem, - ); + if (this.lastItem && isResponseVM(this.lastItem) && this.lastItem.isComplete) { + this.renderFollowups(this.lastItem.replyFollowups, this.lastItem); } else if (!treeItems.length && this.viewModel) { this.renderFollowups(this.viewModel.model.sampleQuestions); } else { @@ -786,58 +558,27 @@ export class ChatWidget extends Disposable implements IChatWidget { } private renderWelcomeViewContentIfNeeded() { - const welcomeContent = - this.viewModel?.model.welcomeMessage ?? - this.persistedWelcomeMessage; - if ( - welcomeContent && - this.welcomeMessageContainer.children.length === 0 && - !this.viewOptions.renderStyle - ) { + const welcomeContent = this.viewModel?.model.welcomeMessage ?? this.persistedWelcomeMessage; + if (welcomeContent && this.welcomeMessageContainer.children.length === 0 && !this.viewOptions.renderStyle) { const tips = this.viewOptions.supportsAdditionalParticipants - ? new MarkdownString( - localize( - "chatWidget.tips", - "{0} or type {1} to attach context\n\n{2} to chat with extensions\n\nType {3} to use commands", - "$(attach)", - "#", - "$(mention)", - "/", - ), - { supportThemeIcons: true }, - ) - : new MarkdownString( - localize( - "chatWidget.tips.withoutParticipants", - "{0} or type {1} to attach context", - "$(attach)", - "#", - ), - { supportThemeIcons: true }, - ); - const welcomePart = this._register( - this.instantiationService.createInstance( - ChatViewWelcomePart, - { ...welcomeContent, tips }, - { location: this.location }, - ), - ); + ? new MarkdownString(localize('chatWidget.tips', "{0} or type {1} to attach context\n\n{2} to chat with extensions\n\nType {3} to use commands", '$(attach)', '#', '$(mention)', '/'), { supportThemeIcons: true }) + : new MarkdownString(localize('chatWidget.tips.withoutParticipants', "{0} or type {1} to attach context", '$(attach)', '#'), { supportThemeIcons: true }); + const welcomePart = this._register(this.instantiationService.createInstance( + ChatViewWelcomePart, + { ...welcomeContent, tips, }, + { location: this.location } + )); dom.append(this.welcomeMessageContainer, welcomePart.element); } if (!this.viewOptions.renderStyle && this.viewModel) { const treeItems = this.viewModel.getItems(); - dom.setVisibility( - treeItems.length === 0, - this.welcomeMessageContainer, - ); + dom.setVisibility(treeItems.length === 0, this.welcomeMessageContainer); dom.setVisibility(treeItems.length !== 0, this.listContainer); } } - private async renderChatEditingSessionState( - session: IChatEditingSession | null, - ) { + private async renderChatEditingSessionState(session: IChatEditingSession | null) { this.inputPart.renderChatEditingSessionState(session, this); if (this.bodyDimension) { @@ -845,10 +586,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } } - private async renderFollowups( - items: IChatFollowup[] | undefined, - response?: IChatResponseViewModel, - ): Promise { + private async renderFollowups(items: IChatFollowup[] | undefined, response?: IChatResponseViewModel): Promise { this.inputPart.renderFollowups(items, response); if (this.bodyDimension) { @@ -864,181 +602,108 @@ export class ChatWidget extends Disposable implements IChatWidget { this.input.setVisible(visible); if (visible) { - this._register( - disposableTimeout(() => { - // Progressive rendering paused while hidden, so start it up again. - // Do it after a timeout because the container is not visible yet (it should be but offsetHeight returns 0 here) - if (this._visible) { - this.onDidChangeItems(true); - } - }, 0), - ); + this._register(disposableTimeout(() => { + // Progressive rendering paused while hidden, so start it up again. + // Do it after a timeout because the container is not visible yet (it should be but offsetHeight returns 0 here) + if (this._visible) { + this.onDidChangeItems(true); + } + }, 0)); } else if (wasVisible) { this._onDidHide.fire(); } } - private createList( - listContainer: HTMLElement, - options: IChatListItemRendererOptions, - ): void { - const scopedInstantiationService = this._register( - this.instantiationService.createChild( - new ServiceCollection([ - IContextKeyService, - this.contextKeyService, - ]), - ), - ); - const delegate = scopedInstantiationService.createInstance( - ChatListDelegate, - this.viewOptions.defaultElementHeight ?? 200, - ); + private createList(listContainer: HTMLElement, options: IChatListItemRendererOptions): void { + const scopedInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.contextKeyService]))); + const delegate = scopedInstantiationService.createInstance(ChatListDelegate, this.viewOptions.defaultElementHeight ?? 200); const rendererDelegate: IChatRendererDelegate = { getListLength: () => this.tree.getNode(null).visibleChildrenCount, onDidScroll: this.onDidScroll, - container: listContainer, + container: listContainer }; // Create a dom element to hold UI from editor widgets embedded in chat messages - const overflowWidgetsContainer = document.createElement("div"); - overflowWidgetsContainer.classList.add( - "chat-overflow-widget-container", - "monaco-editor", - ); + const overflowWidgetsContainer = document.createElement('div'); + overflowWidgetsContainer.classList.add('chat-overflow-widget-container', 'monaco-editor'); listContainer.append(overflowWidgetsContainer); - this.renderer = this._register( - scopedInstantiationService.createInstance( - ChatListItemRenderer, - this.editorOptions, - options, - rendererDelegate, - this._codeBlockModelCollection, - overflowWidgetsContainer, - ), - ); - this._register( - this.renderer.onDidClickFollowup((item) => { - // is this used anymore? - this.acceptInput(item.message); - }), - ); - this._register( - this.renderer.onDidClickRerunWithAgentOrCommandDetection((item) => { - const request = this.chatService - .getSession(item.sessionId) - ?.getRequests() - .find((candidate) => candidate.id === item.requestId); - if (request) { - const options: IChatSendRequestOptions = { - noCommandDetection: true, - attempt: request.attempt + 1, - location: this.location, - userSelectedModelId: this.input.currentLanguageModel, - }; - this.chatService - .resendRequest(request, options) - .catch((e) => - this.logService.error("FAILED to rerun request", e), - ); + this.renderer = this._register(scopedInstantiationService.createInstance( + ChatListItemRenderer, + this.editorOptions, + options, + rendererDelegate, + this._codeBlockModelCollection, + overflowWidgetsContainer, + )); + this._register(this.renderer.onDidClickFollowup(item => { + // is this used anymore? + this.acceptInput(item.message); + })); + this._register(this.renderer.onDidClickRerunWithAgentOrCommandDetection(item => { + const request = this.chatService.getSession(item.sessionId)?.getRequests().find(candidate => candidate.id === item.requestId); + if (request) { + const options: IChatSendRequestOptions = { + noCommandDetection: true, + attempt: request.attempt + 1, + location: this.location, + userSelectedModelId: this.input.currentLanguageModel + }; + this.chatService.resendRequest(request, options).catch(e => this.logService.error('FAILED to rerun request', e)); + } + })); + + this.tree = this._register(scopedInstantiationService.createInstance( + WorkbenchObjectTree, + 'Chat', + listContainer, + delegate, + [this.renderer], + { + identityProvider: { getId: (e: ChatTreeItem) => e.id }, + horizontalScrolling: false, + alwaysConsumeMouseWheel: false, + supportDynamicHeights: true, + hideTwistiesOfChildlessElements: true, + accessibilityProvider: this.instantiationService.createInstance(ChatAccessibilityProvider), + keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: ChatTreeItem) => isRequestVM(e) ? e.message : isResponseVM(e) ? e.response.value : '' }, // TODO + setRowLineHeight: false, + filter: this.viewOptions.filter ? { filter: this.viewOptions.filter.bind(this.viewOptions), } : undefined, + overrideStyles: { + listFocusBackground: this.styles.listBackground, + listInactiveFocusBackground: this.styles.listBackground, + listActiveSelectionBackground: this.styles.listBackground, + listFocusAndSelectionBackground: this.styles.listBackground, + listInactiveSelectionBackground: this.styles.listBackground, + listHoverBackground: this.styles.listBackground, + listBackground: this.styles.listBackground, + listFocusForeground: this.styles.listForeground, + listHoverForeground: this.styles.listForeground, + listInactiveFocusForeground: this.styles.listForeground, + listInactiveSelectionForeground: this.styles.listForeground, + listActiveSelectionForeground: this.styles.listForeground, + listFocusAndSelectionForeground: this.styles.listForeground, + listActiveSelectionIconForeground: undefined, + listInactiveSelectionIconForeground: undefined, } - }), - ); - - this.tree = this._register( - >( - scopedInstantiationService.createInstance( - WorkbenchObjectTree, - "Chat", - listContainer, - delegate, - [this.renderer], - { - identityProvider: { getId: (e: ChatTreeItem) => e.id }, - horizontalScrolling: false, - alwaysConsumeMouseWheel: false, - supportDynamicHeights: true, - hideTwistiesOfChildlessElements: true, - accessibilityProvider: - this.instantiationService.createInstance( - ChatAccessibilityProvider, - ), - keyboardNavigationLabelProvider: { - getKeyboardNavigationLabel: (e: ChatTreeItem) => - isRequestVM(e) - ? e.message - : isResponseVM(e) - ? e.response.value - : "", - }, // TODO - setRowLineHeight: false, - filter: this.viewOptions.filter - ? { - filter: this.viewOptions.filter.bind( - this.viewOptions, - ), - } - : undefined, - overrideStyles: { - listFocusBackground: this.styles.listBackground, - listInactiveFocusBackground: - this.styles.listBackground, - listActiveSelectionBackground: - this.styles.listBackground, - listFocusAndSelectionBackground: - this.styles.listBackground, - listInactiveSelectionBackground: - this.styles.listBackground, - listHoverBackground: this.styles.listBackground, - listBackground: this.styles.listBackground, - listFocusForeground: this.styles.listForeground, - listHoverForeground: this.styles.listForeground, - listInactiveFocusForeground: - this.styles.listForeground, - listInactiveSelectionForeground: - this.styles.listForeground, - listActiveSelectionForeground: - this.styles.listForeground, - listFocusAndSelectionForeground: - this.styles.listForeground, - listActiveSelectionIconForeground: undefined, - listInactiveSelectionIconForeground: undefined, - }, - }, - ) - ), - ); - this._register(this.tree.onContextMenu((e) => this.onContextMenu(e))); - - this._register( - this.tree.onDidChangeContentHeight(() => { - this.onDidChangeTreeContentHeight(); - }), - ); - this._register( - this.renderer.onDidChangeItemHeight((e) => { - this.tree.updateElementHeight(e.element, e.height); - }), - ); - this._register( - this.tree.onDidFocus(() => { - this._onDidFocus.fire(); - }), - ); - this._register( - this.tree.onDidScroll(() => { - this._onDidScroll.fire(); - - const isScrolledDown = - this.tree.scrollTop >= - this.tree.scrollHeight - this.tree.renderHeight - 2; - this.container.classList.toggle( - "show-scroll-down", - !isScrolledDown && !this.scrollLock, - ); - }), - ); + })); + this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); + + this._register(this.tree.onDidChangeContentHeight(() => { + this.onDidChangeTreeContentHeight(); + })); + this._register(this.renderer.onDidChangeItemHeight(e => { + this.tree.updateElementHeight(e.element, e.height); + })); + this._register(this.tree.onDidFocus(() => { + this._onDidFocus.fire(); + })); + this._register(this.tree.onDidScroll(() => { + this._onDidScroll.fire(); + + const isScrolledDown = this.tree.scrollTop >= this.tree.scrollHeight - this.tree.renderHeight - 2; + this.container.classList.toggle('show-scroll-down', !isScrolledDown && !this.scrollLock); + })); } private onContextMenu(e: ITreeContextMenuEvent): void { @@ -1047,11 +712,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const selected = e.element; const scopedContextKeyService = this.contextKeyService.createOverlay([ - [ - ChatContextKeys.responseIsFiltered.key, - isResponseVM(selected) && - !!selected.errorDetails?.responseIsFiltered, - ], + [ChatContextKeys.responseIsFiltered.key, isResponseVM(selected) && !!selected.errorDetails?.responseIsFiltered] ]); this.contextMenuService.showContextMenu({ menuId: MenuId.ChatContext, @@ -1066,24 +727,17 @@ export class ChatWidget extends Disposable implements IChatWidget { // If the list was previously scrolled all the way down, ensure it stays scrolled down, if scroll lock is on if (this.tree.scrollHeight !== this.previousTreeScrollHeight) { const lastItem = this.viewModel?.getItems().at(-1); - const lastResponseIsRendering = - isResponseVM(lastItem) && lastItem.renderData; + const lastResponseIsRendering = isResponseVM(lastItem) && lastItem.renderData; if (!lastResponseIsRendering || this.scrollLock) { // Due to rounding, the scrollTop + renderHeight will not exactly match the scrollHeight. // Consider the tree to be scrolled all the way down if it is within 2px of the bottom. - const lastElementWasVisible = - this.tree.scrollTop + this.tree.renderHeight >= - this.previousTreeScrollHeight - 2; + const lastElementWasVisible = this.tree.scrollTop + this.tree.renderHeight >= this.previousTreeScrollHeight - 2; if (lastElementWasVisible) { - dom.scheduleAtNextAnimationFrame( - dom.getWindow(this.listContainer), - () => { - // Can't set scrollTop during this event listener, the list might overwrite the change - - this.scrollToEnd(); - }, - 0, - ); + dom.scheduleAtNextAnimationFrame(dom.getWindow(this.listContainer), () => { + // Can't set scrollTop during this event listener, the list might overwrite the change + + this.scrollToEnd(); + }, 0); } } } @@ -1096,169 +750,101 @@ export class ChatWidget extends Disposable implements IChatWidget { this._onDidChangeContentHeight.fire(); } - private createInput( - container: HTMLElement, - options?: { - renderFollowups: boolean; - renderStyle?: "compact" | "minimal"; - }, - ): void { - this.inputPart = this._register( - this.instantiationService.createInstance( - ChatInputPart, - this.location, - { - renderFollowups: options?.renderFollowups ?? true, - renderStyle: - options?.renderStyle === "minimal" - ? "compact" - : options?.renderStyle, - menus: { - executeToolbar: MenuId.ChatExecute, - ...this.viewOptions.menus, - }, - editorOverflowWidgetsDomNode: - this.viewOptions.editorOverflowWidgetsDomNode, - enableImplicitContext: - this.viewOptions.enableImplicitContext, - }, - this.styles, - () => this.collectInputState(), - ), - ); - this.inputPart.render(container, "", this); - - this._register( - this.inputPart.onDidLoadInputState((state) => { - this.contribs.forEach((c) => { - if (c.setInputState) { - const contribState = - (typeof state === "object" && state?.[c.id]) ?? {}; - c.setInputState(contribState); - } - }); - this.refreshParsedInput(); - }), - ); - this._register( - this.inputPart.onDidFocus(() => this._onDidFocus.fire()), - ); - this._register( - this.inputPart.onDidAcceptFollowup((e) => { - if (!this.viewModel) { - return; + private createInput(container: HTMLElement, options?: { renderFollowups: boolean; renderStyle?: 'compact' | 'minimal' }): void { + this.inputPart = this._register(this.instantiationService.createInstance(ChatInputPart, + this.location, + { + renderFollowups: options?.renderFollowups ?? true, + renderStyle: options?.renderStyle === 'minimal' ? 'compact' : options?.renderStyle, + menus: { executeToolbar: MenuId.ChatExecute, ...this.viewOptions.menus }, + editorOverflowWidgetsDomNode: this.viewOptions.editorOverflowWidgetsDomNode, + enableImplicitContext: this.viewOptions.enableImplicitContext + }, + this.styles, + () => this.collectInputState() + )); + this.inputPart.render(container, '', this); + + this._register(this.inputPart.onDidLoadInputState(state => { + this.contribs.forEach(c => { + if (c.setInputState) { + const contribState = (typeof state === 'object' && state?.[c.id]) ?? {}; + c.setInputState(contribState); } + }); + this.refreshParsedInput(); + })); + this._register(this.inputPart.onDidFocus(() => this._onDidFocus.fire())); + this._register(this.inputPart.onDidAcceptFollowup(e => { + if (!this.viewModel) { + return; + } - let msg = ""; - if ( - e.followup.agentId && - e.followup.agentId !== - this.chatAgentService.getDefaultAgent(this.location)?.id - ) { - const agent = this.chatAgentService.getAgent( - e.followup.agentId, - ); - if (!agent) { - return; - } + let msg = ''; + if (e.followup.agentId && e.followup.agentId !== this.chatAgentService.getDefaultAgent(this.location)?.id) { + const agent = this.chatAgentService.getAgent(e.followup.agentId); + if (!agent) { + return; + } - this.lastSelectedAgent = agent; - msg = `${chatAgentLeader}${agent.name} `; - if (e.followup.subCommand) { - msg += `${chatSubcommandLeader}${e.followup.subCommand} `; - } - } else if ( - !e.followup.agentId && - e.followup.subCommand && - this.chatSlashCommandService.hasCommand( - e.followup.subCommand, - ) - ) { - msg = `${chatSubcommandLeader}${e.followup.subCommand} `; + this.lastSelectedAgent = agent; + msg = `${chatAgentLeader}${agent.name} `; + if (e.followup.subCommand) { + msg += `${chatSubcommandLeader}${e.followup.subCommand} `; } + } else if (!e.followup.agentId && e.followup.subCommand && this.chatSlashCommandService.hasCommand(e.followup.subCommand)) { + msg = `${chatSubcommandLeader}${e.followup.subCommand} `; + } - msg += e.followup.message; - this.acceptInput(msg); + msg += e.followup.message; + this.acceptInput(msg); - if (!e.response) { - // Followups can be shown by the welcome message, then there is no response associated. - // At some point we probably want telemetry for these too. - return; - } + if (!e.response) { + // Followups can be shown by the welcome message, then there is no response associated. + // At some point we probably want telemetry for these too. + return; + } - this.chatService.notifyUserAction({ - sessionId: this.viewModel.sessionId, - requestId: e.response.requestId, - agentId: e.response.agent?.id, - command: e.response.slashCommand?.name, - result: e.response.result, - action: { - kind: "followUp", - followup: e.followup, - }, - }); - }), - ); - this._register( - this.inputPart.onDidChangeHeight(() => { - if (this.bodyDimension) { - this.layout( - this.bodyDimension.height, - this.bodyDimension.width, - ); - } - this._onDidChangeContentHeight.fire(); - }), - ); - this._register( - this.inputPart.attachmentModel.onDidChangeContext(() => { - if ( - this.chatEditingService.currentEditingSession && - this.chatEditingService.currentEditingSession - ?.chatSessionId === this.viewModel?.sessionId - ) { - // TODO still needed? Do this inside input part and fire onDidChangeHeight? - this.renderChatEditingSessionState( - this.chatEditingService.currentEditingSession, - ); - } - }), - ); - this._register( - this.inputEditor.onDidChangeModelContent(() => { - this.parsedChatRequest = undefined; - this.updateChatInputContext(); - }), - ); - this._register( - this.chatAgentService.onDidChangeAgents( - () => (this.parsedChatRequest = undefined), - ), - ); + this.chatService.notifyUserAction({ + sessionId: this.viewModel.sessionId, + requestId: e.response.requestId, + agentId: e.response.agent?.id, + command: e.response.slashCommand?.name, + result: e.response.result, + action: { + kind: 'followUp', + followup: e.followup + }, + }); + })); + this._register(this.inputPart.onDidChangeHeight(() => { + if (this.bodyDimension) { + this.layout(this.bodyDimension.height, this.bodyDimension.width); + } + this._onDidChangeContentHeight.fire(); + })); + this._register(this.inputPart.attachmentModel.onDidChangeContext(() => { + if (this.chatEditingService.currentEditingSession && this.chatEditingService.currentEditingSession?.chatSessionId === this.viewModel?.sessionId) { + // TODO still needed? Do this inside input part and fire onDidChangeHeight? + this.renderChatEditingSessionState(this.chatEditingService.currentEditingSession); + } + })); + this._register(this.inputEditor.onDidChangeModelContent(() => { + this.parsedChatRequest = undefined; + this.updateChatInputContext(); + })); + this._register(this.chatAgentService.onDidChangeAgents(() => this.parsedChatRequest = undefined)); } private onDidStyleChange(): void { - this.container.style.setProperty( - "--vscode-interactive-result-editor-background-color", - this.editorOptions.configuration.resultEditor.backgroundColor?.toString() ?? - "", - ); - this.container.style.setProperty( - "--vscode-interactive-session-foreground", - this.editorOptions.configuration.foreground?.toString() ?? "", - ); - this.container.style.setProperty( - "--vscode-chat-list-background", - this.themeService - .getColorTheme() - .getColor(this.styles.listBackground) - ?.toString() ?? "", - ); + this.container.style.setProperty('--vscode-interactive-result-editor-background-color', this.editorOptions.configuration.resultEditor.backgroundColor?.toString() ?? ''); + this.container.style.setProperty('--vscode-interactive-session-foreground', this.editorOptions.configuration.foreground?.toString() ?? ''); + this.container.style.setProperty('--vscode-chat-list-background', this.themeService.getColorTheme().getColor(this.styles.listBackground)?.toString() ?? ''); } setModel(model: IChatModel, viewState: IChatViewState): void { if (!this.container) { - throw new Error("Call render() before setModel()"); + throw new Error('Call render() before setModel()'); } if (model.sessionId === this.viewModel?.sessionId) { @@ -1267,69 +853,44 @@ export class ChatWidget extends Disposable implements IChatWidget { this._codeBlockModelCollection.clear(); - this.container.setAttribute("data-session-id", model.sessionId); - this.viewModel = this.instantiationService.createInstance( - ChatViewModel, - model, - this._codeBlockModelCollection, - ); - this.viewModelDisposables.add( - Event.accumulate( - this.viewModel.onDidChange, - 0, - )((events) => { - if (!this.viewModel) { - return; - } + this.container.setAttribute('data-session-id', model.sessionId); + this.viewModel = this.instantiationService.createInstance(ChatViewModel, model, this._codeBlockModelCollection); + this.viewModelDisposables.add(Event.accumulate(this.viewModel.onDidChange, 0)(events => { + if (!this.viewModel) { + return; + } - this.requestInProgress.set(this.viewModel.requestInProgress); + this.requestInProgress.set(this.viewModel.requestInProgress); - this.onDidChangeItems(); - if ( - events.some((e) => e?.kind === "addRequest") && - this.visible - ) { - this.scrollToEnd(); - } + this.onDidChangeItems(); + if (events.some(e => e?.kind === 'addRequest') && this.visible) { + this.scrollToEnd(); + } - if ( - this.chatEditingService.currentEditingSession && - this.chatEditingService.currentEditingSession - ?.chatSessionId === this.viewModel?.sessionId - ) { - this.renderChatEditingSessionState( - this.chatEditingService.currentEditingSession, - ); - } - }), - ); - this.viewModelDisposables.add( - this.viewModel.onDidDisposeModel(() => { - // Ensure that view state is saved here, because we will load it again when a new model is assigned - this.inputPart.saveState(); - - // Disposes the viewmodel and listeners - this.viewModel = undefined; - this.onDidChangeItems(); - }), - ); + if (this.chatEditingService.currentEditingSession && this.chatEditingService.currentEditingSession?.chatSessionId === this.viewModel?.sessionId) { + this.renderChatEditingSessionState(this.chatEditingService.currentEditingSession); + } + })); + this.viewModelDisposables.add(this.viewModel.onDidDisposeModel(() => { + // Ensure that view state is saved here, because we will load it again when a new model is assigned + this.inputPart.saveState(); + + // Disposes the viewmodel and listeners + this.viewModel = undefined; + this.onDidChangeItems(); + })); this.inputPart.initForNewChatModel(viewState); - this.contribs.forEach((c) => { + this.contribs.forEach(c => { if (c.setInputState && viewState.inputState?.[c.id]) { c.setInputState(viewState.inputState?.[c.id]); } }); this.refreshParsedInput(); - this.viewModelDisposables.add( - model.onDidChange((e) => { - if (e.kind === "setAgent") { - this._onDidChangeAgent.fire({ - agent: e.agent, - slashCommand: e.command, - }); - } - }), - ); + this.viewModelDisposables.add(model.onDidChange((e) => { + if (e.kind === 'setAgent') { + this._onDidChangeAgent.fire({ agent: e.agent, slashCommand: e.command }); + } + })); if (this.tree && this.visible) { this.onDidChangeItems(); @@ -1349,7 +910,7 @@ export class ChatWidget extends Disposable implements IChatWidget { focus(item: ChatTreeItem): void { const items = this.tree.getNode(null).children; - const node = items.find((i) => i.element?.id === item.id); + const node = items.find(i => i.element?.id === item.id); if (!node) { return; } @@ -1370,7 +931,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.viewModel?.resetInputPlaceholder(); } - setInput(value = ""): void { + setInput(value = ''): void { this.inputPart.setValue(value, false); this.refreshParsedInput(); } @@ -1383,10 +944,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.inputPart.logInputHistory(); } - async acceptInput( - query?: string, - options?: IChatAcceptInputOptions, - ): Promise { + async acceptInput(query?: string, options?: IChatAcceptInputOptions): Promise { return this._acceptInput(query ? { query } : undefined, options); } @@ -1396,7 +954,7 @@ export class ChatWidget extends Disposable implements IChatWidget { private collectInputState(): IChatInputState { const inputState: IChatInputState = {}; - this.contribs.forEach((c) => { + this.contribs.forEach(c => { if (c.getInputState) { inputState[c.id] = c.getInputState(); } @@ -1404,10 +962,7 @@ export class ChatWidget extends Disposable implements IChatWidget { return inputState; } - private async _acceptInput( - query: { query: string } | { prefix: string } | undefined, - options?: IChatAcceptInputOptions, - ): Promise { + private async _acceptInput(query: { query: string } | { prefix: string } | undefined, options?: IChatAcceptInputOptions): Promise { if (this.viewModel) { this._onDidAcceptInput.fire(); if (!this.viewOptions.autoScroll) { @@ -1416,54 +971,39 @@ export class ChatWidget extends Disposable implements IChatWidget { const editorValue = this.getInput(); const requestId = this.chatAccessibilityService.acceptRequest(); - const input = !query - ? editorValue - : "query" in query - ? query.query - : `${query.prefix} ${editorValue}`; - const isUserQuery = !query || "prefix" in query; + const input = !query ? editorValue : + 'query' in query ? query.query : + `${query.prefix} ${editorValue}`; + const isUserQuery = !query || 'prefix' in query; const requests = this.viewModel.model.getRequests(); for (let i = requests.length - 1; i >= 0; i -= 1) { const request = requests[i]; if (request.isHidden) { - this.chatService.removeRequest( - this.viewModel.sessionId, - request.id, - ); + this.chatService.removeRequest(this.viewModel.sessionId, request.id); } } - let attachedContext = - this.inputPart.getAttachedAndImplicitContext(); + let attachedContext = this.inputPart.getAttachedAndImplicitContext(); let workingSet: URI[] | undefined; if (this.location === ChatAgentLocation.EditingSession) { - const currentEditingSession = - this.chatEditingService.currentEditingSessionObs.get(); + const currentEditingSession = this.chatEditingService.currentEditingSessionObs.get(); const unconfirmedSuggestions = new ResourceSet(); const uniqueWorkingSetEntries = new ResourceSet(); // NOTE: this is used for bookkeeping so the UI can avoid rendering references in the UI that are already shown in the working set - const editingSessionAttachedContext: IChatRequestVariableEntry[] = - []; + const editingSessionAttachedContext: IChatRequestVariableEntry[] = []; // Pick up everything that the user sees is part of the working set. // This should never exceed the maximum file entries limit above. for (const v of this.inputPart.chatEditWorkingSetFiles) { // Skip over any suggested files that haven't been confirmed yet in the working set - if ( - currentEditingSession?.workingSet.get(v)?.state === - WorkingSetEntryState.Suggested - ) { + if (currentEditingSession?.workingSet.get(v)?.state === WorkingSetEntryState.Suggested) { unconfirmedSuggestions.add(v); } else { uniqueWorkingSetEntries.add(v); - editingSessionAttachedContext.push( - this.attachmentModel.asVariableEntry(v), - ); + editingSessionAttachedContext.push(this.attachmentModel.asVariableEntry(v)); } } - let maximumFileEntries = - this.chatEditingService.editingSessionFileLimit - - editingSessionAttachedContext.length; + let maximumFileEntries = this.chatEditingService.editingSessionFileLimit - editingSessionAttachedContext.length; // Then take any attachments that are not files for (const attachment of this.attachmentModel.attachments) { @@ -1483,11 +1023,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const previousRequests = this.viewModel.model.getRequests(); for (const request of previousRequests) { for (const variable of request.variableData.variables) { - if ( - URI.isUri(variable.value) && - variable.isFile && - maximumFileEntries > 0 - ) { + if (URI.isUri(variable.value) && variable.isFile && maximumFileEntries > 0) { const uri = variable.value; if (!uniqueWorkingSetEntries.has(uri)) { editingSessionAttachedContext.push(variable); @@ -1501,77 +1037,39 @@ export class ChatWidget extends Disposable implements IChatWidget { attachedContext = editingSessionAttachedContext; type ChatEditingWorkingSetClassification = { - owner: "joyceerhl"; - comment: "Information about the working set size in a chat editing request"; - originalSize: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "The number of files that the user tried to attach in their editing request."; - }; - actualSize: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "The number of files that were actually sent in their editing request."; - }; + owner: 'joyceerhl'; + comment: 'Information about the working set size in a chat editing request'; + originalSize: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of files that the user tried to attach in their editing request.' }; + actualSize: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of files that were actually sent in their editing request.' }; }; type ChatEditingWorkingSetEvent = { originalSize: number; actualSize: number; }; - this.telemetryService.publicLog2< - ChatEditingWorkingSetEvent, - ChatEditingWorkingSetClassification - >("chatEditing/workingSetSize", { - originalSize: - this.inputPart.attemptedWorkingSetEntriesCount, - actualSize: uniqueWorkingSetEntries.size, - }); - currentEditingSession?.remove( - WorkingSetEntryRemovalReason.User, - ...unconfirmedSuggestions, - ); + this.telemetryService.publicLog2('chatEditing/workingSetSize', { originalSize: this.inputPart.attemptedWorkingSetEntriesCount, actualSize: uniqueWorkingSetEntries.size }); + currentEditingSession?.remove(WorkingSetEntryRemovalReason.User, ...unconfirmedSuggestions); } - const result = await this.chatService.sendRequest( - this.viewModel.sessionId, - input, - { - userSelectedModelId: this.inputPart.currentLanguageModel, - location: this.location, - locationData: this._location.resolveData?.(), - parserContext: { selectedAgent: this._lastSelectedAgent }, - attachedContext, - workingSet, - noCommandDetection: options?.noCommandDetection, - }, - ); + const result = await this.chatService.sendRequest(this.viewModel.sessionId, input, { + userSelectedModelId: this.inputPart.currentLanguageModel, + location: this.location, + locationData: this._location.resolveData?.(), + parserContext: { selectedAgent: this._lastSelectedAgent }, + attachedContext, + workingSet, + noCommandDetection: options?.noCommandDetection, + }); if (result) { this.inputPart.acceptInput(isUserQuery); - this._onDidSubmitAgent.fire({ - agent: result.agent, - slashCommand: result.slashCommand, - }); + this._onDidSubmitAgent.fire({ agent: result.agent, slashCommand: result.slashCommand }); result.responseCompletePromise.then(() => { - const responses = this.viewModel - ?.getItems() - .filter(isResponseVM); + const responses = this.viewModel?.getItems().filter(isResponseVM); const lastResponse = responses?.[responses.length - 1]; - this.chatAccessibilityService.acceptResponse( - lastResponse, - requestId, - options?.isVoiceInput, - ); + this.chatAccessibilityService.acceptResponse(lastResponse, requestId, options?.isVoiceInput); if (lastResponse?.result?.nextQuestion) { - const { prompt, participant, command } = - lastResponse.result.nextQuestion; - const question = formatChatQuestion( - this.chatAgentService, - this.location, - prompt, - participant, - command, - ); + const { prompt, participant, command } = lastResponse.result.nextQuestion; + const question = formatChatQuestion(this.chatAgentService, this.location, prompt, participant, command); if (question) { this.input.setValue(question, false); } @@ -1583,9 +1081,7 @@ export class ChatWidget extends Disposable implements IChatWidget { return undefined; } - getCodeBlockInfosForResponse( - response: IChatResponseViewModel, - ): IChatCodeBlockInfo[] { + getCodeBlockInfosForResponse(response: IChatResponseViewModel): IChatCodeBlockInfo[] { return this.renderer.getCodeBlockInfosForResponse(response); } @@ -1593,15 +1089,11 @@ export class ChatWidget extends Disposable implements IChatWidget { return this.renderer.getCodeBlockInfoForEditor(uri); } - getFileTreeInfosForResponse( - response: IChatResponseViewModel, - ): IChatFileTreeInfo[] { + getFileTreeInfosForResponse(response: IChatResponseViewModel): IChatFileTreeInfo[] { return this.renderer.getFileTreeInfosForResponse(response); } - getLastFocusedFileTreeForResponse( - response: IChatResponseViewModel, - ): IChatFileTreeInfo | undefined { + getLastFocusedFileTreeForResponse(response: IChatResponseViewModel): IChatFileTreeInfo | undefined { return this.renderer.getLastFocusedFileTreeForResponse(response); } @@ -1624,41 +1116,28 @@ export class ChatWidget extends Disposable implements IChatWidget { width = Math.min(width, 850); this.bodyDimension = new dom.Dimension(width, height); - const inputPartMaxHeight = this._dynamicMessageLayoutData?.enabled - ? this._dynamicMessageLayoutData.maxHeight - : height; + const inputPartMaxHeight = this._dynamicMessageLayoutData?.enabled ? this._dynamicMessageLayoutData.maxHeight : height; this.inputPart.layout(inputPartMaxHeight, width); const inputPartHeight = this.inputPart.inputPartHeight; - const lastElementVisible = - this.tree.scrollTop + this.tree.renderHeight >= - this.tree.scrollHeight - 2; + const lastElementVisible = this.tree.scrollTop + this.tree.renderHeight >= this.tree.scrollHeight - 2; const listHeight = Math.max(0, height - inputPartHeight); if (!this.viewOptions.autoScroll) { - this.listContainer.style.setProperty( - "--chat-current-response-min-height", - listHeight * 0.75 + "px", - ); + this.listContainer.style.setProperty('--chat-current-response-min-height', listHeight * .75 + 'px'); } this.tree.layout(listHeight, width); this.tree.getHTMLElement().style.height = `${listHeight}px`; // Push the welcome message down so it doesn't change position when followups appear - const followupsOffset = this.viewOptions.renderFollowups - ? Math.max(100 - this.inputPart.followupsHeight, 0) - : 0; + const followupsOffset = this.viewOptions.renderFollowups ? Math.max(100 - this.inputPart.followupsHeight, 0) : 0; this.welcomeMessageContainer.style.height = `${listHeight - followupsOffset}px`; this.welcomeMessageContainer.style.paddingBottom = `${followupsOffset}px`; this.renderer.layout(width); const lastItem = this.viewModel?.getItems().at(-1); - const lastResponseIsRendering = - isResponseVM(lastItem) && lastItem.renderData; - if ( - lastElementVisible && - (!lastResponseIsRendering || this.viewOptions.autoScroll) - ) { + const lastResponseIsRendering = isResponseVM(lastItem) && lastItem.renderData; + if (lastElementVisible && (!lastResponseIsRendering || this.viewOptions.autoScroll)) { this.scrollToEnd(); } @@ -1667,84 +1146,45 @@ export class ChatWidget extends Disposable implements IChatWidget { this._onDidChangeHeight.fire(height); } - private _dynamicMessageLayoutData?: { - numOfMessages: number; - maxHeight: number; - enabled: boolean; - }; + private _dynamicMessageLayoutData?: { numOfMessages: number; maxHeight: number; enabled: boolean }; // An alternative to layout, this allows you to specify the number of ChatTreeItems // you want to show, and the max height of the container. It will then layout the // tree to show that many items. // TODO@TylerLeonhardt: This could use some refactoring to make it clear which layout strategy is being used - setDynamicChatTreeItemLayout( - numOfChatTreeItems: number, - maxHeight: number, - ) { - this._dynamicMessageLayoutData = { - numOfMessages: numOfChatTreeItems, - maxHeight, - enabled: true, - }; - this._register( - this.renderer.onDidChangeItemHeight(() => - this.layoutDynamicChatTreeItemMode(), - ), - ); + setDynamicChatTreeItemLayout(numOfChatTreeItems: number, maxHeight: number) { + this._dynamicMessageLayoutData = { numOfMessages: numOfChatTreeItems, maxHeight, enabled: true }; + this._register(this.renderer.onDidChangeItemHeight(() => this.layoutDynamicChatTreeItemMode())); const mutableDisposable = this._register(new MutableDisposable()); - this._register( - this.tree.onDidScroll((e) => { - // TODO@TylerLeonhardt this should probably just be disposed when this is disabled - // and then set up again when it is enabled again - if (!this._dynamicMessageLayoutData?.enabled) { + this._register(this.tree.onDidScroll((e) => { + // TODO@TylerLeonhardt this should probably just be disposed when this is disabled + // and then set up again when it is enabled again + if (!this._dynamicMessageLayoutData?.enabled) { + return; + } + mutableDisposable.value = dom.scheduleAtNextAnimationFrame(dom.getWindow(this.listContainer), () => { + if (!e.scrollTopChanged || e.heightChanged || e.scrollHeightChanged) { + return; + } + const renderHeight = e.height; + const diff = e.scrollHeight - renderHeight - e.scrollTop; + if (diff === 0) { return; } - mutableDisposable.value = dom.scheduleAtNextAnimationFrame( - dom.getWindow(this.listContainer), - () => { - if ( - !e.scrollTopChanged || - e.heightChanged || - e.scrollHeightChanged - ) { - return; - } - const renderHeight = e.height; - const diff = - e.scrollHeight - renderHeight - e.scrollTop; - if (diff === 0) { - return; - } - const possibleMaxHeight = - this._dynamicMessageLayoutData?.maxHeight ?? - maxHeight; - const width = - this.bodyDimension?.width ?? - this.container.offsetWidth; - this.inputPart.layout(possibleMaxHeight, width); - const inputPartHeight = this.inputPart.inputPartHeight; - const newHeight = Math.min( - renderHeight + diff, - possibleMaxHeight - inputPartHeight, - ); - this.layout(newHeight + inputPartHeight, width); - }, - ); - }), - ); + const possibleMaxHeight = (this._dynamicMessageLayoutData?.maxHeight ?? maxHeight); + const width = this.bodyDimension?.width ?? this.container.offsetWidth; + this.inputPart.layout(possibleMaxHeight, width); + const inputPartHeight = this.inputPart.inputPartHeight; + const newHeight = Math.min(renderHeight + diff, possibleMaxHeight - inputPartHeight); + this.layout(newHeight + inputPartHeight, width); + }); + })); } - updateDynamicChatTreeItemLayout( - numOfChatTreeItems: number, - maxHeight: number, - ) { - this._dynamicMessageLayoutData = { - numOfMessages: numOfChatTreeItems, - maxHeight, - enabled: true, - }; + updateDynamicChatTreeItemLayout(numOfChatTreeItems: number, maxHeight: number) { + this._dynamicMessageLayoutData = { numOfMessages: numOfChatTreeItems, maxHeight, enabled: true }; let hasChanged = false; let height = this.bodyDimension!.height; let width = this.bodyDimension!.width; @@ -1784,27 +1224,20 @@ export class ChatWidget extends Disposable implements IChatWidget { const totalMessages = this.viewModel.getItems(); // grab the last N messages - const messages = totalMessages.slice( - -this._dynamicMessageLayoutData.numOfMessages, - ); + const messages = totalMessages.slice(-this._dynamicMessageLayoutData.numOfMessages); - const needsRerender = messages.some( - (m) => m.currentRenderedHeight === undefined, - ); + const needsRerender = messages.some(m => m.currentRenderedHeight === undefined); const listHeight = needsRerender ? this._dynamicMessageLayoutData.maxHeight - : messages.reduce( - (acc, message) => acc + message.currentRenderedHeight!, - 0, - ); + : messages.reduce((acc, message) => acc + message.currentRenderedHeight!, 0); this.layout( Math.min( // we add an additional 18px in order to show that there is scrollable content inputHeight + listHeight + (totalMessages.length > 2 ? 18 : 0), - this._dynamicMessageLayoutData.maxHeight, + this._dynamicMessageLayoutData.maxHeight ), - width, + width ); if (needsRerender || !listHeight) { @@ -1816,60 +1249,47 @@ export class ChatWidget extends Disposable implements IChatWidget { this.inputPart.saveState(); if (this.viewModel?.model.welcomeMessage) { - this.storageService.store( - `${PersistWelcomeMessageContentKey}.${this.location}`, - this.viewModel?.model.welcomeMessage, - StorageScope.APPLICATION, - StorageTarget.MACHINE, - ); + this.storageService.store(`${PersistWelcomeMessageContentKey}.${this.location}`, this.viewModel?.model.welcomeMessage, StorageScope.APPLICATION, StorageTarget.MACHINE); } } getViewState(): IChatViewState { return { inputValue: this.getInput(), - inputState: this.inputPart.getViewState(), + inputState: this.inputPart.getViewState() }; } private updateChatInputContext() { - const currentAgent = this.parsedInput.parts.find( - (part) => part instanceof ChatRequestAgentPart, - ); + const currentAgent = this.parsedInput.parts.find(part => part instanceof ChatRequestAgentPart); this.agentInInput.set(!!currentAgent); } } -export class ChatWidgetService - extends Disposable - implements IChatWidgetService -{ +export class ChatWidgetService extends Disposable implements IChatWidgetService { + declare readonly _serviceBrand: undefined; private _widgets: ChatWidget[] = []; private _lastFocusedWidget: ChatWidget | undefined = undefined; - private readonly _onDidAddWidget = this._register( - new Emitter(), - ); + private readonly _onDidAddWidget = this._register(new Emitter()); readonly onDidAddWidget: Event = this._onDidAddWidget.event; get lastFocusedWidget(): IChatWidget | undefined { return this._lastFocusedWidget; } - getWidgetsByLocations( - location: ChatAgentLocation, - ): ReadonlyArray { - return this._widgets.filter((w) => w.location === location); + getWidgetsByLocations(location: ChatAgentLocation): ReadonlyArray { + return this._widgets.filter(w => w.location === location); } getWidgetByInputUri(uri: URI): ChatWidget | undefined { - return this._widgets.find((w) => isEqual(w.inputUri, uri)); + return this._widgets.find(w => isEqual(w.inputUri, uri)); } getWidgetBySessionId(sessionId: string): ChatWidget | undefined { - return this._widgets.find((w) => w.viewModel?.sessionId === sessionId); + return this._widgets.find(w => w.viewModel?.sessionId === sessionId); } private setLastFocusedWidget(widget: ChatWidget | undefined): void { @@ -1881,8 +1301,8 @@ export class ChatWidgetService } register(newWidget: ChatWidget): IDisposable { - if (this._widgets.some((widget) => widget === newWidget)) { - throw new Error("Cannot register the same widget multiple times"); + if (this._widgets.some(widget => widget === newWidget)) { + throw new Error('Cannot register the same widget multiple times'); } this._widgets.push(newWidget); @@ -1890,9 +1310,7 @@ export class ChatWidgetService return combinedDisposable( newWidget.onDidFocus(() => this.setLastFocusedWidget(newWidget)), - toDisposable(() => - this._widgets.splice(this._widgets.indexOf(newWidget), 1), - ), + toDisposable(() => this._widgets.splice(this._widgets.indexOf(newWidget), 1)) ); } } diff --git a/Source/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/Source/vs/workbench/contrib/chat/browser/codeBlockPart.ts index 5ce80cf7a725c..1e6a5a9ea561f 100644 --- a/Source/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/Source/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -3,91 +3,72 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import "./codeBlockPart.css"; - -import * as dom from "../../../../base/browser/dom.js"; -import { renderFormattedText } from "../../../../base/browser/formattedTextRenderer.js"; -import { Button } from "../../../../base/browser/ui/button/button.js"; -import { CancellationToken } from "../../../../base/common/cancellation.js"; -import { Codicon } from "../../../../base/common/codicons.js"; -import { Emitter, Event } from "../../../../base/common/event.js"; -import { - combinedDisposable, - Disposable, - MutableDisposable, -} from "../../../../base/common/lifecycle.js"; -import { Schemas } from "../../../../base/common/network.js"; -import { isEqual } from "../../../../base/common/resources.js"; -import { assertType } from "../../../../base/common/types.js"; -import { URI, UriComponents } from "../../../../base/common/uri.js"; -import { IEditorConstructionOptions } from "../../../../editor/browser/config/editorConfiguration.js"; -import { IDiffEditor } from "../../../../editor/browser/editorBrowser.js"; -import { EditorExtensionsRegistry } from "../../../../editor/browser/editorExtensions.js"; -import { ICodeEditorService } from "../../../../editor/browser/services/codeEditorService.js"; -import { - CodeEditorWidget, - ICodeEditorWidgetOptions, -} from "../../../../editor/browser/widget/codeEditor/codeEditorWidget.js"; -import { DiffEditorWidget } from "../../../../editor/browser/widget/diffEditor/diffEditorWidget.js"; -import { - EDITOR_FONT_DEFAULTS, - EditorOption, - IEditorOptions, -} from "../../../../editor/common/config/editorOptions.js"; -import { IRange, Range } from "../../../../editor/common/core/range.js"; -import { ScrollType } from "../../../../editor/common/editorCommon.js"; -import { TextEdit } from "../../../../editor/common/languages.js"; -import { - EndOfLinePreference, - ITextModel, -} from "../../../../editor/common/model.js"; -import { TextModelText } from "../../../../editor/common/model/textModelText.js"; -import { IModelService } from "../../../../editor/common/services/model.js"; -import { DefaultModelSHA1Computer } from "../../../../editor/common/services/modelService.js"; -import { - ITextModelContentProvider, - ITextModelService, -} from "../../../../editor/common/services/resolverService.js"; -import { BracketMatchingController } from "../../../../editor/contrib/bracketMatching/browser/bracketMatching.js"; -import { ColorDetector } from "../../../../editor/contrib/colorPicker/browser/colorDetector.js"; -import { ContextMenuController } from "../../../../editor/contrib/contextmenu/browser/contextmenu.js"; -import { GotoDefinitionAtPositionEditorContribution } from "../../../../editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.js"; -import { ContentHoverController } from "../../../../editor/contrib/hover/browser/contentHoverController.js"; -import { GlyphHoverController } from "../../../../editor/contrib/hover/browser/glyphHoverController.js"; -import { LinkDetector } from "../../../../editor/contrib/links/browser/links.js"; -import { MessageController } from "../../../../editor/contrib/message/browser/messageController.js"; -import { ViewportSemanticTokensContribution } from "../../../../editor/contrib/semanticTokens/browser/viewportSemanticTokens.js"; -import { SmartSelectController } from "../../../../editor/contrib/smartSelect/browser/smartSelect.js"; -import { WordHighlighterContribution } from "../../../../editor/contrib/wordHighlighter/browser/wordHighlighter.js"; -import { localize } from "../../../../nls.js"; -import { IAccessibilityService } from "../../../../platform/accessibility/common/accessibility.js"; -import { MenuWorkbenchToolBar } from "../../../../platform/actions/browser/toolbar.js"; -import { MenuId } from "../../../../platform/actions/common/actions.js"; -import { IConfigurationService } from "../../../../platform/configuration/common/configuration.js"; -import { IContextKeyService } from "../../../../platform/contextkey/common/contextkey.js"; -import { IDialogService } from "../../../../platform/dialogs/common/dialogs.js"; -import { FileKind } from "../../../../platform/files/common/files.js"; -import { IInstantiationService } from "../../../../platform/instantiation/common/instantiation.js"; -import { ServiceCollection } from "../../../../platform/instantiation/common/serviceCollection.js"; -import { ILabelService } from "../../../../platform/label/common/label.js"; -import { IOpenerService } from "../../../../platform/opener/common/opener.js"; -import { ResourceLabel } from "../../../browser/labels.js"; -import { ResourceContextKey } from "../../../common/contextkeys.js"; -import { AccessibilityVerbositySettingId } from "../../accessibility/browser/accessibilityConfiguration.js"; -import { InspectEditorTokensController } from "../../codeEditor/browser/inspectEditorTokens/inspectEditorTokens.js"; -import { MenuPreventer } from "../../codeEditor/browser/menuPreventer.js"; -import { SelectionClipboardContributionID } from "../../codeEditor/browser/selectionClipboard.js"; -import { getSimpleEditorOptions } from "../../codeEditor/browser/simpleEditorOptions.js"; -import { IMarkdownVulnerability } from "../common/annotations.js"; -import { ChatContextKeys } from "../common/chatContextKeys.js"; -import { IChatResponseModel, IChatTextEditGroup } from "../common/chatModel.js"; -import { - IChatResponseViewModel, - isResponseVM, -} from "../common/chatViewModel.js"; -import { ChatTreeItem } from "./chat.js"; -import { IChatRendererDelegate } from "./chatListRenderer.js"; -import { ChatEditorOptions } from "./chatOptions.js"; +import './codeBlockPart.css'; + +import * as dom from '../../../../base/browser/dom.js'; +import { renderFormattedText } from '../../../../base/browser/formattedTextRenderer.js'; +import { Button } from '../../../../base/browser/ui/button/button.js'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; +import { combinedDisposable, Disposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; +import { Schemas } from '../../../../base/common/network.js'; +import { isEqual } from '../../../../base/common/resources.js'; +import { assertType } from '../../../../base/common/types.js'; +import { URI, UriComponents } from '../../../../base/common/uri.js'; +import { IEditorConstructionOptions } from '../../../../editor/browser/config/editorConfiguration.js'; +import { IDiffEditor } from '../../../../editor/browser/editorBrowser.js'; +import { EditorExtensionsRegistry } from '../../../../editor/browser/editorExtensions.js'; +import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from '../../../../editor/browser/widget/codeEditor/codeEditorWidget.js'; +import { DiffEditorWidget } from '../../../../editor/browser/widget/diffEditor/diffEditorWidget.js'; +import { EDITOR_FONT_DEFAULTS, EditorOption, IEditorOptions } from '../../../../editor/common/config/editorOptions.js'; +import { IRange, Range } from '../../../../editor/common/core/range.js'; +import { ScrollType } from '../../../../editor/common/editorCommon.js'; +import { TextEdit } from '../../../../editor/common/languages.js'; +import { EndOfLinePreference, ITextModel } from '../../../../editor/common/model.js'; +import { TextModelText } from '../../../../editor/common/model/textModelText.js'; +import { IModelService } from '../../../../editor/common/services/model.js'; +import { DefaultModelSHA1Computer } from '../../../../editor/common/services/modelService.js'; +import { ITextModelContentProvider, ITextModelService } from '../../../../editor/common/services/resolverService.js'; +import { BracketMatchingController } from '../../../../editor/contrib/bracketMatching/browser/bracketMatching.js'; +import { ColorDetector } from '../../../../editor/contrib/colorPicker/browser/colorDetector.js'; +import { ContextMenuController } from '../../../../editor/contrib/contextmenu/browser/contextmenu.js'; +import { GotoDefinitionAtPositionEditorContribution } from '../../../../editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.js'; +import { ContentHoverController } from '../../../../editor/contrib/hover/browser/contentHoverController.js'; +import { GlyphHoverController } from '../../../../editor/contrib/hover/browser/glyphHoverController.js'; +import { LinkDetector } from '../../../../editor/contrib/links/browser/links.js'; +import { MessageController } from '../../../../editor/contrib/message/browser/messageController.js'; +import { ViewportSemanticTokensContribution } from '../../../../editor/contrib/semanticTokens/browser/viewportSemanticTokens.js'; +import { SmartSelectController } from '../../../../editor/contrib/smartSelect/browser/smartSelect.js'; +import { WordHighlighterContribution } from '../../../../editor/contrib/wordHighlighter/browser/wordHighlighter.js'; +import { localize } from '../../../../nls.js'; +import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; +import { MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; +import { MenuId } from '../../../../platform/actions/common/actions.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; +import { FileKind } from '../../../../platform/files/common/files.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; +import { ILabelService } from '../../../../platform/label/common/label.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; +import { ResourceLabel } from '../../../browser/labels.js'; +import { ResourceContextKey } from '../../../common/contextkeys.js'; +import { AccessibilityVerbositySettingId } from '../../accessibility/browser/accessibilityConfiguration.js'; +import { InspectEditorTokensController } from '../../codeEditor/browser/inspectEditorTokens/inspectEditorTokens.js'; +import { MenuPreventer } from '../../codeEditor/browser/menuPreventer.js'; +import { SelectionClipboardContributionID } from '../../codeEditor/browser/selectionClipboard.js'; +import { getSimpleEditorOptions } from '../../codeEditor/browser/simpleEditorOptions.js'; +import { IMarkdownVulnerability } from '../common/annotations.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; +import { IChatResponseModel, IChatTextEditGroup } from '../common/chatModel.js'; +import { IChatResponseViewModel, isResponseVM } from '../common/chatViewModel.js'; +import { ChatTreeItem } from './chat.js'; +import { IChatRendererDelegate } from './chatListRenderer.js'; +import { ChatEditorOptions } from './chatOptions.js'; +import { emptyProgressRunner, IEditorProgressService } from '../../../../platform/progress/common/progress.js'; const $ = dom.$; @@ -112,40 +93,34 @@ export interface ICodeBlockData { * * The text of the code path should be a {@link LocalFileCodeBlockData} json object. */ -export const localFileLanguageId = "vscode-local-file"; +export const localFileLanguageId = 'vscode-local-file'; + export function parseLocalFileData(text: string) { + interface RawLocalFileCodeBlockData { readonly uri: UriComponents; readonly range?: IRange; } let data: RawLocalFileCodeBlockData; - try { data = JSON.parse(text); } catch (e) { - throw new Error("Could not parse code block local file data"); + throw new Error('Could not parse code block local file data'); } let uri: URI; - try { uri = URI.revive(data?.uri); } catch (e) { - throw new Error("Invalid code block local file data URI"); + throw new Error('Invalid code block local file data URI'); } let range: IRange | undefined; - if (data.range) { // Note that since this is coming from extensions, position are actually zero based and must be converted. - range = new Range( - data.range.startLineNumber + 1, - data.range.startColumn + 1, - data.range.endLineNumber + 1, - data.range.endColumn + 1, - ); + range = new Range(data.range.startLineNumber + 1, data.range.startColumn + 1, data.range.endLineNumber + 1, data.range.endColumn + 1); } return { uri, range }; @@ -161,11 +136,8 @@ export interface ICodeBlockActionContext { const defaultCodeblockPadding = 10; export class CodeBlockPart extends Disposable { - protected readonly _onDidChangeContentHeight = this._register( - new Emitter(), - ); - public readonly onDidChangeContentHeight = - this._onDidChangeContentHeight.event; + protected readonly _onDidChangeContentHeight = this._register(new Emitter()); + public readonly onDidChangeContentHeight = this._onDidChangeContentHeight.event; public readonly editor: CodeEditorWidget; protected readonly toolbar: MenuWorkbenchToolBar; @@ -191,209 +163,120 @@ export class CodeBlockPart extends Disposable { @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, @IModelService protected readonly modelService: IModelService, - @IConfigurationService - private readonly configurationService: IConfigurationService, - @IAccessibilityService - private readonly accessibilityService: IAccessibilityService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IAccessibilityService private readonly accessibilityService: IAccessibilityService, ) { super(); - this.element = $(".interactive-result-code-block"); - - this.resourceContextKey = this._register( - instantiationService.createInstance(ResourceContextKey), - ); - this.contextKeyService = this._register( - contextKeyService.createScoped(this.element), - ); - - const scopedInstantiationService = this._register( - instantiationService.createChild( - new ServiceCollection([ - IContextKeyService, - this.contextKeyService, - ]), - ), - ); - - const editorElement = dom.append( - this.element, - $(".interactive-result-editor"), - ); - this.editor = this.createEditor( - scopedInstantiationService, - editorElement, - { - ...getSimpleEditorOptions(this.configurationService), - readOnly: true, - lineNumbers: "off", - selectOnLineNumbers: true, - scrollBeyondLastLine: false, - lineDecorationsWidth: 8, - dragAndDrop: false, - padding: { - top: defaultCodeblockPadding, - bottom: defaultCodeblockPadding, - }, - mouseWheelZoom: false, - scrollbar: { - vertical: "hidden", - alwaysConsumeMouseWheel: false, - }, - definitionLinkOpensInPeek: false, - gotoLocation: { - multiple: "goto", - multipleDeclarations: "goto", - multipleDefinitions: "goto", - multipleImplementations: "goto", - }, - ariaLabel: localize("chat.codeBlockHelp", "Code block"), - overflowWidgetsDomNode, - ...this.getEditorOptionsFromConfig(), + this.element = $('.interactive-result-code-block'); + + this.resourceContextKey = this._register(instantiationService.createInstance(ResourceContextKey)); + this.contextKeyService = this._register(contextKeyService.createScoped(this.element)); + const scopedInstantiationService = this._register(instantiationService.createChild(new ServiceCollection([IContextKeyService, this.contextKeyService]))); + const editorElement = dom.append(this.element, $('.interactive-result-editor')); + this.editor = this.createEditor(scopedInstantiationService, editorElement, { + ...getSimpleEditorOptions(this.configurationService), + readOnly: true, + lineNumbers: 'off', + selectOnLineNumbers: true, + scrollBeyondLastLine: false, + lineDecorationsWidth: 8, + dragAndDrop: false, + padding: { top: defaultCodeblockPadding, bottom: defaultCodeblockPadding }, + mouseWheelZoom: false, + scrollbar: { + vertical: 'hidden', + alwaysConsumeMouseWheel: false }, - ); - - const toolbarElement = dom.append( - this.element, - $(".interactive-result-code-block-toolbar"), - ); - - const editorScopedService = - this.editor.contextKeyService.createScoped(toolbarElement); - - const editorScopedInstantiationService = this._register( - scopedInstantiationService.createChild( - new ServiceCollection([ - IContextKeyService, - editorScopedService, - ]), - ), - ); - this.toolbar = this._register( - editorScopedInstantiationService.createInstance( - MenuWorkbenchToolBar, - toolbarElement, - menuId, - { - menuOptions: { - shouldForwardArgs: true, - }, - }, - ), - ); - - const vulnsContainer = dom.append( - this.element, - $(".interactive-result-vulns"), - ); - - const vulnsHeaderElement = dom.append( - vulnsContainer, - $(".interactive-result-vulns-header", undefined), - ); - this.vulnsButton = this._register( - new Button(vulnsHeaderElement, { - buttonBackground: undefined, - buttonBorder: undefined, - buttonForeground: undefined, - buttonHoverBackground: undefined, - buttonSecondaryBackground: undefined, - buttonSecondaryForeground: undefined, - buttonSecondaryHoverBackground: undefined, - buttonSeparator: undefined, - supportIcons: true, - }), - ); - - this.vulnsListElement = dom.append( - vulnsContainer, - $("ul.interactive-result-vulns-list"), - ); - - this._register( - this.vulnsButton.onDidClick(() => { - const element = this.currentCodeBlockData! - .element as IChatResponseViewModel; - element.vulnerabilitiesListExpanded = - !element.vulnerabilitiesListExpanded; - this.vulnsButton.label = this.getVulnerabilitiesLabel(); - this.element.classList.toggle( - "chat-vulnerabilities-collapsed", - !element.vulnerabilitiesListExpanded, - ); - this._onDidChangeContentHeight.fire(); - // this.updateAriaLabel(collapseButton.element, referencesLabel, element.usedReferencesExpanded); - }), - ); + definitionLinkOpensInPeek: false, + gotoLocation: { + multiple: 'goto', + multipleDeclarations: 'goto', + multipleDefinitions: 'goto', + multipleImplementations: 'goto', + }, + ariaLabel: localize('chat.codeBlockHelp', 'Code block'), + overflowWidgetsDomNode, + ...this.getEditorOptionsFromConfig(), + }); + + const toolbarElement = dom.append(this.element, $('.interactive-result-code-block-toolbar')); + const editorScopedService = this.editor.contextKeyService.createScoped(toolbarElement); + const editorScopedInstantiationService = this._register(scopedInstantiationService.createChild(new ServiceCollection([IContextKeyService, editorScopedService]))); + this.toolbar = this._register(editorScopedInstantiationService.createInstance(MenuWorkbenchToolBar, toolbarElement, menuId, { + menuOptions: { + shouldForwardArgs: true + } + })); + + const vulnsContainer = dom.append(this.element, $('.interactive-result-vulns')); + const vulnsHeaderElement = dom.append(vulnsContainer, $('.interactive-result-vulns-header', undefined)); + this.vulnsButton = this._register(new Button(vulnsHeaderElement, { + buttonBackground: undefined, + buttonBorder: undefined, + buttonForeground: undefined, + buttonHoverBackground: undefined, + buttonSecondaryBackground: undefined, + buttonSecondaryForeground: undefined, + buttonSecondaryHoverBackground: undefined, + buttonSeparator: undefined, + supportIcons: true + })); + + this.vulnsListElement = dom.append(vulnsContainer, $('ul.interactive-result-vulns-list')); + + this._register(this.vulnsButton.onDidClick(() => { + const element = this.currentCodeBlockData!.element as IChatResponseViewModel; + element.vulnerabilitiesListExpanded = !element.vulnerabilitiesListExpanded; + this.vulnsButton.label = this.getVulnerabilitiesLabel(); + this.element.classList.toggle('chat-vulnerabilities-collapsed', !element.vulnerabilitiesListExpanded); + this._onDidChangeContentHeight.fire(); + // this.updateAriaLabel(collapseButton.element, referencesLabel, element.usedReferencesExpanded); + })); - this._register( - this.toolbar.onDidChangeDropdownVisibility((e) => { - toolbarElement.classList.toggle("force-visibility", e); - }), - ); + this._register(this.toolbar.onDidChangeDropdownVisibility(e => { + toolbarElement.classList.toggle('force-visibility', e); + })); this._configureForScreenReader(); - this._register( - this.accessibilityService.onDidChangeScreenReaderOptimized(() => - this._configureForScreenReader(), - ), - ); - this._register( - this.configurationService.onDidChangeConfiguration((e) => { - if (e.affectedKeys.has(AccessibilityVerbositySettingId.Chat)) { - this._configureForScreenReader(); - } - }), - ); - - this._register( - this.options.onDidChange(() => { - this.editor.updateOptions(this.getEditorOptionsFromConfig()); - }), - ); - - this._register( - this.editor.onDidScrollChange((e) => { - this.currentScrollWidth = e.scrollWidth; - }), - ); - this._register( - this.editor.onDidContentSizeChange((e) => { - if (e.contentHeightChanged) { - this._onDidChangeContentHeight.fire(); - } - }), - ); - this._register( - this.editor.onDidBlurEditorWidget(() => { - this.element.classList.remove("focused"); - WordHighlighterContribution.get( - this.editor, - )?.stopHighlighting(); - this.clearWidgets(); - }), - ); - this._register( - this.editor.onDidFocusEditorWidget(() => { - this.element.classList.add("focused"); - WordHighlighterContribution.get(this.editor)?.restoreViewState( - true, - ); - }), - ); + this._register(this.accessibilityService.onDidChangeScreenReaderOptimized(() => this._configureForScreenReader())); + this._register(this.configurationService.onDidChangeConfiguration((e) => { + if (e.affectedKeys.has(AccessibilityVerbositySettingId.Chat)) { + this._configureForScreenReader(); + } + })); + + this._register(this.options.onDidChange(() => { + this.editor.updateOptions(this.getEditorOptionsFromConfig()); + })); + + this._register(this.editor.onDidScrollChange(e => { + this.currentScrollWidth = e.scrollWidth; + })); + this._register(this.editor.onDidContentSizeChange(e => { + if (e.contentHeightChanged) { + this._onDidChangeContentHeight.fire(); + } + })); + this._register(this.editor.onDidBlurEditorWidget(() => { + this.element.classList.remove('focused'); + WordHighlighterContribution.get(this.editor)?.stopHighlighting(); + this.clearWidgets(); + })); + this._register(this.editor.onDidFocusEditorWidget(() => { + this.element.classList.add('focused'); + WordHighlighterContribution.get(this.editor)?.restoreViewState(true); + })); // Parent list scrolled if (delegate.onDidScroll) { - this._register( - delegate.onDidScroll((e) => { - this.clearWidgets(); - }), - ); + this._register(delegate.onDidScroll(e => { + this.clearWidgets(); + })); } } override dispose() { this.isDisposed = true; - super.dispose(); } @@ -401,40 +284,28 @@ export class CodeBlockPart extends Disposable { return this.editor.getModel()?.uri; } - private createEditor( - instantiationService: IInstantiationService, - parent: HTMLElement, - options: Readonly, - ): CodeEditorWidget { - return this._register( - instantiationService.createInstance( - CodeEditorWidget, - parent, - options, - { - isSimpleWidget: false, - contributions: - EditorExtensionsRegistry.getSomeEditorContributions([ - MenuPreventer.ID, - SelectionClipboardContributionID, - ContextMenuController.ID, - - WordHighlighterContribution.ID, - ViewportSemanticTokensContribution.ID, - BracketMatchingController.ID, - SmartSelectController.ID, - ContentHoverController.ID, - GlyphHoverController.ID, - MessageController.ID, - GotoDefinitionAtPositionEditorContribution.ID, - ColorDetector.ID, - LinkDetector.ID, - - InspectEditorTokensController.ID, - ]), - }, - ), - ); + private createEditor(instantiationService: IInstantiationService, parent: HTMLElement, options: Readonly): CodeEditorWidget { + return this._register(instantiationService.createInstance(CodeEditorWidget, parent, options, { + isSimpleWidget: false, + contributions: EditorExtensionsRegistry.getSomeEditorContributions([ + MenuPreventer.ID, + SelectionClipboardContributionID, + ContextMenuController.ID, + + WordHighlighterContribution.ID, + ViewportSemanticTokensContribution.ID, + BracketMatchingController.ID, + SmartSelectController.ID, + ContentHoverController.ID, + GlyphHoverController.ID, + MessageController.ID, + GotoDefinitionAtPositionEditorContribution.ID, + ColorDetector.ID, + LinkDetector.ID, + + InspectEditorTokensController.ID, + ]) + })); } focus(): void { @@ -444,49 +315,32 @@ export class CodeBlockPart extends Disposable { private updatePaddingForLayout() { // scrollWidth = "the width of the content that needs to be scrolled" // contentWidth = "the width of the area where content is displayed" - const horizontalScrollbarVisible = - this.currentScrollWidth > this.editor.getLayoutInfo().contentWidth; - - const scrollbarHeight = - this.editor.getLayoutInfo().horizontalScrollbarHeight; - - const bottomPadding = horizontalScrollbarVisible - ? Math.max(defaultCodeblockPadding - scrollbarHeight, 2) - : defaultCodeblockPadding; - this.editor.updateOptions({ - padding: { top: defaultCodeblockPadding, bottom: bottomPadding }, - }); + const horizontalScrollbarVisible = this.currentScrollWidth > this.editor.getLayoutInfo().contentWidth; + const scrollbarHeight = this.editor.getLayoutInfo().horizontalScrollbarHeight; + const bottomPadding = horizontalScrollbarVisible ? + Math.max(defaultCodeblockPadding - scrollbarHeight, 2) : + defaultCodeblockPadding; + this.editor.updateOptions({ padding: { top: defaultCodeblockPadding, bottom: bottomPadding } }); } private _configureForScreenReader(): void { const toolbarElt = this.toolbar.getElement(); - if (this.accessibilityService.isScreenReaderOptimized()) { - toolbarElt.style.display = "block"; - toolbarElt.ariaLabel = this.configurationService.getValue( - AccessibilityVerbositySettingId.Chat, - ) - ? localize( - "chat.codeBlock.toolbarVerbose", - "Toolbar for code block which can be reached via tab", - ) - : localize("chat.codeBlock.toolbar", "Code block toolbar"); + toolbarElt.style.display = 'block'; + toolbarElt.ariaLabel = this.configurationService.getValue(AccessibilityVerbositySettingId.Chat) ? localize('chat.codeBlock.toolbarVerbose', 'Toolbar for code block which can be reached via tab') : localize('chat.codeBlock.toolbar', 'Code block toolbar'); } else { - toolbarElt.style.display = ""; + toolbarElt.style.display = ''; } } private getEditorOptionsFromConfig(): IEditorOptions { return { wordWrap: this.options.configuration.resultEditor.wordWrap, - fontLigatures: - this.options.configuration.resultEditor.fontLigatures, - bracketPairColorization: - this.options.configuration.resultEditor.bracketPairColorization, - fontFamily: - this.options.configuration.resultEditor.fontFamily === "default" - ? EDITOR_FONT_DEFAULTS.fontFamily - : this.options.configuration.resultEditor.fontFamily, + fontLigatures: this.options.configuration.resultEditor.fontLigatures, + bracketPairColorization: this.options.configuration.resultEditor.bracketPairColorization, + fontFamily: this.options.configuration.resultEditor.fontFamily === 'default' ? + EDITOR_FONT_DEFAULTS.fontFamily : + this.options.configuration.resultEditor.fontFamily, fontSize: this.options.configuration.resultEditor.fontSize, fontWeight: this.options.configuration.resultEditor.fontWeight, lineHeight: this.options.configuration.resultEditor.lineHeight, @@ -495,24 +349,15 @@ export class CodeBlockPart extends Disposable { layout(width: number): void { const contentHeight = this.getContentHeight(); - const editorBorder = 2; - this.editor.layout({ - width: width - editorBorder, - height: contentHeight, - }); + this.editor.layout({ width: width - editorBorder, height: contentHeight }); this.updatePaddingForLayout(); } private getContentHeight() { if (this.currentCodeBlockData?.range) { - const lineCount = - this.currentCodeBlockData.range.endLineNumber - - this.currentCodeBlockData.range.startLineNumber + - 1; - + const lineCount = this.currentCodeBlockData.range.endLineNumber - this.currentCodeBlockData.range.startLineNumber + 1; const lineHeight = this.editor.getOption(EditorOption.lineHeight); - return lineCount * lineHeight; } return this.editor.getContentHeight(); @@ -520,31 +365,23 @@ export class CodeBlockPart extends Disposable { async render(data: ICodeBlockData, width: number) { this.currentCodeBlockData = data; - if (data.parentContextKeyService) { this.contextKeyService.updateParent(data.parentContextKeyService); } - if (this.options.configuration.resultEditor.wordWrap === "on") { + if (this.options.configuration.resultEditor.wordWrap === 'on') { // Initialize the editor with the new proper width so that getContentHeight // will be computed correctly in the next call to layout() this.layout(width); } await this.updateEditor(data); - if (this.isDisposed) { return; } this.layout(width); - this.editor.updateOptions({ - ariaLabel: localize( - "chat.codeBlockLabel", - "Code block {0}", - data.codeBlockIndex + 1, - ), - }); + this.editor.updateOptions({ ariaLabel: localize('chat.codeBlockLabel', "Code block {0}", data.codeBlockIndex + 1) }); if (data.hideToolbar) { dom.hide(this.toolbar.getElement()); @@ -554,26 +391,12 @@ export class CodeBlockPart extends Disposable { if (data.vulns?.length && isResponseVM(data.element)) { dom.clearNode(this.vulnsListElement); - this.element.classList.remove("no-vulns"); - this.element.classList.toggle( - "chat-vulnerabilities-collapsed", - !data.element.vulnerabilitiesListExpanded, - ); - - dom.append( - this.vulnsListElement, - ...data.vulns.map((v) => - $( - "li", - undefined, - $("span.chat-vuln-title", undefined, v.title), - " " + v.description, - ), - ), - ); + this.element.classList.remove('no-vulns'); + this.element.classList.toggle('chat-vulnerabilities-collapsed', !data.element.vulnerabilitiesListExpanded); + dom.append(this.vulnsListElement, ...data.vulns.map(v => $('li', undefined, $('span.chat-vuln-title', undefined, v.title), ' ' + v.description))); this.vulnsButton.label = this.getVulnerabilitiesLabel(); } else { - this.element.classList.add("no-vulns"); + this.element.classList.add('no-vulns'); } } @@ -589,19 +412,13 @@ export class CodeBlockPart extends Disposable { private async updateEditor(data: ICodeBlockData): Promise { const textModel = await data.textModel; this.editor.setModel(textModel); - if (data.range) { this.editor.setSelection(data.range); this.editor.revealRangeInCenter(data.range, ScrollType.Immediate); } this.toolbar.context = { - code: textModel - .getTextBuffer() - .getValueInRange( - data.range ?? textModel.getFullModelRange(), - EndOfLinePreference.TextDefined, - ), + code: textModel.getTextBuffer().getValueInRange(data.range ?? textModel.getFullModelRange(), EndOfLinePreference.TextDefined), codeBlockIndex: data.codeBlockIndex, element: data.element, languageId: textModel.getLanguageId(), @@ -612,51 +429,33 @@ export class CodeBlockPart extends Disposable { private getVulnerabilitiesLabel(): string { if (!this.currentCodeBlockData || !this.currentCodeBlockData.vulns) { - return ""; + return ''; } - const referencesLabel = - this.currentCodeBlockData.vulns.length > 1 - ? localize( - "vulnerabilitiesPlural", - "{0} vulnerabilities", - this.currentCodeBlockData.vulns.length, - ) - : localize("vulnerabilitiesSingular", "{0} vulnerability", 1); - - const icon = (element: IChatResponseViewModel) => - element.vulnerabilitiesListExpanded - ? Codicon.chevronDown - : Codicon.chevronRight; - + const referencesLabel = this.currentCodeBlockData.vulns.length > 1 ? + localize('vulnerabilitiesPlural', "{0} vulnerabilities", this.currentCodeBlockData.vulns.length) : + localize('vulnerabilitiesSingular', "{0} vulnerability", 1); + const icon = (element: IChatResponseViewModel) => element.vulnerabilitiesListExpanded ? Codicon.chevronDown : Codicon.chevronRight; return `${referencesLabel} $(${icon(this.currentCodeBlockData.element as IChatResponseViewModel).id})`; } } -export class ChatCodeBlockContentProvider - extends Disposable - implements ITextModelContentProvider -{ +export class ChatCodeBlockContentProvider extends Disposable implements ITextModelContentProvider { + constructor( @ITextModelService textModelService: ITextModelService, @IModelService private readonly _modelService: IModelService, ) { super(); - this._register( - textModelService.registerTextModelContentProvider( - Schemas.vscodeChatCodeBlock, - this, - ), - ); + this._register(textModelService.registerTextModelContentProvider(Schemas.vscodeChatCodeBlock, this)); } async provideTextContent(resource: URI): Promise { const existing = this._modelService.getModel(resource); - if (existing) { return existing; } - return this._modelService.createModel("", null, resource); + return this._modelService.createModel('', null, resource); } } @@ -685,13 +484,11 @@ export interface ICodeCompareBlockData { // readonly hideToolbar?: boolean; } + // long-lived object that sits in the DiffPool and that gets reused export class CodeCompareBlockPart extends Disposable { - protected readonly _onDidChangeContentHeight = this._register( - new Emitter(), - ); - public readonly onDidChangeContentHeight = - this._onDidChangeContentHeight.event; + protected readonly _onDidChangeContentHeight = this._register(new Emitter()); + public readonly onDidChangeContentHeight = this._onDidChangeContentHeight.event; private readonly contextKeyService: IContextKeyService; private readonly diffEditor: DiffEditorWidget; @@ -700,9 +497,7 @@ export class CodeCompareBlockPart extends Disposable { readonly element: HTMLElement; private readonly messageElement: HTMLElement; - private readonly _lastDiffEditorViewModel = this._store.add( - new MutableDisposable(), - ); + private readonly _lastDiffEditorViewModel = this._store.add(new MutableDisposable()); private currentScrollWidth = 0; constructor( @@ -713,167 +508,105 @@ export class CodeCompareBlockPart extends Disposable { @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, @IModelService protected readonly modelService: IModelService, - @IConfigurationService - private readonly configurationService: IConfigurationService, - @IAccessibilityService - private readonly accessibilityService: IAccessibilityService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IAccessibilityService private readonly accessibilityService: IAccessibilityService, @ILabelService private readonly labelService: ILabelService, @IOpenerService private readonly openerService: IOpenerService, ) { super(); - this.element = $(".interactive-result-code-block"); - this.element.classList.add("compare"); + this.element = $('.interactive-result-code-block'); + this.element.classList.add('compare'); - this.messageElement = dom.append(this.element, $(".message")); - this.messageElement.setAttribute("role", "status"); + this.messageElement = dom.append(this.element, $('.message')); + this.messageElement.setAttribute('role', 'status'); this.messageElement.tabIndex = 0; - this.contextKeyService = this._register( - contextKeyService.createScoped(this.element), - ); - - const scopedInstantiationService = this._register( - instantiationService.createChild( - new ServiceCollection([ - IContextKeyService, - this.contextKeyService, - ]), - ), - ); - - const editorHeader = dom.append( - this.element, - $(".interactive-result-header.show-file-icons"), - ); - - const editorElement = dom.append( - this.element, - $(".interactive-result-editor"), - ); - this.diffEditor = this.createDiffEditor( - scopedInstantiationService, - editorElement, - { - ...getSimpleEditorOptions(this.configurationService), - lineNumbers: "on", - selectOnLineNumbers: true, - scrollBeyondLastLine: false, - lineDecorationsWidth: 12, - dragAndDrop: false, - padding: { - top: defaultCodeblockPadding, - bottom: defaultCodeblockPadding, - }, - mouseWheelZoom: false, - scrollbar: { - vertical: "hidden", - alwaysConsumeMouseWheel: false, - }, - definitionLinkOpensInPeek: false, - gotoLocation: { - multiple: "goto", - multipleDeclarations: "goto", - multipleDefinitions: "goto", - multipleImplementations: "goto", - }, - ariaLabel: localize("chat.codeBlockHelp", "Code block"), - overflowWidgetsDomNode, - ...this.getEditorOptionsFromConfig(), + this.contextKeyService = this._register(contextKeyService.createScoped(this.element)); + const scopedInstantiationService = this._register(instantiationService.createChild(new ServiceCollection( + [IContextKeyService, this.contextKeyService], + [IEditorProgressService, new class implements IEditorProgressService { + _serviceBrand: undefined; + show(_total: unknown, _delay?: unknown) { + return emptyProgressRunner; + } + async showWhile(promise: Promise, _delay?: number): Promise { + await promise; + } + }], + ))); + const editorHeader = dom.append(this.element, $('.interactive-result-header.show-file-icons')); + const editorElement = dom.append(this.element, $('.interactive-result-editor')); + this.diffEditor = this.createDiffEditor(scopedInstantiationService, editorElement, { + ...getSimpleEditorOptions(this.configurationService), + lineNumbers: 'on', + selectOnLineNumbers: true, + scrollBeyondLastLine: false, + lineDecorationsWidth: 12, + dragAndDrop: false, + padding: { top: defaultCodeblockPadding, bottom: defaultCodeblockPadding }, + mouseWheelZoom: false, + scrollbar: { + vertical: 'hidden', + alwaysConsumeMouseWheel: false }, - ); - - this.resourceLabel = this._register( - scopedInstantiationService.createInstance( - ResourceLabel, - editorHeader, - { supportIcons: true }, - ), - ); - - const editorScopedService = this.diffEditor - .getModifiedEditor() - .contextKeyService.createScoped(editorHeader); - - const editorScopedInstantiationService = this._register( - scopedInstantiationService.createChild( - new ServiceCollection([ - IContextKeyService, - editorScopedService, - ]), - ), - ); - this.toolbar = this._register( - editorScopedInstantiationService.createInstance( - MenuWorkbenchToolBar, - editorHeader, - menuId, - { - menuOptions: { - shouldForwardArgs: true, - }, - }, - ), - ); + definitionLinkOpensInPeek: false, + gotoLocation: { + multiple: 'goto', + multipleDeclarations: 'goto', + multipleDefinitions: 'goto', + multipleImplementations: 'goto', + }, + ariaLabel: localize('chat.codeBlockHelp', 'Code block'), + overflowWidgetsDomNode, + ...this.getEditorOptionsFromConfig(), + }); + + this.resourceLabel = this._register(scopedInstantiationService.createInstance(ResourceLabel, editorHeader, { supportIcons: true })); + + const editorScopedService = this.diffEditor.getModifiedEditor().contextKeyService.createScoped(editorHeader); + const editorScopedInstantiationService = this._register(scopedInstantiationService.createChild(new ServiceCollection([IContextKeyService, editorScopedService]))); + this.toolbar = this._register(editorScopedInstantiationService.createInstance(MenuWorkbenchToolBar, editorHeader, menuId, { + menuOptions: { + shouldForwardArgs: true + } + })); this._configureForScreenReader(); - this._register( - this.accessibilityService.onDidChangeScreenReaderOptimized(() => - this._configureForScreenReader(), - ), - ); - this._register( - this.configurationService.onDidChangeConfiguration((e) => { - if (e.affectedKeys.has(AccessibilityVerbositySettingId.Chat)) { - this._configureForScreenReader(); - } - }), - ); - - this._register( - this.options.onDidChange(() => { - this.diffEditor.updateOptions( - this.getEditorOptionsFromConfig(), - ); - }), - ); - - this._register( - this.diffEditor.getModifiedEditor().onDidScrollChange((e) => { - this.currentScrollWidth = e.scrollWidth; - }), - ); - this._register( - this.diffEditor.onDidContentSizeChange((e) => { - if (e.contentHeightChanged) { - this._onDidChangeContentHeight.fire(); - } - }), - ); - this._register( - this.diffEditor.getModifiedEditor().onDidBlurEditorWidget(() => { - this.element.classList.remove("focused"); - WordHighlighterContribution.get( - this.diffEditor.getModifiedEditor(), - )?.stopHighlighting(); - this.clearWidgets(); - }), - ); - this._register( - this.diffEditor.getModifiedEditor().onDidFocusEditorWidget(() => { - this.element.classList.add("focused"); - WordHighlighterContribution.get( - this.diffEditor.getModifiedEditor(), - )?.restoreViewState(true); - }), - ); + this._register(this.accessibilityService.onDidChangeScreenReaderOptimized(() => this._configureForScreenReader())); + this._register(this.configurationService.onDidChangeConfiguration((e) => { + if (e.affectedKeys.has(AccessibilityVerbositySettingId.Chat)) { + this._configureForScreenReader(); + } + })); + + this._register(this.options.onDidChange(() => { + this.diffEditor.updateOptions(this.getEditorOptionsFromConfig()); + })); + + this._register(this.diffEditor.getModifiedEditor().onDidScrollChange(e => { + this.currentScrollWidth = e.scrollWidth; + })); + this._register(this.diffEditor.onDidContentSizeChange(e => { + if (e.contentHeightChanged) { + this._onDidChangeContentHeight.fire(); + } + })); + this._register(this.diffEditor.getModifiedEditor().onDidBlurEditorWidget(() => { + this.element.classList.remove('focused'); + WordHighlighterContribution.get(this.diffEditor.getModifiedEditor())?.stopHighlighting(); + this.clearWidgets(); + })); + this._register(this.diffEditor.getModifiedEditor().onDidFocusEditorWidget(() => { + this.element.classList.add('focused'); + WordHighlighterContribution.get(this.diffEditor.getModifiedEditor())?.restoreViewState(true); + })); + // Parent list scrolled if (delegate.onDidScroll) { - this._register( - delegate.onDidScroll((e) => { - this.clearWidgets(); - }), - ); + this._register(delegate.onDidScroll(e => { + this.clearWidgets(); + })); } } @@ -881,11 +614,7 @@ export class CodeCompareBlockPart extends Disposable { return this.diffEditor.getModifiedEditor().getModel()?.uri; } - private createDiffEditor( - instantiationService: IInstantiationService, - parent: HTMLElement, - options: Readonly, - ): DiffEditorWidget { + private createDiffEditor(instantiationService: IInstantiationService, parent: HTMLElement, options: Readonly): DiffEditorWidget { const widgetOptions: ICodeEditorWidgetOptions = { isSimpleWidget: false, contributions: EditorExtensionsRegistry.getSomeEditorContributions([ @@ -900,49 +629,32 @@ export class CodeCompareBlockPart extends Disposable { ContentHoverController.ID, GlyphHoverController.ID, GotoDefinitionAtPositionEditorContribution.ID, - ]), + ]) }; - return this._register( - instantiationService.createInstance( - DiffEditorWidget, - parent, - { - scrollbar: { - useShadows: false, - alwaysConsumeMouseWheel: false, - ignoreHorizontalScrollbarInContentHeight: true, - }, - renderMarginRevertIcon: false, - diffCodeLens: false, - scrollBeyondLastLine: false, - stickyScroll: { enabled: false }, - originalAriaLabel: localize("original", "Original"), - modifiedAriaLabel: localize("modified", "Modified"), - diffAlgorithm: "advanced", - readOnly: false, - isInEmbeddedEditor: true, - useInlineViewWhenSpaceIsLimited: true, - experimental: { - useTrueInlineView: true, - }, - renderSideBySideInlineBreakpoint: 300, - renderOverviewRuler: false, - compactMode: true, - hideUnchangedRegions: { - enabled: true, - contextLineCount: 1, - }, - renderGutterMenu: false, - lineNumbersMinChars: 1, - ...options, - }, - { - originalEditor: widgetOptions, - modifiedEditor: widgetOptions, - }, - ), - ); + return this._register(instantiationService.createInstance(DiffEditorWidget, parent, { + scrollbar: { useShadows: false, alwaysConsumeMouseWheel: false, ignoreHorizontalScrollbarInContentHeight: true, }, + renderMarginRevertIcon: false, + diffCodeLens: false, + scrollBeyondLastLine: false, + stickyScroll: { enabled: false }, + originalAriaLabel: localize('original', 'Original'), + modifiedAriaLabel: localize('modified', 'Modified'), + diffAlgorithm: 'advanced', + readOnly: false, + isInEmbeddedEditor: true, + useInlineViewWhenSpaceIsLimited: true, + experimental: { + useTrueInlineView: true, + }, + renderSideBySideInlineBreakpoint: 300, + renderOverviewRuler: false, + compactMode: true, + hideUnchangedRegions: { enabled: true, contextLineCount: 1 }, + renderGutterMenu: false, + lineNumbersMinChars: 1, + ...options + }, { originalEditor: widgetOptions, modifiedEditor: widgetOptions })); } focus(): void { @@ -952,51 +664,32 @@ export class CodeCompareBlockPart extends Disposable { private updatePaddingForLayout() { // scrollWidth = "the width of the content that needs to be scrolled" // contentWidth = "the width of the area where content is displayed" - const horizontalScrollbarVisible = - this.currentScrollWidth > - this.diffEditor.getModifiedEditor().getLayoutInfo().contentWidth; - - const scrollbarHeight = this.diffEditor - .getModifiedEditor() - .getLayoutInfo().horizontalScrollbarHeight; - - const bottomPadding = horizontalScrollbarVisible - ? Math.max(defaultCodeblockPadding - scrollbarHeight, 2) - : defaultCodeblockPadding; - this.diffEditor.updateOptions({ - padding: { top: defaultCodeblockPadding, bottom: bottomPadding }, - }); + const horizontalScrollbarVisible = this.currentScrollWidth > this.diffEditor.getModifiedEditor().getLayoutInfo().contentWidth; + const scrollbarHeight = this.diffEditor.getModifiedEditor().getLayoutInfo().horizontalScrollbarHeight; + const bottomPadding = horizontalScrollbarVisible ? + Math.max(defaultCodeblockPadding - scrollbarHeight, 2) : + defaultCodeblockPadding; + this.diffEditor.updateOptions({ padding: { top: defaultCodeblockPadding, bottom: bottomPadding } }); } private _configureForScreenReader(): void { const toolbarElt = this.toolbar.getElement(); - if (this.accessibilityService.isScreenReaderOptimized()) { - toolbarElt.style.display = "block"; - toolbarElt.ariaLabel = this.configurationService.getValue( - AccessibilityVerbositySettingId.Chat, - ) - ? localize( - "chat.codeBlock.toolbarVerbose", - "Toolbar for code block which can be reached via tab", - ) - : localize("chat.codeBlock.toolbar", "Code block toolbar"); + toolbarElt.style.display = 'block'; + toolbarElt.ariaLabel = this.configurationService.getValue(AccessibilityVerbositySettingId.Chat) ? localize('chat.codeBlock.toolbarVerbose', 'Toolbar for code block which can be reached via tab') : localize('chat.codeBlock.toolbar', 'Code block toolbar'); } else { - toolbarElt.style.display = ""; + toolbarElt.style.display = ''; } } private getEditorOptionsFromConfig(): IEditorOptions { return { wordWrap: this.options.configuration.resultEditor.wordWrap, - fontLigatures: - this.options.configuration.resultEditor.fontLigatures, - bracketPairColorization: - this.options.configuration.resultEditor.bracketPairColorization, - fontFamily: - this.options.configuration.resultEditor.fontFamily === "default" - ? EDITOR_FONT_DEFAULTS.fontFamily - : this.options.configuration.resultEditor.fontFamily, + fontLigatures: this.options.configuration.resultEditor.fontLigatures, + bracketPairColorization: this.options.configuration.resultEditor.bracketPairColorization, + fontFamily: this.options.configuration.resultEditor.fontFamily === 'default' ? + EDITOR_FONT_DEFAULTS.fontFamily : + this.options.configuration.resultEditor.fontFamily, fontSize: this.options.configuration.resultEditor.fontSize, fontWeight: this.options.configuration.resultEditor.fontWeight, lineHeight: this.options.configuration.resultEditor.lineHeight, @@ -1007,33 +700,24 @@ export class CodeCompareBlockPart extends Disposable { const editorBorder = 2; const toolbar = dom.getTotalHeight(this.toolbar.getElement()); - const content = this.diffEditor.getModel() ? this.diffEditor.getContentHeight() : dom.getTotalHeight(this.messageElement); - const dimension = new dom.Dimension( - width - editorBorder, - toolbar + content, - ); + const dimension = new dom.Dimension(width - editorBorder, toolbar + content); this.element.style.height = `${dimension.height}px`; this.element.style.width = `${dimension.width}px`; - this.diffEditor.layout( - dimension.with(undefined, content - editorBorder), - ); + this.diffEditor.layout(dimension.with(undefined, content - editorBorder)); this.updatePaddingForLayout(); } - async render( - data: ICodeCompareBlockData, - width: number, - token: CancellationToken, - ) { + + async render(data: ICodeCompareBlockData, width: number, token: CancellationToken) { if (data.parentContextKeyService) { this.contextKeyService.updateParent(data.parentContextKeyService); } - if (this.options.configuration.resultEditor.wordWrap === "on") { + if (this.options.configuration.resultEditor.wordWrap === 'on') { // Initialize the editor with the new proper width so that getContentHeight // will be computed correctly in the next call to layout() this.layout(width); @@ -1042,13 +726,11 @@ export class CodeCompareBlockPart extends Disposable { await this.updateEditor(data, token); this.layout(width); - this.diffEditor.updateOptions({ - ariaLabel: localize("chat.compareCodeBlockLabel", "Code Edits"), - }); + this.diffEditor.updateOptions({ ariaLabel: localize('chat.compareCodeBlockLabel', "Code Edits") }); this.resourceLabel.element.setFile(data.edit.uri, { fileKind: FileKind.FILE, - fileDecorations: { colors: true, badges: false }, + fileDecorations: { colors: true, badges: false } }); } @@ -1057,78 +739,46 @@ export class CodeCompareBlockPart extends Disposable { } private clearWidgets() { - ContentHoverController.get( - this.diffEditor.getOriginalEditor(), - )?.hideContentHover(); - ContentHoverController.get( - this.diffEditor.getModifiedEditor(), - )?.hideContentHover(); - GlyphHoverController.get( - this.diffEditor.getOriginalEditor(), - )?.hideContentHover(); - GlyphHoverController.get( - this.diffEditor.getModifiedEditor(), - )?.hideContentHover(); + ContentHoverController.get(this.diffEditor.getOriginalEditor())?.hideContentHover(); + ContentHoverController.get(this.diffEditor.getModifiedEditor())?.hideContentHover(); + GlyphHoverController.get(this.diffEditor.getOriginalEditor())?.hideContentHover(); + GlyphHoverController.get(this.diffEditor.getModifiedEditor())?.hideContentHover(); } - private async updateEditor( - data: ICodeCompareBlockData, - token: CancellationToken, - ): Promise { + private async updateEditor(data: ICodeCompareBlockData, token: CancellationToken): Promise { + if (!isResponseVM(data.element)) { return; } const isEditApplied = Boolean(data.edit.state?.applied ?? 0); - ChatContextKeys.editApplied - .bindTo(this.contextKeyService) - .set(isEditApplied); + ChatContextKeys.editApplied.bindTo(this.contextKeyService).set(isEditApplied); - this.element.classList.toggle("no-diff", isEditApplied); + this.element.classList.toggle('no-diff', isEditApplied); if (isEditApplied) { assertType(data.edit.state?.applied); - const uriLabel = this.labelService.getUriLabel(data.edit.uri, { - relative: true, - noPrefix: true, - }); + const uriLabel = this.labelService.getUriLabel(data.edit.uri, { relative: true, noPrefix: true }); let template: string; - if (data.edit.state.applied === 1) { - template = localize( - "chat.edits.1", - "Made 1 change in [[``{0}``]]", - uriLabel, - ); + template = localize('chat.edits.1', "Made 1 change in [[``{0}``]]", uriLabel); } else if (data.edit.state.applied < 0) { - template = localize( - "chat.edits.rejected", - "Edits in [[``{0}``]] have been rejected", - uriLabel, - ); + template = localize('chat.edits.rejected', "Edits in [[``{0}``]] have been rejected", uriLabel); } else { - template = localize( - "chat.edits.N", - "Made {0} changes in [[``{1}``]]", - data.edit.state.applied, - uriLabel, - ); + template = localize('chat.edits.N', "Made {0} changes in [[``{1}``]]", data.edit.state.applied, uriLabel); } const message = renderFormattedText(template, { renderCodeSegments: true, actionHandler: { callback: () => { - this.openerService.open(data.edit.uri, { - fromUserGesture: true, - allowCommands: false, - }); + this.openerService.open(data.edit.uri, { fromUserGesture: true, allowCommands: false }); }, disposables: this._store, - }, + } }); dom.reset(this.messageElement, message); @@ -1139,7 +789,7 @@ export class CodeCompareBlockPart extends Disposable { if (!isEditApplied && diffData) { const viewModel = this.diffEditor.createViewModel({ original: diffData.original, - modified: diffData.modified, + modified: diffData.modified }); await viewModel.waitForDiff(); @@ -1148,19 +798,14 @@ export class CodeCompareBlockPart extends Disposable { return; } - const listener = Event.any( - diffData.original.onWillDispose, - diffData.modified.onWillDispose, - )(() => { + const listener = Event.any(diffData.original.onWillDispose, diffData.modified.onWillDispose)(() => { // this a bit weird and basically duplicates https://github.com/microsoft/vscode/blob/7cbcafcbcc88298cfdcd0238018fbbba8eb6853e/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts#L328 // which cannot call `setModel(null)` without first complaining this.diffEditor.setModel(null); }); this.diffEditor.setModel(viewModel); - this._lastDiffEditorViewModel.value = combinedDisposable( - listener, - viewModel, - ); + this._lastDiffEditorViewModel.value = combinedDisposable(listener, viewModel); + } else { this.diffEditor.setModel(null); this._lastDiffEditorViewModel.value = undefined; @@ -1176,19 +821,17 @@ export class CodeCompareBlockPart extends Disposable { } export class DefaultChatTextEditor { + private readonly _sha1 = new DefaultModelSHA1Computer(); constructor( @ITextModelService private readonly modelService: ITextModelService, @ICodeEditorService private readonly editorService: ICodeEditorService, @IDialogService private readonly dialogService: IDialogService, - ) {} + ) { } + + async apply(response: IChatResponseModel | IChatResponseViewModel, item: IChatTextEditGroup, diffEditor: IDiffEditor | undefined): Promise { - async apply( - response: IChatResponseModel | IChatResponseViewModel, - item: IChatTextEditGroup, - diffEditor: IDiffEditor | undefined, - ): Promise { if (!response.response.value.includes(item)) { // bogous item return; @@ -1205,15 +848,8 @@ export class DefaultChatTextEditor { continue; } const model = candidate.getModel(); - - if ( - !model || - !isEqual(model.original.uri, item.uri) || - model.modified.uri.scheme !== - Schemas.vscodeChatCodeCompareBlock - ) { + if (!model || !isEqual(model.original.uri, item.uri) || model.modified.uri.scheme !== Schemas.vscodeChatCodeCompareBlock) { diffEditor = candidate; - break; } } @@ -1226,31 +862,24 @@ export class DefaultChatTextEditor { response.setEditApplied(item, edits); } - private async _applyWithDiffEditor( - diffEditor: IDiffEditor, - item: IChatTextEditGroup, - ) { + private async _applyWithDiffEditor(diffEditor: IDiffEditor, item: IChatTextEditGroup) { const model = diffEditor.getModel(); - if (!model) { return 0; } const diff = diffEditor.getDiffComputationResult(); - if (!diff || diff.identical) { return 0; } - if (!(await this._checkSha1(model.original, item))) { + + if (!await this._checkSha1(model.original, item)) { return 0; } const modified = new TextModelText(model.modified); - - const edits = diff.changes2.map((i) => - i.toRangeMapping().toTextEdit(modified).toSingleEditOperation(), - ); + const edits = diff.changes2.map(i => i.toRangeMapping().toTextEdit(modified).toSingleEditOperation()); model.original.pushStackElement(); model.original.pushEditOperations(null, edits, () => null); @@ -1261,48 +890,32 @@ export class DefaultChatTextEditor { private async _apply(item: IChatTextEditGroup) { const ref = await this.modelService.createModelReference(item.uri); - try { - if (!(await this._checkSha1(ref.object.textEditorModel, item))) { + + if (!await this._checkSha1(ref.object.textEditorModel, item)) { return 0; } ref.object.textEditorModel.pushStackElement(); - let total = 0; - for (const group of item.edits) { const edits = group.map(TextEdit.asEditOperation); - ref.object.textEditorModel.pushEditOperations( - null, - edits, - () => null, - ); + ref.object.textEditorModel.pushEditOperations(null, edits, () => null); total += edits.length; } ref.object.textEditorModel.pushStackElement(); - return total; + } finally { ref.dispose(); } } private async _checkSha1(model: ITextModel, item: IChatTextEditGroup) { - if ( - item.state?.sha1 && - this._sha1.computeSHA1(model) && - this._sha1.computeSHA1(model) !== item.state.sha1 - ) { + if (item.state?.sha1 && this._sha1.computeSHA1(model) && this._sha1.computeSHA1(model) !== item.state.sha1) { const result = await this.dialogService.confirm({ - message: localize( - "interactive.compare.apply.confirm", - "The original file has been modified.", - ), - detail: localize( - "interactive.compare.apply.confirm.detail", - "Do you want to apply the changes anyway?", - ), + message: localize('interactive.compare.apply.confirm', "The original file has been modified."), + detail: localize('interactive.compare.apply.confirm.detail', "Do you want to apply the changes anyway?"), }); if (!result.confirmed) { @@ -1312,10 +925,7 @@ export class DefaultChatTextEditor { return true; } - discard( - response: IChatResponseModel | IChatResponseViewModel, - item: IChatTextEditGroup, - ) { + discard(response: IChatResponseModel | IChatResponseViewModel, item: IChatTextEditGroup) { if (!response.response.value.includes(item)) { // bogous item return; diff --git a/Source/vs/workbench/contrib/chat/browser/media/chat.css b/Source/vs/workbench/contrib/chat/browser/media/chat.css index 2a12effbe0610..f3ed46644586e 100644 --- a/Source/vs/workbench/contrib/chat/browser/media/chat.css +++ b/Source/vs/workbench/contrib/chat/browser/media/chat.css @@ -1621,6 +1621,11 @@ have to be updated for changes to the rules above, or to support more deeply nes border: none; } +.chat-attached-context-attachment .attachment-additional-info { + opacity: 0.7; + font-size: .9em; +} + .chat-attached-context-attachment .chat-attached-context-pill-image { width: 14px; height: 14px; diff --git a/Source/vs/workbench/contrib/chat/browser/media/chatViewSetup.css b/Source/vs/workbench/contrib/chat/browser/media/chatViewSetup.css index f6ead5c9a4bb8..4f03a6128cb3f 100644 --- a/Source/vs/workbench/contrib/chat/browser/media/chatViewSetup.css +++ b/Source/vs/workbench/contrib/chat/browser/media/chatViewSetup.css @@ -22,4 +22,17 @@ width: 100%; padding: 4px 7px; } + + .checkbox-container { + display: flex; + } + + .checkbox-label { + flex-basis: fit-content; + cursor: pointer; + } + + .checkbox-label p { + display: inline; + } } diff --git a/Source/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcomeHandler.ts b/Source/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcomeHandler.ts new file mode 100644 index 0000000000000..27602c060937a --- /dev/null +++ b/Source/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcomeHandler.ts @@ -0,0 +1,84 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { MarkdownString } from '../../../../../base/common/htmlContent.js'; +import { ThemeIcon } from '../../../../../base/common/themables.js'; +import { localize } from '../../../../../nls.js'; +import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; +import { ILogService } from '../../../../../platform/log/common/log.js'; +import { Registry } from '../../../../../platform/registry/common/platform.js'; +import { IWorkbenchContribution } from '../../../../common/contributions.js'; +import { checkProposedApiEnabled } from '../../../../services/extensions/common/extensions.js'; +import * as extensionsRegistry from '../../../../services/extensions/common/extensionsRegistry.js'; +import { ChatViewsWelcomeExtensions, IChatViewsWelcomeContributionRegistry, IChatViewsWelcomeDescriptor } from './chatViewsWelcome.js'; + +interface IRawChatViewsWelcomeContribution { + icon: string; + title: string; + content: string; + when: string; +} + +const chatViewsWelcomeExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'chatViewsWelcome', + jsonSchema: { + description: localize('vscode.extension.contributes.chatViewsWelcome', 'Contributes a welcome message to a chat view'), + type: 'array', + items: { + additionalProperties: false, + type: 'object', + properties: { + icon: { + type: 'string', + description: localize('chatViewsWelcome.icon', 'The icon for the welcome message.'), + }, + title: { + type: 'string', + description: localize('chatViewsWelcome.title', 'The title of the welcome message.'), + }, + content: { + type: 'string', + description: localize('chatViewsWelcome.content', 'The content of the welcome message. The first command link will be rendered as a button.'), + }, + when: { + type: 'string', + description: localize('chatViewsWelcome.when', 'Condition when the welcome message is shown.'), + } + } + }, + required: ['icon', 'title', 'contents', 'when'], + } +}); + +export class ChatViewsWelcomeHandler implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.chatViewsWelcomeHandler'; + + constructor( + @ILogService private readonly logService: ILogService, + ) { + chatViewsWelcomeExtensionPoint.setHandler((extensions, delta) => { + for (const extension of delta.added) { + for (const providerDescriptor of extension.value) { + checkProposedApiEnabled(extension.description, 'chatParticipantPrivate'); + + const when = ContextKeyExpr.deserialize(providerDescriptor.when); + if (!when) { + this.logService.error(`Could not deserialize 'when' clause for chatViewsWelcome contribution: ${providerDescriptor.when}`); + continue; + } + + const descriptor: IChatViewsWelcomeDescriptor = { + ...providerDescriptor, + when, + icon: ThemeIcon.fromString(providerDescriptor.icon), + content: new MarkdownString(providerDescriptor.content, { isTrusted: true }), // private API with command links + }; + Registry.as(ChatViewsWelcomeExtensions.ChatViewsWelcomeRegistry).register(descriptor); + } + } + }); + } +} diff --git a/Source/vs/workbench/contrib/chat/common/chatContextKeys.ts b/Source/vs/workbench/contrib/chat/common/chatContextKeys.ts index 85de56425e6ce..64493ef1734e0 100644 --- a/Source/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/Source/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -3,292 +3,48 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from "../../../../nls.js"; -import { RawContextKey } from "../../../../platform/contextkey/common/contextkey.js"; -import { ChatAgentLocation } from "./chatAgents.js"; +import { localize } from '../../../../nls.js'; +import { RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { ChatAgentLocation } from './chatAgents.js'; export namespace ChatContextKeys { - export const responseVote = new RawContextKey( - "chatSessionResponseVote", - "", - { - type: "string", - description: localize( - "interactiveSessionResponseVote", - "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.", - ), - }, - ); - export const responseDetectedAgentCommand = new RawContextKey( - "chatSessionResponseDetectedAgentOrCommand", - false, - { - type: "boolean", - description: localize( - "chatSessionResponseDetectedAgentOrCommand", - "When the agent or command was automatically detected", - ), - }, - ); - export const responseSupportsIssueReporting = new RawContextKey( - "chatResponseSupportsIssueReporting", - false, - { - type: "boolean", - description: localize( - "chatResponseSupportsIssueReporting", - "True when the current chat response supports issue reporting.", - ), - }, - ); - export const responseIsFiltered = new RawContextKey( - "chatSessionResponseFiltered", - false, - { - type: "boolean", - description: localize( - "chatResponseFiltered", - "True when the chat response was filtered out by the server.", - ), - }, - ); - export const responseHasError = new RawContextKey( - "chatSessionResponseError", - false, - { - type: "boolean", - description: localize( - "chatResponseErrored", - "True when the chat response resulted in an error.", - ), - }, - ); - export const requestInProgress = new RawContextKey( - "chatSessionRequestInProgress", - false, - { - type: "boolean", - description: localize( - "interactiveSessionRequestInProgress", - "True when the current request is still in progress.", - ), - }, - ); - - export const isResponse = new RawContextKey( - "chatResponse", - false, - { - type: "boolean", - description: localize( - "chatResponse", - "The chat item is a response.", - ), - }, - ); - export const isRequest = new RawContextKey("chatRequest", false, { - type: "boolean", - description: localize("chatRequest", "The chat item is a request"), - }); - export const itemId = new RawContextKey("chatItemId", "", { - type: "string", - description: localize("chatItemId", "The id of the chat item."), - }); - export const lastItemId = new RawContextKey( - "chatLastItemId", - [], - { - type: "string", - description: localize( - "chatLastItemId", - "The id of the last chat item.", - ), - }, - ); - - export const editApplied = new RawContextKey( - "chatEditApplied", - false, - { - type: "boolean", - description: localize( - "chatEditApplied", - "True when the chat text edits have been applied.", - ), - }, - ); - - export const inputHasText = new RawContextKey( - "chatInputHasText", - false, - { - type: "boolean", - description: localize( - "interactiveInputHasText", - "True when the chat input has text.", - ), - }, - ); - export const inputHasFocus = new RawContextKey( - "chatInputHasFocus", - false, - { - type: "boolean", - description: localize( - "interactiveInputHasFocus", - "True when the chat input has focus.", - ), - }, - ); - export const inChatInput = new RawContextKey( - "inChatInput", - false, - { - type: "boolean", - description: localize( - "inInteractiveInput", - "True when focus is in the chat input, false otherwise.", - ), - }, - ); - export const inChatSession = new RawContextKey("inChat", false, { - type: "boolean", - description: localize( - "inChat", - "True when focus is in the chat widget, false otherwise.", - ), - }); - - export const enabled = new RawContextKey("chatIsEnabled", false, { - type: "boolean", - description: localize( - "chatIsEnabled", - "True when chat is enabled because a default chat participant is activated with an implementation.", - ), - }); - export const panelParticipantRegistered = new RawContextKey( - "chatPanelParticipantRegistered", - false, - { - type: "boolean", - description: localize( - "chatParticipantRegistered", - "True when a default chat participant is registered for the panel.", - ), - }, - ); - export const editingParticipantRegistered = new RawContextKey( - "chatEditingParticipantRegistered", - false, - { - type: "boolean", - description: localize( - "chatEditingParticipantRegistered", - "True when a default chat participant is registered for editing.", - ), - }, - ); - export const chatEditingCanUndo = new RawContextKey( - "chatEditingCanUndo", - false, - { - type: "boolean", - description: localize( - "chatEditingCanUndo", - "True when it is possible to undo an interaction in the editing panel.", - ), - }, - ); - export const chatEditingCanRedo = new RawContextKey( - "chatEditingCanRedo", - false, - { - type: "boolean", - description: localize( - "chatEditingCanRedo", - "True when it is possible to redo an interaction in the editing panel.", - ), - }, - ); - export const extensionInvalid = new RawContextKey( - "chatExtensionInvalid", - false, - { - type: "boolean", - description: localize( - "chatExtensionInvalid", - "True when the installed chat extension is invalid and needs to be updated.", - ), - }, - ); - export const inputCursorAtTop = new RawContextKey( - "chatCursorAtTop", - false, - ); - export const inputHasAgent = new RawContextKey( - "chatInputHasAgent", - false, - ); - export const location = new RawContextKey( - "chatLocation", - undefined, - ); - export const inQuickChat = new RawContextKey( - "quickChatHasFocus", - false, - { - type: "boolean", - description: localize( - "inQuickChat", - "True when the quick chat UI has focus, false otherwise.", - ), - }, - ); - export const hasFileAttachments = new RawContextKey( - "chatHasFileAttachments", - false, - { - type: "boolean", - description: localize( - "chatHasFileAttachments", - "True when the chat has file attachments.", - ), - }, - ); - - export const languageModelsAreUserSelectable = new RawContextKey( - "chatModelsAreUserSelectable", - false, - { - type: "boolean", - description: localize( - "chatModelsAreUserSelectable", - "True when the chat model can be selected manually by the user.", - ), - }, - ); + export const responseVote = new RawContextKey('chatSessionResponseVote', '', { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") }); + export const responseDetectedAgentCommand = new RawContextKey('chatSessionResponseDetectedAgentOrCommand', false, { type: 'boolean', description: localize('chatSessionResponseDetectedAgentOrCommand', "When the agent or command was automatically detected") }); + export const responseSupportsIssueReporting = new RawContextKey('chatResponseSupportsIssueReporting', false, { type: 'boolean', description: localize('chatResponseSupportsIssueReporting', "True when the current chat response supports issue reporting.") }); + export const responseIsFiltered = new RawContextKey('chatSessionResponseFiltered', false, { type: 'boolean', description: localize('chatResponseFiltered', "True when the chat response was filtered out by the server.") }); + export const responseHasError = new RawContextKey('chatSessionResponseError', false, { type: 'boolean', description: localize('chatResponseErrored', "True when the chat response resulted in an error.") }); + export const requestInProgress = new RawContextKey('chatSessionRequestInProgress', false, { type: 'boolean', description: localize('interactiveSessionRequestInProgress', "True when the current request is still in progress.") }); + + export const isResponse = new RawContextKey('chatResponse', false, { type: 'boolean', description: localize('chatResponse', "The chat item is a response.") }); + export const isRequest = new RawContextKey('chatRequest', false, { type: 'boolean', description: localize('chatRequest', "The chat item is a request") }); + export const itemId = new RawContextKey('chatItemId', '', { type: 'string', description: localize('chatItemId', "The id of the chat item.") }); + export const lastItemId = new RawContextKey('chatLastItemId', [], { type: 'string', description: localize('chatLastItemId', "The id of the last chat item.") }); + + export const editApplied = new RawContextKey('chatEditApplied', false, { type: 'boolean', description: localize('chatEditApplied', "True when the chat text edits have been applied.") }); + + export const inputHasText = new RawContextKey('chatInputHasText', false, { type: 'boolean', description: localize('interactiveInputHasText', "True when the chat input has text.") }); + export const inputHasFocus = new RawContextKey('chatInputHasFocus', false, { type: 'boolean', description: localize('interactiveInputHasFocus', "True when the chat input has focus.") }); + export const inChatInput = new RawContextKey('inChatInput', false, { type: 'boolean', description: localize('inInteractiveInput', "True when focus is in the chat input, false otherwise.") }); + export const inChatSession = new RawContextKey('inChat', false, { type: 'boolean', description: localize('inChat', "True when focus is in the chat widget, false otherwise.") }); + + export const enabled = new RawContextKey('chatIsEnabled', false, { type: 'boolean', description: localize('chatIsEnabled', "True when chat is enabled because a default chat participant is activated with an implementation.") }); + export const panelParticipantRegistered = new RawContextKey('chatPanelParticipantRegistered', false, { type: 'boolean', description: localize('chatParticipantRegistered', "True when a default chat participant is registered for the panel.") }); + export const editingParticipantRegistered = new RawContextKey('chatEditingParticipantRegistered', false, { type: 'boolean', description: localize('chatEditingParticipantRegistered', "True when a default chat participant is registered for editing.") }); + export const chatEditingCanUndo = new RawContextKey('chatEditingCanUndo', false, { type: 'boolean', description: localize('chatEditingCanUndo', "True when it is possible to undo an interaction in the editing panel.") }); + export const chatEditingCanRedo = new RawContextKey('chatEditingCanRedo', false, { type: 'boolean', description: localize('chatEditingCanRedo', "True when it is possible to redo an interaction in the editing panel.") }); + export const extensionInvalid = new RawContextKey('chatExtensionInvalid', false, { type: 'boolean', description: localize('chatExtensionInvalid', "True when the installed chat extension is invalid and needs to be updated.") }); + export const inputCursorAtTop = new RawContextKey('chatCursorAtTop', false); + export const inputHasAgent = new RawContextKey('chatInputHasAgent', false); + export const location = new RawContextKey('chatLocation', undefined); + export const inQuickChat = new RawContextKey('quickChatHasFocus', false, { type: 'boolean', description: localize('inQuickChat', "True when the quick chat UI has focus, false otherwise.") }); + export const hasFileAttachments = new RawContextKey('chatHasFileAttachments', false, { type: 'boolean', description: localize('chatHasFileAttachments', "True when the chat has file attachments.") }); + + export const languageModelsAreUserSelectable = new RawContextKey('chatModelsAreUserSelectable', false, { type: 'boolean', description: localize('chatModelsAreUserSelectable', "True when the chat model can be selected manually by the user.") }); export const Setup = { - entitled: new RawContextKey("chatSetupEntitled", false, { - type: "boolean", - description: localize( - "chatSetupEntitled", - "True when chat setup is offered for a signed-in, entitled user.", - ), - }), - triggered: new RawContextKey("chatSetupTriggered", false, { - type: "boolean", - description: localize( - "chatSetupTriggered", - "True when chat setup is triggered.", - ), - }), - installed: new RawContextKey("chatSetupInstalled", false, { - type: "boolean", - description: localize( - "chatSetupInstalled", - "True when the chat extension is installed.", - ), - }), + entitled: new RawContextKey('chatSetupEntitled', false, { type: 'boolean', description: localize('chatSetupEntitled', "True when user is a chat entitled user.") }), + limited: new RawContextKey('chatSetupLimited', false, { type: 'boolean', description: localize('chatSetupLimited', "True when user is a chat limited user.") }), + triggered: new RawContextKey('chatSetupTriggered', false, { type: 'boolean', description: localize('chatSetupTriggered', "True when chat setup is triggered.") }), + installed: new RawContextKey('chatSetupInstalled', false, { type: 'boolean', description: localize('chatSetupInstalled', "True when the chat extension is installed.") }), }; } diff --git a/Source/vs/workbench/contrib/chat/common/chatModel.ts b/Source/vs/workbench/contrib/chat/common/chatModel.ts index b934411838c22..dc3f1d89cffc1 100644 --- a/Source/vs/workbench/contrib/chat/common/chatModel.ts +++ b/Source/vs/workbench/contrib/chat/common/chatModel.ts @@ -105,9 +105,16 @@ export interface IChatRequestImplicitVariableEntry enabled: boolean; } -export interface ISymbolVariableEntry - extends Omit { - readonly kind: "symbol"; +export interface IChatRequestPasteVariableEntry extends Omit { + readonly kind: 'paste'; + code: string; + language: string; + fileName: string; + pastedLines: string; +} + +export interface ISymbolVariableEntry extends Omit { + readonly kind: 'symbol'; readonly isDynamic: true; readonly value: Location; } @@ -118,11 +125,7 @@ export interface ICommandResultVariableEntry readonly isDynamic: true; } -export type IChatRequestVariableEntry = - | IChatRequestImplicitVariableEntry - | ISymbolVariableEntry - | ICommandResultVariableEntry - | IBaseChatRequestVariableEntry; +export type IChatRequestVariableEntry = IChatRequestImplicitVariableEntry | IChatRequestPasteVariableEntry | ISymbolVariableEntry | ICommandResultVariableEntry | IBaseChatRequestVariableEntry; export function isImplicitVariableEntry( obj: IChatRequestVariableEntry, @@ -130,9 +133,11 @@ export function isImplicitVariableEntry( return obj.kind === "implicit"; } -export function isChatRequestVariableEntry( - obj: unknown, -): obj is IChatRequestVariableEntry { +export function isPasteVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestPasteVariableEntry { + return obj.kind === 'paste'; +} + +export function isChatRequestVariableEntry(obj: unknown): obj is IChatRequestVariableEntry { const entry = obj as IChatRequestVariableEntry; return ( diff --git a/Source/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts b/Source/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts index 9b49209698b36..a0f1261b5d2d2 100644 --- a/Source/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts +++ b/Source/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts @@ -3,99 +3,47 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDataSource } from "../../../../../base/browser/ui/tree/tree.js"; -import { - Barrier, - raceCancellation, - timeout, - TimeoutTimer, -} from "../../../../../base/common/async.js"; -import { - CancellationToken, - CancellationTokenSource, -} from "../../../../../base/common/cancellation.js"; -import { onUnexpectedError } from "../../../../../base/common/errors.js"; -import { Emitter, Event } from "../../../../../base/common/event.js"; -import { - Disposable, - DisposableStore, - IDisposable, - toDisposable, -} from "../../../../../base/common/lifecycle.js"; -import { isEqual } from "../../../../../base/common/resources.js"; -import { URI } from "../../../../../base/common/uri.js"; -import { - ICodeEditor, - isCodeEditor, - isDiffEditor, -} from "../../../../../editor/browser/editorBrowser.js"; -import { ICodeEditorService } from "../../../../../editor/browser/services/codeEditorService.js"; -import { IPosition } from "../../../../../editor/common/core/position.js"; -import { Range } from "../../../../../editor/common/core/range.js"; -import { ScrollType } from "../../../../../editor/common/editorCommon.js"; -import { ITextModel } from "../../../../../editor/common/model.js"; -import { ILanguageFeaturesService } from "../../../../../editor/common/services/languageFeatures.js"; -import { IMarkerDecorationsService } from "../../../../../editor/common/services/markerDecorations.js"; -import { ITextResourceConfigurationService } from "../../../../../editor/common/services/textResourceConfiguration.js"; -import { IModelContentChangedEvent } from "../../../../../editor/common/textModelEvents.js"; -import { - IOutlineMarker, - IOutlineModelService, - OutlineElement, - OutlineGroup, - OutlineModel, - TreeElement, -} from "../../../../../editor/contrib/documentSymbols/browser/outlineModel.js"; -import { localize } from "../../../../../nls.js"; -import { IConfigurationService } from "../../../../../platform/configuration/common/configuration.js"; -import { - IEditorOptions, - TextEditorSelectionRevealType, -} from "../../../../../platform/editor/common/editor.js"; -import { IInstantiationService } from "../../../../../platform/instantiation/common/instantiation.js"; -import { MarkerSeverity } from "../../../../../platform/markers/common/markers.js"; -import { Registry } from "../../../../../platform/registry/common/platform.js"; -import { - IWorkbenchContributionsRegistry, - Extensions as WorkbenchExtensions, -} from "../../../../common/contributions.js"; -import { IEditorPane } from "../../../../common/editor.js"; -import { LifecyclePhase } from "../../../../services/lifecycle/common/lifecycle.js"; -import { - IBreadcrumbsDataSource, - IOutline, - IOutlineCreator, - IOutlineListConfig, - IOutlineService, - OutlineChangeEvent, - OutlineConfigCollapseItemsValues, - OutlineConfigKeys, - OutlineTarget, -} from "../../../../services/outline/browser/outline.js"; -import { - DocumentSymbolAccessibilityProvider, - DocumentSymbolComparator, - DocumentSymbolDragAndDrop, - DocumentSymbolFilter, - DocumentSymbolGroupRenderer, - DocumentSymbolIdentityProvider, - DocumentSymbolNavigationLabelProvider, - DocumentSymbolRenderer, - DocumentSymbolVirtualDelegate, -} from "./documentSymbolsTree.js"; +import { Emitter, Event } from '../../../../../base/common/event.js'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../../base/common/lifecycle.js'; +import { OutlineConfigCollapseItemsValues, IBreadcrumbsDataSource, IOutline, IOutlineCreator, IOutlineListConfig, IOutlineService, OutlineChangeEvent, OutlineConfigKeys, OutlineTarget, } from '../../../../services/outline/browser/outline.js'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from '../../../../common/contributions.js'; +import { Registry } from '../../../../../platform/registry/common/platform.js'; +import { LifecyclePhase } from '../../../../services/lifecycle/common/lifecycle.js'; +import { IEditorPane } from '../../../../common/editor.js'; +import { DocumentSymbolComparator, DocumentSymbolAccessibilityProvider, DocumentSymbolRenderer, DocumentSymbolFilter, DocumentSymbolGroupRenderer, DocumentSymbolIdentityProvider, DocumentSymbolNavigationLabelProvider, DocumentSymbolVirtualDelegate, DocumentSymbolDragAndDrop } from './documentSymbolsTree.js'; +import { ICodeEditor, isCodeEditor, isDiffEditor } from '../../../../../editor/browser/editorBrowser.js'; +import { OutlineGroup, OutlineElement, OutlineModel, TreeElement, IOutlineMarker, IOutlineModelService } from '../../../../../editor/contrib/documentSymbols/browser/outlineModel.js'; +import { CancellationToken, CancellationTokenSource } from '../../../../../base/common/cancellation.js'; +import { raceCancellation, TimeoutTimer, timeout, Barrier } from '../../../../../base/common/async.js'; +import { onUnexpectedError } from '../../../../../base/common/errors.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { ITextModel } from '../../../../../editor/common/model.js'; +import { ITextResourceConfigurationService } from '../../../../../editor/common/services/textResourceConfiguration.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { IPosition } from '../../../../../editor/common/core/position.js'; +import { ScrollType } from '../../../../../editor/common/editorCommon.js'; +import { Range } from '../../../../../editor/common/core/range.js'; +import { IEditorOptions, TextEditorSelectionRevealType } from '../../../../../platform/editor/common/editor.js'; +import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js'; +import { IModelContentChangedEvent } from '../../../../../editor/common/textModelEvents.js'; +import { IDataSource } from '../../../../../base/browser/ui/tree/tree.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { localize } from '../../../../../nls.js'; +import { IMarkerDecorationsService } from '../../../../../editor/common/services/markerDecorations.js'; +import { MarkerSeverity } from '../../../../../platform/markers/common/markers.js'; +import { isEqual } from '../../../../../base/common/resources.js'; +import { ILanguageFeaturesService } from '../../../../../editor/common/services/languageFeatures.js'; type DocumentSymbolItem = OutlineGroup | OutlineElement; -class DocumentSymbolBreadcrumbsSource - implements IBreadcrumbsDataSource -{ +class DocumentSymbolBreadcrumbsSource implements IBreadcrumbsDataSource { + private _breadcrumbs: (OutlineGroup | OutlineElement)[] = []; constructor( private readonly _editor: ICodeEditor, - @ITextResourceConfigurationService - private readonly _textResourceConfigurationService: ITextResourceConfigurationService, - ) {} + @ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService, + ) { } getBreadcrumbElements(): readonly DocumentSymbolItem[] { return this._breadcrumbs; @@ -110,12 +58,8 @@ class DocumentSymbolBreadcrumbsSource this._breadcrumbs = newElements; } - private _computeBreadcrumbs( - model: OutlineModel, - position: IPosition, - ): Array { - let item: OutlineGroup | OutlineElement | undefined = - model.getItemEnclosingPosition(position); + private _computeBreadcrumbs(model: OutlineModel, position: IPosition): Array { + let item: OutlineGroup | OutlineElement | undefined = model.getItemEnclosingPosition(position); if (!item) { return []; } @@ -126,11 +70,7 @@ class DocumentSymbolBreadcrumbsSource if (parent instanceof OutlineModel) { break; } - if ( - parent instanceof OutlineGroup && - parent.parent && - parent.parent.children.size === 1 - ) { + if (parent instanceof OutlineGroup && parent.parent && parent.parent.children.size === 1) { break; } item = parent; @@ -159,14 +99,13 @@ class DocumentSymbolBreadcrumbsSource const model = this._editor.getModel() as ITextModel; uri = model.uri; } - return !this._textResourceConfigurationService.getValue( - uri, - key, - ); + return !this._textResourceConfigurationService.getValue(uri, key); } } + class DocumentSymbolsOutline implements IOutline { + private readonly _disposables = new DisposableStore(); private readonly _onDidChange = new Emitter(); @@ -179,7 +118,7 @@ class DocumentSymbolsOutline implements IOutline { readonly config: IOutlineListConfig; - readonly outlineKind = "documentSymbols"; + readonly outlineKind = 'documentSymbols'; get activeElement(): DocumentSymbolItem | undefined { const posistion = this._editor.getPosition(); @@ -194,80 +133,44 @@ class DocumentSymbolsOutline implements IOutline { private readonly _editor: ICodeEditor, target: OutlineTarget, firstLoadBarrier: Barrier, - @ILanguageFeaturesService - private readonly _languageFeaturesService: ILanguageFeaturesService, - @ICodeEditorService - private readonly _codeEditorService: ICodeEditorService, - @IOutlineModelService - private readonly _outlineModelService: IOutlineModelService, - @IConfigurationService - private readonly _configurationService: IConfigurationService, - @IMarkerDecorationsService - private readonly _markerDecorationsService: IMarkerDecorationsService, - @ITextResourceConfigurationService - textResourceConfigurationService: ITextResourceConfigurationService, + @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, + @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, + @IOutlineModelService private readonly _outlineModelService: IOutlineModelService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IMarkerDecorationsService private readonly _markerDecorationsService: IMarkerDecorationsService, + @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @IInstantiationService instantiationService: IInstantiationService, ) { - this._breadcrumbsDataSource = new DocumentSymbolBreadcrumbsSource( - _editor, - textResourceConfigurationService, - ); + + this._breadcrumbsDataSource = new DocumentSymbolBreadcrumbsSource(_editor, textResourceConfigurationService); const delegate = new DocumentSymbolVirtualDelegate(); - const renderers = [ - new DocumentSymbolGroupRenderer(), - instantiationService.createInstance( - DocumentSymbolRenderer, - true, - target, - ), - ]; + const renderers = [new DocumentSymbolGroupRenderer(), instantiationService.createInstance(DocumentSymbolRenderer, true, target)]; const treeDataSource: IDataSource = { getChildren: (parent) => { - if ( - parent instanceof OutlineElement || - parent instanceof OutlineGroup - ) { + if (parent instanceof OutlineElement || parent instanceof OutlineGroup) { return parent.children.values(); } if (parent === this && this._outlineModel) { return this._outlineModel.children.values(); } return []; - }, + } }; const comparator = new DocumentSymbolComparator(); - const initialState = - textResourceConfigurationService.getValue( - _editor.getModel()?.uri, - OutlineConfigKeys.collapseItems, - ); + const initialState = textResourceConfigurationService.getValue(_editor.getModel()?.uri, OutlineConfigKeys.collapseItems); const options = { - collapseByDefault: - target === OutlineTarget.Breadcrumbs || - (target === OutlineTarget.OutlinePane && - initialState === - OutlineConfigCollapseItemsValues.Collapsed), + collapseByDefault: target === OutlineTarget.Breadcrumbs || (target === OutlineTarget.OutlinePane && initialState === OutlineConfigCollapseItemsValues.Collapsed), expandOnlyOnTwistieClick: true, multipleSelectionSupport: false, identityProvider: new DocumentSymbolIdentityProvider(), - keyboardNavigationLabelProvider: - new DocumentSymbolNavigationLabelProvider(), - accessibilityProvider: new DocumentSymbolAccessibilityProvider( - localize("document", "Document Symbols"), - ), - filter: - target === OutlineTarget.OutlinePane - ? instantiationService.createInstance( - DocumentSymbolFilter, - "outline", - ) - : target === OutlineTarget.Breadcrumbs - ? instantiationService.createInstance( - DocumentSymbolFilter, - "breadcrumbs", - ) - : undefined, - dnd: new DocumentSymbolDragAndDrop(), + keyboardNavigationLabelProvider: new DocumentSymbolNavigationLabelProvider(), + accessibilityProvider: new DocumentSymbolAccessibilityProvider(localize('document', "Document Symbols")), + filter: target === OutlineTarget.OutlinePane + ? instantiationService.createInstance(DocumentSymbolFilter, 'outline') + : target === OutlineTarget.Breadcrumbs + ? instantiationService.createInstance(DocumentSymbolFilter, 'breadcrumbs') + : undefined, + dnd: instantiationService.createInstance(DocumentSymbolDragAndDrop), }; this.config = { @@ -277,47 +180,28 @@ class DocumentSymbolsOutline implements IOutline { treeDataSource, comparator, options, - quickPickDataSource: { - getQuickPickElements: () => { - throw new Error("not implemented"); - }, - }, + quickPickDataSource: { getQuickPickElements: () => { throw new Error('not implemented'); } } }; + // update as language, model, providers changes - this._disposables.add( - _languageFeaturesService.documentSymbolProvider.onDidChange((_) => - this._createOutline(), - ), - ); - this._disposables.add( - this._editor.onDidChangeModel((_) => this._createOutline()), - ); - this._disposables.add( - this._editor.onDidChangeModelLanguage((_) => this._createOutline()), - ); + this._disposables.add(_languageFeaturesService.documentSymbolProvider.onDidChange(_ => this._createOutline())); + this._disposables.add(this._editor.onDidChangeModel(_ => this._createOutline())); + this._disposables.add(this._editor.onDidChangeModelLanguage(_ => this._createOutline())); // update soon'ish as model content change const updateSoon = new TimeoutTimer(); this._disposables.add(updateSoon); - this._disposables.add( - this._editor.onDidChangeModelContent((event) => { - const model = this._editor.getModel(); - if (model) { - const timeout = - _outlineModelService.getDebounceValue(model); - updateSoon.cancelAndSet( - () => this._createOutline(event), - timeout, - ); - } - }), - ); + this._disposables.add(this._editor.onDidChangeModelContent(event => { + const model = this._editor.getModel(); + if (model) { + const timeout = _outlineModelService.getDebounceValue(model); + updateSoon.cancelAndSet(() => this._createOutline(event), timeout); + } + })); // stop when editor dies - this._disposables.add( - this._editor.onDidDispose(() => this._outlineDisposables.clear()), - ); + this._disposables.add(this._editor.onDidDispose(() => this._outlineDisposables.clear())); // initial load this._createOutline().finally(() => firstLoadBarrier.open()); @@ -336,31 +220,19 @@ class DocumentSymbolsOutline implements IOutline { return this._outlineModel?.uri; } - async reveal( - entry: DocumentSymbolItem, - options: IEditorOptions, - sideBySide: boolean, - select: boolean, - ): Promise { + async reveal(entry: DocumentSymbolItem, options: IEditorOptions, sideBySide: boolean, select: boolean): Promise { const model = OutlineModel.get(entry); if (!model || !(entry instanceof OutlineElement)) { return; } - await this._codeEditorService.openCodeEditor( - { - resource: model.uri, - options: { - ...options, - selection: select - ? entry.symbol.range - : Range.collapseToStart(entry.symbol.selectionRange), - selectionRevealType: - TextEditorSelectionRevealType.NearTopIfOutsideViewport, - }, - }, - this._editor, - sideBySide, - ); + await this._codeEditorService.openCodeEditor({ + resource: model.uri, + options: { + ...options, + selection: select ? entry.symbol.range : Range.collapseToStart(entry.symbol.selectionRange), + selectionRevealType: TextEditorSelectionRevealType.NearTopIfOutsideViewport, + } + }, this._editor, sideBySide); } preview(entry: DocumentSymbolItem): IDisposable { @@ -369,20 +241,15 @@ class DocumentSymbolsOutline implements IOutline { } const { symbol } = entry; - this._editor.revealRangeInCenterIfOutsideViewport( - symbol.range, - ScrollType.Smooth, - ); - const decorationsCollection = this._editor.createDecorationsCollection([ - { - range: symbol.range, - options: { - description: "document-symbols-outline-range-highlight", - className: "rangeHighlight", - isWholeLine: true, - }, - }, - ]); + this._editor.revealRangeInCenterIfOutsideViewport(symbol.range, ScrollType.Smooth); + const decorationsCollection = this._editor.createDecorationsCollection([{ + range: symbol.range, + options: { + description: 'document-symbols-outline-range-highlight', + className: 'rangeHighlight', + isWholeLine: true + } + }]); return toDisposable(() => decorationsCollection.clear()); } @@ -395,9 +262,8 @@ class DocumentSymbolsOutline implements IOutline { }); } - private async _createOutline( - contentChangeEvent?: IModelContentChangedEvent, - ): Promise { + private async _createOutline(contentChangeEvent?: IModelContentChangedEvent): Promise { + this._outlineDisposables.clear(); if (!contentChangeEvent) { this._setOutlineModel(undefined); @@ -419,10 +285,7 @@ class DocumentSymbolsOutline implements IOutline { this._outlineDisposables.add(toDisposable(() => cts.dispose(true))); try { - const model = await this._outlineModelService.getOrCreate( - buffer, - cts.token, - ); + const model = await this._outlineModelService.getOrCreate(buffer, cts.token); if (cts.token.isCancellationRequested) { // cancelled -> do nothing return; @@ -436,30 +299,17 @@ class DocumentSymbolsOutline implements IOutline { // heuristic: when the symbols-to-lines ratio changes by 50% between edits // wait a little (and hope that the next change isn't as drastic). - if ( - contentChangeEvent && - this._outlineModel && - buffer.getLineCount() >= 25 - ) { + if (contentChangeEvent && this._outlineModel && buffer.getLineCount() >= 25) { const newSize = TreeElement.size(model); const newLength = buffer.getValueLength(); const newRatio = newSize / newLength; const oldSize = TreeElement.size(this._outlineModel); - const oldLength = - newLength - - contentChangeEvent.changes.reduce( - (prev, value) => prev + value.rangeLength, - 0, - ); + const oldLength = newLength - contentChangeEvent.changes.reduce((prev, value) => prev + value.rangeLength, 0); const oldRatio = oldSize / oldLength; if (newRatio <= oldRatio * 0.5 || newRatio >= oldRatio * 1.5) { // wait for a better state and ignore current model when more // typing has happened - const value = await raceCancellation( - timeout(2000).then(() => true), - cts.token, - false, - ); + const value = await raceCancellation(timeout(2000).then(() => true), cts.token, false); if (!value) { return; } @@ -468,91 +318,58 @@ class DocumentSymbolsOutline implements IOutline { // feature: show markers with outline element this._applyMarkersToOutline(model); - this._outlineDisposables.add( - this._markerDecorationsService.onDidChangeMarker( - (textModel) => { - if (isEqual(model.uri, textModel.uri)) { - this._applyMarkersToOutline(model); - this._onDidChange.fire({}); - } - }, - ), - ); - this._outlineDisposables.add( - this._configurationService.onDidChangeConfiguration((e) => { - if ( - e.affectsConfiguration( - OutlineConfigKeys.problemsEnabled, - ) || - e.affectsConfiguration("problems.visibility") - ) { - const problem = this._configurationService.getValue( - "problems.visibility", - ); - const config = this._configurationService.getValue( - OutlineConfigKeys.problemsEnabled, - ); - - if (!problem || !config) { - model.updateMarker([]); - } else { - this._applyMarkersToOutline(model); - } - this._onDidChange.fire({}); - } - if (e.affectsConfiguration("outline")) { - // outline filtering, problems on/off - this._onDidChange.fire({}); - } - if ( - e.affectsConfiguration("breadcrumbs") && - this._editor.hasModel() - ) { - // breadcrumbs filtering - this._breadcrumbsDataSource.update( - model, - this._editor.getPosition(), - ); - this._onDidChange.fire({}); + this._outlineDisposables.add(this._markerDecorationsService.onDidChangeMarker(textModel => { + if (isEqual(model.uri, textModel.uri)) { + this._applyMarkersToOutline(model); + this._onDidChange.fire({}); + } + })); + this._outlineDisposables.add(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(OutlineConfigKeys.problemsEnabled) || e.affectsConfiguration('problems.visibility')) { + const problem = this._configurationService.getValue('problems.visibility'); + const config = this._configurationService.getValue(OutlineConfigKeys.problemsEnabled); + + if (!problem || !config) { + model.updateMarker([]); + } else { + this._applyMarkersToOutline(model); } - }), - ); + this._onDidChange.fire({}); + } + if (e.affectsConfiguration('outline')) { + // outline filtering, problems on/off + this._onDidChange.fire({}); + } + if (e.affectsConfiguration('breadcrumbs') && this._editor.hasModel()) { + // breadcrumbs filtering + this._breadcrumbsDataSource.update(model, this._editor.getPosition()); + this._onDidChange.fire({}); + } + })); // feature: toggle icons - this._outlineDisposables.add( - this._configurationService.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration(OutlineConfigKeys.icons)) { - this._onDidChange.fire({}); - } - if (e.affectsConfiguration("outline")) { - this._onDidChange.fire({}); - } - }), - ); + this._outlineDisposables.add(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(OutlineConfigKeys.icons)) { + this._onDidChange.fire({}); + } + if (e.affectsConfiguration('outline')) { + this._onDidChange.fire({}); + } + })); // feature: update active when cursor changes - this._outlineDisposables.add( - this._editor.onDidChangeCursorPosition((_) => { - timeoutTimer.cancelAndSet(() => { - if ( - !buffer.isDisposed() && - versionIdThen === buffer.getVersionId() && - this._editor.hasModel() - ) { - this._breadcrumbsDataSource.update( - model, - this._editor.getPosition(), - ); - this._onDidChange.fire({ - affectOnlyActiveElement: true, - }); - } - }, 150); - }), - ); + this._outlineDisposables.add(this._editor.onDidChangeCursorPosition(_ => { + timeoutTimer.cancelAndSet(() => { + if (!buffer.isDisposed() && versionIdThen === buffer.getVersionId() && this._editor.hasModel()) { + this._breadcrumbsDataSource.update(model, this._editor.getPosition()); + this._onDidChange.fire({ affectOnlyActiveElement: true }); + } + }, 150); + })); // update properties, send event this._setOutlineModel(model); + } catch (err) { this._setOutlineModel(undefined); onUnexpectedError(err); @@ -560,24 +377,14 @@ class DocumentSymbolsOutline implements IOutline { } private _applyMarkersToOutline(model: OutlineModel | undefined): void { - const problem = this._configurationService.getValue( - "problems.visibility", - ); - const config = this._configurationService.getValue( - OutlineConfigKeys.problemsEnabled, - ); + const problem = this._configurationService.getValue('problems.visibility'); + const config = this._configurationService.getValue(OutlineConfigKeys.problemsEnabled); if (!model || !problem || !config) { return; } const markers: IOutlineMarker[] = []; - for (const [ - range, - marker, - ] of this._markerDecorationsService.getLiveMarkers(model.uri)) { - if ( - marker.severity === MarkerSeverity.Error || - marker.severity === MarkerSeverity.Warning - ) { + for (const [range, marker] of this._markerDecorationsService.getLiveMarkers(model.uri)) { + if (marker.severity === MarkerSeverity.Error || marker.severity === MarkerSeverity.Warning) { markers.push({ ...range, severity: marker.severity }); } } @@ -599,12 +406,13 @@ class DocumentSymbolsOutline implements IOutline { } } -class DocumentSymbolsOutlineCreator - implements IOutlineCreator -{ +class DocumentSymbolsOutlineCreator implements IOutlineCreator { + readonly dispose: () => void; - constructor(@IOutlineService outlineService: IOutlineService) { + constructor( + @IOutlineService outlineService: IOutlineService + ) { const reg = outlineService.registerOutlineCreator(this); this.dispose = () => reg.dispose(); } @@ -614,11 +422,7 @@ class DocumentSymbolsOutlineCreator return isCodeEditor(ctrl) || isDiffEditor(ctrl); } - async createOutline( - pane: IEditorPane, - target: OutlineTarget, - _token: CancellationToken, - ): Promise | undefined> { + async createOutline(pane: IEditorPane, target: OutlineTarget, _token: CancellationToken): Promise | undefined> { const control = pane.getControl(); let editor: ICodeEditor | undefined; if (isCodeEditor(control)) { @@ -630,24 +434,10 @@ class DocumentSymbolsOutlineCreator return undefined; } const firstLoadBarrier = new Barrier(); - const result = editor.invokeWithinContext((accessor) => - accessor - .get(IInstantiationService) - .createInstance( - DocumentSymbolsOutline, - editor, - target, - firstLoadBarrier, - ), - ); + const result = editor.invokeWithinContext(accessor => accessor.get(IInstantiationService).createInstance(DocumentSymbolsOutline, editor, target, firstLoadBarrier)); await firstLoadBarrier.wait(); return result; } } -Registry.as( - WorkbenchExtensions.Workbench, -).registerWorkbenchContribution( - DocumentSymbolsOutlineCreator, - LifecyclePhase.Eventually, -); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DocumentSymbolsOutlineCreator, LifecyclePhase.Eventually); diff --git a/Source/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts b/Source/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts index e5a8c15908ae1..192a7529c1d6e 100644 --- a/Source/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts +++ b/Source/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts @@ -3,79 +3,40 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import "./documentSymbolsTree.css"; -import "../../../../../editor/contrib/symbolIcons/browser/symbolIcons.js"; // The codicon symbol colors are defined here and must be loaded to get colors - -import { - DataTransfers, - IDragAndDropData, -} from "../../../../../base/browser/dnd.js"; -import * as dom from "../../../../../base/browser/dom.js"; -import { HighlightedLabel } from "../../../../../base/browser/ui/highlightedlabel/highlightedLabel.js"; -import { - IconLabel, - IIconLabelValueOptions, -} from "../../../../../base/browser/ui/iconLabel/iconLabel.js"; -import { - IIdentityProvider, - IKeyboardNavigationLabelProvider, - IListVirtualDelegate, -} from "../../../../../base/browser/ui/list/list.js"; -import { ElementsDragAndDropData } from "../../../../../base/browser/ui/list/listView.js"; -import { IListAccessibilityProvider } from "../../../../../base/browser/ui/list/listWidget.js"; -import { - ITreeDragAndDrop, - ITreeDragOverReaction, - ITreeFilter, - ITreeNode, - ITreeRenderer, -} from "../../../../../base/browser/ui/tree/tree.js"; -import { mainWindow } from "../../../../../base/browser/window.js"; -import { - createMatches, - FuzzyScore, -} from "../../../../../base/common/filters.js"; -import { ThemeIcon } from "../../../../../base/common/themables.js"; -import { URI } from "../../../../../base/common/uri.js"; -import { Range } from "../../../../../editor/common/core/range.js"; -import { - DocumentSymbol, - getAriaLabelForSymbol, - SymbolKind, - symbolKindNames, - SymbolKinds, - SymbolTag, -} from "../../../../../editor/common/languages.js"; -import { ITextResourceConfigurationService } from "../../../../../editor/common/services/textResourceConfiguration.js"; -import { - OutlineElement, - OutlineGroup, - OutlineModel, -} from "../../../../../editor/contrib/documentSymbols/browser/outlineModel.js"; -import { localize } from "../../../../../nls.js"; -import { IConfigurationService } from "../../../../../platform/configuration/common/configuration.js"; -import { CodeDataTransfers } from "../../../../../platform/dnd/browser/dnd.js"; -import { MarkerSeverity } from "../../../../../platform/markers/common/markers.js"; -import { withSelection } from "../../../../../platform/opener/common/opener.js"; -import { - listErrorForeground, - listWarningForeground, -} from "../../../../../platform/theme/common/colorRegistry.js"; -import { IThemeService } from "../../../../../platform/theme/common/themeService.js"; -import { - IOutlineComparator, - OutlineConfigKeys, - OutlineTarget, -} from "../../../../services/outline/browser/outline.js"; +import { IDragAndDropData } from '../../../../../base/browser/dnd.js'; +import * as dom from '../../../../../base/browser/dom.js'; +import { HighlightedLabel } from '../../../../../base/browser/ui/highlightedlabel/highlightedLabel.js'; +import { IconLabel, IIconLabelValueOptions } from '../../../../../base/browser/ui/iconLabel/iconLabel.js'; +import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListVirtualDelegate } from '../../../../../base/browser/ui/list/list.js'; +import { ElementsDragAndDropData } from '../../../../../base/browser/ui/list/listView.js'; +import { IListAccessibilityProvider } from '../../../../../base/browser/ui/list/listWidget.js'; +import { ITreeDragAndDrop, ITreeDragOverReaction, ITreeFilter, ITreeNode, ITreeRenderer } from '../../../../../base/browser/ui/tree/tree.js'; +import { mainWindow } from '../../../../../base/browser/window.js'; +import { createMatches, FuzzyScore } from '../../../../../base/common/filters.js'; +import { ThemeIcon } from '../../../../../base/common/themables.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { Range } from '../../../../../editor/common/core/range.js'; +import { DocumentSymbol, getAriaLabelForSymbol, SymbolKind, symbolKindNames, SymbolKinds, SymbolTag } from '../../../../../editor/common/languages.js'; +import { ITextResourceConfigurationService } from '../../../../../editor/common/services/textResourceConfiguration.js'; +import { OutlineElement, OutlineGroup, OutlineModel } from '../../../../../editor/contrib/documentSymbols/browser/outlineModel.js'; +import '../../../../../editor/contrib/symbolIcons/browser/symbolIcons.js'; // The codicon symbol colors are defined here and must be loaded to get colors +import { localize } from '../../../../../nls.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { fillInSymbolsDragData, IResourceStat } from '../../../../../platform/dnd/browser/dnd.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { MarkerSeverity } from '../../../../../platform/markers/common/markers.js'; +import { withSelection } from '../../../../../platform/opener/common/opener.js'; +import { listErrorForeground, listWarningForeground } from '../../../../../platform/theme/common/colorRegistry.js'; +import { IThemeService } from '../../../../../platform/theme/common/themeService.js'; +import { fillEditorsDragData } from '../../../../browser/dnd.js'; +import { IOutlineComparator, OutlineConfigKeys, OutlineTarget } from '../../../../services/outline/browser/outline.js'; +import './documentSymbolsTree.css'; export type DocumentSymbolItem = OutlineGroup | OutlineElement; -export class DocumentSymbolNavigationLabelProvider - implements IKeyboardNavigationLabelProvider -{ - getKeyboardNavigationLabel(element: DocumentSymbolItem): { - toString(): string; - } { +export class DocumentSymbolNavigationLabelProvider implements IKeyboardNavigationLabelProvider { + + getKeyboardNavigationLabel(element: DocumentSymbolItem): { toString(): string } { if (element instanceof OutlineGroup) { return element.label; } else { @@ -84,10 +45,9 @@ export class DocumentSymbolNavigationLabelProvider } } -export class DocumentSymbolAccessibilityProvider - implements IListAccessibilityProvider -{ - constructor(private readonly _ariaLabel: string) {} +export class DocumentSymbolAccessibilityProvider implements IListAccessibilityProvider { + + constructor(private readonly _ariaLabel: string) { } getWidgetAriaLabel(): string { return this._ariaLabel; @@ -96,26 +56,22 @@ export class DocumentSymbolAccessibilityProvider if (element instanceof OutlineGroup) { return element.label; } else { - return getAriaLabelForSymbol( - element.symbol.name, - element.symbol.kind, - ); + return getAriaLabelForSymbol(element.symbol.name, element.symbol.kind); } } } -export class DocumentSymbolIdentityProvider - implements IIdentityProvider -{ +export class DocumentSymbolIdentityProvider implements IIdentityProvider { getId(element: DocumentSymbolItem): { toString(): string } { return element.id; } } -export class DocumentSymbolDragAndDrop - implements ITreeDragAndDrop -{ - constructor() {} +export class DocumentSymbolDragAndDrop implements ITreeDragAndDrop { + + constructor( + @IInstantiationService private readonly _instantiationService: IInstantiationService + ) { } getDragURI(element: DocumentSymbolItem): string | null { const resource = OutlineModel.get(element)?.uri; @@ -124,34 +80,25 @@ export class DocumentSymbolDragAndDrop } if (element instanceof OutlineElement) { - return symbolRangeUri(resource, element.symbol).toString(); + const symbolUri = symbolRangeUri(resource, element.symbol); + return symbolUri.fsPath + (symbolUri.fragment ? '#' + symbolUri.fragment : ''); } else { - return resource.toString(); + return resource.fsPath; } } - getDragLabel( - elements: DocumentSymbolItem[], - originalEvent: DragEvent, - ): string | undefined { + getDragLabel(elements: DocumentSymbolItem[], originalEvent: DragEvent): string | undefined { // Multi select not supported if (elements.length !== 1) { return undefined; } const element = elements[0]; - return element instanceof OutlineElement - ? element.symbol.name - : element.label; + return element instanceof OutlineElement ? element.symbol.name : element.label; } onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void { - const elements = ( - data as ElementsDragAndDropData< - DocumentSymbolItem, - DocumentSymbolItem[] - > - ).elements; + const elements = (data as ElementsDragAndDropData).elements; const item = elements[0]; if (!item || !originalEvent.dataTransfer) { return; @@ -162,36 +109,24 @@ export class DocumentSymbolDragAndDrop return; } - const outlineElements = - item instanceof OutlineElement - ? [item] - : Array.from(item.children.values()); - const symbolsData = outlineElements.map((oe) => ({ + const outlineElements = item instanceof OutlineElement ? [item] : Array.from(item.children.values()); + + fillInSymbolsDragData(outlineElements.map(oe => ({ name: oe.symbol.name, fsPath: resource.fsPath, range: oe.symbol.range, - kind: oe.symbol.kind, - })); - - originalEvent.dataTransfer.setData( - CodeDataTransfers.SYMBOLS, - JSON.stringify(symbolsData), - ); - originalEvent.dataTransfer.setData( - DataTransfers.RESOURCES, - JSON.stringify( - outlineElements.map((oe) => - symbolRangeUri(resource, oe.symbol), - ), - ), - ); - } + kind: oe.symbol.kind + })), originalEvent); - onDragOver(): boolean | ITreeDragOverReaction { - return false; + this._instantiationService.invokeFunction(accessor => fillEditorsDragData(accessor, outlineElements.map((oe): IResourceStat => ({ + resource, + selection: oe.symbol.range, + })), originalEvent)); } - drop(): void {} - dispose(): void {} + + onDragOver(): boolean | ITreeDragOverReaction { return false; } + drop(): void { } + dispose(): void { } } function symbolRangeUri(resource: URI, symbol: DocumentSymbol): URI { @@ -199,11 +134,11 @@ function symbolRangeUri(resource: URI, symbol: DocumentSymbol): URI { } class DocumentSymbolGroupTemplate { - static readonly id = "DocumentSymbolGroupTemplate"; + static readonly id = 'DocumentSymbolGroupTemplate'; constructor( readonly labelContainer: HTMLElement, readonly label: HighlightedLabel, - ) {} + ) { } dispose() { this.label.dispose(); @@ -211,18 +146,17 @@ class DocumentSymbolGroupTemplate { } class DocumentSymbolTemplate { - static readonly id = "DocumentSymbolTemplate"; + static readonly id = 'DocumentSymbolTemplate'; constructor( readonly container: HTMLElement, readonly iconLabel: IconLabel, readonly iconClass: HTMLElement, readonly decoration: HTMLElement, - ) {} + ) { } } -export class DocumentSymbolVirtualDelegate - implements IListVirtualDelegate -{ +export class DocumentSymbolVirtualDelegate implements IListVirtualDelegate { + getHeight(_element: DocumentSymbolItem): number { return 22; } @@ -234,27 +168,18 @@ export class DocumentSymbolVirtualDelegate } } -export class DocumentSymbolGroupRenderer - implements - ITreeRenderer -{ +export class DocumentSymbolGroupRenderer implements ITreeRenderer { + readonly templateId: string = DocumentSymbolGroupTemplate.id; renderTemplate(container: HTMLElement): DocumentSymbolGroupTemplate { - const labelContainer = dom.$(".outline-element-label"); - container.classList.add("outline-element"); + const labelContainer = dom.$('.outline-element-label'); + container.classList.add('outline-element'); dom.append(container, labelContainer); - return new DocumentSymbolGroupTemplate( - labelContainer, - new HighlightedLabel(labelContainer), - ); + return new DocumentSymbolGroupTemplate(labelContainer, new HighlightedLabel(labelContainer)); } - renderElement( - node: ITreeNode, - _index: number, - template: DocumentSymbolGroupTemplate, - ): void { + renderElement(node: ITreeNode, _index: number, template: DocumentSymbolGroupTemplate): void { template.label.set(node.element.label, createMatches(node.filterData)); } @@ -263,114 +188,72 @@ export class DocumentSymbolGroupRenderer } } -export class DocumentSymbolRenderer - implements - ITreeRenderer -{ +export class DocumentSymbolRenderer implements ITreeRenderer { + readonly templateId: string = DocumentSymbolTemplate.id; constructor( private _renderMarker: boolean, target: OutlineTarget, - @IConfigurationService - private readonly _configurationService: IConfigurationService, + @IConfigurationService private readonly _configurationService: IConfigurationService, @IThemeService private readonly _themeService: IThemeService, - ) {} + ) { } renderTemplate(container: HTMLElement): DocumentSymbolTemplate { - container.classList.add("outline-element"); + container.classList.add('outline-element'); const iconLabel = new IconLabel(container, { supportHighlights: true }); - const iconClass = dom.$(".outline-element-icon"); - const decoration = dom.$(".outline-element-decoration"); + const iconClass = dom.$('.outline-element-icon'); + const decoration = dom.$('.outline-element-decoration'); container.prepend(iconClass); container.appendChild(decoration); - return new DocumentSymbolTemplate( - container, - iconLabel, - iconClass, - decoration, - ); + return new DocumentSymbolTemplate(container, iconLabel, iconClass, decoration); } - renderElement( - node: ITreeNode, - _index: number, - template: DocumentSymbolTemplate, - ): void { + renderElement(node: ITreeNode, _index: number, template: DocumentSymbolTemplate): void { const { element } = node; - const extraClasses = ["nowrap"]; + const extraClasses = ['nowrap']; const options: IIconLabelValueOptions = { matches: createMatches(node.filterData), labelEscapeNewLines: true, extraClasses, - title: localize( - "title.template", - "{0} ({1})", - element.symbol.name, - symbolKindNames[element.symbol.kind], - ), + title: localize('title.template', "{0} ({1})", element.symbol.name, symbolKindNames[element.symbol.kind]) }; if (this._configurationService.getValue(OutlineConfigKeys.icons)) { // add styles for the icons - template.iconClass.className = ""; - template.iconClass.classList.add( - "outline-element-icon", - "inline", - ...ThemeIcon.asClassNameArray( - SymbolKinds.toIcon(element.symbol.kind), - ), - ); + template.iconClass.className = ''; + template.iconClass.classList.add('outline-element-icon', 'inline', ...ThemeIcon.asClassNameArray(SymbolKinds.toIcon(element.symbol.kind))); } if (element.symbol.tags.indexOf(SymbolTag.Deprecated) >= 0) { extraClasses.push(`deprecated`); options.matches = []; } - template.iconLabel.setLabel( - element.symbol.name, - element.symbol.detail, - options, - ); + template.iconLabel.setLabel(element.symbol.name, element.symbol.detail, options); if (this._renderMarker) { this._renderMarkerInfo(element, template); } } - private _renderMarkerInfo( - element: OutlineElement, - template: DocumentSymbolTemplate, - ): void { + private _renderMarkerInfo(element: OutlineElement, template: DocumentSymbolTemplate): void { + if (!element.marker) { dom.hide(template.decoration); - template.container.style.removeProperty("--outline-element-color"); + template.container.style.removeProperty('--outline-element-color'); return; } const { count, topSev } = element.marker; - const color = this._themeService - .getColorTheme() - .getColor( - topSev === MarkerSeverity.Error - ? listErrorForeground - : listWarningForeground, - ); - const cssColor = color ? color.toString() : "inherit"; + const color = this._themeService.getColorTheme().getColor(topSev === MarkerSeverity.Error ? listErrorForeground : listWarningForeground); + const cssColor = color ? color.toString() : 'inherit'; // color of the label - const problem = this._configurationService.getValue( - "problems.visibility", - ); - const configProblems = this._configurationService.getValue( - OutlineConfigKeys.problemsColors, - ); + const problem = this._configurationService.getValue('problems.visibility'); + const configProblems = this._configurationService.getValue(OutlineConfigKeys.problemsColors); if (!problem || !configProblems) { - template.container.style.removeProperty("--outline-element-color"); + template.container.style.removeProperty('--outline-element-color'); } else { - template.container.style.setProperty( - "--outline-element-color", - cssColor, - ); + template.container.style.setProperty('--outline-element-color', cssColor); } // badge with color/rollup @@ -378,40 +261,22 @@ export class DocumentSymbolRenderer return; } - const configBadges = this._configurationService.getValue( - OutlineConfigKeys.problemsBadges, - ); + const configBadges = this._configurationService.getValue(OutlineConfigKeys.problemsBadges); if (!configBadges || !problem) { dom.hide(template.decoration); } else if (count > 0) { dom.show(template.decoration); - template.decoration.classList.remove("bubble"); - template.decoration.innerText = - count < 10 ? count.toString() : "+9"; - template.decoration.title = - count === 1 - ? localize("1.problem", "1 problem in this element") - : localize( - "N.problem", - "{0} problems in this element", - count, - ); - template.decoration.style.setProperty( - "--outline-element-color", - cssColor, - ); + template.decoration.classList.remove('bubble'); + template.decoration.innerText = count < 10 ? count.toString() : '+9'; + template.decoration.title = count === 1 ? localize('1.problem', "1 problem in this element") : localize('N.problem', "{0} problems in this element", count); + template.decoration.style.setProperty('--outline-element-color', cssColor); + } else { dom.show(template.decoration); - template.decoration.classList.add("bubble"); - template.decoration.innerText = "\uea71"; - template.decoration.title = localize( - "deep.problem", - "Contains elements with problems", - ); - template.decoration.style.setProperty( - "--outline-element-color", - cssColor, - ); + template.decoration.classList.add('bubble'); + template.decoration.innerText = '\uea71'; + template.decoration.title = localize('deep.problem', "Contains elements with problems"); + template.decoration.style.setProperty('--outline-element-color', cssColor); } } @@ -421,74 +286,61 @@ export class DocumentSymbolRenderer } export class DocumentSymbolFilter implements ITreeFilter { + static readonly kindToConfigName = Object.freeze({ - [SymbolKind.File]: "showFiles", - [SymbolKind.Module]: "showModules", - [SymbolKind.Namespace]: "showNamespaces", - [SymbolKind.Package]: "showPackages", - [SymbolKind.Class]: "showClasses", - [SymbolKind.Method]: "showMethods", - [SymbolKind.Property]: "showProperties", - [SymbolKind.Field]: "showFields", - [SymbolKind.Constructor]: "showConstructors", - [SymbolKind.Enum]: "showEnums", - [SymbolKind.Interface]: "showInterfaces", - [SymbolKind.Function]: "showFunctions", - [SymbolKind.Variable]: "showVariables", - [SymbolKind.Constant]: "showConstants", - [SymbolKind.String]: "showStrings", - [SymbolKind.Number]: "showNumbers", - [SymbolKind.Boolean]: "showBooleans", - [SymbolKind.Array]: "showArrays", - [SymbolKind.Object]: "showObjects", - [SymbolKind.Key]: "showKeys", - [SymbolKind.Null]: "showNull", - [SymbolKind.EnumMember]: "showEnumMembers", - [SymbolKind.Struct]: "showStructs", - [SymbolKind.Event]: "showEvents", - [SymbolKind.Operator]: "showOperators", - [SymbolKind.TypeParameter]: "showTypeParameters", + [SymbolKind.File]: 'showFiles', + [SymbolKind.Module]: 'showModules', + [SymbolKind.Namespace]: 'showNamespaces', + [SymbolKind.Package]: 'showPackages', + [SymbolKind.Class]: 'showClasses', + [SymbolKind.Method]: 'showMethods', + [SymbolKind.Property]: 'showProperties', + [SymbolKind.Field]: 'showFields', + [SymbolKind.Constructor]: 'showConstructors', + [SymbolKind.Enum]: 'showEnums', + [SymbolKind.Interface]: 'showInterfaces', + [SymbolKind.Function]: 'showFunctions', + [SymbolKind.Variable]: 'showVariables', + [SymbolKind.Constant]: 'showConstants', + [SymbolKind.String]: 'showStrings', + [SymbolKind.Number]: 'showNumbers', + [SymbolKind.Boolean]: 'showBooleans', + [SymbolKind.Array]: 'showArrays', + [SymbolKind.Object]: 'showObjects', + [SymbolKind.Key]: 'showKeys', + [SymbolKind.Null]: 'showNull', + [SymbolKind.EnumMember]: 'showEnumMembers', + [SymbolKind.Struct]: 'showStructs', + [SymbolKind.Event]: 'showEvents', + [SymbolKind.Operator]: 'showOperators', + [SymbolKind.TypeParameter]: 'showTypeParameters', }); constructor( - private readonly _prefix: "breadcrumbs" | "outline", - @ITextResourceConfigurationService - private readonly _textResourceConfigService: ITextResourceConfigurationService, - ) {} + private readonly _prefix: 'breadcrumbs' | 'outline', + @ITextResourceConfigurationService private readonly _textResourceConfigService: ITextResourceConfigurationService, + ) { } filter(element: DocumentSymbolItem): boolean { const outline = OutlineModel.get(element); if (!(element instanceof OutlineElement)) { return true; } - const configName = - DocumentSymbolFilter.kindToConfigName[element.symbol.kind]; + const configName = DocumentSymbolFilter.kindToConfigName[element.symbol.kind]; const configKey = `${this._prefix}.${configName}`; - return this._textResourceConfigService.getValue( - outline?.uri, - configKey, - ); + return this._textResourceConfigService.getValue(outline?.uri, configKey); } } -export class DocumentSymbolComparator - implements IOutlineComparator -{ - private readonly _collator = new dom.WindowIdleValue( - mainWindow, - () => new Intl.Collator(undefined, { numeric: true }), - ); +export class DocumentSymbolComparator implements IOutlineComparator { + + private readonly _collator = new dom.WindowIdleValue(mainWindow, () => new Intl.Collator(undefined, { numeric: true })); compareByPosition(a: DocumentSymbolItem, b: DocumentSymbolItem): number { if (a instanceof OutlineGroup && b instanceof OutlineGroup) { return a.order - b.order; } else if (a instanceof OutlineElement && b instanceof OutlineElement) { - return ( - Range.compareRangesUsingStarts( - a.symbol.range, - b.symbol.range, - ) || this._collator.value.compare(a.symbol.name, b.symbol.name) - ); + return Range.compareRangesUsingStarts(a.symbol.range, b.symbol.range) || this._collator.value.compare(a.symbol.name, b.symbol.name); } return 0; } @@ -496,10 +348,7 @@ export class DocumentSymbolComparator if (a instanceof OutlineGroup && b instanceof OutlineGroup) { return a.order - b.order; } else if (a instanceof OutlineElement && b instanceof OutlineElement) { - return ( - a.symbol.kind - b.symbol.kind || - this._collator.value.compare(a.symbol.name, b.symbol.name) - ); + return a.symbol.kind - b.symbol.kind || this._collator.value.compare(a.symbol.name, b.symbol.name); } return 0; } @@ -507,10 +356,7 @@ export class DocumentSymbolComparator if (a instanceof OutlineGroup && b instanceof OutlineGroup) { return a.order - b.order; } else if (a instanceof OutlineElement && b instanceof OutlineElement) { - return ( - this._collator.value.compare(a.symbol.name, b.symbol.name) || - Range.compareRangesUsingStarts(a.symbol.range, b.symbol.range) - ); + return this._collator.value.compare(a.symbol.name, b.symbol.name) || Range.compareRangesUsingStarts(a.symbol.range, b.symbol.range); } return 0; } diff --git a/Source/vs/workbench/contrib/comments/browser/media/review.css b/Source/vs/workbench/contrib/comments/browser/media/review.css index 6c482a0713502..edc2c30dd7a43 100644 --- a/Source/vs/workbench/contrib/comments/browser/media/review.css +++ b/Source/vs/workbench/contrib/comments/browser/media/review.css @@ -285,7 +285,7 @@ background-color: var(--vscode-statusBarItem-activeBackground); border: 1px solid transparent; } -.review-widget .body .review-comment .review-comment-contents a { +.review-widget .body .review-comment .review-comment-contents .comment-body a { cursor: pointer; } diff --git a/Source/vs/workbench/contrib/debug/browser/callStackView.ts b/Source/vs/workbench/contrib/debug/browser/callStackView.ts index 18adb734fd74b..2126b5ba952b3 100644 --- a/Source/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/Source/vs/workbench/contrib/debug/browser/callStackView.ts @@ -3,163 +3,75 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as dom from "../../../../base/browser/dom.js"; -import { ActionBar } from "../../../../base/browser/ui/actionbar/actionbar.js"; -import { AriaRole } from "../../../../base/browser/ui/aria/aria.js"; -import { HighlightedLabel } from "../../../../base/browser/ui/highlightedlabel/highlightedLabel.js"; -import type { IManagedHover } from "../../../../base/browser/ui/hover/hover.js"; -import { getDefaultHoverDelegate } from "../../../../base/browser/ui/hover/hoverDelegateFactory.js"; -import { IListVirtualDelegate } from "../../../../base/browser/ui/list/list.js"; -import { IListAccessibilityProvider } from "../../../../base/browser/ui/list/listWidget.js"; -import { ITreeCompressionDelegate } from "../../../../base/browser/ui/tree/asyncDataTree.js"; -import { ICompressedTreeNode } from "../../../../base/browser/ui/tree/compressedObjectTreeModel.js"; -import { ICompressibleTreeRenderer } from "../../../../base/browser/ui/tree/objectTree.js"; -import { - IAsyncDataSource, - ITreeContextMenuEvent, - ITreeNode, -} from "../../../../base/browser/ui/tree/tree.js"; -import { Action } from "../../../../base/common/actions.js"; -import { RunOnceScheduler } from "../../../../base/common/async.js"; -import { Codicon } from "../../../../base/common/codicons.js"; -import { Event } from "../../../../base/common/event.js"; -import { - createMatches, - FuzzyScore, - IMatch, -} from "../../../../base/common/filters.js"; -import { - DisposableStore, - dispose, - IDisposable, -} from "../../../../base/common/lifecycle.js"; -import { posix } from "../../../../base/common/path.js"; -import { commonSuffixLength } from "../../../../base/common/strings.js"; -import { ThemeIcon } from "../../../../base/common/themables.js"; -import { localize } from "../../../../nls.js"; -import { - ICommandActionTitle, - Icon, -} from "../../../../platform/action/common/action.js"; -import { - getActionBarActions, - getContextMenuActions, - MenuEntryActionViewItem, - SubmenuEntryActionViewItem, -} from "../../../../platform/actions/browser/menuEntryActionViewItem.js"; -import { - IMenuService, - MenuId, - MenuItemAction, - MenuRegistry, - registerAction2, - SubmenuItemAction, -} from "../../../../platform/actions/common/actions.js"; -import { IConfigurationService } from "../../../../platform/configuration/common/configuration.js"; -import { - ContextKeyExpr, - ContextKeyExpression, - IContextKeyService, -} from "../../../../platform/contextkey/common/contextkey.js"; -import { IContextMenuService } from "../../../../platform/contextview/browser/contextView.js"; -import { IHoverService } from "../../../../platform/hover/browser/hover.js"; -import { - IInstantiationService, - ServicesAccessor, -} from "../../../../platform/instantiation/common/instantiation.js"; -import { IKeybindingService } from "../../../../platform/keybinding/common/keybinding.js"; -import { ILabelService } from "../../../../platform/label/common/label.js"; -import { WorkbenchCompressibleAsyncDataTree } from "../../../../platform/list/browser/listService.js"; -import { INotificationService } from "../../../../platform/notification/common/notification.js"; -import { IOpenerService } from "../../../../platform/opener/common/opener.js"; -import { ITelemetryService } from "../../../../platform/telemetry/common/telemetry.js"; -import { - asCssVariable, - textLinkForeground, -} from "../../../../platform/theme/common/colorRegistry.js"; -import { IThemeService } from "../../../../platform/theme/common/themeService.js"; -import { ViewAction, ViewPane } from "../../../browser/parts/views/viewPane.js"; -import { IViewletViewOptions } from "../../../browser/parts/views/viewsViewlet.js"; -import { IViewDescriptorService } from "../../../common/views.js"; -import { - CALLSTACK_VIEW_ID, - CONTEXT_CALLSTACK_ITEM_STOPPED, - CONTEXT_CALLSTACK_ITEM_TYPE, - CONTEXT_CALLSTACK_SESSION_HAS_ONE_THREAD, - CONTEXT_CALLSTACK_SESSION_IS_ATTACH, - CONTEXT_DEBUG_STATE, - CONTEXT_FOCUSED_SESSION_IS_NO_DEBUG, - CONTEXT_STACK_FRAME_SUPPORTS_RESTART, - getStateLabel, - IDebugModel, - IDebugService, - IDebugSession, - IRawStoppedDetails, - isFrameDeemphasized, - IStackFrame, - IThread, - State, -} from "../common/debug.js"; -import { - StackFrame, - Thread, - ThreadAndSessionIds, -} from "../common/debugModel.js"; -import { isSessionAttach } from "../common/debugUtils.js"; -import { renderViewTree } from "./baseDebugView.js"; -import { - CONTINUE_ID, - CONTINUE_LABEL, - DISCONNECT_ID, - DISCONNECT_LABEL, - PAUSE_ID, - PAUSE_LABEL, - RESTART_LABEL, - RESTART_SESSION_ID, - STEP_INTO_ID, - STEP_INTO_LABEL, - STEP_OUT_ID, - STEP_OUT_LABEL, - STEP_OVER_ID, - STEP_OVER_LABEL, - STOP_ID, - STOP_LABEL, -} from "./debugCommands.js"; -import * as icons from "./debugIcons.js"; -import { createDisconnectMenuItemAction } from "./debugToolBar.js"; +import * as dom from '../../../../base/browser/dom.js'; +import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js'; +import { AriaRole } from '../../../../base/browser/ui/aria/aria.js'; +import { HighlightedLabel } from '../../../../base/browser/ui/highlightedlabel/highlightedLabel.js'; +import { IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js'; +import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js'; +import { ITreeCompressionDelegate } from '../../../../base/browser/ui/tree/asyncDataTree.js'; +import { ICompressedTreeNode } from '../../../../base/browser/ui/tree/compressedObjectTreeModel.js'; +import { ICompressibleTreeRenderer } from '../../../../base/browser/ui/tree/objectTree.js'; +import { IAsyncDataSource, ITreeContextMenuEvent, ITreeNode } from '../../../../base/browser/ui/tree/tree.js'; +import { Action } from '../../../../base/common/actions.js'; +import { RunOnceScheduler } from '../../../../base/common/async.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { Event } from '../../../../base/common/event.js'; +import { createMatches, FuzzyScore, IMatch } from '../../../../base/common/filters.js'; +import { DisposableStore, dispose, IDisposable } from '../../../../base/common/lifecycle.js'; +import { posix } from '../../../../base/common/path.js'; +import { commonSuffixLength } from '../../../../base/common/strings.js'; +import { localize } from '../../../../nls.js'; +import { ICommandActionTitle, Icon } from '../../../../platform/action/common/action.js'; +import { getActionBarActions, getContextMenuActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { IMenuService, MenuId, MenuItemAction, MenuRegistry, registerAction2, SubmenuItemAction } from '../../../../platform/actions/common/actions.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { ILabelService } from '../../../../platform/label/common/label.js'; +import { WorkbenchCompressibleAsyncDataTree } from '../../../../platform/list/browser/listService.js'; +import { INotificationService } from '../../../../platform/notification/common/notification.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { asCssVariable, textLinkForeground } from '../../../../platform/theme/common/colorRegistry.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { ViewAction, ViewPane } from '../../../browser/parts/views/viewPane.js'; +import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js'; +import { IViewDescriptorService } from '../../../common/views.js'; +import { renderViewTree } from './baseDebugView.js'; +import { CONTINUE_ID, CONTINUE_LABEL, DISCONNECT_ID, DISCONNECT_LABEL, PAUSE_ID, PAUSE_LABEL, RESTART_LABEL, RESTART_SESSION_ID, STEP_INTO_ID, STEP_INTO_LABEL, STEP_OUT_ID, STEP_OUT_LABEL, STEP_OVER_ID, STEP_OVER_LABEL, STOP_ID, STOP_LABEL } from './debugCommands.js'; +import * as icons from './debugIcons.js'; +import { createDisconnectMenuItemAction } from './debugToolBar.js'; +import { CALLSTACK_VIEW_ID, CONTEXT_CALLSTACK_ITEM_STOPPED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_CALLSTACK_SESSION_HAS_ONE_THREAD, CONTEXT_CALLSTACK_SESSION_IS_ATTACH, CONTEXT_DEBUG_STATE, CONTEXT_FOCUSED_SESSION_IS_NO_DEBUG, CONTEXT_STACK_FRAME_SUPPORTS_RESTART, getStateLabel, IDebugModel, IDebugService, IDebugSession, IRawStoppedDetails, isFrameDeemphasized, IStackFrame, IThread, State } from '../common/debug.js'; +import { StackFrame, Thread, ThreadAndSessionIds } from '../common/debugModel.js'; +import { isSessionAttach } from '../common/debugUtils.js'; +import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; +import type { IManagedHover } from '../../../../base/browser/ui/hover/hover.js'; +import { IHoverService } from '../../../../platform/hover/browser/hover.js'; const $ = dom.$; -type CallStackItem = - | IStackFrame - | IThread - | IDebugSession - | string - | ThreadAndSessionIds - | IStackFrame[]; +type CallStackItem = IStackFrame | IThread | IDebugSession | string | ThreadAndSessionIds | IStackFrame[]; function assignSessionContext(element: IDebugSession, context: any) { context.sessionId = element.getId(); - return context; } function assignThreadContext(element: IThread, context: any) { context.threadId = element.getId(); assignSessionContext(element.session, context); - return context; } function assignStackFrameContext(element: StackFrame, context: any) { context.frameId = element.getId(); context.frameName = element.name; - context.frameLocation = { - range: element.range, - source: element.source.raw, - }; + context.frameLocation = { range: element.range, source: element.source.raw }; assignThreadContext(element.thread, context); - return context; } @@ -176,16 +88,10 @@ export function getContext(element: CallStackItem | null): any { } // Extensions depend on this context, should not be changed even though it is not fully deterministic -export function getContextForContributedActions( - element: CallStackItem | null, -): string | number { +export function getContextForContributedActions(element: CallStackItem | null): string | number { if (element instanceof StackFrame) { if (element.source.inMemory) { - return ( - element.source.raw.path || - element.source.reference || - element.source.name - ); + return element.source.raw.path || element.source.reference || element.source.name; } return element.source.uri.toString(); @@ -197,53 +103,30 @@ export function getContextForContributedActions( return element.getId(); } - return ""; + return ''; } export function getSpecificSourceName(stackFrame: IStackFrame): string { // To reduce flashing of the path name and the way we fetch stack frames // We need to compute the source name based on the other frames in the stale call stack let callStack = (stackFrame.thread).getStaleCallStack(); - callStack = - callStack.length > 0 ? callStack : stackFrame.thread.getCallStack(); - - const otherSources = callStack - .map((sf) => sf.source) - .filter((s) => s !== stackFrame.source); - + callStack = callStack.length > 0 ? callStack : stackFrame.thread.getCallStack(); + const otherSources = callStack.map(sf => sf.source).filter(s => s !== stackFrame.source); let suffixLength = 0; - otherSources.forEach((s) => { + otherSources.forEach(s => { if (s.name === stackFrame.source.name) { - suffixLength = Math.max( - suffixLength, - commonSuffixLength(stackFrame.source.uri.path, s.uri.path), - ); + suffixLength = Math.max(suffixLength, commonSuffixLength(stackFrame.source.uri.path, s.uri.path)); } }); - if (suffixLength === 0) { return stackFrame.source.name; } - const from = Math.max( - 0, - stackFrame.source.uri.path.lastIndexOf( - posix.sep, - stackFrame.source.uri.path.length - suffixLength - 1, - ), - ); - - return (from > 0 ? "..." : "") + stackFrame.source.uri.path.substring(from); + const from = Math.max(0, stackFrame.source.uri.path.lastIndexOf(posix.sep, stackFrame.source.uri.path.length - suffixLength - 1)); + return (from > 0 ? '...' : '') + stackFrame.source.uri.path.substring(from); } -async function expandTo( - session: IDebugSession, - tree: WorkbenchCompressibleAsyncDataTree< - IDebugModel, - CallStackItem, - FuzzyScore - >, -): Promise { +async function expandTo(session: IDebugSession, tree: WorkbenchCompressibleAsyncDataTree): Promise { if (session.parentSession) { await expandTo(session.parentSession, tree); } @@ -260,11 +143,7 @@ export class CallStackView extends ViewPane { private ignoreFocusStackFrameEvent = false; private dataSource!: CallStackDataSource; - private tree!: WorkbenchCompressibleAsyncDataTree< - IDebugModel, - CallStackItem, - FuzzyScore - >; + private tree!: WorkbenchCompressibleAsyncDataTree; private autoExpandedSessions = new Set(); private selectionNeedsUpdate = false; @@ -283,363 +162,230 @@ export class CallStackView extends ViewPane { @IHoverService hoverService: IHoverService, @IMenuService private readonly menuService: IMenuService, ) { - super( - options, - keybindingService, - contextMenuService, - configurationService, - contextKeyService, - viewDescriptorService, - instantiationService, - openerService, - themeService, - telemetryService, - hoverService, - ); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService); // Create scheduler to prevent unnecessary flashing of tree when reacting to changes - this.onCallStackChangeScheduler = this._register( - new RunOnceScheduler(async () => { - // Only show the global pause message if we do not display threads. - // Otherwise there will be a pause message per thread and there is no need for a global one. - const sessions = this.debugService.getModel().getSessions(); - - if (sessions.length === 0) { - this.autoExpandedSessions.clear(); - } - - const thread = - sessions.length === 1 && - sessions[0].getAllThreads().length === 1 - ? sessions[0].getAllThreads()[0] - : undefined; - - const stoppedDetails = - sessions.length === 1 - ? sessions[0].getStoppedDetails() - : undefined; - - if ( - stoppedDetails && - (thread || typeof stoppedDetails.threadId !== "number") - ) { - this.stateMessageLabel.textContent = - stoppedDescription(stoppedDetails); - this.stateMessageLabelHover.update( - stoppedText(stoppedDetails), - ); - this.stateMessageLabel.classList.toggle( - "exception", - stoppedDetails.reason === "exception", - ); - this.stateMessage.hidden = false; - } else if ( - sessions.length === 1 && - sessions[0].state === State.Running - ) { - this.stateMessageLabel.textContent = localize( - { key: "running", comment: ["indicates state"] }, - "Running", - ); - this.stateMessageLabelHover.update(sessions[0].getLabel()); - this.stateMessageLabel.classList.remove("exception"); - this.stateMessage.hidden = false; - } else { - this.stateMessage.hidden = true; - } - this.updateActions(); - - this.needsRefresh = false; - this.dataSource.deemphasizedStackFramesToShow = []; - await this.tree.updateChildren(); + this.onCallStackChangeScheduler = this._register(new RunOnceScheduler(async () => { + // Only show the global pause message if we do not display threads. + // Otherwise there will be a pause message per thread and there is no need for a global one. + const sessions = this.debugService.getModel().getSessions(); + if (sessions.length === 0) { + this.autoExpandedSessions.clear(); + } - try { - const toExpand = new Set(); - sessions.forEach((s) => { - // Automatically expand sessions that have children, but only do this once. - if ( - s.parentSession && - !this.autoExpandedSessions.has(s.parentSession) - ) { - toExpand.add(s.parentSession); - } - }); + const thread = sessions.length === 1 && sessions[0].getAllThreads().length === 1 ? sessions[0].getAllThreads()[0] : undefined; + const stoppedDetails = sessions.length === 1 ? sessions[0].getStoppedDetails() : undefined; + if (stoppedDetails && (thread || typeof stoppedDetails.threadId !== 'number')) { + this.stateMessageLabel.textContent = stoppedDescription(stoppedDetails); + this.stateMessageLabelHover.update(stoppedText(stoppedDetails)); + this.stateMessageLabel.classList.toggle('exception', stoppedDetails.reason === 'exception'); + this.stateMessage.hidden = false; + } else if (sessions.length === 1 && sessions[0].state === State.Running) { + this.stateMessageLabel.textContent = localize({ key: 'running', comment: ['indicates state'] }, "Running"); + this.stateMessageLabelHover.update(sessions[0].getLabel()); + this.stateMessageLabel.classList.remove('exception'); + this.stateMessage.hidden = false; + } else { + this.stateMessage.hidden = true; + } + this.updateActions(); - for (const session of toExpand) { - await expandTo(session, this.tree); - this.autoExpandedSessions.add(session); + this.needsRefresh = false; + this.dataSource.deemphasizedStackFramesToShow = []; + await this.tree.updateChildren(); + try { + const toExpand = new Set(); + sessions.forEach(s => { + // Automatically expand sessions that have children, but only do this once. + if (s.parentSession && !this.autoExpandedSessions.has(s.parentSession)) { + toExpand.add(s.parentSession); } - } catch (e) { - // Ignore tree expand errors if element no longer present + }); + for (const session of toExpand) { + await expandTo(session, this.tree); + this.autoExpandedSessions.add(session); } - if (this.selectionNeedsUpdate) { - this.selectionNeedsUpdate = false; - await this.updateTreeSelection(); - } - }, 50), - ); + } catch (e) { + // Ignore tree expand errors if element no longer present + } + if (this.selectionNeedsUpdate) { + this.selectionNeedsUpdate = false; + await this.updateTreeSelection(); + } + }, 50)); } protected override renderHeaderTitle(container: HTMLElement): void { super.renderHeaderTitle(container, this.options.title); - this.stateMessage = dom.append( - container, - $("span.call-stack-state-message"), - ); + this.stateMessage = dom.append(container, $('span.call-stack-state-message')); this.stateMessage.hidden = true; - this.stateMessageLabel = dom.append(this.stateMessage, $("span.label")); - this.stateMessageLabelHover = this._register( - this.hoverService.setupManagedHover( - getDefaultHoverDelegate("mouse"), - this.stateMessage, - "", - ), - ); + this.stateMessageLabel = dom.append(this.stateMessage, $('span.label')); + this.stateMessageLabelHover = this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.stateMessage, '')); } protected override renderBody(container: HTMLElement): void { super.renderBody(container); - this.element.classList.add("debug-pane"); - container.classList.add("debug-call-stack"); - + this.element.classList.add('debug-pane'); + container.classList.add('debug-call-stack'); const treeContainer = renderViewTree(container); this.dataSource = new CallStackDataSource(this.debugService); - this.tree = < - WorkbenchCompressibleAsyncDataTree< - IDebugModel, - CallStackItem, - FuzzyScore - > - >this.instantiationService.createInstance( - WorkbenchCompressibleAsyncDataTree, - "CallStackView", - treeContainer, - new CallStackDelegate(), - new CallStackCompressionDelegate(this.debugService), - [ - this.instantiationService.createInstance(SessionsRenderer), - this.instantiationService.createInstance(ThreadsRenderer), - this.instantiationService.createInstance(StackFramesRenderer), - this.instantiationService.createInstance(ErrorsRenderer), - new LoadMoreRenderer(), - new ShowMoreRenderer(), - ], - this.dataSource, - { - accessibilityProvider: new CallStackAccessibilityProvider(), - compressionEnabled: true, - autoExpandSingleChildren: true, - identityProvider: { - getId: (element: CallStackItem) => { - if (typeof element === "string") { - return element; - } - if (element instanceof Array) { - return `showMore ${element[0].getId()}`; - } - - return element.getId(); - }, - }, - keyboardNavigationLabelProvider: { - getKeyboardNavigationLabel: (e: CallStackItem) => { - if (isDebugSession(e)) { - return e.getLabel(); - } - if (e instanceof Thread) { - return `${e.name} ${e.stateLabel}`; - } - if (e instanceof StackFrame || typeof e === "string") { - return e; - } - if (e instanceof ThreadAndSessionIds) { - return LoadMoreRenderer.LABEL; - } - - return localize( - "showMoreStackFrames2", - "Show More Stack Frames", - ); - }, - getCompressedNodeKeyboardNavigationLabel: ( - e: CallStackItem[], - ) => { - const firstItem = e[0]; - - if (isDebugSession(firstItem)) { - return firstItem.getLabel(); - } - return ""; - }, - }, - expandOnlyOnTwistieClick: true, - overrideStyles: - this.getLocationBasedColors().listOverrideStyles, - }, - ); + this.tree = this.instantiationService.createInstance(WorkbenchCompressibleAsyncDataTree, 'CallStackView', treeContainer, new CallStackDelegate(), new CallStackCompressionDelegate(this.debugService), [ + this.instantiationService.createInstance(SessionsRenderer), + this.instantiationService.createInstance(ThreadsRenderer), + this.instantiationService.createInstance(StackFramesRenderer), + this.instantiationService.createInstance(ErrorsRenderer), + new LoadMoreRenderer(), + new ShowMoreRenderer() + ], this.dataSource, { + accessibilityProvider: new CallStackAccessibilityProvider(), + compressionEnabled: true, + autoExpandSingleChildren: true, + identityProvider: { + getId: (element: CallStackItem) => { + if (typeof element === 'string') { + return element; + } + if (element instanceof Array) { + return `showMore ${element[0].getId()}`; + } - this.tree.setInput(this.debugService.getModel()); - this._register(this.tree); - this._register( - this.tree.onDidOpen(async (e) => { - if (this.ignoreSelectionChangedEvent) { - return; + return element.getId(); } - - const focusStackFrame = ( - stackFrame: IStackFrame | undefined, - thread: IThread | undefined, - session: IDebugSession, - options: { - explicit?: boolean; - preserveFocus?: boolean; - sideBySide?: boolean; - pinned?: boolean; - } = {}, - ) => { - this.ignoreFocusStackFrameEvent = true; - - try { - this.debugService.focusStackFrame( - stackFrame, - thread, - session, - { ...options, ...{ explicit: true } }, - ); - } finally { - this.ignoreFocusStackFrameEvent = false; + }, + keyboardNavigationLabelProvider: { + getKeyboardNavigationLabel: (e: CallStackItem) => { + if (isDebugSession(e)) { + return e.getLabel(); + } + if (e instanceof Thread) { + return `${e.name} ${e.stateLabel}`; + } + if (e instanceof StackFrame || typeof e === 'string') { + return e; + } + if (e instanceof ThreadAndSessionIds) { + return LoadMoreRenderer.LABEL; } - }; - const element = e.element; - - if (element instanceof StackFrame) { - const opts = { - preserveFocus: e.editorOptions.preserveFocus, - sideBySide: e.sideBySide, - pinned: e.editorOptions.pinned, - }; - focusStackFrame( - element, - element.thread, - element.thread.session, - opts, - ); - } - if (element instanceof Thread) { - focusStackFrame(undefined, element, element.session); - } - if (isDebugSession(element)) { - focusStackFrame(undefined, undefined, element); - } - if (element instanceof ThreadAndSessionIds) { - const session = this.debugService - .getModel() - .getSession(element.sessionId); - - const thread = - session && session.getThread(element.threadId); - - if (thread) { - const totalFrames = thread.stoppedDetails?.totalFrames; - - const remainingFramesCount = - typeof totalFrames === "number" - ? totalFrames - thread.getCallStack().length - : undefined; - // Get all the remaining frames - await (thread).fetchCallStack( - remainingFramesCount, - ); - await this.tree.updateChildren(); + return localize('showMoreStackFrames2', "Show More Stack Frames"); + }, + getCompressedNodeKeyboardNavigationLabel: (e: CallStackItem[]) => { + const firstItem = e[0]; + if (isDebugSession(firstItem)) { + return firstItem.getLabel(); } + return ''; } - if (element instanceof Array) { - this.dataSource.deemphasizedStackFramesToShow.push( - ...element, - ); - this.tree.updateChildren(); - } - }), - ); - - this._register( - this.debugService.getModel().onDidChangeCallStack(() => { - if (!this.isBodyVisible()) { - this.needsRefresh = true; + }, + expandOnlyOnTwistieClick: true, + overrideStyles: this.getLocationBasedColors().listOverrideStyles + }); - return; - } + this.tree.setInput(this.debugService.getModel()); + this._register(this.tree); + this._register(this.tree.onDidOpen(async e => { + if (this.ignoreSelectionChangedEvent) { + return; + } - if (!this.onCallStackChangeScheduler.isScheduled()) { - this.onCallStackChangeScheduler.schedule(); + const focusStackFrame = (stackFrame: IStackFrame | undefined, thread: IThread | undefined, session: IDebugSession, options: { explicit?: boolean; preserveFocus?: boolean; sideBySide?: boolean; pinned?: boolean } = {}) => { + this.ignoreFocusStackFrameEvent = true; + try { + this.debugService.focusStackFrame(stackFrame, thread, session, { ...options, ...{ explicit: true } }); + } finally { + this.ignoreFocusStackFrameEvent = false; } - }), - ); - - const onFocusChange = Event.any( - this.debugService.getViewModel().onDidFocusStackFrame, - this.debugService.getViewModel().onDidFocusSession, - ); - this._register( - onFocusChange(async () => { - if (this.ignoreFocusStackFrameEvent) { - return; + }; + + const element = e.element; + if (element instanceof StackFrame) { + const opts = { + preserveFocus: e.editorOptions.preserveFocus, + sideBySide: e.sideBySide, + pinned: e.editorOptions.pinned + }; + focusStackFrame(element, element.thread, element.thread.session, opts); + } + if (element instanceof Thread) { + focusStackFrame(undefined, element, element.session); + } + if (isDebugSession(element)) { + focusStackFrame(undefined, undefined, element); + } + if (element instanceof ThreadAndSessionIds) { + const session = this.debugService.getModel().getSession(element.sessionId); + const thread = session && session.getThread(element.threadId); + if (thread) { + const totalFrames = thread.stoppedDetails?.totalFrames; + const remainingFramesCount = typeof totalFrames === 'number' ? (totalFrames - thread.getCallStack().length) : undefined; + // Get all the remaining frames + await (thread).fetchCallStack(remainingFramesCount); + await this.tree.updateChildren(); } - if (!this.isBodyVisible()) { - this.needsRefresh = true; - this.selectionNeedsUpdate = true; + } + if (element instanceof Array) { + this.dataSource.deemphasizedStackFramesToShow.push(...element); + this.tree.updateChildren(); + } + })); - return; - } - if (this.onCallStackChangeScheduler.isScheduled()) { - this.selectionNeedsUpdate = true; + this._register(this.debugService.getModel().onDidChangeCallStack(() => { + if (!this.isBodyVisible()) { + this.needsRefresh = true; + return; + } - return; - } + if (!this.onCallStackChangeScheduler.isScheduled()) { + this.onCallStackChangeScheduler.schedule(); + } + })); + const onFocusChange = Event.any(this.debugService.getViewModel().onDidFocusStackFrame, this.debugService.getViewModel().onDidFocusSession); + this._register(onFocusChange(async () => { + if (this.ignoreFocusStackFrameEvent) { + return; + } + if (!this.isBodyVisible()) { + this.needsRefresh = true; + this.selectionNeedsUpdate = true; + return; + } + if (this.onCallStackChangeScheduler.isScheduled()) { + this.selectionNeedsUpdate = true; + return; + } - await this.updateTreeSelection(); - }), - ); - this._register(this.tree.onContextMenu((e) => this.onContextMenu(e))); + await this.updateTreeSelection(); + })); + this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); // Schedule the update of the call stack tree if the viewlet is opened after a session started #14684 if (this.debugService.state === State.Stopped) { this.onCallStackChangeScheduler.schedule(0); } - this._register( - this.onDidChangeBodyVisibility((visible) => { - if (visible && this.needsRefresh) { - this.onCallStackChangeScheduler.schedule(); - } - }), - ); - - this._register( - this.debugService.onDidNewSession((s) => { - const sessionListeners: IDisposable[] = []; - sessionListeners.push( - s.onDidChangeName(() => { - // this.tree.updateChildren is called on a delay after a session is added, - // so don't rerender if the tree doesn't have the node yet - if (this.tree.hasNode(s)) { - this.tree.rerender(s); - } - }), - ); - sessionListeners.push( - s.onDidEndAdapter(() => dispose(sessionListeners)), - ); - - if (s.parentSession) { - // A session we already expanded has a new child session, allow to expand it again. - this.autoExpandedSessions.delete(s.parentSession); + this._register(this.onDidChangeBodyVisibility(visible => { + if (visible && this.needsRefresh) { + this.onCallStackChangeScheduler.schedule(); + } + })); + + this._register(this.debugService.onDidNewSession(s => { + const sessionListeners: IDisposable[] = []; + sessionListeners.push(s.onDidChangeName(() => { + // this.tree.updateChildren is called on a delay after a session is added, + // so don't rerender if the tree doesn't have the node yet + if (this.tree.hasNode(s)) { + this.tree.rerender(s); } - }), - ); + })); + sessionListeners.push(s.onDidEndAdapter(() => dispose(sessionListeners))); + if (s.parentSession) { + // A session we already expanded has a new child session, allow to expand it again. + this.autoExpandedSessions.delete(s.parentSession); + } + })); } protected override layoutBody(height: number, width: number): void { @@ -662,11 +408,8 @@ export class CallStackView extends ViewPane { return; } - const updateSelectionAndReveal = ( - element: IStackFrame | IDebugSession, - ) => { + const updateSelectionAndReveal = (element: IStackFrame | IDebugSession) => { this.ignoreSelectionChangedEvent = true; - try { this.tree.setSelection([element]); // If the element is outside of the screen bounds, @@ -676,18 +419,15 @@ export class CallStackView extends ViewPane { } else { this.tree.reveal(element); } - } catch (e) { - } finally { + } catch (e) { } + finally { this.ignoreSelectionChangedEvent = false; } }; const thread = this.debugService.getViewModel().focusedThread; - const session = this.debugService.getViewModel().focusedSession; - const stackFrame = this.debugService.getViewModel().focusedStackFrame; - if (!thread) { if (!session) { this.tree.setSelection([]); @@ -698,13 +438,12 @@ export class CallStackView extends ViewPane { // Ignore errors from this expansions because we are not aware if we rendered the threads and sessions or we hide them to declutter the view try { await expandTo(thread.session, this.tree); - } catch (e) {} + } catch (e) { } try { await this.tree.expand(thread); - } catch (e) {} + } catch (e) { } const toReveal = stackFrame || session; - if (toReveal) { updateSelectionAndReveal(toReveal); } @@ -713,9 +452,7 @@ export class CallStackView extends ViewPane { private onContextMenu(e: ITreeContextMenuEvent): void { const element = e.element; - let overlay: [string, any][] = []; - if (isDebugSession(element)) { overlay = getSessionContextOverlay(element); } else if (element instanceof Thread) { @@ -725,21 +462,12 @@ export class CallStackView extends ViewPane { } const contextKeyService = this.contextKeyService.createOverlay(overlay); - - const menu = this.menuService.getMenuActions( - MenuId.DebugCallStackContext, - contextKeyService, - { - arg: getContextForContributedActions(element), - shouldForwardArgs: true, - }, - ); - - const result = getContextMenuActions(menu, "inline"); + const menu = this.menuService.getMenuActions(MenuId.DebugCallStackContext, contextKeyService, { arg: getContextForContributedActions(element), shouldForwardArgs: true }); + const result = getContextMenuActions(menu, 'inline'); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => result.secondary, - getActionsContext: () => getContext(element), + getActionsContext: () => getContext(element) }); } } @@ -785,222 +513,104 @@ interface IStackFrameTemplateData { function getSessionContextOverlay(session: IDebugSession): [string, any][] { return [ - [CONTEXT_CALLSTACK_ITEM_TYPE.key, "session"], + [CONTEXT_CALLSTACK_ITEM_TYPE.key, 'session'], [CONTEXT_CALLSTACK_SESSION_IS_ATTACH.key, isSessionAttach(session)], [CONTEXT_CALLSTACK_ITEM_STOPPED.key, session.state === State.Stopped], - [ - CONTEXT_CALLSTACK_SESSION_HAS_ONE_THREAD.key, - session.getAllThreads().length === 1, - ], + [CONTEXT_CALLSTACK_SESSION_HAS_ONE_THREAD.key, session.getAllThreads().length === 1], ]; } -class SessionsRenderer - implements - ICompressibleTreeRenderer< - IDebugSession, - FuzzyScore, - ISessionTemplateData - > -{ - static readonly ID = "session"; +class SessionsRenderer implements ICompressibleTreeRenderer { + static readonly ID = 'session'; constructor( - @IInstantiationService - private readonly instantiationService: IInstantiationService, - @IContextKeyService - private readonly contextKeyService: IContextKeyService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, @IHoverService private readonly hoverService: IHoverService, @IMenuService private readonly menuService: IMenuService, - ) {} + ) { } get templateId(): string { return SessionsRenderer.ID; } renderTemplate(container: HTMLElement): ISessionTemplateData { - const session = dom.append(container, $(".session")); - - dom.append( - session, - $(ThemeIcon.asCSSSelector(icons.callstackViewSession)), - ); - - const name = dom.append(session, $(".name")); - - const stateLabel = dom.append( - session, - $("span.state.label.monaco-count-badge.long"), - ); - + const session = dom.append(container, $('.session')); + dom.append(session, $(ThemeIcon.asCSSSelector(icons.callstackViewSession))); + const name = dom.append(session, $('.name')); + const stateLabel = dom.append(session, $('span.state.label.monaco-count-badge.long')); const templateDisposable = new DisposableStore(); - const label = templateDisposable.add(new HighlightedLabel(name)); - const stopActionViewItemDisposables = templateDisposable.add( - new DisposableStore(), - ); - - const actionBar = templateDisposable.add( - new ActionBar(session, { - actionViewItemProvider: (action, options) => { - if ( - (action.id === STOP_ID || - action.id === DISCONNECT_ID) && - action instanceof MenuItemAction - ) { - stopActionViewItemDisposables.clear(); - - const item = this.instantiationService.invokeFunction( - (accessor) => - createDisconnectMenuItemAction( - action as MenuItemAction, - stopActionViewItemDisposables, - accessor, - { ...options, menuAsChild: false }, - ), - ); - - if (item) { - return item; - } + const stopActionViewItemDisposables = templateDisposable.add(new DisposableStore()); + const actionBar = templateDisposable.add(new ActionBar(session, { + actionViewItemProvider: (action, options) => { + if ((action.id === STOP_ID || action.id === DISCONNECT_ID) && action instanceof MenuItemAction) { + stopActionViewItemDisposables.clear(); + const item = this.instantiationService.invokeFunction(accessor => createDisconnectMenuItemAction(action as MenuItemAction, stopActionViewItemDisposables, accessor, { ...options, menuAsChild: false })); + if (item) { + return item; } + } - if (action instanceof MenuItemAction) { - return this.instantiationService.createInstance( - MenuEntryActionViewItem, - action, - { hoverDelegate: options.hoverDelegate }, - ); - } else if (action instanceof SubmenuItemAction) { - return this.instantiationService.createInstance( - SubmenuEntryActionViewItem, - action, - { hoverDelegate: options.hoverDelegate }, - ); - } + if (action instanceof MenuItemAction) { + return this.instantiationService.createInstance(MenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }); + } else if (action instanceof SubmenuItemAction) { + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }); + } - return undefined; - }, - }), - ); + return undefined; + } + })); const elementDisposable = templateDisposable.add(new DisposableStore()); - - return { - session, - name, - stateLabel, - label, - actionBar, - elementDisposable, - templateDisposable, - }; + return { session, name, stateLabel, label, actionBar, elementDisposable, templateDisposable }; } - renderElement( - element: ITreeNode, - _: number, - data: ISessionTemplateData, - ): void { - this.doRenderElement( - element.element, - createMatches(element.filterData), - data, - ); + renderElement(element: ITreeNode, _: number, data: ISessionTemplateData): void { + this.doRenderElement(element.element, createMatches(element.filterData), data); } - renderCompressedElements( - node: ITreeNode, FuzzyScore>, - _index: number, - templateData: ISessionTemplateData, - ): void { - const lastElement = - node.element.elements[node.element.elements.length - 1]; - + renderCompressedElements(node: ITreeNode, FuzzyScore>, _index: number, templateData: ISessionTemplateData): void { + const lastElement = node.element.elements[node.element.elements.length - 1]; const matches = createMatches(node.filterData); this.doRenderElement(lastElement, matches, templateData); } - private doRenderElement( - session: IDebugSession, - matches: IMatch[], - data: ISessionTemplateData, - ): void { - const sessionHover = data.elementDisposable.add( - this.hoverService.setupManagedHover( - getDefaultHoverDelegate("mouse"), - data.session, - localize( - { key: "session", comment: ["Session is a noun"] }, - "Session", - ), - ), - ); + private doRenderElement(session: IDebugSession, matches: IMatch[], data: ISessionTemplateData): void { + const sessionHover = data.elementDisposable.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.session, localize({ key: 'session', comment: ['Session is a noun'] }, "Session"))); data.label.set(session.getLabel(), matches); - const stoppedDetails = session.getStoppedDetails(); + const thread = session.getAllThreads().find(t => t.stopped); - const thread = session.getAllThreads().find((t) => t.stopped); - - const contextKeyService = this.contextKeyService.createOverlay( - getSessionContextOverlay(session), - ); - - const menu = data.elementDisposable.add( - this.menuService.createMenu( - MenuId.DebugCallStackContext, - contextKeyService, - ), - ); + const contextKeyService = this.contextKeyService.createOverlay(getSessionContextOverlay(session)); + const menu = data.elementDisposable.add(this.menuService.createMenu(MenuId.DebugCallStackContext, contextKeyService)); const setupActionBar = () => { data.actionBar.clear(); - const { primary } = getActionBarActions( - menu.getActions({ - arg: getContextForContributedActions(session), - shouldForwardArgs: true, - }), - "inline", - ); + const { primary } = getActionBarActions(menu.getActions({ arg: getContextForContributedActions(session), shouldForwardArgs: true }), 'inline'); data.actionBar.push(primary, { icon: true, label: false }); // We need to set our internal context on the action bar, since our commands depend on that one // While the external context our extensions rely on data.actionBar.context = getContext(session); }; data.elementDisposable.add(menu.onDidChange(() => setupActionBar())); - setupActionBar(); - data.stateLabel.style.display = ""; + data.stateLabel.style.display = ''; if (stoppedDetails) { data.stateLabel.textContent = stoppedDescription(stoppedDetails); - sessionHover.update( - `${session.getLabel()}: ${stoppedText(stoppedDetails)}`, - ); - data.stateLabel.classList.toggle( - "exception", - stoppedDetails.reason === "exception", - ); + sessionHover.update(`${session.getLabel()}: ${stoppedText(stoppedDetails)}`); + data.stateLabel.classList.toggle('exception', stoppedDetails.reason === 'exception'); } else if (thread && thread.stoppedDetails) { - data.stateLabel.textContent = stoppedDescription( - thread.stoppedDetails, - ); - sessionHover.update( - `${session.getLabel()}: ${stoppedText(thread.stoppedDetails)}`, - ); - data.stateLabel.classList.toggle( - "exception", - thread.stoppedDetails.reason === "exception", - ); + data.stateLabel.textContent = stoppedDescription(thread.stoppedDetails); + sessionHover.update(`${session.getLabel()}: ${stoppedText(thread.stoppedDetails)}`); + data.stateLabel.classList.toggle('exception', thread.stoppedDetails.reason === 'exception'); } else { - data.stateLabel.textContent = localize( - { key: "running", comment: ["indicates state"] }, - "Running", - ); - data.stateLabel.classList.remove("exception"); + data.stateLabel.textContent = localize({ key: 'running', comment: ['indicates state'] }, "Running"); + data.stateLabel.classList.remove('exception'); } } @@ -1008,142 +618,77 @@ class SessionsRenderer templateData.templateDisposable.dispose(); } - disposeElement( - _element: ITreeNode, - _: number, - templateData: ISessionTemplateData, - ): void { + disposeElement(_element: ITreeNode, _: number, templateData: ISessionTemplateData): void { templateData.elementDisposable.clear(); } - disposeCompressedElements( - node: ITreeNode, FuzzyScore>, - index: number, - templateData: ISessionTemplateData, - height: number | undefined, - ): void { + disposeCompressedElements(node: ITreeNode, FuzzyScore>, index: number, templateData: ISessionTemplateData, height: number | undefined): void { templateData.elementDisposable.clear(); } } function getThreadContextOverlay(thread: IThread): [string, any][] { return [ - [CONTEXT_CALLSTACK_ITEM_TYPE.key, "thread"], - [CONTEXT_CALLSTACK_ITEM_STOPPED.key, thread.stopped], + [CONTEXT_CALLSTACK_ITEM_TYPE.key, 'thread'], + [CONTEXT_CALLSTACK_ITEM_STOPPED.key, thread.stopped] ]; } -class ThreadsRenderer - implements - ICompressibleTreeRenderer -{ - static readonly ID = "thread"; +class ThreadsRenderer implements ICompressibleTreeRenderer { + static readonly ID = 'thread'; constructor( - @IContextKeyService - private readonly contextKeyService: IContextKeyService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, @IHoverService private readonly hoverService: IHoverService, @IMenuService private readonly menuService: IMenuService, - ) {} + ) { } get templateId(): string { return ThreadsRenderer.ID; } renderTemplate(container: HTMLElement): IThreadTemplateData { - const thread = dom.append(container, $(".thread")); - - const name = dom.append(thread, $(".name")); - - const stateLabel = dom.append( - thread, - $("span.state.label.monaco-count-badge.long"), - ); + const thread = dom.append(container, $('.thread')); + const name = dom.append(thread, $('.name')); + const stateLabel = dom.append(thread, $('span.state.label.monaco-count-badge.long')); const templateDisposable = new DisposableStore(); - const label = templateDisposable.add(new HighlightedLabel(name)); const actionBar = templateDisposable.add(new ActionBar(thread)); - const elementDisposable = templateDisposable.add(new DisposableStore()); - return { - thread, - name, - stateLabel, - label, - actionBar, - elementDisposable, - templateDisposable, - }; + return { thread, name, stateLabel, label, actionBar, elementDisposable, templateDisposable }; } - renderElement( - element: ITreeNode, - _index: number, - data: IThreadTemplateData, - ): void { + renderElement(element: ITreeNode, _index: number, data: IThreadTemplateData): void { const thread = element.element; - data.elementDisposable.add( - this.hoverService.setupManagedHover( - getDefaultHoverDelegate("mouse"), - data.thread, - thread.name, - ), - ); + data.elementDisposable.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.thread, thread.name)); data.label.set(thread.name, createMatches(element.filterData)); data.stateLabel.textContent = thread.stateLabel; - data.stateLabel.classList.toggle( - "exception", - thread.stoppedDetails?.reason === "exception", - ); - - const contextKeyService = this.contextKeyService.createOverlay( - getThreadContextOverlay(thread), - ); - - const menu = data.elementDisposable.add( - this.menuService.createMenu( - MenuId.DebugCallStackContext, - contextKeyService, - ), - ); + data.stateLabel.classList.toggle('exception', thread.stoppedDetails?.reason === 'exception'); + + const contextKeyService = this.contextKeyService.createOverlay(getThreadContextOverlay(thread)); + const menu = data.elementDisposable.add(this.menuService.createMenu(MenuId.DebugCallStackContext, contextKeyService)); const setupActionBar = () => { data.actionBar.clear(); - const { primary } = getActionBarActions( - menu.getActions({ - arg: getContextForContributedActions(thread), - shouldForwardArgs: true, - }), - "inline", - ); + const { primary } = getActionBarActions(menu.getActions({ arg: getContextForContributedActions(thread), shouldForwardArgs: true }), 'inline'); data.actionBar.push(primary, { icon: true, label: false }); // We need to set our internal context on the action bar, since our commands depend on that one // While the external context our extensions rely on data.actionBar.context = getContext(thread); }; data.elementDisposable.add(menu.onDidChange(() => setupActionBar())); - setupActionBar(); } - renderCompressedElements( - _node: ITreeNode, FuzzyScore>, - _index: number, - _templateData: IThreadTemplateData, - _height: number | undefined, - ): void { - throw new Error("Method not implemented."); + renderCompressedElements(_node: ITreeNode, FuzzyScore>, _index: number, _templateData: IThreadTemplateData, _height: number | undefined): void { + throw new Error('Method not implemented.'); } - disposeElement( - _element: any, - _index: number, - templateData: IThreadTemplateData, - ): void { + disposeElement(_element: any, _index: number, templateData: IThreadTemplateData): void { templateData.elementDisposable.clear(); } @@ -1154,149 +699,80 @@ class ThreadsRenderer function getStackFrameContextOverlay(stackFrame: IStackFrame): [string, any][] { return [ - [CONTEXT_CALLSTACK_ITEM_TYPE.key, "stackFrame"], - [CONTEXT_STACK_FRAME_SUPPORTS_RESTART.key, stackFrame.canRestart], + [CONTEXT_CALLSTACK_ITEM_TYPE.key, 'stackFrame'], + [CONTEXT_STACK_FRAME_SUPPORTS_RESTART.key, stackFrame.canRestart] ]; } -class StackFramesRenderer - implements - ICompressibleTreeRenderer< - IStackFrame, - FuzzyScore, - IStackFrameTemplateData - > -{ - static readonly ID = "stackFrame"; +class StackFramesRenderer implements ICompressibleTreeRenderer { + static readonly ID = 'stackFrame'; constructor( @IHoverService private readonly hoverService: IHoverService, @ILabelService private readonly labelService: ILabelService, - @INotificationService - private readonly notificationService: INotificationService, - ) {} + @INotificationService private readonly notificationService: INotificationService, + ) { } get templateId(): string { return StackFramesRenderer.ID; } renderTemplate(container: HTMLElement): IStackFrameTemplateData { - const stackFrame = dom.append(container, $(".stack-frame")); - - const labelDiv = dom.append(stackFrame, $("span.label.expression")); - - const file = dom.append(stackFrame, $(".file")); - - const fileName = dom.append(file, $("span.file-name")); - - const wrapper = dom.append(file, $("span.line-number-wrapper")); - - const lineNumber = dom.append( - wrapper, - $("span.line-number.monaco-count-badge"), - ); + const stackFrame = dom.append(container, $('.stack-frame')); + const labelDiv = dom.append(stackFrame, $('span.label.expression')); + const file = dom.append(stackFrame, $('.file')); + const fileName = dom.append(file, $('span.file-name')); + const wrapper = dom.append(file, $('span.line-number-wrapper')); + const lineNumber = dom.append(wrapper, $('span.line-number.monaco-count-badge')); const templateDisposable = new DisposableStore(); - const label = templateDisposable.add(new HighlightedLabel(labelDiv)); const actionBar = templateDisposable.add(new ActionBar(stackFrame)); - return { - file, - fileName, - label, - lineNumber, - stackFrame, - actionBar, - templateDisposable, - }; + return { file, fileName, label, lineNumber, stackFrame, actionBar, templateDisposable }; } - renderElement( - element: ITreeNode, - index: number, - data: IStackFrameTemplateData, - ): void { + renderElement(element: ITreeNode, index: number, data: IStackFrameTemplateData): void { const stackFrame = element.element; - data.stackFrame.classList.toggle( - "disabled", - !stackFrame.source || - !stackFrame.source.available || - isFrameDeemphasized(stackFrame), - ); - data.stackFrame.classList.toggle( - "label", - stackFrame.presentationHint === "label", - ); - - const hasActions = - !!stackFrame.thread.session.capabilities.supportsRestartFrame && - stackFrame.presentationHint !== "label" && - stackFrame.presentationHint !== "subtle" && - stackFrame.canRestart; - data.stackFrame.classList.toggle("has-actions", hasActions); - - let title = stackFrame.source.inMemory - ? stackFrame.source.uri.path - : this.labelService.getUriLabel(stackFrame.source.uri); + data.stackFrame.classList.toggle('disabled', !stackFrame.source || !stackFrame.source.available || isFrameDeemphasized(stackFrame)); + data.stackFrame.classList.toggle('label', stackFrame.presentationHint === 'label'); + const hasActions = !!stackFrame.thread.session.capabilities.supportsRestartFrame && stackFrame.presentationHint !== 'label' && stackFrame.presentationHint !== 'subtle' && stackFrame.canRestart; + data.stackFrame.classList.toggle('has-actions', hasActions); + let title = stackFrame.source.inMemory ? stackFrame.source.uri.path : this.labelService.getUriLabel(stackFrame.source.uri); if (stackFrame.source.raw.origin) { title += `\n${stackFrame.source.raw.origin}`; } - data.templateDisposable.add( - this.hoverService.setupManagedHover( - getDefaultHoverDelegate("mouse"), - data.file, - title, - ), - ); - - data.label.set( - stackFrame.name, - createMatches(element.filterData), - stackFrame.name, - ); - data.fileName.textContent = getSpecificSourceName(stackFrame); + data.templateDisposable.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.file, title)); + data.label.set(stackFrame.name, createMatches(element.filterData), stackFrame.name); + data.fileName.textContent = getSpecificSourceName(stackFrame); if (stackFrame.range.startLineNumber !== undefined) { data.lineNumber.textContent = `${stackFrame.range.startLineNumber}`; - if (stackFrame.range.startColumn) { data.lineNumber.textContent += `:${stackFrame.range.startColumn}`; } - data.lineNumber.classList.remove("unavailable"); + data.lineNumber.classList.remove('unavailable'); } else { - data.lineNumber.classList.add("unavailable"); + data.lineNumber.classList.add('unavailable'); } data.actionBar.clear(); - if (hasActions) { - const action = new Action( - "debug.callStack.restartFrame", - localize("restartFrame", "Restart Frame"), - ThemeIcon.asClassName(icons.debugRestartFrame), - true, - async () => { - try { - await stackFrame.restart(); - } catch (e) { - this.notificationService.error(e); - } - }, - ); + const action = new Action('debug.callStack.restartFrame', localize('restartFrame', "Restart Frame"), ThemeIcon.asClassName(icons.debugRestartFrame), true, async () => { + try { + await stackFrame.restart(); + } catch (e) { + this.notificationService.error(e); + } + }); data.actionBar.push(action, { icon: true, label: false }); } } - renderCompressedElements( - node: ITreeNode, FuzzyScore>, - index: number, - templateData: IStackFrameTemplateData, - height: number | undefined, - ): void { - throw new Error("Method not implemented."); + renderCompressedElements(node: ITreeNode, FuzzyScore>, index: number, templateData: IStackFrameTemplateData, height: number | undefined): void { + throw new Error('Method not implemented.'); } disposeTemplate(templateData: IStackFrameTemplateData): void { @@ -1304,47 +780,32 @@ class StackFramesRenderer } } -class ErrorsRenderer - implements - ICompressibleTreeRenderer -{ - static readonly ID = "error"; +class ErrorsRenderer implements ICompressibleTreeRenderer { + static readonly ID = 'error'; get templateId(): string { return ErrorsRenderer.ID; } - constructor(@IHoverService private readonly hoverService: IHoverService) {} + constructor( + @IHoverService private readonly hoverService: IHoverService + ) { + } renderTemplate(container: HTMLElement): IErrorTemplateData { - const label = dom.append(container, $(".error")); + const label = dom.append(container, $('.error')); return { label, templateDisposable: new DisposableStore() }; } - renderElement( - element: ITreeNode, - index: number, - data: IErrorTemplateData, - ): void { + renderElement(element: ITreeNode, index: number, data: IErrorTemplateData): void { const error = element.element; data.label.textContent = error; - data.templateDisposable.add( - this.hoverService.setupManagedHover( - getDefaultHoverDelegate("mouse"), - data.label, - error, - ), - ); + data.templateDisposable.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.label, error)); } - renderCompressedElements( - node: ITreeNode, FuzzyScore>, - index: number, - templateData: IErrorTemplateData, - height: number | undefined, - ): void { - throw new Error("Method not implemented."); + renderCompressedElements(node: ITreeNode, FuzzyScore>, index: number, templateData: IErrorTemplateData, height: number | undefined): void { + throw new Error('Method not implemented.'); } disposeTemplate(templateData: IErrorTemplateData): void { @@ -1352,48 +813,28 @@ class ErrorsRenderer } } -class LoadMoreRenderer - implements - ICompressibleTreeRenderer< - ThreadAndSessionIds, - FuzzyScore, - ILabelTemplateData - > -{ - static readonly ID = "loadMore"; - static readonly LABEL = localize( - "loadAllStackFrames", - "Load More Stack Frames", - ); - - constructor() {} +class LoadMoreRenderer implements ICompressibleTreeRenderer { + static readonly ID = 'loadMore'; + static readonly LABEL = localize('loadAllStackFrames', "Load More Stack Frames"); + + constructor() { } get templateId(): string { return LoadMoreRenderer.ID; } renderTemplate(container: HTMLElement): ILabelTemplateData { - const label = dom.append(container, $(".load-all")); + const label = dom.append(container, $('.load-all')); label.style.color = asCssVariable(textLinkForeground); - return { label }; } - renderElement( - element: ITreeNode, - index: number, - data: ILabelTemplateData, - ): void { + renderElement(element: ITreeNode, index: number, data: ILabelTemplateData): void { data.label.textContent = LoadMoreRenderer.LABEL; } - renderCompressedElements( - node: ITreeNode, FuzzyScore>, - index: number, - templateData: ILabelTemplateData, - height: number | undefined, - ): void { - throw new Error("Method not implemented."); + renderCompressedElements(node: ITreeNode, FuzzyScore>, index: number, templateData: ILabelTemplateData, height: number | undefined): void { + throw new Error('Method not implemented.'); } disposeTemplate(templateData: ILabelTemplateData): void { @@ -1401,68 +842,33 @@ class LoadMoreRenderer } } -class ShowMoreRenderer - implements - ICompressibleTreeRenderer< - IStackFrame[], - FuzzyScore, - ILabelTemplateData - > -{ - static readonly ID = "showMore"; +class ShowMoreRenderer implements ICompressibleTreeRenderer { + static readonly ID = 'showMore'; + + constructor() { } - constructor() {} get templateId(): string { return ShowMoreRenderer.ID; } renderTemplate(container: HTMLElement): ILabelTemplateData { - const label = dom.append(container, $(".show-more")); + const label = dom.append(container, $('.show-more')); label.style.color = asCssVariable(textLinkForeground); - return { label }; } - renderElement( - element: ITreeNode, - index: number, - data: ILabelTemplateData, - ): void { + renderElement(element: ITreeNode, index: number, data: ILabelTemplateData): void { const stackFrames = element.element; - - if ( - stackFrames.every( - (sf) => - !!( - sf.source && - sf.source.origin && - sf.source.origin === stackFrames[0].source.origin - ), - ) - ) { - data.label.textContent = localize( - "showMoreAndOrigin", - "Show {0} More: {1}", - stackFrames.length, - stackFrames[0].source.origin, - ); + if (stackFrames.every(sf => !!(sf.source && sf.source.origin && sf.source.origin === stackFrames[0].source.origin))) { + data.label.textContent = localize('showMoreAndOrigin', "Show {0} More: {1}", stackFrames.length, stackFrames[0].source.origin); } else { - data.label.textContent = localize( - "showMoreStackFrames", - "Show {0} More Stack Frames", - stackFrames.length, - ); + data.label.textContent = localize('showMoreStackFrames', "Show {0} More Stack Frames", stackFrames.length); } } - renderCompressedElements( - node: ITreeNode, FuzzyScore>, - index: number, - templateData: ILabelTemplateData, - height: number | undefined, - ): void { - throw new Error("Method not implemented."); + renderCompressedElements(node: ITreeNode, FuzzyScore>, index: number, templateData: ILabelTemplateData, height: number | undefined): void { + throw new Error('Method not implemented.'); } disposeTemplate(templateData: ILabelTemplateData): void { @@ -1471,17 +877,12 @@ class ShowMoreRenderer } class CallStackDelegate implements IListVirtualDelegate { + getHeight(element: CallStackItem): number { - if ( - element instanceof StackFrame && - element.presentationHint === "label" - ) { + if (element instanceof StackFrame && element.presentationHint === 'label') { return 16; } - if ( - element instanceof ThreadAndSessionIds || - element instanceof Array - ) { + if (element instanceof ThreadAndSessionIds || element instanceof Array) { return 16; } @@ -1498,7 +899,7 @@ class CallStackDelegate implements IListVirtualDelegate { if (element instanceof StackFrame) { return StackFramesRenderer.ID; } - if (typeof element === "string") { + if (typeof element === 'string') { return ErrorsRenderer.ID; } if (element instanceof ThreadAndSessionIds) { @@ -1515,93 +916,51 @@ function stoppedText(stoppedDetails: IRawStoppedDetails): string { } function stoppedDescription(stoppedDetails: IRawStoppedDetails): string { - return ( - stoppedDetails.description || - (stoppedDetails.reason - ? localize( - { - key: "pausedOn", - comment: ["indicates reason for program being paused"], - }, - "Paused on {0}", - stoppedDetails.reason, - ) - : localize("paused", "Paused")) - ); + return stoppedDetails.description || + (stoppedDetails.reason ? localize({ key: 'pausedOn', comment: ['indicates reason for program being paused'] }, "Paused on {0}", stoppedDetails.reason) : localize('paused', "Paused")); } function isDebugModel(obj: any): obj is IDebugModel { - return typeof obj.getSessions === "function"; + return typeof obj.getSessions === 'function'; } function isDebugSession(obj: any): obj is IDebugSession { - return obj && typeof obj.getAllThreads === "function"; + return obj && typeof obj.getAllThreads === 'function'; } -class CallStackDataSource - implements IAsyncDataSource -{ +class CallStackDataSource implements IAsyncDataSource { deemphasizedStackFramesToShow: IStackFrame[] = []; - constructor(private debugService: IDebugService) {} + constructor(private debugService: IDebugService) { } hasChildren(element: IDebugModel | CallStackItem): boolean { if (isDebugSession(element)) { const threads = element.getAllThreads(); - - return ( - threads.length > 1 || - (threads.length === 1 && threads[0].stopped) || - !!this.debugService - .getModel() - .getSessions() - .find((s) => s.parentSession === element) - ); + return (threads.length > 1) || (threads.length === 1 && threads[0].stopped) || !!(this.debugService.getModel().getSessions().find(s => s.parentSession === element)); } - return ( - isDebugModel(element) || - (element instanceof Thread && element.stopped) - ); + return isDebugModel(element) || (element instanceof Thread && element.stopped); } - async getChildren( - element: IDebugModel | CallStackItem, - ): Promise { + async getChildren(element: IDebugModel | CallStackItem): Promise { if (isDebugModel(element)) { const sessions = element.getSessions(); - if (sessions.length === 0) { return Promise.resolve([]); } - if ( - sessions.length > 1 || - this.debugService.getViewModel().isMultiSessionView() - ) { - return Promise.resolve( - sessions.filter((s) => !s.parentSession), - ); + if (sessions.length > 1 || this.debugService.getViewModel().isMultiSessionView()) { + return Promise.resolve(sessions.filter(s => !s.parentSession)); } const threads = sessions[0].getAllThreads(); // Only show the threads in the call stack if there is more than 1 thread. - return threads.length === 1 - ? this.getThreadChildren(threads[0]) - : Promise.resolve(threads); + return threads.length === 1 ? this.getThreadChildren(threads[0]) : Promise.resolve(threads); } else if (isDebugSession(element)) { - const childSessions = this.debugService - .getModel() - .getSessions() - .filter((s) => s.parentSession === element); - + const childSessions = this.debugService.getModel().getSessions().filter(s => s.parentSession === element); const threads: CallStackItem[] = element.getAllThreads(); - if (threads.length === 1) { // Do not show thread when there is only one to be compact. - const children = await this.getThreadChildren( - threads[0], - ); - + const children = await this.getThreadChildren(threads[0]); return children.concat(childSessions); } @@ -1612,43 +971,26 @@ class CallStackDataSource } private getThreadChildren(thread: Thread): Promise { - return this.getThreadCallstack(thread).then((children) => { + return this.getThreadCallstack(thread).then(children => { // Check if some stack frames should be hidden under a parent element since they are deemphasized const result: CallStackItem[] = []; children.forEach((child, index) => { - if ( - child instanceof StackFrame && - child.source && - isFrameDeemphasized(child) - ) { + if (child instanceof StackFrame && child.source && isFrameDeemphasized(child)) { // Check if the user clicked to show the deemphasized source - if ( - this.deemphasizedStackFramesToShow.indexOf(child) === -1 - ) { + if (this.deemphasizedStackFramesToShow.indexOf(child) === -1) { if (result.length) { const last = result[result.length - 1]; - if (last instanceof Array) { // Collect all the stackframes that will be "collapsed" last.push(child); - return; } } - const nextChild = - index < children.length - 1 - ? children[index + 1] - : undefined; - - if ( - nextChild instanceof StackFrame && - nextChild.source && - isFrameDeemphasized(nextChild) - ) { + const nextChild = index < children.length - 1 ? children[index + 1] : undefined; + if (nextChild instanceof StackFrame && nextChild.source && isFrameDeemphasized(nextChild)) { // Start collecting stackframes that will be "collapsed" result.push([child]); - return; } } @@ -1661,122 +1003,62 @@ class CallStackDataSource }); } - private async getThreadCallstack( - thread: Thread, - ): Promise> { + private async getThreadCallstack(thread: Thread): Promise> { let callStack: any[] = thread.getCallStack(); - if (!callStack || !callStack.length) { await thread.fetchCallStack(); callStack = thread.getCallStack(); } - if ( - callStack.length === 1 && - thread.session.capabilities.supportsDelayedStackTraceLoading && - thread.stoppedDetails && - thread.stoppedDetails.totalFrames && - thread.stoppedDetails.totalFrames > 1 - ) { + if (callStack.length === 1 && thread.session.capabilities.supportsDelayedStackTraceLoading && thread.stoppedDetails && thread.stoppedDetails.totalFrames && thread.stoppedDetails.totalFrames > 1) { // To reduce flashing of the call stack view simply append the stale call stack // once we have the correct data the tree will refresh and we will no longer display it. callStack = callStack.concat(thread.getStaleCallStack().slice(1)); } if (thread.stoppedDetails && thread.stoppedDetails.framesErrorMessage) { - callStack = callStack.concat([ - thread.stoppedDetails.framesErrorMessage, - ]); + callStack = callStack.concat([thread.stoppedDetails.framesErrorMessage]); } if (!thread.reachedEndOfCallStack && thread.stoppedDetails) { - callStack = callStack.concat([ - new ThreadAndSessionIds( - thread.session.getId(), - thread.threadId, - ), - ]); + callStack = callStack.concat([new ThreadAndSessionIds(thread.session.getId(), thread.threadId)]); } return callStack; } } -class CallStackAccessibilityProvider - implements IListAccessibilityProvider -{ +class CallStackAccessibilityProvider implements IListAccessibilityProvider { + getWidgetAriaLabel(): string { - return localize( - { - comment: ["Debug is a noun in this context, not a verb."], - key: "callStackAriaLabel", - }, - "Debug Call Stack", - ); + return localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'callStackAriaLabel' }, "Debug Call Stack"); } getWidgetRole(): AriaRole { // Use treegrid as a role since each element can have additional actions inside #146210 - return "treegrid"; + return 'treegrid'; } getRole(_element: CallStackItem): AriaRole | undefined { - return "row"; + return 'row'; } getAriaLabel(element: CallStackItem): string { if (element instanceof Thread) { - return localize( - { - key: "threadAriaLabel", - comment: [ - 'Placeholders stand for the thread name and the thread state.For example "Thread 1" and "Stopped', - ], - }, - "Thread {0} {1}", - element.name, - element.stateLabel, - ); + return localize({ key: 'threadAriaLabel', comment: ['Placeholders stand for the thread name and the thread state.For example "Thread 1" and "Stopped'] }, "Thread {0} {1}", element.name, element.stateLabel); } if (element instanceof StackFrame) { - return localize( - "stackFrameAriaLabel", - "Stack Frame {0}, line {1}, {2}", - element.name, - element.range.startLineNumber, - getSpecificSourceName(element), - ); + return localize('stackFrameAriaLabel', "Stack Frame {0}, line {1}, {2}", element.name, element.range.startLineNumber, getSpecificSourceName(element)); } if (isDebugSession(element)) { - const thread = element.getAllThreads().find((t) => t.stopped); - - const state = thread - ? thread.stateLabel - : localize( - { key: "running", comment: ["indicates state"] }, - "Running", - ); - - return localize( - { - key: "sessionLabel", - comment: [ - 'Placeholders stand for the session name and the session state. For example "Launch Program" and "Running"', - ], - }, - "Session {0} {1}", - element.getLabel(), - state, - ); + const thread = element.getAllThreads().find(t => t.stopped); + const state = thread ? thread.stateLabel : localize({ key: 'running', comment: ['indicates state'] }, "Running"); + return localize({ key: 'sessionLabel', comment: ['Placeholders stand for the session name and the session state. For example "Launch Program" and "Running"'] }, "Session {0} {1}", element.getLabel(), state); } - if (typeof element === "string") { + if (typeof element === 'string') { return element; } if (element instanceof Array) { - return localize( - "showMoreStackFrames", - "Show {0} More Stack Frames", - element.length, - ); + return localize('showMoreStackFrames', "Show {0} More Stack Frames", element.length); } // element instanceof ThreadAndSessionIds @@ -1784,10 +1066,9 @@ class CallStackAccessibilityProvider } } -class CallStackCompressionDelegate - implements ITreeCompressionDelegate -{ - constructor(private readonly debugService: IDebugService) {} +class CallStackCompressionDelegate implements ITreeCompressionDelegate { + + constructor(private readonly debugService: IDebugService) { } isIncompressible(stat: CallStackItem): boolean { if (isDebugSession(stat)) { @@ -1795,8 +1076,7 @@ class CallStackCompressionDelegate return false; } const sessions = this.debugService.getModel().getSessions(); - - if (sessions.some((s) => s.parentSession === stat && s.compact)) { + if (sessions.some(s => s.parentSession === stat && s.compact)) { return false; } @@ -1807,125 +1087,44 @@ class CallStackCompressionDelegate } } -registerAction2( - class Collapse extends ViewAction { - constructor() { - super({ - id: "callStack.collapse", - viewId: CALLSTACK_VIEW_ID, - title: localize("collapse", "Collapse All"), - f1: false, - icon: Codicon.collapseAll, - precondition: CONTEXT_DEBUG_STATE.isEqualTo( - getStateLabel(State.Stopped), - ), - menu: { - id: MenuId.ViewTitle, - order: 10, - group: "navigation", - when: ContextKeyExpr.equals("view", CALLSTACK_VIEW_ID), - }, - }); - } +registerAction2(class Collapse extends ViewAction { + constructor() { + super({ + id: 'callStack.collapse', + viewId: CALLSTACK_VIEW_ID, + title: localize('collapse', "Collapse All"), + f1: false, + icon: Codicon.collapseAll, + precondition: CONTEXT_DEBUG_STATE.isEqualTo(getStateLabel(State.Stopped)), + menu: { + id: MenuId.ViewTitle, + order: 10, + group: 'navigation', + when: ContextKeyExpr.equals('view', CALLSTACK_VIEW_ID) + } + }); + } - runInView(_accessor: ServicesAccessor, view: CallStackView) { - view.collapseAll(); - } - }, -); - -function registerCallStackInlineMenuItem( - id: string, - title: string | ICommandActionTitle, - icon: Icon, - when: ContextKeyExpression, - order: number, - precondition?: ContextKeyExpression, -): void { + runInView(_accessor: ServicesAccessor, view: CallStackView) { + view.collapseAll(); + } +}); + +function registerCallStackInlineMenuItem(id: string, title: string | ICommandActionTitle, icon: Icon, when: ContextKeyExpression, order: number, precondition?: ContextKeyExpression): void { MenuRegistry.appendMenuItem(MenuId.DebugCallStackContext, { - group: "inline", + group: 'inline', order, when, - command: { id, title, icon, precondition }, + command: { id, title, icon, precondition } }); } -const threadOrSessionWithOneThread = ContextKeyExpr.or( - CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo("thread"), - ContextKeyExpr.and( - CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo("session"), - CONTEXT_CALLSTACK_SESSION_HAS_ONE_THREAD, - ), -)!; -registerCallStackInlineMenuItem( - PAUSE_ID, - PAUSE_LABEL, - icons.debugPause, - ContextKeyExpr.and( - threadOrSessionWithOneThread, - CONTEXT_CALLSTACK_ITEM_STOPPED.toNegated(), - )!, - 10, - CONTEXT_FOCUSED_SESSION_IS_NO_DEBUG.toNegated(), -); -registerCallStackInlineMenuItem( - CONTINUE_ID, - CONTINUE_LABEL, - icons.debugContinue, - ContextKeyExpr.and( - threadOrSessionWithOneThread, - CONTEXT_CALLSTACK_ITEM_STOPPED, - )!, - 10, -); -registerCallStackInlineMenuItem( - STEP_OVER_ID, - STEP_OVER_LABEL, - icons.debugStepOver, - threadOrSessionWithOneThread, - 20, - CONTEXT_CALLSTACK_ITEM_STOPPED, -); -registerCallStackInlineMenuItem( - STEP_INTO_ID, - STEP_INTO_LABEL, - icons.debugStepInto, - threadOrSessionWithOneThread, - 30, - CONTEXT_CALLSTACK_ITEM_STOPPED, -); -registerCallStackInlineMenuItem( - STEP_OUT_ID, - STEP_OUT_LABEL, - icons.debugStepOut, - threadOrSessionWithOneThread, - 40, - CONTEXT_CALLSTACK_ITEM_STOPPED, -); -registerCallStackInlineMenuItem( - RESTART_SESSION_ID, - RESTART_LABEL, - icons.debugRestart, - CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo("session"), - 50, -); -registerCallStackInlineMenuItem( - STOP_ID, - STOP_LABEL, - icons.debugStop, - ContextKeyExpr.and( - CONTEXT_CALLSTACK_SESSION_IS_ATTACH.toNegated(), - CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo("session"), - )!, - 60, -); -registerCallStackInlineMenuItem( - DISCONNECT_ID, - DISCONNECT_LABEL, - icons.debugDisconnect, - ContextKeyExpr.and( - CONTEXT_CALLSTACK_SESSION_IS_ATTACH, - CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo("session"), - )!, - 60, -); +const threadOrSessionWithOneThread = ContextKeyExpr.or(CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('thread'), ContextKeyExpr.and(CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('session'), CONTEXT_CALLSTACK_SESSION_HAS_ONE_THREAD))!; +registerCallStackInlineMenuItem(PAUSE_ID, PAUSE_LABEL, icons.debugPause, ContextKeyExpr.and(threadOrSessionWithOneThread, CONTEXT_CALLSTACK_ITEM_STOPPED.toNegated())!, 10, CONTEXT_FOCUSED_SESSION_IS_NO_DEBUG.toNegated()); +registerCallStackInlineMenuItem(CONTINUE_ID, CONTINUE_LABEL, icons.debugContinue, ContextKeyExpr.and(threadOrSessionWithOneThread, CONTEXT_CALLSTACK_ITEM_STOPPED)!, 10); +registerCallStackInlineMenuItem(STEP_OVER_ID, STEP_OVER_LABEL, icons.debugStepOver, threadOrSessionWithOneThread, 20, CONTEXT_CALLSTACK_ITEM_STOPPED); +registerCallStackInlineMenuItem(STEP_INTO_ID, STEP_INTO_LABEL, icons.debugStepInto, threadOrSessionWithOneThread, 30, CONTEXT_CALLSTACK_ITEM_STOPPED); +registerCallStackInlineMenuItem(STEP_OUT_ID, STEP_OUT_LABEL, icons.debugStepOut, threadOrSessionWithOneThread, 40, CONTEXT_CALLSTACK_ITEM_STOPPED); +registerCallStackInlineMenuItem(RESTART_SESSION_ID, RESTART_LABEL, icons.debugRestart, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('session'), 50); +registerCallStackInlineMenuItem(STOP_ID, STOP_LABEL, icons.debugStop, ContextKeyExpr.and(CONTEXT_CALLSTACK_SESSION_IS_ATTACH.toNegated(), CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('session'))!, 60); +registerCallStackInlineMenuItem(DISCONNECT_ID, DISCONNECT_LABEL, icons.debugDisconnect, ContextKeyExpr.and(CONTEXT_CALLSTACK_SESSION_IS_ATTACH, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('session'))!, 60); diff --git a/Source/vs/workbench/contrib/debug/browser/debugHover.ts b/Source/vs/workbench/contrib/debug/browser/debugHover.ts index b8cf445d52cbb..ddd5e8bdbc685 100644 --- a/Source/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/Source/vs/workbench/contrib/debug/browser/debugHover.ts @@ -2,125 +2,86 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as dom from "../../../../base/browser/dom.js"; -import { IKeyboardEvent } from "../../../../base/browser/keyboardEvent.js"; -import { IMouseEvent } from "../../../../base/browser/mouseEvent.js"; -import { IListVirtualDelegate } from "../../../../base/browser/ui/list/list.js"; -import { IListAccessibilityProvider } from "../../../../base/browser/ui/list/listWidget.js"; -import { DomScrollableElement } from "../../../../base/browser/ui/scrollbar/scrollableElement.js"; -import { AsyncDataTree } from "../../../../base/browser/ui/tree/asyncDataTree.js"; -import { ITreeContextMenuEvent } from "../../../../base/browser/ui/tree/tree.js"; -import { coalesce } from "../../../../base/common/arrays.js"; -import { - CancellationToken, - CancellationTokenSource, -} from "../../../../base/common/cancellation.js"; -import { KeyCode } from "../../../../base/common/keyCodes.js"; -import * as lifecycle from "../../../../base/common/lifecycle.js"; -import { clamp } from "../../../../base/common/numbers.js"; -import { isMacintosh } from "../../../../base/common/platform.js"; -import { ScrollbarVisibility } from "../../../../base/common/scrollable.js"; -import { - ContentWidgetPositionPreference, - ICodeEditor, - IContentWidget, - IContentWidgetPosition, -} from "../../../../editor/browser/editorBrowser.js"; -import { - ConfigurationChangedEvent, - EditorOption, -} from "../../../../editor/common/config/editorOptions.js"; -import { IDimension } from "../../../../editor/common/core/dimension.js"; -import { Position } from "../../../../editor/common/core/position.js"; -import { Range } from "../../../../editor/common/core/range.js"; -import { ModelDecorationOptions } from "../../../../editor/common/model/textModel.js"; -import { ILanguageFeaturesService } from "../../../../editor/common/services/languageFeatures.js"; -import * as nls from "../../../../nls.js"; -import { - IMenuService, - MenuId, -} from "../../../../platform/actions/common/actions.js"; -import { IContextKeyService } from "../../../../platform/contextkey/common/contextkey.js"; -import { IContextMenuService } from "../../../../platform/contextview/browser/contextView.js"; -import { IInstantiationService } from "../../../../platform/instantiation/common/instantiation.js"; -import { WorkbenchAsyncDataTree } from "../../../../platform/list/browser/listService.js"; -import { ILogService } from "../../../../platform/log/common/log.js"; -import { - asCssVariable, - editorHoverBackground, - editorHoverBorder, - editorHoverForeground, -} from "../../../../platform/theme/common/colorRegistry.js"; -import { - IDebugService, - IDebugSession, - IExpression, - IExpressionContainer, - IStackFrame, -} from "../common/debug.js"; -import { - Expression, - Variable, - VisualizedExpression, -} from "../common/debugModel.js"; -import { getEvaluatableExpressionAtPosition } from "../common/debugUtils.js"; -import { AbstractExpressionDataSource } from "./baseDebugView.js"; -import { DebugExpressionRenderer } from "./debugExpressionRenderer.js"; -import { - openContextMenuForVariableTreeElement, - VariablesRenderer, - VisualizedVariableRenderer, -} from "./variablesView.js"; + +import * as dom from '../../../../base/browser/dom.js'; +import { IKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; +import { IMouseEvent } from '../../../../base/browser/mouseEvent.js'; +import { IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js'; +import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js'; +import { DomScrollableElement } from '../../../../base/browser/ui/scrollbar/scrollableElement.js'; +import { AsyncDataTree } from '../../../../base/browser/ui/tree/asyncDataTree.js'; +import { ITreeContextMenuEvent } from '../../../../base/browser/ui/tree/tree.js'; +import { coalesce } from '../../../../base/common/arrays.js'; +import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; +import { KeyCode } from '../../../../base/common/keyCodes.js'; +import * as lifecycle from '../../../../base/common/lifecycle.js'; +import { clamp } from '../../../../base/common/numbers.js'; +import { isMacintosh } from '../../../../base/common/platform.js'; +import { ScrollbarVisibility } from '../../../../base/common/scrollable.js'; +import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from '../../../../editor/browser/editorBrowser.js'; +import { ConfigurationChangedEvent, EditorOption } from '../../../../editor/common/config/editorOptions.js'; +import { IDimension } from '../../../../editor/common/core/dimension.js'; +import { Position } from '../../../../editor/common/core/position.js'; +import { Range } from '../../../../editor/common/core/range.js'; +import { ModelDecorationOptions } from '../../../../editor/common/model/textModel.js'; +import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js'; +import * as nls from '../../../../nls.js'; +import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; +import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { WorkbenchAsyncDataTree } from '../../../../platform/list/browser/listService.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; +import { asCssVariable, editorHoverBackground, editorHoverBorder, editorHoverForeground } from '../../../../platform/theme/common/colorRegistry.js'; +import { IDebugService, IDebugSession, IExpression, IExpressionContainer, IStackFrame } from '../common/debug.js'; +import { Expression, Variable, VisualizedExpression } from '../common/debugModel.js'; +import { getEvaluatableExpressionAtPosition } from '../common/debugUtils.js'; +import { AbstractExpressionDataSource } from './baseDebugView.js'; +import { DebugExpressionRenderer } from './debugExpressionRenderer.js'; +import { VariablesRenderer, VisualizedVariableRenderer, openContextMenuForVariableTreeElement } from './variablesView.js'; const $ = dom.$; + export const enum ShowDebugHoverResult { NOT_CHANGED, NOT_AVAILABLE, CANCELLED, } -async function doFindExpression( - container: IExpressionContainer, - namesToFind: string[], -): Promise { + +async function doFindExpression(container: IExpressionContainer, namesToFind: string[]): Promise { if (!container) { return null; } + const children = await container.getChildren(); // look for our variable in the list. First find the parents of the hovered variable if there are any. - const filtered = children.filter((v) => namesToFind[0] === v.name); - + const filtered = children.filter(v => namesToFind[0] === v.name); if (filtered.length !== 1) { return null; } + if (namesToFind.length === 1) { return filtered[0]; } else { return doFindExpression(filtered[0], namesToFind.slice(1)); } } -export async function findExpressionInStackFrame( - stackFrame: IStackFrame, - namesToFind: string[], -): Promise { - const scopes = await stackFrame.getScopes(); - const nonExpensive = scopes.filter((s) => !s.expensive); +export async function findExpressionInStackFrame(stackFrame: IStackFrame, namesToFind: string[]): Promise { + const scopes = await stackFrame.getScopes(); + const nonExpensive = scopes.filter(s => !s.expensive); + const expressions = coalesce(await Promise.all(nonExpensive.map(scope => doFindExpression(scope, namesToFind)))); - const expressions = coalesce( - await Promise.all( - nonExpensive.map((scope) => doFindExpression(scope, namesToFind)), - ), - ); // only show if all expressions found have the same value - return expressions.length > 0 && - expressions.every((e) => e.value === expressions[0].value) - ? expressions[0] - : undefined; + return expressions.length > 0 && expressions.every(e => e.value === expressions[0].value) ? expressions[0] : undefined; } + export class DebugHoverWidget implements IContentWidget { - static readonly ID = "debug.hoverWidget"; + + static readonly ID = 'debug.hoverWidget'; // editor.IContentWidget.allowEditorOverflow readonly allowEditorOverflow = true; + // todo@connor4312: move more properties that are only valid while a hover // is happening into `_isVisible` private _isVisible?: { @@ -132,8 +93,7 @@ export class DebugHoverWidget implements IContentWidget { private tree!: AsyncDataTree; private showAtPosition: Position | null; private positionPreference: ContentWidgetPositionPreference[]; - private readonly highlightDecorations = - this.editor.createDecorationsCollection(); + private readonly highlightDecorations = this.editor.createDecorationsCollection(); private complexValueContainer!: HTMLElement; private complexValueTitle!: HTMLElement; private valueContainer!: HTMLElement; @@ -142,205 +102,136 @@ export class DebugHoverWidget implements IContentWidget { private scrollbar!: DomScrollableElement; private debugHoverComputer: DebugHoverComputer; private expressionRenderer: DebugExpressionRenderer; + private expressionToRender: IExpression | undefined; private isUpdatingTree = false; + public get isShowingComplexValue() { return this.complexValueContainer?.hidden === false; } + constructor( private editor: ICodeEditor, - @IDebugService - private readonly debugService: IDebugService, - @IInstantiationService - private readonly instantiationService: IInstantiationService, - @IMenuService - private readonly menuService: IMenuService, - @IContextKeyService - private readonly contextKeyService: IContextKeyService, - @IContextMenuService - private readonly contextMenuService: IContextMenuService, + @IDebugService private readonly debugService: IDebugService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IMenuService private readonly menuService: IMenuService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IContextMenuService private readonly contextMenuService: IContextMenuService, ) { this.toDispose = []; + this.showAtPosition = null; - this.positionPreference = [ - ContentWidgetPositionPreference.ABOVE, - ContentWidgetPositionPreference.BELOW, - ]; - this.debugHoverComputer = this.instantiationService.createInstance( - DebugHoverComputer, - this.editor, - ); - this.expressionRenderer = this.instantiationService.createInstance( - DebugExpressionRenderer, - ); + this.positionPreference = [ContentWidgetPositionPreference.ABOVE, ContentWidgetPositionPreference.BELOW]; + this.debugHoverComputer = this.instantiationService.createInstance(DebugHoverComputer, this.editor); + this.expressionRenderer = this.instantiationService.createInstance(DebugExpressionRenderer); } + private create(): void { - this.domNode = $(".debug-hover-widget"); - this.complexValueContainer = dom.append( - this.domNode, - $(".complex-value"), - ); - this.complexValueTitle = dom.append( - this.complexValueContainer, - $(".title"), - ); - this.treeContainer = dom.append( - this.complexValueContainer, - $(".debug-hover-tree"), - ); - this.treeContainer.setAttribute("role", "tree"); - - const tip = dom.append(this.complexValueContainer, $(".tip")); - tip.textContent = nls.localize( - { - key: "quickTip", - comment: [ - '"switch to editor language hover" means to show the programming language hover widget instead of the debug hover', - ], - }, - "Hold {0} key to switch to editor language hover", - isMacintosh ? "Option" : "Alt", - ); - - const dataSource = - this.instantiationService.createInstance(DebugHoverDataSource); - this.tree = >( - this.instantiationService.createInstance( - WorkbenchAsyncDataTree, - "DebugHover", - this.treeContainer, - new DebugHoverDelegate(), - [ - this.instantiationService.createInstance( - VariablesRenderer, - this.expressionRenderer, - ), - this.instantiationService.createInstance( - VisualizedVariableRenderer, - this.expressionRenderer, - ), - ], - dataSource, - { - accessibilityProvider: - new DebugHoverAccessibilityProvider(), - mouseSupport: false, - horizontalScrolling: true, - useShadows: false, - keyboardNavigationLabelProvider: { - getKeyboardNavigationLabel: (e: IExpression) => e.name, - }, - overrideStyles: { - listBackground: editorHoverBackground, - }, - }, - ) - ); - this.toDispose.push( - VisualizedVariableRenderer.rendererOnVisualizationRange( - this.debugService.getViewModel(), - this.tree, - ), - ); - this.valueContainer = $(".value"); - this.valueContainer.tabIndex = 0; - this.valueContainer.setAttribute("role", "tooltip"); - this.scrollbar = new DomScrollableElement(this.valueContainer, { - horizontal: ScrollbarVisibility.Hidden, + this.domNode = $('.debug-hover-widget'); + this.complexValueContainer = dom.append(this.domNode, $('.complex-value')); + this.complexValueTitle = dom.append(this.complexValueContainer, $('.title')); + this.treeContainer = dom.append(this.complexValueContainer, $('.debug-hover-tree')); + this.treeContainer.setAttribute('role', 'tree'); + const tip = dom.append(this.complexValueContainer, $('.tip')); + tip.textContent = nls.localize({ key: 'quickTip', comment: ['"switch to editor language hover" means to show the programming language hover widget instead of the debug hover'] }, 'Hold {0} key to switch to editor language hover', isMacintosh ? 'Option' : 'Alt'); + const dataSource = this.instantiationService.createInstance(DebugHoverDataSource); + this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'DebugHover', this.treeContainer, new DebugHoverDelegate(), [ + this.instantiationService.createInstance(VariablesRenderer, this.expressionRenderer), + this.instantiationService.createInstance(VisualizedVariableRenderer, this.expressionRenderer), + ], + dataSource, { + accessibilityProvider: new DebugHoverAccessibilityProvider(), + mouseSupport: false, + horizontalScrolling: true, + useShadows: false, + keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IExpression) => e.name }, + overrideStyles: { + listBackground: editorHoverBackground + } }); + + this.toDispose.push(VisualizedVariableRenderer.rendererOnVisualizationRange(this.debugService.getViewModel(), this.tree)); + + this.valueContainer = $('.value'); + this.valueContainer.tabIndex = 0; + this.valueContainer.setAttribute('role', 'tooltip'); + this.scrollbar = new DomScrollableElement(this.valueContainer, { horizontal: ScrollbarVisibility.Hidden }); this.domNode.appendChild(this.scrollbar.getDomNode()); this.toDispose.push(this.scrollbar); + this.editor.applyFontInfo(this.domNode); - this.domNode.style.backgroundColor = asCssVariable( - editorHoverBackground, - ); + this.domNode.style.backgroundColor = asCssVariable(editorHoverBackground); this.domNode.style.border = `1px solid ${asCssVariable(editorHoverBorder)}`; this.domNode.style.color = asCssVariable(editorHoverForeground); - this.toDispose.push( - this.tree.onContextMenu(async (e) => await this.onContextMenu(e)), - ); - this.toDispose.push( - this.tree.onDidChangeContentHeight(() => { - if (!this.isUpdatingTree) { - // Don't do a layout in the middle of the async setInput - this.layoutTreeAndContainer(); - } - }), - ); - this.toDispose.push( - this.tree.onDidChangeContentWidth(() => { - if (!this.isUpdatingTree) { - // Don't do a layout in the middle of the async setInput - this.layoutTreeAndContainer(); - } - }), - ); + + this.toDispose.push(this.tree.onContextMenu(async e => await this.onContextMenu(e))); + + this.toDispose.push(this.tree.onDidChangeContentHeight(() => { + if (!this.isUpdatingTree) { + // Don't do a layout in the middle of the async setInput + this.layoutTreeAndContainer(); + } + })); + this.toDispose.push(this.tree.onDidChangeContentWidth(() => { + if (!this.isUpdatingTree) { + // Don't do a layout in the middle of the async setInput + this.layoutTreeAndContainer(); + } + })); + this.registerListeners(); this.editor.addContentWidget(this); } - private async onContextMenu( - e: ITreeContextMenuEvent, - ): Promise { - const variable = e.element; + private async onContextMenu(e: ITreeContextMenuEvent): Promise { + const variable = e.element; if (!(variable instanceof Variable) || !variable.value) { return; } - return openContextMenuForVariableTreeElement( - this.contextKeyService, - this.menuService, - this.contextMenuService, - MenuId.DebugHoverContext, - e, - ); + + return openContextMenuForVariableTreeElement(this.contextKeyService, this.menuService, this.contextMenuService, MenuId.DebugHoverContext, e); } + private registerListeners(): void { - this.toDispose.push( - dom.addStandardDisposableListener( - this.domNode, - "keydown", - (e: IKeyboardEvent) => { - if (e.equals(KeyCode.Escape)) { - this.hide(); - } - }, - ), - ); - this.toDispose.push( - this.editor.onDidChangeConfiguration( - (e: ConfigurationChangedEvent) => { - if (e.hasChanged(EditorOption.fontInfo)) { - this.editor.applyFontInfo(this.domNode); - } - }, - ), - ); - this.toDispose.push( - this.debugService - .getViewModel() - .onDidEvaluateLazyExpression(async (e) => { - if (e instanceof Variable && this.tree.hasNode(e)) { - await this.tree.updateChildren(e, false, true); - await this.tree.expand(e); - } - }), - ); + this.toDispose.push(dom.addStandardDisposableListener(this.domNode, 'keydown', (e: IKeyboardEvent) => { + if (e.equals(KeyCode.Escape)) { + this.hide(); + } + })); + this.toDispose.push(this.editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { + if (e.hasChanged(EditorOption.fontInfo)) { + this.editor.applyFontInfo(this.domNode); + } + })); + + this.toDispose.push(this.debugService.getViewModel().onDidEvaluateLazyExpression(async e => { + if (e instanceof Variable && this.tree.hasNode(e)) { + await this.tree.updateChildren(e, false, true); + await this.tree.expand(e); + } + })); } + isHovered(): boolean { - return !!this.domNode?.matches(":hover"); + return !!this.domNode?.matches(':hover'); } + isVisible(): boolean { return !!this._isVisible; } + willBeVisible(): boolean { return !!this.showCancellationSource; } + getId(): string { return DebugHoverWidget.ID; } + getDomNode(): HTMLElement { return this.domNode; } + /** * Gets whether the given coordinates are in the safe triangle formed from * the position at which the hover was initiated. @@ -348,140 +239,92 @@ export class DebugHoverWidget implements IContentWidget { isInSafeTriangle(x: number, y: number) { return this._isVisible && !!this.safeTriangle?.contains(x, y); } - async showAt( - position: Position, - focus: boolean, - mouseEvent?: IMouseEvent, - ): Promise { - this.showCancellationSource?.dispose(true); - - const cancellationSource = (this.showCancellationSource = - new CancellationTokenSource()); + async showAt(position: Position, focus: boolean, mouseEvent?: IMouseEvent): Promise { + this.showCancellationSource?.dispose(true); + const cancellationSource = this.showCancellationSource = new CancellationTokenSource(); const session = this.debugService.getViewModel().focusedSession; if (!session || !this.editor.hasModel()) { this.hide(); - return ShowDebugHoverResult.NOT_AVAILABLE; } - const result = await this.debugHoverComputer.compute( - position, - cancellationSource.token, - ); + const result = await this.debugHoverComputer.compute(position, cancellationSource.token); if (cancellationSource.token.isCancellationRequested) { this.hide(); - return ShowDebugHoverResult.CANCELLED; } + if (!result.range) { this.hide(); - return ShowDebugHoverResult.NOT_AVAILABLE; } + if (this.isVisible() && !result.rangeChanged) { return ShowDebugHoverResult.NOT_CHANGED; } - const expression = await this.debugHoverComputer.evaluate(session); + const expression = await this.debugHoverComputer.evaluate(session); if (cancellationSource.token.isCancellationRequested) { this.hide(); - return ShowDebugHoverResult.CANCELLED; } - if ( - !expression || - (expression instanceof Expression && !expression.available) - ) { - this.hide(); + if (!expression || (expression instanceof Expression && !expression.available)) { + this.hide(); return ShowDebugHoverResult.NOT_AVAILABLE; } - this.highlightDecorations.set([ - { - range: result.range, - options: DebugHoverWidget._HOVER_HIGHLIGHT_DECORATION_OPTIONS, - }, - ]); - - return this.doShow( - session, - result.range.getStartPosition(), - expression, - focus, - mouseEvent, - ); + + this.highlightDecorations.set([{ + range: result.range, + options: DebugHoverWidget._HOVER_HIGHLIGHT_DECORATION_OPTIONS + }]); + + return this.doShow(session, result.range.getStartPosition(), expression, focus, mouseEvent); } - private static readonly _HOVER_HIGHLIGHT_DECORATION_OPTIONS = - ModelDecorationOptions.register({ - description: "bdebug-hover-highlight", - className: "hoverHighlight", - }); - private async doShow( - session: IDebugSession | undefined, - position: Position, - expression: IExpression, - focus: boolean, - mouseEvent: IMouseEvent | undefined, - ): Promise { + + private static readonly _HOVER_HIGHLIGHT_DECORATION_OPTIONS = ModelDecorationOptions.register({ + description: 'bdebug-hover-highlight', + className: 'hoverHighlight' + }); + + private async doShow(session: IDebugSession | undefined, position: Position, expression: IExpression, focus: boolean, mouseEvent: IMouseEvent | undefined): Promise { if (!this.domNode) { this.create(); } - this.showAtPosition = position; + this.showAtPosition = position; const store = new lifecycle.DisposableStore(); this._isVisible = { store }; if (!expression.hasChildren) { this.complexValueContainer.hidden = true; this.valueContainer.hidden = false; - store.add( - this.expressionRenderer.renderValue( - this.valueContainer, - expression, - { - showChanged: false, - colorize: true, - hover: false, - session, - }, - ), - ); - this.valueContainer.title = ""; + store.add(this.expressionRenderer.renderValue(this.valueContainer, expression, { + showChanged: false, + colorize: true, + hover: false, + session, + })); + this.valueContainer.title = ''; this.editor.layoutContentWidget(this); - this.safeTriangle = - mouseEvent && - new dom.SafeTriangle( - mouseEvent.posx, - mouseEvent.posy, - this.domNode, - ); + this.safeTriangle = mouseEvent && new dom.SafeTriangle(mouseEvent.posx, mouseEvent.posy, this.domNode); this.scrollbar.scanDomNode(); - if (focus) { this.editor.render(); this.valueContainer.focus(); } + return undefined; } + this.valueContainer.hidden = true; + this.expressionToRender = expression; - store.add( - this.expressionRenderer.renderValue( - this.complexValueTitle, - expression, - { hover: false, session }, - ), - ); + store.add(this.expressionRenderer.renderValue(this.complexValueTitle, expression, { hover: false, session })); this.editor.layoutContentWidget(this); - this.safeTriangle = - mouseEvent && - new dom.SafeTriangle( - mouseEvent.posx, - mouseEvent.posy, - this.domNode, - ); + this.safeTriangle = mouseEvent && new dom.SafeTriangle(mouseEvent.posx, mouseEvent.posy, this.domNode); this.tree.scrollTop = 0; this.tree.scrollLeft = 0; this.complexValueContainer.hidden = false; @@ -491,131 +334,116 @@ export class DebugHoverWidget implements IContentWidget { this.tree.domFocus(); } } + private layoutTreeAndContainer(): void { this.layoutTree(); this.editor.layoutContentWidget(this); } + private layoutTree(): void { const scrollBarHeight = 10; - let maxHeightToAvoidCursorOverlay = Infinity; - if (this.showAtPosition) { const editorTop = this.editor.getDomNode()?.offsetTop || 0; - const containerTop = this.treeContainer.offsetTop + editorTop; - - const hoveredCharTop = - this.editor.getTopForLineNumber( - this.showAtPosition.lineNumber, - true, - ) - this.editor.getScrollTop(); - + const hoveredCharTop = this.editor.getTopForLineNumber(this.showAtPosition.lineNumber, true) - this.editor.getScrollTop(); if (containerTop < hoveredCharTop) { maxHeightToAvoidCursorOverlay = hoveredCharTop + editorTop - 22; // 22 is monaco top padding https://github.com/microsoft/vscode/blob/a1df2d7319382d42f66ad7f411af01e4cc49c80a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts#L364 } } - const treeHeight = Math.min( - Math.max(266, this.editor.getLayoutInfo().height * 0.55), - this.tree.contentHeight + scrollBarHeight, - maxHeightToAvoidCursorOverlay, - ); + const treeHeight = Math.min(Math.max(266, this.editor.getLayoutInfo().height * 0.55), this.tree.contentHeight + scrollBarHeight, maxHeightToAvoidCursorOverlay); const realTreeWidth = this.tree.contentWidth; - const treeWidth = clamp(realTreeWidth, 400, 550); this.tree.layout(treeHeight, treeWidth); this.treeContainer.style.height = `${treeHeight}px`; this.scrollbar.scanDomNode(); } + beforeRender(): IDimension | null { // beforeRender will be called each time the hover size changes, and the content widget is layed out again. if (this.expressionToRender) { const expression = this.expressionToRender; this.expressionToRender = undefined; + // Do this in beforeRender once the content widget is no longer display=none so that its elements' sizes will be measured correctly. this.isUpdatingTree = true; this.tree.setInput(expression).finally(() => { this.isUpdatingTree = false; }); } + return null; } + afterRender(positionPreference: ContentWidgetPositionPreference | null) { if (positionPreference) { // Remember where the editor placed you to keep position stable #109226 this.positionPreference = [positionPreference]; } } + + hide(): void { if (this.showCancellationSource) { this.showCancellationSource.dispose(true); this.showCancellationSource = undefined; } + if (!this._isVisible) { return; } + if (dom.isAncestorOfActiveElement(this.domNode)) { this.editor.focus(); } this._isVisible.store.dispose(); this._isVisible = undefined; + this.highlightDecorations.clear(); this.editor.layoutContentWidget(this); - this.positionPreference = [ - ContentWidgetPositionPreference.ABOVE, - ContentWidgetPositionPreference.BELOW, - ]; + this.positionPreference = [ContentWidgetPositionPreference.ABOVE, ContentWidgetPositionPreference.BELOW]; } + getPosition(): IContentWidgetPosition | null { - return this._isVisible - ? { - position: this.showAtPosition, - preference: this.positionPreference, - } - : null; + return this._isVisible ? { + position: this.showAtPosition, + preference: this.positionPreference + } : null; } + dispose(): void { this.toDispose = lifecycle.dispose(this.toDispose); } } -class DebugHoverAccessibilityProvider - implements IListAccessibilityProvider -{ + +class DebugHoverAccessibilityProvider implements IListAccessibilityProvider { + getWidgetAriaLabel(): string { - return nls.localize("treeAriaLabel", "Debug Hover"); + return nls.localize('treeAriaLabel', "Debug Hover"); } + getAriaLabel(element: IExpression): string { - return nls.localize( - { - key: "variableAriaLabel", - comment: [ - "Do not translate placeholders. Placeholders are name and value of a variable.", - ], - }, - "{0}, value {1}, variables, debug", - element.name, - element.value, - ); + return nls.localize({ key: 'variableAriaLabel', comment: ['Do not translate placeholders. Placeholders are name and value of a variable.'] }, "{0}, value {1}, variables, debug", element.name, element.value); } } -class DebugHoverDataSource extends AbstractExpressionDataSource< - IExpression, - IExpression -> { + +class DebugHoverDataSource extends AbstractExpressionDataSource { + public override hasChildren(element: IExpression): boolean { return element.hasChildren; } - protected override doGetChildren( - element: IExpression, - ): Promise { + + protected override doGetChildren(element: IExpression): Promise { return element.getChildren(); } } + class DebugHoverDelegate implements IListVirtualDelegate { getHeight(element: IExpression): number { return 18; } + getTemplateId(element: IExpression): string { if (element instanceof VisualizedExpression) { return VisualizedVariableRenderer.ID; @@ -623,10 +451,12 @@ class DebugHoverDelegate implements IListVirtualDelegate { return VariablesRenderer.ID; } } + interface IDebugHoverComputeResult { rangeChanged: boolean; range?: Range; } + class DebugHoverComputer { private _current?: { range: Range; @@ -635,87 +465,56 @@ class DebugHoverComputer { constructor( private editor: ICodeEditor, - @IDebugService - private readonly debugService: IDebugService, - @ILanguageFeaturesService - private readonly languageFeaturesService: ILanguageFeaturesService, - @ILogService - private readonly logService: ILogService, - ) {} - public async compute( - position: Position, - token: CancellationToken, - ): Promise { - const session = this.debugService.getViewModel().focusedSession; + @IDebugService private readonly debugService: IDebugService, + @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, + @ILogService private readonly logService: ILogService, + ) { } + public async compute(position: Position, token: CancellationToken): Promise { + const session = this.debugService.getViewModel().focusedSession; if (!session || !this.editor.hasModel()) { return { rangeChanged: false }; } - const model = this.editor.getModel(); - - const result = await getEvaluatableExpressionAtPosition( - this.languageFeaturesService, - model, - position, - token, - ); + const model = this.editor.getModel(); + const result = await getEvaluatableExpressionAtPosition(this.languageFeaturesService, model, position, token); if (!result) { return { rangeChanged: false }; } - const { range, matchingExpression } = result; + const { range, matchingExpression } = result; const rangeChanged = !this._current?.range.equalsRange(range); - this._current = { - expression: matchingExpression, - range: Range.lift(range), - }; - + this._current = { expression: matchingExpression, range: Range.lift(range) }; return { rangeChanged, range: this._current.range }; } + async evaluate(session: IDebugSession): Promise { if (!this._current) { - this.logService.error("No expression to evaluate"); - + this.logService.error('No expression to evaluate'); return; } - const textModel = this.editor.getModel(); - const debugSource = - textModel && session.getSourceForUri(textModel?.uri); + const textModel = this.editor.getModel(); + const debugSource = textModel && session.getSourceForUri(textModel?.uri); if (session.capabilities.supportsEvaluateForHovers) { const expression = new Expression(this._current.expression); - await expression.evaluate( - session, - this.debugService.getViewModel().focusedStackFrame, - "hover", - undefined, - debugSource - ? { - line: this._current.range.startLineNumber, - column: this._current.range.startColumn, - source: debugSource.raw, - } - : undefined, - ); - + await expression.evaluate(session, this.debugService.getViewModel().focusedStackFrame, 'hover', undefined, debugSource ? { + line: this._current.range.startLineNumber, + column: this._current.range.startColumn, + source: debugSource.raw, + } : undefined); return expression; } else { - const focusedStackFrame = - this.debugService.getViewModel().focusedStackFrame; - + const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame; if (focusedStackFrame) { return await findExpressionInStackFrame( focusedStackFrame, - coalesce( - this._current.expression - .split(".") - .map((word) => word.trim()), - ), + coalesce(this._current.expression.split('.').map(word => word.trim())) ); } } + return undefined; } } diff --git a/Source/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/Source/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index a1f10c82af49c..3c6e37f7bc0f2 100644 --- a/Source/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/Source/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -2,164 +2,136 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IListVirtualDelegate } from "../../../../base/browser/ui/list/list.js"; -import { IListAccessibilityProvider } from "../../../../base/browser/ui/list/listWidget.js"; -import { TreeFindMode } from "../../../../base/browser/ui/tree/abstractTree.js"; -import type { ICompressedTreeNode } from "../../../../base/browser/ui/tree/compressedObjectTreeModel.js"; -import type { ICompressibleTreeRenderer } from "../../../../base/browser/ui/tree/objectTree.js"; -import { - ITreeElement, - ITreeFilter, - ITreeNode, - TreeFilterResult, - TreeVisibility, -} from "../../../../base/browser/ui/tree/tree.js"; -import { RunOnceScheduler } from "../../../../base/common/async.js"; -import { Codicon } from "../../../../base/common/codicons.js"; -import { createMatches, FuzzyScore } from "../../../../base/common/filters.js"; -import { - normalizeDriveLetter, - tildify, -} from "../../../../base/common/labels.js"; -import { dispose } from "../../../../base/common/lifecycle.js"; -import { isAbsolute, normalize, posix } from "../../../../base/common/path.js"; -import { isWindows } from "../../../../base/common/platform.js"; -import { ltrim } from "../../../../base/common/strings.js"; -import { URI } from "../../../../base/common/uri.js"; -import * as nls from "../../../../nls.js"; -import { - MenuId, - registerAction2, -} from "../../../../platform/actions/common/actions.js"; -import { IConfigurationService } from "../../../../platform/configuration/common/configuration.js"; -import { - ContextKeyExpr, - IContextKey, - IContextKeyService, -} from "../../../../platform/contextkey/common/contextkey.js"; -import { IContextMenuService } from "../../../../platform/contextview/browser/contextView.js"; -import { FileKind } from "../../../../platform/files/common/files.js"; -import { IHoverService } from "../../../../platform/hover/browser/hover.js"; -import { - IInstantiationService, - ServicesAccessor, -} from "../../../../platform/instantiation/common/instantiation.js"; -import { IKeybindingService } from "../../../../platform/keybinding/common/keybinding.js"; -import { ILabelService } from "../../../../platform/label/common/label.js"; -import { WorkbenchCompressibleObjectTree } from "../../../../platform/list/browser/listService.js"; -import { IOpenerService } from "../../../../platform/opener/common/opener.js"; -import { ITelemetryService } from "../../../../platform/telemetry/common/telemetry.js"; -import { IThemeService } from "../../../../platform/theme/common/themeService.js"; -import { - IWorkspaceContextService, - IWorkspaceFolder, -} from "../../../../platform/workspace/common/workspace.js"; -import { - IResourceLabel, - IResourceLabelOptions, - IResourceLabelProps, - ResourceLabels, -} from "../../../browser/labels.js"; -import { ViewAction, ViewPane } from "../../../browser/parts/views/viewPane.js"; -import { IViewletViewOptions } from "../../../browser/parts/views/viewsViewlet.js"; -import { IViewDescriptorService } from "../../../common/views.js"; -import { IEditorService } from "../../../services/editor/common/editorService.js"; -import { IPathService } from "../../../services/path/common/pathService.js"; -import { - CONTEXT_LOADED_SCRIPTS_ITEM_TYPE, - IDebugService, - IDebugSession, - LOADED_SCRIPTS_VIEW_ID, -} from "../common/debug.js"; -import { DebugContentProvider } from "../common/debugContentProvider.js"; -import { Source } from "../common/debugSource.js"; -import { renderViewTree } from "./baseDebugView.js"; + +import * as nls from '../../../../nls.js'; +import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js'; +import { normalize, isAbsolute, posix } from '../../../../base/common/path.js'; +import { ViewPane, ViewAction } from '../../../browser/parts/views/viewPane.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; +import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { renderViewTree } from './baseDebugView.js'; +import { IDebugSession, IDebugService, CONTEXT_LOADED_SCRIPTS_ITEM_TYPE, LOADED_SCRIPTS_VIEW_ID } from '../common/debug.js'; +import { Source } from '../common/debugSource.js'; +import { IWorkspaceContextService, IWorkspaceFolder } from '../../../../platform/workspace/common/workspace.js'; +import { IContextKey, IContextKeyService, ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; +import { normalizeDriveLetter, tildify } from '../../../../base/common/labels.js'; +import { isWindows } from '../../../../base/common/platform.js'; +import { URI } from '../../../../base/common/uri.js'; +import { ltrim } from '../../../../base/common/strings.js'; +import { RunOnceScheduler } from '../../../../base/common/async.js'; +import { ResourceLabels, IResourceLabelProps, IResourceLabelOptions, IResourceLabel } from '../../../browser/labels.js'; +import { FileKind } from '../../../../platform/files/common/files.js'; +import { IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js'; +import { ITreeNode, ITreeFilter, TreeVisibility, TreeFilterResult, ITreeElement } from '../../../../base/browser/ui/tree/tree.js'; +import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js'; +import { IEditorService } from '../../../services/editor/common/editorService.js'; +import { WorkbenchCompressibleObjectTree } from '../../../../platform/list/browser/listService.js'; +import { dispose } from '../../../../base/common/lifecycle.js'; +import { createMatches, FuzzyScore } from '../../../../base/common/filters.js'; +import { DebugContentProvider } from '../common/debugContentProvider.js'; +import { ILabelService } from '../../../../platform/label/common/label.js'; +import type { ICompressedTreeNode } from '../../../../base/browser/ui/tree/compressedObjectTreeModel.js'; +import type { ICompressibleTreeRenderer } from '../../../../base/browser/ui/tree/objectTree.js'; +import { registerAction2, MenuId } from '../../../../platform/actions/common/actions.js'; +import { Codicon } from '../../../../base/common/codicons.js'; + +import { IViewDescriptorService } from '../../../common/views.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { IPathService } from '../../../services/path/common/pathService.js'; +import { TreeFindMode } from '../../../../base/browser/ui/tree/abstractTree.js'; +import { IHoverService } from '../../../../platform/hover/browser/hover.js'; const NEW_STYLE_COMPRESS = true; + // RFC 2396, Appendix A: https://www.ietf.org/rfc/rfc2396.txt const URI_SCHEMA_PATTERN = /^[a-zA-Z][a-zA-Z0-9\+\-\.]+:/; + type LoadedScriptsItem = BaseTreeItem; + class BaseTreeItem { + private _showedMoreThanOne: boolean; private _children = new Map(); private _source: Source | undefined; - constructor( - private _parent: BaseTreeItem | undefined, - private _label: string, - public readonly isIncompressible = false, - ) { + constructor(private _parent: BaseTreeItem | undefined, private _label: string, public readonly isIncompressible = false) { this._showedMoreThanOne = false; } + updateLabel(label: string) { this._label = label; } + isLeaf(): boolean { return this._children.size === 0; } + getSession(): IDebugSession | undefined { if (this._parent) { return this._parent.getSession(); } return undefined; } + setSource(session: IDebugSession, source: Source): void { this._source = source; this._children.clear(); - if (source.raw && source.raw.sources) { for (const src of source.raw.sources) { if (src.name && src.path) { const s = new BaseTreeItem(this, src.name); this._children.set(src.path, s); - const ss = session.getSource(src); s.setSource(session, ss); } } } } - createIfNeeded( - key: string, - factory: (parent: BaseTreeItem, label: string) => T, - ): T { - let child = this._children.get(key); + createIfNeeded(key: string, factory: (parent: BaseTreeItem, label: string) => T): T { + let child = this._children.get(key); if (!child) { child = factory(this, key); this._children.set(key, child); } return child; } + getChild(key: string): BaseTreeItem | undefined { return this._children.get(key); } + remove(key: string): void { this._children.delete(key); } + removeFromParent(): void { if (this._parent) { this._parent.remove(this._label); - if (this._parent._children.size === 0) { this._parent.removeFromParent(); } } } + getTemplateId(): string { - return "id"; + return 'id'; } + // a dynamic ID based on the parent chain; required for reparenting (see #55448) getId(): string { const parent = this.getParent(); - - return parent - ? `${parent.getId()}/${this.getInternalId()}` - : this.getInternalId(); + return parent ? `${parent.getId()}/${this.getInternalId()}` : this.getInternalId(); } + getInternalId(): string { return this._label; } + // skips intermediate single-child nodes getParent(): BaseTreeItem | undefined { if (this._parent) { @@ -170,85 +142,81 @@ class BaseTreeItem { } return undefined; } + isSkipped(): boolean { if (this._parent) { if (this._parent.oneChild()) { - return true; // skipped if I'm the only child of my parents + return true; // skipped if I'm the only child of my parents } return false; } - return true; // roots are never skipped + return true; // roots are never skipped } + // skips intermediate single-child nodes hasChildren(): boolean { const child = this.oneChild(); - if (child) { return child.hasChildren(); } return this._children.size > 0; } + // skips intermediate single-child nodes getChildren(): BaseTreeItem[] { const child = this.oneChild(); - if (child) { return child.getChildren(); } const array: BaseTreeItem[] = []; - for (const child of this._children.values()) { array.push(child); } return array.sort((a, b) => this.compare(a, b)); } + // skips intermediate single-child nodes getLabel(separateRootFolder = true): string { const child = this.oneChild(); - if (child) { - const sep = - this instanceof RootFolderTreeItem && separateRootFolder - ? " • " - : posix.sep; - + const sep = (this instanceof RootFolderTreeItem && separateRootFolder) ? ' • ' : posix.sep; return `${this._label}${sep}${child.getLabel()}`; } return this._label; } + // skips intermediate single-child nodes getHoverLabel(): string | undefined { if (this._source && this._parent && this._parent._source) { return this._source.raw.path || this._source.raw.name; } const label = this.getLabel(false); - const parent = this.getParent(); - if (parent) { const hover = parent.getHoverLabel(); - if (hover) { return `${hover}/${label}`; } } return label; } + // skips intermediate single-child nodes getSource(): Source | undefined { const child = this.oneChild(); - if (child) { return child.getSource(); } return this._source; } + protected compare(a: BaseTreeItem, b: BaseTreeItem): number { if (a._label && b._label) { return a._label.localeCompare(b._label); } return 0; } + private oneChild(): BaseTreeItem | undefined { if (!this._source && !this._showedMoreThanOne && this.skipOneChild()) { if (this._children.size === 1) { @@ -261,142 +229,123 @@ class BaseTreeItem { } return undefined; } + private skipOneChild(): boolean { if (NEW_STYLE_COMPRESS) { // if the root node has only one Session, don't show the session return this instanceof RootTreeItem; } else { - return ( - !(this instanceof RootFolderTreeItem) && - !(this instanceof SessionTreeItem) - ); + return !(this instanceof RootFolderTreeItem) && !(this instanceof SessionTreeItem); } } } + class RootFolderTreeItem extends BaseTreeItem { - constructor( - parent: BaseTreeItem, - public folder: IWorkspaceFolder, - ) { + + constructor(parent: BaseTreeItem, public folder: IWorkspaceFolder) { super(parent, folder.name, true); } } + class RootTreeItem extends BaseTreeItem { - constructor( - private _pathService: IPathService, - private _contextService: IWorkspaceContextService, - private _labelService: ILabelService, - ) { - super(undefined, "Root"); + + constructor(private _pathService: IPathService, private _contextService: IWorkspaceContextService, private _labelService: ILabelService) { + super(undefined, 'Root'); } + add(session: IDebugSession): SessionTreeItem { - return this.createIfNeeded( - session.getId(), - () => - new SessionTreeItem( - this._labelService, - this, - session, - this._pathService, - this._contextService, - ), - ); + return this.createIfNeeded(session.getId(), () => new SessionTreeItem(this._labelService, this, session, this._pathService, this._contextService)); } + find(session: IDebugSession): SessionTreeItem { return this.getChild(session.getId()); } } + class SessionTreeItem extends BaseTreeItem { + private static readonly URL_REGEXP = /^(https?:\/\/[^/]+)(\/.*)$/; + private _session: IDebugSession; private _map = new Map(); private _labelService: ILabelService; - constructor( - labelService: ILabelService, - parent: BaseTreeItem, - session: IDebugSession, - private _pathService: IPathService, - private rootProvider: IWorkspaceContextService, - ) { + constructor(labelService: ILabelService, parent: BaseTreeItem, session: IDebugSession, private _pathService: IPathService, private rootProvider: IWorkspaceContextService) { super(parent, session.getLabel(), true); this._labelService = labelService; this._session = session; } + override getInternalId(): string { return this._session.getId(); } + override getSession(): IDebugSession { return this._session; } + override getHoverLabel(): string | undefined { return undefined; } + override hasChildren(): boolean { return true; } + protected override compare(a: BaseTreeItem, b: BaseTreeItem): number { const acat = this.category(a); - const bcat = this.category(b); - if (acat !== bcat) { return acat - bcat; } return super.compare(a, b); } + private category(item: BaseTreeItem): number { + // workspace scripts come at the beginning in "folder" order if (item instanceof RootFolderTreeItem) { return item.folder.index; } + // <...> come at the very end const l = item.getLabel(); - if (l && /^<.+>$/.test(l)) { return 1000; } + // everything else in between return 999; } + async addPath(source: Source): Promise { - let folder: IWorkspaceFolder | null; + let folder: IWorkspaceFolder | null; let url: string; let path = source.raw.path; - if (!path) { return; } + if (this._labelService && URI_SCHEMA_PATTERN.test(path)) { path = this._labelService.getUriLabel(URI.parse(path)); } - const match = SessionTreeItem.URL_REGEXP.exec(path); + const match = SessionTreeItem.URL_REGEXP.exec(path); if (match && match.length === 3) { url = match[1]; path = decodeURI(match[2]); } else { if (isAbsolute(path)) { const resource = URI.file(path); - // return early if we can resolve a relative path label from the root folder - folder = this.rootProvider - ? this.rootProvider.getWorkspaceFolder(resource) - : null; + // return early if we can resolve a relative path label from the root folder + folder = this.rootProvider ? this.rootProvider.getWorkspaceFolder(resource) : null; if (folder) { // strip off the root folder path - path = normalize( - ltrim( - resource.path.substring(folder.uri.path.length), - posix.sep, - ), - ); - - const hasMultipleRoots = - this.rootProvider.getWorkspace().folders.length > 1; - + path = normalize(ltrim(resource.path.substring(folder.uri.path.length), posix.sep)); + const hasMultipleRoots = this.rootProvider.getWorkspace().folders.length > 1; if (hasMultipleRoots) { path = posix.sep + path; } else { @@ -406,87 +355,69 @@ class SessionTreeItem extends BaseTreeItem { } else { // on unix try to tildify absolute paths path = normalize(path); - if (isWindows) { path = normalizeDriveLetter(path); } else { - path = tildify( - path, - (await this._pathService.userHome()).fsPath, - ); + path = tildify(path, (await this._pathService.userHome()).fsPath); } } } } + let leaf: BaseTreeItem = this; path.split(/[\/\\]/).forEach((segment, i) => { if (i === 0 && folder) { const f = folder; - leaf = leaf.createIfNeeded( - folder.name, - (parent) => new RootFolderTreeItem(parent, f), - ); + leaf = leaf.createIfNeeded(folder.name, parent => new RootFolderTreeItem(parent, f)); } else if (i === 0 && url) { - leaf = leaf.createIfNeeded( - url, - (parent) => new BaseTreeItem(parent, url), - ); + leaf = leaf.createIfNeeded(url, parent => new BaseTreeItem(parent, url)); } else { - leaf = leaf.createIfNeeded( - segment, - (parent) => new BaseTreeItem(parent, segment), - ); + leaf = leaf.createIfNeeded(segment, parent => new BaseTreeItem(parent, segment)); } }); - leaf.setSource(this._session, source); + leaf.setSource(this._session, source); if (source.raw.path) { this._map.set(source.raw.path, leaf); } } + removePath(source: Source): boolean { if (source.raw.path) { const leaf = this._map.get(source.raw.path); - if (leaf) { leaf.removeFromParent(); - return true; } } return false; } } + interface IViewState { readonly expanded: Set; } + /** * This maps a model item into a view model item. */ -function asTreeElement( - item: BaseTreeItem, - viewState?: IViewState, -): ITreeElement { +function asTreeElement(item: BaseTreeItem, viewState?: IViewState): ITreeElement { const children = item.getChildren(); - - const collapsed = viewState - ? !viewState.expanded.has(item.getId()) - : !(item instanceof SessionTreeItem); + const collapsed = viewState ? !viewState.expanded.has(item.getId()) : !(item instanceof SessionTreeItem); return { element: item, collapsed, collapsible: item.hasChildren(), - children: children.map((i) => asTreeElement(i, viewState)), + children: children.map(i => asTreeElement(i, viewState)) }; } + export class LoadedScriptsView extends ViewPane { + private treeContainer!: HTMLElement; private loadedScriptsItemType: IContextKey; - private tree!: WorkbenchCompressibleObjectTree< - LoadedScriptsItem, - FuzzyScore - >; + private tree!: WorkbenchCompressibleObjectTree; private treeLabels!: ResourceLabels; private changeScheduler!: RunOnceScheduler; private treeNeedsRefreshOnVisible = false; @@ -494,76 +425,44 @@ export class LoadedScriptsView extends ViewPane { constructor( options: IViewletViewOptions, - @IContextMenuService - contextMenuService: IContextMenuService, - @IKeybindingService - keybindingService: IKeybindingService, - @IInstantiationService - instantiationService: IInstantiationService, - @IViewDescriptorService - viewDescriptorService: IViewDescriptorService, - @IConfigurationService - configurationService: IConfigurationService, - @IEditorService - private readonly editorService: IEditorService, - @IContextKeyService - contextKeyService: IContextKeyService, - @IWorkspaceContextService - private readonly contextService: IWorkspaceContextService, - @IDebugService - private readonly debugService: IDebugService, - @ILabelService - private readonly labelService: ILabelService, - @IPathService - private readonly pathService: IPathService, - @IOpenerService - openerService: IOpenerService, - @IThemeService - themeService: IThemeService, - @ITelemetryService - telemetryService: ITelemetryService, - @IHoverService - hoverService: IHoverService, + @IContextMenuService contextMenuService: IContextMenuService, + @IKeybindingService keybindingService: IKeybindingService, + @IInstantiationService instantiationService: IInstantiationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IConfigurationService configurationService: IConfigurationService, + @IEditorService private readonly editorService: IEditorService, + @IContextKeyService contextKeyService: IContextKeyService, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IDebugService private readonly debugService: IDebugService, + @ILabelService private readonly labelService: ILabelService, + @IPathService private readonly pathService: IPathService, + @IOpenerService openerService: IOpenerService, + @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, + @IHoverService hoverService: IHoverService ) { - super( - options, - keybindingService, - contextMenuService, - configurationService, - contextKeyService, - viewDescriptorService, - instantiationService, - openerService, - themeService, - telemetryService, - hoverService, - ); - this.loadedScriptsItemType = - CONTEXT_LOADED_SCRIPTS_ITEM_TYPE.bindTo(contextKeyService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService); + this.loadedScriptsItemType = CONTEXT_LOADED_SCRIPTS_ITEM_TYPE.bindTo(contextKeyService); } + protected override renderBody(container: HTMLElement): void { super.renderBody(container); - this.element.classList.add("debug-pane"); - container.classList.add("debug-loaded-scripts"); - container.classList.add("show-file-icons"); + + this.element.classList.add('debug-pane'); + container.classList.add('debug-loaded-scripts'); + container.classList.add('show-file-icons'); + this.treeContainer = renderViewTree(container); + this.filter = new LoadedScriptsFilter(); - const root = new RootTreeItem( - this.pathService, - this.contextService, - this.labelService, - ); - this.treeLabels = this.instantiationService.createInstance( - ResourceLabels, - { onDidChangeVisibility: this.onDidChangeBodyVisibility }, - ); + const root = new RootTreeItem(this.pathService, this.contextService, this.labelService); + + this.treeLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); this._register(this.treeLabels); - this.tree = < - WorkbenchCompressibleObjectTree - >this.instantiationService.createInstance( - WorkbenchCompressibleObjectTree, - "LoadedScriptsView", + + this.tree = this.instantiationService.createInstance(WorkbenchCompressibleObjectTree, + 'LoadedScriptsView', this.treeContainer, new LoadedScriptsDelegate(), [new LoadedScriptsRenderer(this.treeLabels)], @@ -572,75 +471,52 @@ export class LoadedScriptsView extends ViewPane { collapseByDefault: true, hideTwistiesOfChildlessElements: true, identityProvider: { - getId: (element: LoadedScriptsItem) => element.getId(), + getId: (element: LoadedScriptsItem) => element.getId() }, keyboardNavigationLabelProvider: { - getKeyboardNavigationLabel: ( - element: LoadedScriptsItem, - ) => { + getKeyboardNavigationLabel: (element: LoadedScriptsItem) => { return element.getLabel(); }, - getCompressedNodeKeyboardNavigationLabel: ( - elements: LoadedScriptsItem[], - ) => { - return elements.map((e) => e.getLabel()).join("/"); - }, + getCompressedNodeKeyboardNavigationLabel: (elements: LoadedScriptsItem[]) => { + return elements.map(e => e.getLabel()).join('/'); + } }, filter: this.filter, accessibilityProvider: new LoadedSciptsAccessibilityProvider(), - overrideStyles: - this.getLocationBasedColors().listOverrideStyles, - }, + overrideStyles: this.getLocationBasedColors().listOverrideStyles + } ); - const updateView = (viewState?: IViewState) => - this.tree.setChildren( - null, - asTreeElement(root, viewState).children, - ); + const updateView = (viewState?: IViewState) => this.tree.setChildren(null, asTreeElement(root, viewState).children); + updateView(); + this.changeScheduler = new RunOnceScheduler(() => { this.treeNeedsRefreshOnVisible = false; - if (this.tree) { updateView(); } }, 300); this._register(this.changeScheduler); - this._register( - this.tree.onDidOpen((e) => { - if (e.element instanceof BaseTreeItem) { - const source = e.element.getSource(); - - if (source && source.available) { - const nullRange = { - startLineNumber: 0, - startColumn: 0, - endLineNumber: 0, - endColumn: 0, - }; - source.openInEditor( - this.editorService, - nullRange, - e.editorOptions.preserveFocus, - e.sideBySide, - e.editorOptions.pinned, - ); - } - } - }), - ); - this._register( - this.tree.onDidChangeFocus(() => { - const focus = this.tree.getFocus(); - if (focus instanceof SessionTreeItem) { - this.loadedScriptsItemType.set("session"); - } else { - this.loadedScriptsItemType.reset(); + this._register(this.tree.onDidOpen(e => { + if (e.element instanceof BaseTreeItem) { + const source = e.element.getSource(); + if (source && source.available) { + const nullRange = { startLineNumber: 0, startColumn: 0, endLineNumber: 0, endColumn: 0 }; + source.openInEditor(this.editorService, nullRange, e.editorOptions.preserveFocus, e.sideBySide, e.editorOptions.pinned); } - }), - ); + } + })); + + this._register(this.tree.onDidChangeFocus(() => { + const focus = this.tree.getFocus(); + if (focus instanceof SessionTreeItem) { + this.loadedScriptsItemType.set('session'); + } else { + this.loadedScriptsItemType.reset(); + } + })); const scheduleRefreshOnVisible = () => { if (this.isBodyVisible()) { @@ -653,9 +529,7 @@ export class LoadedScriptsView extends ViewPane { const addSourcePathsToSession = async (session: IDebugSession) => { if (session.capabilities.supportsLoadedSourcesRequest) { const sessionNode = root.add(session); - const paths = await session.getLoadedSources(); - for (const path of paths) { await sessionNode.addPath(path); } @@ -664,203 +538,174 @@ export class LoadedScriptsView extends ViewPane { }; const registerSessionListeners = (session: IDebugSession) => { - this._register( - session.onDidChangeName(async () => { - const sessionRoot = root.find(session); - - if (sessionRoot) { - sessionRoot.updateLabel(session.getLabel()); + this._register(session.onDidChangeName(async () => { + const sessionRoot = root.find(session); + if (sessionRoot) { + sessionRoot.updateLabel(session.getLabel()); + scheduleRefreshOnVisible(); + } + })); + this._register(session.onDidLoadedSource(async event => { + let sessionRoot: SessionTreeItem; + switch (event.reason) { + case 'new': + case 'changed': + sessionRoot = root.add(session); + await sessionRoot.addPath(event.source); scheduleRefreshOnVisible(); - } - }), - ); - this._register( - session.onDidLoadedSource(async (event) => { - let sessionRoot: SessionTreeItem; - - switch (event.reason) { - case "new": - case "changed": - sessionRoot = root.add(session); - await sessionRoot.addPath(event.source); + if (event.reason === 'changed') { + DebugContentProvider.refreshDebugContent(event.source.uri); + } + break; + case 'removed': + sessionRoot = root.find(session); + if (sessionRoot && sessionRoot.removePath(event.source)) { scheduleRefreshOnVisible(); - - if (event.reason === "changed") { - DebugContentProvider.refreshDebugContent( - event.source.uri, - ); - } - break; - - case "removed": - sessionRoot = root.find(session); - - if ( - sessionRoot && - sessionRoot.removePath(event.source) - ) { - scheduleRefreshOnVisible(); - } - break; - - default: - this.filter.setFilter(event.source.name); - this.tree.refilter(); - - break; - } - }), - ); + } + break; + default: + this.filter.setFilter(event.source.name); + this.tree.refilter(); + break; + } + })); }; - this._register( - this.debugService.onDidNewSession(registerSessionListeners), - ); - this.debugService - .getModel() - .getSessions() - .forEach(registerSessionListeners); - this._register( - this.debugService.onDidEndSession(({ session }) => { - root.remove(session.getId()); - this.changeScheduler.schedule(); - }), - ); + + this._register(this.debugService.onDidNewSession(registerSessionListeners)); + this.debugService.getModel().getSessions().forEach(registerSessionListeners); + + this._register(this.debugService.onDidEndSession(({ session }) => { + root.remove(session.getId()); + this.changeScheduler.schedule(); + })); + this.changeScheduler.schedule(0); - this._register( - this.onDidChangeBodyVisibility((visible) => { - if (visible && this.treeNeedsRefreshOnVisible) { - this.changeScheduler.schedule(); - } - }), - ); + + this._register(this.onDidChangeBodyVisibility(visible => { + if (visible && this.treeNeedsRefreshOnVisible) { + this.changeScheduler.schedule(); + } + })); + // feature: expand all nodes when filtering (not when finding) let viewState: IViewState | undefined; - this._register( - this.tree.onDidChangeFindPattern((pattern) => { - if (this.tree.findMode === TreeFindMode.Highlight) { - return; - } - if (!viewState && pattern) { - const expanded = new Set(); - - const visit = ( - node: ITreeNode, - ) => { - if (node.element && !node.collapsed) { - expanded.add(node.element.getId()); - } - for (const child of node.children) { - visit(child); - } - }; - visit(this.tree.getNode()); - viewState = { expanded }; - this.tree.expandAll(); - } else if (!pattern && viewState) { - this.tree.setFocus([]); - updateView(viewState); - viewState = undefined; - } - }), - ); + this._register(this.tree.onDidChangeFindPattern(pattern => { + if (this.tree.findMode === TreeFindMode.Highlight) { + return; + } + + if (!viewState && pattern) { + const expanded = new Set(); + const visit = (node: ITreeNode) => { + if (node.element && !node.collapsed) { + expanded.add(node.element.getId()); + } + + for (const child of node.children) { + visit(child); + } + }; + + visit(this.tree.getNode()); + viewState = { expanded }; + this.tree.expandAll(); + } else if (!pattern && viewState) { + this.tree.setFocus([]); + updateView(viewState); + viewState = undefined; + } + })); + // populate tree model with source paths from all debug sessions - this.debugService - .getModel() - .getSessions() - .forEach((session) => addSourcePathsToSession(session)); + this.debugService.getModel().getSessions().forEach(session => addSourcePathsToSession(session)); } + protected override layoutBody(height: number, width: number): void { super.layoutBody(height, width); this.tree.layout(height, width); } + collapseAll(): void { this.tree.collapseAll(); } + override dispose(): void { dispose(this.tree); dispose(this.treeLabels); - super.dispose(); } } + class LoadedScriptsDelegate implements IListVirtualDelegate { + getHeight(element: LoadedScriptsItem): number { return 22; } + getTemplateId(element: LoadedScriptsItem): string { return LoadedScriptsRenderer.ID; } } + interface ILoadedScriptsItemTemplateData { label: IResourceLabel; } -class LoadedScriptsRenderer - implements - ICompressibleTreeRenderer< - BaseTreeItem, - FuzzyScore, - ILoadedScriptsItemTemplateData - > -{ - static readonly ID = "lsrenderer"; - - constructor(private labels: ResourceLabels) {} + +class LoadedScriptsRenderer implements ICompressibleTreeRenderer { + + static readonly ID = 'lsrenderer'; + + constructor( + private labels: ResourceLabels + ) { + } + get templateId(): string { return LoadedScriptsRenderer.ID; } - renderTemplate(container: HTMLElement): ILoadedScriptsItemTemplateData { - const label = this.labels.create(container, { - supportHighlights: true, - }); + renderTemplate(container: HTMLElement): ILoadedScriptsItemTemplateData { + const label = this.labels.create(container, { supportHighlights: true }); return { label }; } - renderElement( - node: ITreeNode, - index: number, - data: ILoadedScriptsItemTemplateData, - ): void { - const element = node.element; + renderElement(node: ITreeNode, index: number, data: ILoadedScriptsItemTemplateData): void { + + const element = node.element; const label = element.getLabel(); + this.render(element, label, data, node.filterData); } - renderCompressedElements( - node: ITreeNode, FuzzyScore>, - index: number, - data: ILoadedScriptsItemTemplateData, - height: number | undefined, - ): void { + + renderCompressedElements(node: ITreeNode, FuzzyScore>, index: number, data: ILoadedScriptsItemTemplateData, height: number | undefined): void { + const element = node.element.elements[node.element.elements.length - 1]; + const labels = node.element.elements.map(e => e.getLabel()); - const labels = node.element.elements.map((e) => e.getLabel()); this.render(element, labels, data, node.filterData); } - private render( - element: BaseTreeItem, - labels: string | string[], - data: ILoadedScriptsItemTemplateData, - filterData: FuzzyScore | undefined, - ) { + + private render(element: BaseTreeItem, labels: string | string[], data: ILoadedScriptsItemTemplateData, filterData: FuzzyScore | undefined) { + const label: IResourceLabelProps = { - name: labels, + name: labels }; - const options: IResourceLabelOptions = { - title: element.getHoverLabel(), + title: element.getHoverLabel() }; if (element instanceof RootFolderTreeItem) { + options.fileKind = FileKind.ROOT_FOLDER; + } else if (element instanceof SessionTreeItem) { - options.title = nls.localize( - "loadedScriptsSession", - "Debug Session", - ); + + options.title = nls.localize('loadedScriptsSession', "Debug Session"); options.hideIcon = true; + } else if (element instanceof BaseTreeItem) { - const src = element.getSource(); + const src = element.getSource(); if (src && src.uri) { label.resource = src.uri; options.fileKind = FileKind.FILE; @@ -869,70 +714,55 @@ class LoadedScriptsRenderer } } options.matches = createMatches(filterData); + data.label.setResource(label, options); } + disposeTemplate(templateData: ILoadedScriptsItemTemplateData): void { templateData.label.dispose(); } } -class LoadedSciptsAccessibilityProvider - implements IListAccessibilityProvider -{ + +class LoadedSciptsAccessibilityProvider implements IListAccessibilityProvider { + getWidgetAriaLabel(): string { - return nls.localize( - { - comment: ["Debug is a noun in this context, not a verb."], - key: "loadedScriptsAriaLabel", - }, - "Debug Loaded Scripts", - ); + return nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'loadedScriptsAriaLabel' }, "Debug Loaded Scripts"); } + getAriaLabel(element: LoadedScriptsItem): string { + if (element instanceof RootFolderTreeItem) { - return nls.localize( - "loadedScriptsRootFolderAriaLabel", - "Workspace folder {0}, loaded script, debug", - element.getLabel(), - ); + return nls.localize('loadedScriptsRootFolderAriaLabel', "Workspace folder {0}, loaded script, debug", element.getLabel()); } + if (element instanceof SessionTreeItem) { - return nls.localize( - "loadedScriptsSessionAriaLabel", - "Session {0}, loaded script, debug", - element.getLabel(), - ); + return nls.localize('loadedScriptsSessionAriaLabel', "Session {0}, loaded script, debug", element.getLabel()); } + if (element.hasChildren()) { - return nls.localize( - "loadedScriptsFolderAriaLabel", - "Folder {0}, loaded script, debug", - element.getLabel(), - ); + return nls.localize('loadedScriptsFolderAriaLabel', "Folder {0}, loaded script, debug", element.getLabel()); } else { - return nls.localize( - "loadedScriptsSourceAriaLabel", - "{0}, loaded script, debug", - element.getLabel(), - ); + return nls.localize('loadedScriptsSourceAriaLabel', "{0}, loaded script, debug", element.getLabel()); } } } + class LoadedScriptsFilter implements ITreeFilter { + private filterText: string | undefined; setFilter(filterText: string) { this.filterText = filterText; } - filter( - element: BaseTreeItem, - parentVisibility: TreeVisibility, - ): TreeFilterResult { + + filter(element: BaseTreeItem, parentVisibility: TreeVisibility): TreeFilterResult { + if (!this.filterText) { return TreeVisibility.Visible; } + if (element.isLeaf()) { const name = element.getLabel(); - if (name.indexOf(this.filterText) >= 0) { return TreeVisibility.Visible; } @@ -941,25 +771,24 @@ class LoadedScriptsFilter implements ITreeFilter { return TreeVisibility.Recurse; } } -registerAction2( - class Collapse extends ViewAction { - constructor() { - super({ - id: "loadedScripts.collapse", - viewId: LOADED_SCRIPTS_VIEW_ID, - title: nls.localize("collapse", "Collapse All"), - f1: false, - icon: Codicon.collapseAll, - menu: { - id: MenuId.ViewTitle, - order: 30, - group: "navigation", - when: ContextKeyExpr.equals("view", LOADED_SCRIPTS_VIEW_ID), - }, - }); - } - runInView(_accessor: ServicesAccessor, view: LoadedScriptsView) { - view.collapseAll(); - } - }, -); +registerAction2(class Collapse extends ViewAction { + constructor() { + super({ + id: 'loadedScripts.collapse', + viewId: LOADED_SCRIPTS_VIEW_ID, + title: nls.localize('collapse', "Collapse All"), + f1: false, + icon: Codicon.collapseAll, + menu: { + id: MenuId.ViewTitle, + order: 30, + group: 'navigation', + when: ContextKeyExpr.equals('view', LOADED_SCRIPTS_VIEW_ID) + } + }); + } + + runInView(_accessor: ServicesAccessor, view: LoadedScriptsView) { + view.collapseAll(); + } +}); diff --git a/Source/vs/workbench/contrib/debug/browser/repl.ts b/Source/vs/workbench/contrib/debug/browser/repl.ts index 6dc9c155aa40f..d09086152d66d 100644 --- a/Source/vs/workbench/contrib/debug/browser/repl.ts +++ b/Source/vs/workbench/contrib/debug/browser/repl.ts @@ -3,168 +3,86 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as dom from "../../../../base/browser/dom.js"; -import * as domStylesheetsJs from "../../../../base/browser/domStylesheets.js"; -import { IHistoryNavigationWidget } from "../../../../base/browser/history.js"; -import { IActionViewItem } from "../../../../base/browser/ui/actionbar/actionbar.js"; -import * as aria from "../../../../base/browser/ui/aria/aria.js"; -import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from "../../../../base/browser/ui/mouseCursor/mouseCursor.js"; -import { - IAsyncDataSource, - ITreeContextMenuEvent, - ITreeNode, -} from "../../../../base/browser/ui/tree/tree.js"; -import { IAction } from "../../../../base/common/actions.js"; -import { RunOnceScheduler } from "../../../../base/common/async.js"; -import { CancellationToken } from "../../../../base/common/cancellation.js"; -import { Codicon } from "../../../../base/common/codicons.js"; -import { memoize } from "../../../../base/common/decorators.js"; -import { Emitter } from "../../../../base/common/event.js"; -import { FuzzyScore } from "../../../../base/common/filters.js"; -import { HistoryNavigator } from "../../../../base/common/history.js"; -import { KeyCode, KeyMod } from "../../../../base/common/keyCodes.js"; -import { Disposable, IDisposable } from "../../../../base/common/lifecycle.js"; -import { removeAnsiEscapeCodes } from "../../../../base/common/strings.js"; -import { ThemeIcon } from "../../../../base/common/themables.js"; -import { URI as uri } from "../../../../base/common/uri.js"; -import { - ICodeEditor, - isCodeEditor, -} from "../../../../editor/browser/editorBrowser.js"; -import { - EditorAction, - registerEditorAction, -} from "../../../../editor/browser/editorExtensions.js"; -import { ICodeEditorService } from "../../../../editor/browser/services/codeEditorService.js"; -import { CodeEditorWidget } from "../../../../editor/browser/widget/codeEditor/codeEditorWidget.js"; -import { - EDITOR_FONT_DEFAULTS, - EditorOption, -} from "../../../../editor/common/config/editorOptions.js"; -import { Position } from "../../../../editor/common/core/position.js"; -import { Range } from "../../../../editor/common/core/range.js"; -import { IDecorationOptions } from "../../../../editor/common/editorCommon.js"; -import { EditorContextKeys } from "../../../../editor/common/editorContextKeys.js"; -import { - CompletionContext, - CompletionItem, - CompletionItemInsertTextRule, - CompletionItemKind, - CompletionItemKinds, - CompletionList, -} from "../../../../editor/common/languages.js"; -import { ITextModel } from "../../../../editor/common/model.js"; -import { ILanguageFeaturesService } from "../../../../editor/common/services/languageFeatures.js"; -import { IModelService } from "../../../../editor/common/services/model.js"; -import { ITextResourcePropertiesService } from "../../../../editor/common/services/textResourceConfiguration.js"; -import { SuggestController } from "../../../../editor/contrib/suggest/browser/suggestController.js"; -import { localize, localize2 } from "../../../../nls.js"; -import { - AccessibilitySignal, - IAccessibilitySignalService, -} from "../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js"; -import { getFlatContextMenuActions } from "../../../../platform/actions/browser/menuEntryActionViewItem.js"; -import { - Action2, - IMenu, - IMenuService, - MenuId, - registerAction2, -} from "../../../../platform/actions/common/actions.js"; -import { IClipboardService } from "../../../../platform/clipboard/common/clipboardService.js"; -import { IConfigurationService } from "../../../../platform/configuration/common/configuration.js"; -import { - ContextKeyExpr, - IContextKey, - IContextKeyService, -} from "../../../../platform/contextkey/common/contextkey.js"; -import { IContextMenuService } from "../../../../platform/contextview/browser/contextView.js"; -import { registerAndCreateHistoryNavigationContext } from "../../../../platform/history/browser/contextScopedHistoryWidget.js"; -import { IHoverService } from "../../../../platform/hover/browser/hover.js"; -import { - IInstantiationService, - ServicesAccessor, -} from "../../../../platform/instantiation/common/instantiation.js"; -import { ServiceCollection } from "../../../../platform/instantiation/common/serviceCollection.js"; -import { IKeybindingService } from "../../../../platform/keybinding/common/keybinding.js"; -import { KeybindingWeight } from "../../../../platform/keybinding/common/keybindingsRegistry.js"; -import { WorkbenchAsyncDataTree } from "../../../../platform/list/browser/listService.js"; -import { ILogService } from "../../../../platform/log/common/log.js"; -import { IOpenerService } from "../../../../platform/opener/common/opener.js"; -import { - IStorageService, - StorageScope, - StorageTarget, -} from "../../../../platform/storage/common/storage.js"; -import { ITelemetryService } from "../../../../platform/telemetry/common/telemetry.js"; -import { - editorForeground, - resolveColorValue, -} from "../../../../platform/theme/common/colorRegistry.js"; -import { IThemeService } from "../../../../platform/theme/common/themeService.js"; -import { registerNavigableContainer } from "../../../browser/actions/widgetNavigationCommands.js"; -import { - FilterViewPane, - IViewPaneOptions, - ViewAction, -} from "../../../browser/parts/views/viewPane.js"; -import { IViewDescriptorService } from "../../../common/views.js"; -import { IEditorService } from "../../../services/editor/common/editorService.js"; -import { IViewsService } from "../../../services/views/common/viewsService.js"; -import { AccessibilityVerbositySettingId } from "../../accessibility/browser/accessibilityConfiguration.js"; -import { AccessibilityCommandId } from "../../accessibility/common/accessibilityCommands.js"; -import { - getSimpleCodeEditorWidgetOptions, - getSimpleEditorOptions, -} from "../../codeEditor/browser/simpleEditorOptions.js"; -import { - CONTEXT_DEBUG_STATE, - CONTEXT_IN_DEBUG_REPL, - CONTEXT_MULTI_SESSION_REPL, - DEBUG_SCHEME, - getStateLabel, - IDebugConfiguration, - IDebugService, - IDebugSession, - IReplConfiguration, - IReplElement, - IReplOptions, - REPL_VIEW_ID, - State, -} from "../common/debug.js"; -import { Variable } from "../common/debugModel.js"; -import { ReplEvaluationResult, ReplGroup } from "../common/replModel.js"; -import { FocusSessionActionViewItem } from "./debugActionViewItems.js"; -import { DebugExpressionRenderer } from "./debugExpressionRenderer.js"; -import { - debugConsoleClearAll, - debugConsoleEvaluationPrompt, -} from "./debugIcons.js"; - -import "./media/repl.css"; - -import { ReplFilter } from "./replFilter.js"; -import { - ReplAccessibilityProvider, - ReplDataSource, - ReplDelegate, - ReplEvaluationInputsRenderer, - ReplEvaluationResultsRenderer, - ReplGroupRenderer, - ReplOutputElementRenderer, - ReplRawObjectsRenderer, - ReplVariablesRenderer, -} from "./replViewer.js"; +import * as dom from '../../../../base/browser/dom.js'; +import * as domStylesheetsJs from '../../../../base/browser/domStylesheets.js'; +import { IHistoryNavigationWidget } from '../../../../base/browser/history.js'; +import { IActionViewItem } from '../../../../base/browser/ui/actionbar/actionbar.js'; +import * as aria from '../../../../base/browser/ui/aria/aria.js'; +import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from '../../../../base/browser/ui/mouseCursor/mouseCursor.js'; +import { IAsyncDataSource, ITreeContextMenuEvent, ITreeNode } from '../../../../base/browser/ui/tree/tree.js'; +import { IAction } from '../../../../base/common/actions.js'; +import { RunOnceScheduler } from '../../../../base/common/async.js'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { memoize } from '../../../../base/common/decorators.js'; +import { Emitter } from '../../../../base/common/event.js'; +import { FuzzyScore } from '../../../../base/common/filters.js'; +import { HistoryNavigator } from '../../../../base/common/history.js'; +import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; +import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js'; +import { removeAnsiEscapeCodes } from '../../../../base/common/strings.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { URI as uri } from '../../../../base/common/uri.js'; +import { ICodeEditor, isCodeEditor } from '../../../../editor/browser/editorBrowser.js'; +import { EditorAction, registerEditorAction } from '../../../../editor/browser/editorExtensions.js'; +import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; +import { CodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/codeEditorWidget.js'; +import { EDITOR_FONT_DEFAULTS, EditorOption } from '../../../../editor/common/config/editorOptions.js'; +import { Position } from '../../../../editor/common/core/position.js'; +import { Range } from '../../../../editor/common/core/range.js'; +import { IDecorationOptions } from '../../../../editor/common/editorCommon.js'; +import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; +import { CompletionContext, CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemKinds, CompletionList } from '../../../../editor/common/languages.js'; +import { ITextModel } from '../../../../editor/common/model.js'; +import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js'; +import { IModelService } from '../../../../editor/common/services/model.js'; +import { ITextResourcePropertiesService } from '../../../../editor/common/services/textResourceConfiguration.js'; +import { SuggestController } from '../../../../editor/contrib/suggest/browser/suggestController.js'; +import { localize, localize2 } from '../../../../nls.js'; +import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js'; +import { getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { Action2, IMenu, IMenuService, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { ContextKeyExpr, IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; +import { registerAndCreateHistoryNavigationContext } from '../../../../platform/history/browser/contextScopedHistoryWidget.js'; +import { IHoverService } from '../../../../platform/hover/browser/hover.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; +import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { WorkbenchAsyncDataTree } from '../../../../platform/list/browser/listService.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { editorForeground, resolveColorValue } from '../../../../platform/theme/common/colorRegistry.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { registerNavigableContainer } from '../../../browser/actions/widgetNavigationCommands.js'; +import { FilterViewPane, IViewPaneOptions, ViewAction } from '../../../browser/parts/views/viewPane.js'; +import { IViewDescriptorService } from '../../../common/views.js'; +import { IEditorService } from '../../../services/editor/common/editorService.js'; +import { IViewsService } from '../../../services/views/common/viewsService.js'; +import { AccessibilityVerbositySettingId } from '../../accessibility/browser/accessibilityConfiguration.js'; +import { AccessibilityCommandId } from '../../accessibility/common/accessibilityCommands.js'; +import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions } from '../../codeEditor/browser/simpleEditorOptions.js'; +import { CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_REPL, CONTEXT_MULTI_SESSION_REPL, DEBUG_SCHEME, IDebugConfiguration, IDebugService, IDebugSession, IReplConfiguration, IReplElement, IReplOptions, REPL_VIEW_ID, State, getStateLabel } from '../common/debug.js'; +import { Variable } from '../common/debugModel.js'; +import { ReplEvaluationResult, ReplGroup } from '../common/replModel.js'; +import { FocusSessionActionViewItem } from './debugActionViewItems.js'; +import { DebugExpressionRenderer } from './debugExpressionRenderer.js'; +import { debugConsoleClearAll, debugConsoleEvaluationPrompt } from './debugIcons.js'; +import './media/repl.css'; +import { ReplFilter } from './replFilter.js'; +import { ReplAccessibilityProvider, ReplDataSource, ReplDelegate, ReplEvaluationInputsRenderer, ReplEvaluationResultsRenderer, ReplGroupRenderer, ReplOutputElementRenderer, ReplRawObjectsRenderer, ReplVariablesRenderer } from './replViewer.js'; const $ = dom.$; -const HISTORY_STORAGE_KEY = "debug.repl.history"; - -const FILTER_HISTORY_STORAGE_KEY = "debug.repl.filterHistory"; - -const FILTER_VALUE_STORAGE_KEY = "debug.repl.filterValue"; - -const DECORATION_KEY = "replinputdecoration"; +const HISTORY_STORAGE_KEY = 'debug.repl.history'; +const FILTER_HISTORY_STORAGE_KEY = 'debug.repl.filterHistory'; +const FILTER_VALUE_STORAGE_KEY = 'debug.repl.filterValue'; +const DECORATION_KEY = 'replinputdecoration'; function revealLastElement(tree: WorkbenchAsyncDataTree) { tree.scrollTop = tree.scrollHeight - tree.renderHeight; @@ -172,7 +90,6 @@ function revealLastElement(tree: WorkbenchAsyncDataTree) { } const sessionsToIgnore = new Set(); - const identityProvider = { getId: (element: IReplElement) => element.getId() }; export class Repl extends FilterViewPane implements IHistoryNavigationWidget { @@ -182,11 +99,7 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { private static readonly URI = uri.parse(`${DEBUG_SCHEME}:replinput`); private history: HistoryNavigator; - private tree?: WorkbenchAsyncDataTree< - IDebugSession, - IReplElement, - FuzzyScore - >; + private tree?: WorkbenchAsyncDataTree; private replOptions: ReplOptions; private previousTreeScrollHeight: number = 0; private replDelegate!: ReplDelegate; @@ -207,9 +120,7 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { private filter: ReplFilter; private multiSessionRepl: IContextKey; private menu: IMenu; - private replDataSource: - | IAsyncDataSource - | undefined; + private replDataSource: IAsyncDataSource | undefined; private findIsOpen: boolean = false; constructor( @@ -223,411 +134,184 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { @ICodeEditorService codeEditorService: ICodeEditorService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IContextMenuService contextMenuService: IContextMenuService, - @IConfigurationService - protected override readonly configurationService: IConfigurationService, - @ITextResourcePropertiesService - private readonly textResourcePropertiesService: ITextResourcePropertiesService, + @IConfigurationService protected override readonly configurationService: IConfigurationService, + @ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService, @IEditorService private readonly editorService: IEditorService, - @IKeybindingService - protected override readonly keybindingService: IKeybindingService, + @IKeybindingService protected override readonly keybindingService: IKeybindingService, @IOpenerService openerService: IOpenerService, @ITelemetryService telemetryService: ITelemetryService, @IHoverService hoverService: IHoverService, @IMenuService menuService: IMenuService, - @ILanguageFeaturesService - private readonly languageFeaturesService: ILanguageFeaturesService, + @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, @ILogService private readonly logService: ILogService, ) { - const filterText = storageService.get( - FILTER_VALUE_STORAGE_KEY, - StorageScope.WORKSPACE, - "", - ); + const filterText = storageService.get(FILTER_VALUE_STORAGE_KEY, StorageScope.WORKSPACE, ''); + super({ + ...options, + filterOptions: { + placeholder: localize({ key: 'workbench.debug.filter.placeholder', comment: ['Text in the brackets after e.g. is not localizable'] }, "Filter (e.g. text, !exclude, \\escape)"), + text: filterText, + history: JSON.parse(storageService.get(FILTER_HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')) as string[], + } + }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService); - super( - { - ...options, - filterOptions: { - placeholder: localize( - { - key: "workbench.debug.filter.placeholder", - comment: [ - "Text in the brackets after e.g. is not localizable", - ], - }, - "Filter (e.g. text, !exclude, \\escape)", - ), - text: filterText, - history: JSON.parse( - storageService.get( - FILTER_HISTORY_STORAGE_KEY, - StorageScope.WORKSPACE, - "[]", - ), - ) as string[], - }, - }, - keybindingService, - contextMenuService, - configurationService, - contextKeyService, - viewDescriptorService, - instantiationService, - openerService, - themeService, - telemetryService, - hoverService, - ); - - this.menu = menuService.createMenu( - MenuId.DebugConsoleContext, - contextKeyService, - ); + this.menu = menuService.createMenu(MenuId.DebugConsoleContext, contextKeyService); this._register(this.menu); - this.history = new HistoryNavigator( - new Set( - JSON.parse( - this.storageService.get( - HISTORY_STORAGE_KEY, - StorageScope.WORKSPACE, - "[]", - ), - ), - ), - 100, - ); + this.history = new HistoryNavigator(new Set(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]'))), 100); this.filter = new ReplFilter(); this.filter.filterQuery = filterText; - this.multiSessionRepl = - CONTEXT_MULTI_SESSION_REPL.bindTo(contextKeyService); - this.replOptions = this._register( - this.instantiationService.createInstance( - ReplOptions, - this.id, - () => this.getLocationBasedColors().background, - ), - ); - this._register( - this.replOptions.onDidChange(() => this.onDidStyleChange()), - ); - - codeEditorService.registerDecorationType( - "repl-decoration", - DECORATION_KEY, - {}, - ); + this.multiSessionRepl = CONTEXT_MULTI_SESSION_REPL.bindTo(contextKeyService); + this.replOptions = this._register(this.instantiationService.createInstance(ReplOptions, this.id, () => this.getLocationBasedColors().background)); + this._register(this.replOptions.onDidChange(() => this.onDidStyleChange())); + + codeEditorService.registerDecorationType('repl-decoration', DECORATION_KEY, {}); this.multiSessionRepl.set(this.isMultiSessionView); this.registerListeners(); } private registerListeners(): void { if (this.debugService.getViewModel().focusedSession) { - this.onDidFocusSession( - this.debugService.getViewModel().focusedSession, - ); + this.onDidFocusSession(this.debugService.getViewModel().focusedSession); } - this._register( - this.debugService - .getViewModel() - .onDidFocusSession(async (session) => - this.onDidFocusSession(session), - ), - ); - this._register( - this.debugService - .getViewModel() - .onDidEvaluateLazyExpression(async (e) => { - if (e instanceof Variable && this.tree?.hasNode(e)) { - await this.tree.updateChildren(e, false, true); - await this.tree.expand(e); - } - }), - ); - this._register( - this.debugService.onWillNewSession(async (newSession) => { - // Need to listen to output events for sessions which are not yet fully initialised - const input = this.tree?.getInput(); - - if (!input || input.state === State.Inactive) { - await this.selectSession(newSession); - } - this.multiSessionRepl.set(this.isMultiSessionView); - }), - ); - this._register( - this.debugService.onDidEndSession(async () => { - // Update view, since orphaned sessions might now be separate - await Promise.resolve(); // allow other listeners to go first, so sessions can update parents - this.multiSessionRepl.set(this.isMultiSessionView); - }), - ); - this._register( - this.themeService.onDidColorThemeChange(() => { - this.refreshReplElements(false); - - if (this.isVisible()) { - this.updateInputDecoration(); - } - }), - ); - this._register( - this.onDidChangeBodyVisibility((visible) => { - if (visible) { - if (!this.model) { - this.model = - this.modelService.getModel(Repl.URI) || - this.modelService.createModel( - "", - null, - Repl.URI, - true, - ); - } - this.setMode(); - this.replInput.setModel(this.model); - this.updateInputDecoration(); - this.refreshReplElements(true); - - if (this.styleChangedWhenInvisible) { - this.styleChangedWhenInvisible = false; - this.tree?.updateChildren(undefined, true, false); - this.onDidStyleChange(); - } - } - }), - ); - this._register( - this.configurationService.onDidChangeConfiguration((e) => { - if ( - e.affectsConfiguration("debug.console.wordWrap") && - this.tree - ) { - this.tree.dispose(); - this.treeContainer.innerText = ""; - - dom.clearNode(this.treeContainer); - this.createReplTree(); - } - if ( - e.affectsConfiguration( - "debug.console.acceptSuggestionOnEnter", - ) - ) { - const config = - this.configurationService.getValue( - "debug", - ); - this.replInput.updateOptions({ - acceptSuggestionOnEnter: - config.console.acceptSuggestionOnEnter === "on" - ? "on" - : "off", - }); + this._register(this.debugService.getViewModel().onDidFocusSession(async session => this.onDidFocusSession(session))); + this._register(this.debugService.getViewModel().onDidEvaluateLazyExpression(async e => { + if (e instanceof Variable && this.tree?.hasNode(e)) { + await this.tree.updateChildren(e, false, true); + await this.tree.expand(e); + } + })); + this._register(this.debugService.onWillNewSession(async newSession => { + // Need to listen to output events for sessions which are not yet fully initialised + const input = this.tree?.getInput(); + if (!input || input.state === State.Inactive) { + await this.selectSession(newSession); + } + this.multiSessionRepl.set(this.isMultiSessionView); + })); + this._register(this.debugService.onDidEndSession(async () => { + // Update view, since orphaned sessions might now be separate + await Promise.resolve(); // allow other listeners to go first, so sessions can update parents + this.multiSessionRepl.set(this.isMultiSessionView); + })); + this._register(this.themeService.onDidColorThemeChange(() => { + this.refreshReplElements(false); + if (this.isVisible()) { + this.updateInputDecoration(); + } + })); + this._register(this.onDidChangeBodyVisibility(visible => { + if (visible) { + if (!this.model) { + this.model = this.modelService.getModel(Repl.URI) || this.modelService.createModel('', null, Repl.URI, true); } - }), - ); - - this._register( - this.editorService.onDidActiveEditorChange(() => { this.setMode(); - }), - ); + this.replInput.setModel(this.model); + this.updateInputDecoration(); + this.refreshReplElements(true); + if (this.styleChangedWhenInvisible) { + this.styleChangedWhenInvisible = false; + this.tree?.updateChildren(undefined, true, false); + this.onDidStyleChange(); + } + } + })); + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('debug.console.wordWrap') && this.tree) { + this.tree.dispose(); + this.treeContainer.innerText = ''; + dom.clearNode(this.treeContainer); + this.createReplTree(); + } + if (e.affectsConfiguration('debug.console.acceptSuggestionOnEnter')) { + const config = this.configurationService.getValue('debug'); + this.replInput.updateOptions({ + acceptSuggestionOnEnter: config.console.acceptSuggestionOnEnter === 'on' ? 'on' : 'off' + }); + } + })); - this._register( - this.filterWidget.onDidChangeFilterText(() => { - this.filter.filterQuery = this.filterWidget.getFilterText(); + this._register(this.editorService.onDidActiveEditorChange(() => { + this.setMode(); + })); - if (this.tree) { - this.tree.refilter(); - revealLastElement(this.tree); - } - }), - ); + this._register(this.filterWidget.onDidChangeFilterText(() => { + this.filter.filterQuery = this.filterWidget.getFilterText(); + if (this.tree) { + this.tree.refilter(); + revealLastElement(this.tree); + } + })); } - private async onDidFocusSession( - session: IDebugSession | undefined, - ): Promise { + private async onDidFocusSession(session: IDebugSession | undefined): Promise { if (session) { sessionsToIgnore.delete(session); this.completionItemProvider?.dispose(); - if (session.capabilities.supportsCompletionsRequest) { - this.completionItemProvider = - this.languageFeaturesService.completionProvider.register( - { - scheme: DEBUG_SCHEME, - pattern: "**/replinput", - hasAccessToAllModels: true, - }, - { - _debugDisplayName: "debugConsole", - triggerCharacters: session.capabilities - .completionTriggerCharacters || ["."], - provideCompletionItems: async ( - _: ITextModel, - position: Position, - _context: CompletionContext, - token: CancellationToken, - ): Promise => { - // Disable history navigation because up and down are used to navigate through the suggest widget - this.setHistoryNavigationEnablement(false); - - const model = this.replInput.getModel(); - - if (model) { - const word = - model.getWordAtPosition(position); - - const overwriteBefore = word - ? word.word.length - : 0; - - const text = model.getValue(); - - const focusedStackFrame = - this.debugService.getViewModel() - .focusedStackFrame; - - const frameId = focusedStackFrame - ? focusedStackFrame.frameId - : undefined; - - const response = await session.completions( - frameId, - focusedStackFrame?.thread.threadId || 0, - text, - position, - overwriteBefore, - token, - ); - - const suggestions: CompletionItem[] = []; - - const computeRange = (length: number) => - Range.fromPositions( - position.delta(0, -length), - position, - ); - - if ( - response && - response.body && - response.body.targets - ) { - response.body.targets.forEach( - (item) => { - if (item && item.label) { - let insertTextRules: - | CompletionItemInsertTextRule - | undefined = undefined; - - let insertText = - item.text || item.label; - - if ( - typeof item.selectionStart === - "number" - ) { - // If a debug completion item sets a selection we need to use snippets to make sure the selection is selected #90974 - insertTextRules = - CompletionItemInsertTextRule.InsertAsSnippet; - - const selectionLength = - typeof item.selectionLength === - "number" - ? item.selectionLength - : 0; - - const placeholder = - selectionLength > 0 - ? "${1:" + - insertText.substring( - item.selectionStart, - item.selectionStart + - selectionLength, - ) + - "}$0" - : "$0"; - insertText = - insertText.substring( - 0, - item.selectionStart, - ) + - placeholder + - insertText.substring( - item.selectionStart + - selectionLength, - ); - } - - suggestions.push({ - label: item.label, - insertText, - detail: item.detail, - kind: CompletionItemKinds.fromString( - item.type || - "property", - ), - filterText: - item.start && - item.length - ? text - .substring( - item.start, - item.start + - item.length, - ) - .concat( - item.label, - ) - : undefined, - range: computeRange( - item.length || - overwriteBefore, - ), - sortText: item.sortText, - insertTextRules, - }); - } - }, - ); + this.completionItemProvider = this.languageFeaturesService.completionProvider.register({ scheme: DEBUG_SCHEME, pattern: '**/replinput', hasAccessToAllModels: true }, { + _debugDisplayName: 'debugConsole', + triggerCharacters: session.capabilities.completionTriggerCharacters || ['.'], + provideCompletionItems: async (_: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken): Promise => { + // Disable history navigation because up and down are used to navigate through the suggest widget + this.setHistoryNavigationEnablement(false); + + const model = this.replInput.getModel(); + if (model) { + const word = model.getWordAtPosition(position); + const overwriteBefore = word ? word.word.length : 0; + const text = model.getValue(); + const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame; + const frameId = focusedStackFrame ? focusedStackFrame.frameId : undefined; + const response = await session.completions(frameId, focusedStackFrame?.thread.threadId || 0, text, position, overwriteBefore, token); + + const suggestions: CompletionItem[] = []; + const computeRange = (length: number) => Range.fromPositions(position.delta(0, -length), position); + if (response && response.body && response.body.targets) { + response.body.targets.forEach(item => { + if (item && item.label) { + let insertTextRules: CompletionItemInsertTextRule | undefined = undefined; + let insertText = item.text || item.label; + if (typeof item.selectionStart === 'number') { + // If a debug completion item sets a selection we need to use snippets to make sure the selection is selected #90974 + insertTextRules = CompletionItemInsertTextRule.InsertAsSnippet; + const selectionLength = typeof item.selectionLength === 'number' ? item.selectionLength : 0; + const placeholder = selectionLength > 0 ? '${1:' + insertText.substring(item.selectionStart, item.selectionStart + selectionLength) + '}$0' : '$0'; + insertText = insertText.substring(0, item.selectionStart) + placeholder + insertText.substring(item.selectionStart + selectionLength); + } + + suggestions.push({ + label: item.label, + insertText, + detail: item.detail, + kind: CompletionItemKinds.fromString(item.type || 'property'), + filterText: (item.start && item.length) ? text.substring(item.start, item.start + item.length).concat(item.label) : undefined, + range: computeRange(item.length || overwriteBefore), + sortText: item.sortText, + insertTextRules + }); } + }); + } - if ( - this.configurationService.getValue( - "debug", - ).console.historySuggestions - ) { - const history = - this.history.getHistory(); - - const idxLength = String( - history.length, - ).length; - history.forEach((h, i) => - suggestions.push({ - label: h, - insertText: h, - kind: CompletionItemKind.Text, - range: computeRange(h.length), - sortText: - "ZZZ" + - String( - history.length - i, - ).padStart(idxLength, "0"), - }), - ); - } + if (this.configurationService.getValue('debug').console.historySuggestions) { + const history = this.history.getHistory(); + const idxLength = String(history.length).length; + history.forEach((h, i) => suggestions.push({ + label: h, + insertText: h, + kind: CompletionItemKind.Text, + range: computeRange(h.length), + sortText: 'ZZZ' + String(history.length - i).padStart(idxLength, '0') + })); + } - return { suggestions }; - } + return { suggestions }; + } - return Promise.resolve({ suggestions: [] }); - }, - }, - ); + return Promise.resolve({ suggestions: [] }); + } + }); } } @@ -638,16 +322,13 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { // This could be called before the tree is created when setting this.filterState.filterText value return { total: this.tree?.getNode().children.length ?? 0, - filtered: - this.tree?.getNode().children.filter((c) => c.visible).length ?? - 0, + filtered: this.tree?.getNode().children.filter(c => c.visible).length ?? 0 }; } get isReadonly(): boolean { // Do not allow to edit inactive sessions const session = this.tree?.getInput(); - if (session && session.state !== State.Inactive) { return false; } @@ -681,18 +362,11 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { } const activeEditorControl = this.editorService.activeTextEditorControl; - if (isCodeEditor(activeEditorControl)) { this.modelChangeListener.dispose(); - this.modelChangeListener = - activeEditorControl.onDidChangeModelLanguage(() => - this.setMode(), - ); - + this.modelChangeListener = activeEditorControl.onDidChangeModelLanguage(() => this.setMode()); if (this.model && activeEditorControl.hasModel()) { - this.model.setLanguage( - activeEditorControl.getModel().getLanguageId(), - ); + this.model.setLanguage(activeEditorControl.getModel().getLanguageId()); } } } @@ -700,22 +374,16 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { private onDidStyleChange(): void { if (!this.isVisible()) { this.styleChangedWhenInvisible = true; - return; } if (this.styleElement) { this.replInput.updateOptions({ fontSize: this.replOptions.replConfiguration.fontSize, lineHeight: this.replOptions.replConfiguration.lineHeight, - fontFamily: - this.replOptions.replConfiguration.fontFamily === "default" - ? EDITOR_FONT_DEFAULTS.fontFamily - : this.replOptions.replConfiguration.fontFamily, + fontFamily: this.replOptions.replConfiguration.fontFamily === 'default' ? EDITOR_FONT_DEFAULTS.fontFamily : this.replOptions.replConfiguration.fontFamily }); - const replInputLineHeight = this.replInput.getOption( - EditorOption.lineHeight, - ); + const replInputLineHeight = this.replInput.getOption(EditorOption.lineHeight); // Set the font size, font family, line height and align the twistie to be centered, and input theme color this.styleElement.textContent = ` @@ -727,79 +395,47 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { background-color: ${this.replOptions.replConfiguration.backgroundColor}; } `; - - const cssFontFamily = - this.replOptions.replConfiguration.fontFamily === "default" - ? "var(--monaco-monospace-font)" - : this.replOptions.replConfiguration.fontFamily; - this.container.style.setProperty( - `--vscode-repl-font-family`, - cssFontFamily, - ); - this.container.style.setProperty( - `--vscode-repl-font-size`, - `${this.replOptions.replConfiguration.fontSize}px`, - ); - this.container.style.setProperty( - `--vscode-repl-font-size-for-twistie`, - `${this.replOptions.replConfiguration.fontSizeForTwistie}px`, - ); - this.container.style.setProperty( - `--vscode-repl-line-height`, - this.replOptions.replConfiguration.cssLineHeight, - ); + const cssFontFamily = this.replOptions.replConfiguration.fontFamily === 'default' ? 'var(--monaco-monospace-font)' : this.replOptions.replConfiguration.fontFamily; + this.container.style.setProperty(`--vscode-repl-font-family`, cssFontFamily); + this.container.style.setProperty(`--vscode-repl-font-size`, `${this.replOptions.replConfiguration.fontSize}px`); + this.container.style.setProperty(`--vscode-repl-font-size-for-twistie`, `${this.replOptions.replConfiguration.fontSizeForTwistie}px`); + this.container.style.setProperty(`--vscode-repl-line-height`, this.replOptions.replConfiguration.cssLineHeight); this.tree?.rerender(); if (this.bodyContentDimension) { - this.layoutBodyContent( - this.bodyContentDimension.height, - this.bodyContentDimension.width, - ); + this.layoutBodyContent(this.bodyContentDimension.height, this.bodyContentDimension.width); } } } private navigateHistory(previous: boolean): void { - const historyInput = - (previous - ? (this.history.previous() ?? this.history.first()) - : this.history.next()) ?? ""; + const historyInput = (previous ? + (this.history.previous() ?? this.history.first()) : this.history.next()) + ?? ''; this.replInput.setValue(historyInput); aria.status(historyInput); // always leave cursor at the end. - this.replInput.setPosition({ - lineNumber: 1, - column: historyInput.length + 1, - }); + this.replInput.setPosition({ lineNumber: 1, column: historyInput.length + 1 }); this.setHistoryNavigationEnablement(true); } async selectSession(session?: IDebugSession): Promise { const treeInput = this.tree?.getInput(); - if (!session) { - const focusedSession = - this.debugService.getViewModel().focusedSession; + const focusedSession = this.debugService.getViewModel().focusedSession; // If there is a focusedSession focus on that one, otherwise just show any other not ignored session if (focusedSession) { session = focusedSession; } else if (!treeInput || sessionsToIgnore.has(treeInput)) { - session = this.debugService - .getModel() - .getSessions(true) - .find((s) => !sessionsToIgnore.has(s)); + session = this.debugService.getModel().getSessions(true).find(s => !sessionsToIgnore.has(s)); } } if (session) { this.replElementsChangeListener?.dispose(); - this.replElementsChangeListener = session.onDidChangeReplElements( - () => { - this.refreshReplElements( - session.getReplElements().length === 0, - ); - }, - ); + this.replElementsChangeListener = session.onDidChangeReplElements(() => { + this.refreshReplElements(session.getReplElements().length === 0); + }); if (this.tree && treeInput !== session) { try { @@ -819,10 +455,8 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { async clearRepl(): Promise { const session = this.tree?.getInput(); - if (session) { session.removeReplExpressions(); - if (session.state === State.Inactive) { // Ignore inactive sessions which got cleared - so they are not shown any more sessionsToIgnore.add(session); @@ -835,59 +469,37 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { acceptReplInput(): void { const session = this.tree?.getInput(); - if (session && !this.isReadonly) { - session.addReplExpression( - this.debugService.getViewModel().focusedStackFrame, - this.replInput.getValue(), - ); + session.addReplExpression(this.debugService.getViewModel().focusedStackFrame, this.replInput.getValue()); revealLastElement(this.tree!); this.history.add(this.replInput.getValue()); - this.replInput.setValue(""); - + this.replInput.setValue(''); const shouldRelayout = this.replInputLineCount > 1; this.replInputLineCount = 1; - if (shouldRelayout && this.bodyContentDimension) { // Trigger a layout to shrink a potential multi line input - this.layoutBodyContent( - this.bodyContentDimension.height, - this.bodyContentDimension.width, - ); + this.layoutBodyContent(this.bodyContentDimension.height, this.bodyContentDimension.width); } } } sendReplInput(input: string): void { const session = this.tree?.getInput(); - if (session && !this.isReadonly) { - session.addReplExpression( - this.debugService.getViewModel().focusedStackFrame, - input, - ); + session.addReplExpression(this.debugService.getViewModel().focusedStackFrame, input); revealLastElement(this.tree!); this.history.add(input); } } getVisibleContent(): string { - let text = ""; - + let text = ''; if (this.model && this.tree) { - const lineDelimiter = this.textResourcePropertiesService.getEOL( - this.model.uri, - ); - - const traverseAndAppend = ( - node: ITreeNode, - ) => { - node.children.forEach((child) => { + const lineDelimiter = this.textResourcePropertiesService.getEOL(this.model.uri); + const traverseAndAppend = (node: ITreeNode) => { + node.children.forEach(child => { if (child.visible) { - text += - child.element.toString().trimRight() + - lineDelimiter; - + text += child.element.toString().trimRight() + lineDelimiter; if (!child.collapsed && child.children.length) { traverseAndAppend(child); } @@ -902,21 +514,12 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { protected layoutBodyContent(height: number, width: number): void { this.bodyContentDimension = new dom.Dimension(width, height); - - const replInputHeight = Math.min( - this.replInput.getContentHeight(), - height, - ); - + const replInputHeight = Math.min(this.replInput.getContentHeight(), height); if (this.tree) { - const lastElementVisible = - this.tree.scrollTop + this.tree.renderHeight >= - this.tree.scrollHeight; - + const lastElementVisible = this.tree.scrollTop + this.tree.renderHeight >= this.tree.scrollHeight; const treeHeight = height - replInputHeight; this.tree.getHTMLElement().style.height = `${treeHeight}px`; this.tree.layout(treeHeight, width); - if (lastElementVisible) { revealLastElement(this.tree); } @@ -938,9 +541,7 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { return this.replInput; } - getReplDataSource(): - | IAsyncDataSource - | undefined { + getReplDataSource(): IAsyncDataSource | undefined { return this.replDataSource; } @@ -954,34 +555,20 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { override focus(): void { super.focus(); - setTimeout(() => this.replInput.focus(), 0); } override getActionViewItem(action: IAction): IActionViewItem | undefined { if (action.id === selectReplCommandId) { - const session = - (this.tree ? this.tree.getInput() : undefined) ?? - this.debugService.getViewModel().focusedSession; - - return this.instantiationService.createInstance( - SelectReplActionViewItem, - action, - session, - ); + const session = (this.tree ? this.tree.getInput() : undefined) ?? this.debugService.getViewModel().focusedSession; + return this.instantiationService.createInstance(SelectReplActionViewItem, action, session); } return super.getActionViewItem(action); } private get isMultiSessionView(): boolean { - return ( - this.debugService - .getModel() - .getSessions(true) - .filter((s) => s.hasSeparateRepl() && !sessionsToIgnore.has(s)) - .length > 1 - ); + return this.debugService.getModel().getSessions(true).filter(s => s.hasSeparateRepl() && !sessionsToIgnore.has(s)).length > 1; } // --- Cached locals @@ -989,27 +576,20 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { @memoize private get refreshScheduler(): RunOnceScheduler { const autoExpanded = new Set(); - return new RunOnceScheduler(async () => { if (!this.tree || !this.tree.getInput() || !this.isVisible()) { return; } - await this.tree.updateChildren(undefined, true, false, { - diffIdentityProvider: identityProvider, - }); + await this.tree.updateChildren(undefined, true, false, { diffIdentityProvider: identityProvider }); const session = this.tree.getInput(); - if (session) { // Automatically expand repl group elements when specified const autoExpandElements = async (elements: IReplElement[]) => { for (const element of elements) { if (element instanceof ReplGroup) { - if ( - element.autoExpand && - !autoExpanded.has(element.getId()) - ) { + if (element.autoExpand && !autoExpanded.has(element.getId())) { autoExpanded.add(element.getId()); await this.tree!.expand(element); } @@ -1024,16 +604,7 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { } // Repl elements count changed, need to update filter stats on the badge const { total, filtered } = this.getFilterStats(); - this.filterWidget.updateBadge( - total === filtered || total === 0 - ? undefined - : localize( - "showing filtered repl lines", - "Showing {0} of {1}", - filtered, - total, - ), - ); + this.filterWidget.updateBadge(total === filtered || total === 0 ? undefined : localize('showing filtered repl lines', "Showing {0} of {1}", filtered, total)); }, Repl.REFRESH_DELAY); } @@ -1041,80 +612,53 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { override render(): void { super.render(); - this._register( - registerNavigableContainer({ - name: "repl", - focusNotifiers: [this, this.filterWidget], - focusNextWidget: () => { - const element = this.tree?.getHTMLElement(); - - if (this.filterWidget.hasFocus()) { - this.tree?.domFocus(); - } else if (element && dom.isActiveElement(element)) { - this.focus(); - } - }, - focusPreviousWidget: () => { - const element = this.tree?.getHTMLElement(); - - if (this.replInput.hasTextFocus()) { - this.tree?.domFocus(); - } else if (element && dom.isActiveElement(element)) { - this.focusFilter(); - } - }, - }), - ); + this._register(registerNavigableContainer({ + name: 'repl', + focusNotifiers: [this, this.filterWidget], + focusNextWidget: () => { + const element = this.tree?.getHTMLElement(); + if (this.filterWidget.hasFocus()) { + this.tree?.domFocus(); + } else if (element && dom.isActiveElement(element)) { + this.focus(); + } + }, + focusPreviousWidget: () => { + const element = this.tree?.getHTMLElement(); + if (this.replInput.hasTextFocus()) { + this.tree?.domFocus(); + } else if (element && dom.isActiveElement(element)) { + this.focusFilter(); + } + } + })); } protected override renderBody(parent: HTMLElement): void { super.renderBody(parent); - this.container = dom.append(parent, $(".repl")); - this.treeContainer = dom.append( - this.container, - $(`.repl-tree.${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`), - ); + this.container = dom.append(parent, $('.repl')); + this.treeContainer = dom.append(this.container, $(`.repl-tree.${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`)); this.createReplInput(this.container); this.createReplTree(); } private createReplTree(): void { - this.replDelegate = new ReplDelegate( - this.configurationService, - this.replOptions, - ); - - const wordWrap = - this.configurationService.getValue("debug") - .console.wordWrap; - this.treeContainer.classList.toggle("word-wrap", wordWrap); - - const expressionRenderer = this.instantiationService.createInstance( - DebugExpressionRenderer, - ); + this.replDelegate = new ReplDelegate(this.configurationService, this.replOptions); + const wordWrap = this.configurationService.getValue('debug').console.wordWrap; + this.treeContainer.classList.toggle('word-wrap', wordWrap); + const expressionRenderer = this.instantiationService.createInstance(DebugExpressionRenderer); this.replDataSource = new ReplDataSource(); - const tree = (this.tree = < - WorkbenchAsyncDataTree - >this.instantiationService.createInstance( - WorkbenchAsyncDataTree, - "DebugRepl", + const tree = this.tree = this.instantiationService.createInstance( + WorkbenchAsyncDataTree, + 'DebugRepl', this.treeContainer, this.replDelegate, [ - this.instantiationService.createInstance( - ReplVariablesRenderer, - expressionRenderer, - ), - this.instantiationService.createInstance( - ReplOutputElementRenderer, - expressionRenderer, - ), + this.instantiationService.createInstance(ReplVariablesRenderer, expressionRenderer), + this.instantiationService.createInstance(ReplOutputElementRenderer, expressionRenderer), new ReplEvaluationInputsRenderer(), - this.instantiationService.createInstance( - ReplGroupRenderer, - expressionRenderer, - ), + this.instantiationService.createInstance(ReplGroupRenderer, expressionRenderer), new ReplEvaluationResultsRenderer(expressionRenderer), new ReplRawObjectsRenderer(expressionRenderer), ], @@ -1125,65 +669,44 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { identityProvider, mouseSupport: false, findWidgetEnabled: true, - keyboardNavigationLabelProvider: { - getKeyboardNavigationLabel: (e: IReplElement) => - e.toString(true), - }, + keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IReplElement) => e.toString(true) }, horizontalScrolling: !wordWrap, setRowLineHeight: false, supportDynamicHeights: wordWrap, - overrideStyles: - this.getLocationBasedColors().listOverrideStyles, - }, - )); - - this._register( - tree.onDidChangeContentHeight(() => { - if (tree.scrollHeight !== this.previousTreeScrollHeight) { - // Due to rounding, the scrollTop + renderHeight will not exactly match the scrollHeight. - // Consider the tree to be scrolled all the way down if it is within 2px of the bottom. - const lastElementWasVisible = - tree.scrollTop + tree.renderHeight >= - this.previousTreeScrollHeight - 2; - - if (lastElementWasVisible) { - setTimeout(() => { - // Can't set scrollTop during this event listener, the list might overwrite the change - revealLastElement(tree); - }, 0); - } + overrideStyles: this.getLocationBasedColors().listOverrideStyles + }); + + this._register(tree.onDidChangeContentHeight(() => { + if (tree.scrollHeight !== this.previousTreeScrollHeight) { + // Due to rounding, the scrollTop + renderHeight will not exactly match the scrollHeight. + // Consider the tree to be scrolled all the way down if it is within 2px of the bottom. + const lastElementWasVisible = tree.scrollTop + tree.renderHeight >= this.previousTreeScrollHeight - 2; + if (lastElementWasVisible) { + setTimeout(() => { + // Can't set scrollTop during this event listener, the list might overwrite the change + revealLastElement(tree); + }, 0); } + } - this.previousTreeScrollHeight = tree.scrollHeight; - }), - ); + this.previousTreeScrollHeight = tree.scrollHeight; + })); - this._register(tree.onContextMenu((e) => this.onContextMenu(e))); - this._register( - tree.onDidChangeFindOpenState((open) => (this.findIsOpen = open)), - ); + this._register(tree.onContextMenu(e => this.onContextMenu(e))); + this._register(tree.onDidChangeFindOpenState((open) => this.findIsOpen = open)); let lastSelectedString: string; - this._register( - tree.onMouseClick(() => { - if (this.findIsOpen) { - return; - } - const selection = dom - .getWindow(this.treeContainer) - .getSelection(); - - if ( - !selection || - selection.type !== "Range" || - lastSelectedString === selection.toString() - ) { - // only focus the input if the user is not currently selecting and find isn't open. - this.replInput.focus(); - } - lastSelectedString = selection ? selection.toString() : ""; - }), - ); + this._register(tree.onMouseClick(() => { + if (this.findIsOpen) { + return; + } + const selection = dom.getWindow(this.treeContainer).getSelection(); + if (!selection || selection.type !== 'Range' || lastSelectedString === selection.toString()) { + // only focus the input if the user is not currently selecting and find isn't open. + this.replInput.focus(); + } + lastSelectedString = selection ? selection.toString() : ''; + })); // Make sure to select the session if debugging is already active this.selectSession(); this.styleElement = domStylesheetsJs.createStyleSheet(this.container); @@ -1191,152 +714,66 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { } private createReplInput(container: HTMLElement): void { - this.replInputContainer = dom.append( - container, - $(".repl-input-wrapper"), - ); - - dom.append( - this.replInputContainer, - $( - ".repl-input-chevron" + - ThemeIcon.asCSSSelector(debugConsoleEvaluationPrompt), - ), - ); - - const { - historyNavigationBackwardsEnablement, - historyNavigationForwardsEnablement, - } = this._register( - registerAndCreateHistoryNavigationContext( - this.scopedContextKeyService, - this, - ), - ); - this.setHistoryNavigationEnablement = (enabled) => { + this.replInputContainer = dom.append(container, $('.repl-input-wrapper')); + dom.append(this.replInputContainer, $('.repl-input-chevron' + ThemeIcon.asCSSSelector(debugConsoleEvaluationPrompt))); + + const { historyNavigationBackwardsEnablement, historyNavigationForwardsEnablement } = this._register(registerAndCreateHistoryNavigationContext(this.scopedContextKeyService, this)); + this.setHistoryNavigationEnablement = enabled => { historyNavigationBackwardsEnablement.set(enabled); historyNavigationForwardsEnablement.set(enabled); }; CONTEXT_IN_DEBUG_REPL.bindTo(this.scopedContextKeyService).set(true); - this.scopedInstantiationService = this._register( - this.instantiationService.createChild( - new ServiceCollection([ - IContextKeyService, - this.scopedContextKeyService, - ]), - ), - ); - + this.scopedInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]))); const options = getSimpleEditorOptions(this.configurationService); options.readOnly = true; options.suggest = { showStatusBar: true }; - - const config = - this.configurationService.getValue("debug"); - options.acceptSuggestionOnEnter = - config.console.acceptSuggestionOnEnter === "on" ? "on" : "off"; + const config = this.configurationService.getValue('debug'); + options.acceptSuggestionOnEnter = config.console.acceptSuggestionOnEnter === 'on' ? 'on' : 'off'; options.ariaLabel = this.getAriaLabel(); - this.replInput = this.scopedInstantiationService.createInstance( - CodeEditorWidget, - this.replInputContainer, - options, - getSimpleCodeEditorWidgetOptions(), - ); - - this._register( - this.replInput.onDidChangeModelContent(() => { - const model = this.replInput.getModel(); - this.setHistoryNavigationEnablement( - !!model && model.getValue() === "", - ); - - const lineCount = model - ? Math.min(10, model.getLineCount()) - : 1; - - if (lineCount !== this.replInputLineCount) { - this.replInputLineCount = lineCount; - - if (this.bodyContentDimension) { - this.layoutBodyContent( - this.bodyContentDimension.height, - this.bodyContentDimension.width, - ); - } + this.replInput = this.scopedInstantiationService.createInstance(CodeEditorWidget, this.replInputContainer, options, getSimpleCodeEditorWidgetOptions()); + + this._register(this.replInput.onDidChangeModelContent(() => { + const model = this.replInput.getModel(); + this.setHistoryNavigationEnablement(!!model && model.getValue() === ''); + const lineCount = model ? Math.min(10, model.getLineCount()) : 1; + if (lineCount !== this.replInputLineCount) { + this.replInputLineCount = lineCount; + if (this.bodyContentDimension) { + this.layoutBodyContent(this.bodyContentDimension.height, this.bodyContentDimension.width); } - }), - ); + } + })); // We add the input decoration only when the focus is in the input #61126 - this._register( - this.replInput.onDidFocusEditorText(() => - this.updateInputDecoration(), - ), - ); - this._register( - this.replInput.onDidBlurEditorText(() => - this.updateInputDecoration(), - ), - ); - - this._register( - dom.addStandardDisposableListener( - this.replInputContainer, - dom.EventType.FOCUS, - () => this.replInputContainer.classList.add("synthetic-focus"), - ), - ); - this._register( - dom.addStandardDisposableListener( - this.replInputContainer, - dom.EventType.BLUR, - () => - this.replInputContainer.classList.remove("synthetic-focus"), - ), - ); + this._register(this.replInput.onDidFocusEditorText(() => this.updateInputDecoration())); + this._register(this.replInput.onDidBlurEditorText(() => this.updateInputDecoration())); + + this._register(dom.addStandardDisposableListener(this.replInputContainer, dom.EventType.FOCUS, () => this.replInputContainer.classList.add('synthetic-focus'))); + this._register(dom.addStandardDisposableListener(this.replInputContainer, dom.EventType.BLUR, () => this.replInputContainer.classList.remove('synthetic-focus'))); } private getAriaLabel(): string { - let ariaLabel = localize("debugConsole", "Debug Console"); - - if ( - !this.configurationService.getValue( - AccessibilityVerbositySettingId.Debug, - ) - ) { + let ariaLabel = localize('debugConsole', "Debug Console"); + if (!this.configurationService.getValue(AccessibilityVerbositySettingId.Debug)) { return ariaLabel; } - const keybinding = this.keybindingService - .lookupKeybinding(AccessibilityCommandId.OpenAccessibilityHelp) - ?.getAriaLabel(); - + const keybinding = this.keybindingService.lookupKeybinding(AccessibilityCommandId.OpenAccessibilityHelp)?.getAriaLabel(); if (keybinding) { - ariaLabel = localize( - "commentLabelWithKeybinding", - "{0}, use ({1}) for accessibility help", - ariaLabel, - keybinding, - ); + ariaLabel = localize('commentLabelWithKeybinding', "{0}, use ({1}) for accessibility help", ariaLabel, keybinding); } else { - ariaLabel = localize( - "commentLabelWithKeybindingNoKeybinding", - "{0}, run the command Open Accessibility Help which is currently not triggerable via keybinding.", - ariaLabel, - ); + ariaLabel = localize('commentLabelWithKeybindingNoKeybinding', "{0}, run the command Open Accessibility Help which is currently not triggerable via keybinding.", ariaLabel); } return ariaLabel; } private onContextMenu(e: ITreeContextMenuEvent): void { - const actions = getFlatContextMenuActions( - this.menu.getActions({ arg: e.element, shouldForwardArgs: false }), - ); + const actions = getFlatContextMenuActions(this.menu.getActions({ arg: e.element, shouldForwardArgs: false })); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => actions, - getActionsContext: () => e.element, + getActionsContext: () => e.element }); } @@ -1358,89 +795,45 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { } const decorations: IDecorationOptions[] = []; - - if ( - this.isReadonly && - this.replInput.hasTextFocus() && - !this.replInput.getValue() - ) { - const transparentForeground = resolveColorValue( - editorForeground, - this.themeService.getColorTheme(), - )?.transparent(0.4); + if (this.isReadonly && this.replInput.hasTextFocus() && !this.replInput.getValue()) { + const transparentForeground = resolveColorValue(editorForeground, this.themeService.getColorTheme())?.transparent(0.4); decorations.push({ range: { startLineNumber: 0, endLineNumber: 0, startColumn: 0, - endColumn: 1, + endColumn: 1 }, renderOptions: { after: { - contentText: localize( - "startDebugFirst", - "Please start a debug session to evaluate expressions", - ), - color: transparentForeground - ? transparentForeground.toString() - : undefined, - }, - }, + contentText: localize('startDebugFirst', "Please start a debug session to evaluate expressions"), + color: transparentForeground ? transparentForeground.toString() : undefined + } + } }); } - this.replInput.setDecorationsByType( - "repl-decoration", - DECORATION_KEY, - decorations, - ); + this.replInput.setDecorationsByType('repl-decoration', DECORATION_KEY, decorations); } override saveState(): void { const replHistory = this.history.getHistory(); - if (replHistory.length) { - this.storageService.store( - HISTORY_STORAGE_KEY, - JSON.stringify(replHistory), - StorageScope.WORKSPACE, - StorageTarget.MACHINE, - ); + this.storageService.store(HISTORY_STORAGE_KEY, JSON.stringify(replHistory), StorageScope.WORKSPACE, StorageTarget.MACHINE); } else { - this.storageService.remove( - HISTORY_STORAGE_KEY, - StorageScope.WORKSPACE, - ); + this.storageService.remove(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE); } const filterHistory = this.filterWidget.getHistory(); - if (filterHistory.length) { - this.storageService.store( - FILTER_HISTORY_STORAGE_KEY, - JSON.stringify(filterHistory), - StorageScope.WORKSPACE, - StorageTarget.MACHINE, - ); + this.storageService.store(FILTER_HISTORY_STORAGE_KEY, JSON.stringify(filterHistory), StorageScope.WORKSPACE, StorageTarget.MACHINE); } else { - this.storageService.remove( - FILTER_HISTORY_STORAGE_KEY, - StorageScope.WORKSPACE, - ); + this.storageService.remove(FILTER_HISTORY_STORAGE_KEY, StorageScope.WORKSPACE); } const filterValue = this.filterWidget.getFilterText(); - if (filterValue) { - this.storageService.store( - FILTER_VALUE_STORAGE_KEY, - filterValue, - StorageScope.WORKSPACE, - StorageTarget.MACHINE, - ); + this.storageService.store(FILTER_VALUE_STORAGE_KEY, filterValue, StorageScope.WORKSPACE, StorageTarget.MACHINE); } else { - this.storageService.remove( - FILTER_VALUE_STORAGE_KEY, - StorageScope.WORKSPACE, - ); + this.storageService.remove(FILTER_VALUE_STORAGE_KEY, StorageScope.WORKSPACE); } super.saveState(); @@ -1451,7 +844,6 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { this.replElementsChangeListener?.dispose(); this.refreshScheduler.dispose(); this.modelChangeListener.dispose(); - super.dispose(); } } @@ -1470,57 +862,35 @@ class ReplOptions extends Disposable implements IReplOptions { constructor( viewId: string, private readonly backgroundColorDelegate: () => string, - @IConfigurationService - private readonly configurationService: IConfigurationService, + @IConfigurationService private readonly configurationService: IConfigurationService, @IThemeService private readonly themeService: IThemeService, - @IViewDescriptorService - private readonly viewDescriptorService: IViewDescriptorService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService ) { super(); - this._register( - this.themeService.onDidColorThemeChange((e) => this.update()), - ); - this._register( - this.viewDescriptorService.onDidChangeLocation((e) => { - if (e.views.some((v) => v.id === viewId)) { - this.update(); - } - }), - ); - this._register( - this.configurationService.onDidChangeConfiguration((e) => { - if ( - e.affectsConfiguration("debug.console.lineHeight") || - e.affectsConfiguration("debug.console.fontSize") || - e.affectsConfiguration("debug.console.fontFamily") - ) { - this.update(); - } - }), - ); + this._register(this.themeService.onDidColorThemeChange(e => this.update())); + this._register(this.viewDescriptorService.onDidChangeLocation(e => { + if (e.views.some(v => v.id === viewId)) { + this.update(); + } + })); + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('debug.console.lineHeight') || e.affectsConfiguration('debug.console.fontSize') || e.affectsConfiguration('debug.console.fontFamily')) { + this.update(); + } + })); this.update(); } private update() { - const debugConsole = - this.configurationService.getValue( - "debug", - ).console; + const debugConsole = this.configurationService.getValue('debug').console; this._replConfig = { fontSize: debugConsole.fontSize, fontFamily: debugConsole.fontFamily, - lineHeight: debugConsole.lineHeight - ? debugConsole.lineHeight - : ReplOptions.lineHeightEm * debugConsole.fontSize, - cssLineHeight: debugConsole.lineHeight - ? `${debugConsole.lineHeight}px` - : `${ReplOptions.lineHeightEm}em`, - backgroundColor: this.themeService - .getColorTheme() - .getColor(this.backgroundColorDelegate()), - fontSizeForTwistie: - (debugConsole.fontSize * ReplOptions.lineHeightEm) / 2 - 8, + lineHeight: debugConsole.lineHeight ? debugConsole.lineHeight : ReplOptions.lineHeightEm * debugConsole.fontSize, + cssLineHeight: debugConsole.lineHeight ? `${debugConsole.lineHeight}px` : `${ReplOptions.lineHeightEm}em`, + backgroundColor: this.themeService.getColorTheme().getColor(this.backgroundColorDelegate()), + fontSizeForTwistie: debugConsole.fontSize * ReplOptions.lineHeightEm / 2 - 8 }; this._onDidChange.fire(); } @@ -1529,50 +899,40 @@ class ReplOptions extends Disposable implements IReplOptions { // Repl actions and commands class AcceptReplInputAction extends EditorAction { + constructor() { super({ - id: "repl.action.acceptInput", - label: localize2( - { - key: "actions.repl.acceptInput", - comment: ["Apply input from the debug console input box"], - }, - "Debug Console: Accept Input", - ), + id: 'repl.action.acceptInput', + label: localize2({ key: 'actions.repl.acceptInput', comment: ['Apply input from the debug console input box'] }, "Debug Console: Accept Input"), precondition: CONTEXT_IN_DEBUG_REPL, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, primary: KeyCode.Enter, - weight: KeybindingWeight.EditorContrib, - }, + weight: KeybindingWeight.EditorContrib + } }); } run(accessor: ServicesAccessor, editor: ICodeEditor): void | Promise { SuggestController.get(editor)?.cancelSuggestWidget(); - const repl = getReplView(accessor.get(IViewsService)); repl?.acceptReplInput(); } } class FilterReplAction extends ViewAction { + constructor() { super({ viewId: REPL_VIEW_ID, - id: "repl.action.filter", - title: localize( - "repl.action.filter", - "Debug Console: Focus Filter", - ), + id: 'repl.action.filter', + title: localize('repl.action.filter', "Debug Console: Focus Filter"), precondition: CONTEXT_IN_DEBUG_REPL, - keybinding: [ - { - when: EditorContextKeys.textInputFocus, - primary: KeyMod.CtrlCmd | KeyCode.KeyF, - weight: KeybindingWeight.EditorContrib, - }, - ], + keybinding: [{ + when: EditorContextKeys.textInputFocus, + primary: KeyMod.CtrlCmd | KeyCode.KeyF, + weight: KeybindingWeight.EditorContrib + }] }); } @@ -1581,40 +941,31 @@ class FilterReplAction extends ViewAction { } } + class FindReplAction extends ViewAction { + constructor() { super({ viewId: REPL_VIEW_ID, - id: "repl.action.find", - title: localize("repl.action.find", "Debug Console: Focus Find"), + id: 'repl.action.find', + title: localize('repl.action.find', "Debug Console: Focus Find"), precondition: CONTEXT_IN_DEBUG_REPL, - keybinding: [ - { - when: ContextKeyExpr.or( - CONTEXT_IN_DEBUG_REPL, - ContextKeyExpr.equals( - "focusedView", - "workbench.panel.repl.view", - ), - ), - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyF, - weight: KeybindingWeight.EditorContrib, - }, - ], + keybinding: [{ + when: ContextKeyExpr.or(CONTEXT_IN_DEBUG_REPL, ContextKeyExpr.equals('focusedView', 'workbench.panel.repl.view')), + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyF, + weight: KeybindingWeight.EditorContrib + }], icon: Codicon.search, - menu: [ - { - id: MenuId.ViewTitle, - group: "navigation", - when: ContextKeyExpr.equals("view", REPL_VIEW_ID), - order: 15, - }, - { - id: MenuId.DebugConsoleContext, - group: "z_commands", - order: 25, - }, - ], + menu: [{ + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyExpr.equals('view', REPL_VIEW_ID), + order: 15 + }, { + id: MenuId.DebugConsoleContext, + group: 'z_commands', + order: 25 + }], }); } @@ -1624,20 +975,19 @@ class FindReplAction extends ViewAction { } class ReplCopyAllAction extends EditorAction { + constructor() { super({ - id: "repl.action.copyAll", - label: localize("actions.repl.copyAll", "Debug: Console Copy All"), - alias: "Debug Console Copy All", + id: 'repl.action.copyAll', + label: localize('actions.repl.copyAll', "Debug: Console Copy All"), + alias: 'Debug Console Copy All', precondition: CONTEXT_IN_DEBUG_REPL, }); } run(accessor: ServicesAccessor, editor: ICodeEditor): void | Promise { const clipboardService = accessor.get(IClipboardService); - const repl = getReplView(accessor.get(IViewsService)); - if (repl) { return clipboardService.writeText(repl.getVisibleContent()); } @@ -1650,20 +1000,13 @@ registerAction2(FilterReplAction); registerAction2(FindReplAction); class SelectReplActionViewItem extends FocusSessionActionViewItem { + protected override getSessions(): ReadonlyArray { - return this.debugService - .getModel() - .getSessions(true) - .filter((s) => s.hasSeparateRepl() && !sessionsToIgnore.has(s)); - } - - protected override mapFocusedSessionToSelected( - focusedSession: IDebugSession, - ): IDebugSession { - while ( - focusedSession.parentSession && - !focusedSession.hasSeparateRepl() - ) { + return this.debugService.getModel().getSessions(true).filter(s => s.hasSeparateRepl() && !sessionsToIgnore.has(s)); + } + + protected override mapFocusedSessionToSelected(focusedSession: IDebugSession): IDebugSession { + while (focusedSession.parentSession && !focusedSession.hasSeparateRepl()) { focusedSession = focusedSession.parentSession; } return focusedSession; @@ -1671,283 +1014,197 @@ class SelectReplActionViewItem extends FocusSessionActionViewItem { } export function getReplView(viewsService: IViewsService): Repl | undefined { - return ( - (viewsService.getActiveViewWithId(REPL_VIEW_ID) as Repl) ?? undefined - ); + return viewsService.getActiveViewWithId(REPL_VIEW_ID) as Repl ?? undefined; } -const selectReplCommandId = "workbench.action.debug.selectRepl"; -registerAction2( - class extends ViewAction { - constructor() { - super({ - id: selectReplCommandId, - viewId: REPL_VIEW_ID, - title: localize("selectRepl", "Select Debug Console"), - f1: false, - menu: { - id: MenuId.ViewTitle, - group: "navigation", - when: ContextKeyExpr.and( - ContextKeyExpr.equals("view", REPL_VIEW_ID), - CONTEXT_MULTI_SESSION_REPL, - ), - order: 20, - }, - }); - } - - async runInView( - accessor: ServicesAccessor, - view: Repl, - session: IDebugSession | undefined, - ) { - const debugService = accessor.get(IDebugService); - // If session is already the focused session we need to manualy update the tree since view model will not send a focused change event - if ( - session && - session.state !== State.Inactive && - session !== debugService.getViewModel().focusedSession - ) { - if (session.state !== State.Stopped) { - // Focus child session instead if it is stopped #112595 - const stopppedChildSession = debugService - .getModel() - .getSessions() - .find( - (s) => - s.parentSession === session && - s.state === State.Stopped, - ); - - if (stopppedChildSession) { - session = stopppedChildSession; - } - } - await debugService.focusStackFrame( - undefined, - undefined, - session, - { explicit: true }, - ); +const selectReplCommandId = 'workbench.action.debug.selectRepl'; +registerAction2(class extends ViewAction { + constructor() { + super({ + id: selectReplCommandId, + viewId: REPL_VIEW_ID, + title: localize('selectRepl', "Select Debug Console"), + f1: false, + menu: { + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', REPL_VIEW_ID), CONTEXT_MULTI_SESSION_REPL), + order: 20 } - // Need to select the session in the view since the focussed session might not have changed - await view.selectSession(session); - } - }, -); - -registerAction2( - class extends ViewAction { - constructor() { - super({ - id: "workbench.debug.panel.action.clearReplAction", - viewId: REPL_VIEW_ID, - title: localize2("clearRepl", "Clear Console"), - metadata: { - description: localize2( - "clearRepl.descriotion", - "Clears all program output from your debug REPL", - ), - }, - f1: true, - icon: debugConsoleClearAll, - menu: [ - { - id: MenuId.ViewTitle, - group: "navigation", - when: ContextKeyExpr.equals("view", REPL_VIEW_ID), - order: 30, - }, - { - id: MenuId.DebugConsoleContext, - group: "z_commands", - order: 20, - }, - ], - keybinding: [ - { - primary: 0, - mac: { primary: KeyMod.CtrlCmd | KeyCode.KeyK }, - // Weight is higher than work workbench contributions so the keybinding remains - // highest priority when chords are registered afterwards - weight: KeybindingWeight.WorkbenchContrib + 1, - when: ContextKeyExpr.equals( - "focusedView", - "workbench.panel.repl.view", - ), - }, - ], - }); - } - - runInView(_accessor: ServicesAccessor, view: Repl): void { - const accessibilitySignalService = _accessor.get( - IAccessibilitySignalService, - ); - view.clearRepl(); - accessibilitySignalService.playSignal(AccessibilitySignal.clear); - } - }, -); - -registerAction2( - class extends ViewAction { - constructor() { - super({ - id: "debug.collapseRepl", - title: localize("collapse", "Collapse All"), - viewId: REPL_VIEW_ID, - menu: { - id: MenuId.DebugConsoleContext, - group: "z_commands", - order: 10, - }, - }); - } - - runInView(_accessor: ServicesAccessor, view: Repl): void { - view.collapseAll(); - view.focus(); - } - }, -); - -registerAction2( - class extends ViewAction { - constructor() { - super({ - id: "debug.replPaste", - title: localize("paste", "Paste"), - viewId: REPL_VIEW_ID, - precondition: CONTEXT_DEBUG_STATE.notEqualsTo( - getStateLabel(State.Inactive), - ), - menu: { - id: MenuId.DebugConsoleContext, - group: "2_cutcopypaste", - order: 30, - }, - }); - } - - async runInView(accessor: ServicesAccessor, view: Repl): Promise { - const clipboardService = accessor.get(IClipboardService); - - const clipboardText = await clipboardService.readText(); - - if (clipboardText) { - const replInput = view.getReplInput(); - replInput.setValue(replInput.getValue().concat(clipboardText)); - view.focus(); - - const model = replInput.getModel(); - - const lineNumber = model ? model.getLineCount() : 0; - - const column = model?.getLineMaxColumn(lineNumber); + }); + } - if ( - typeof lineNumber === "number" && - typeof column === "number" - ) { - replInput.setPosition({ lineNumber, column }); + async runInView(accessor: ServicesAccessor, view: Repl, session: IDebugSession | undefined) { + const debugService = accessor.get(IDebugService); + // If session is already the focused session we need to manualy update the tree since view model will not send a focused change event + if (session && session.state !== State.Inactive && session !== debugService.getViewModel().focusedSession) { + if (session.state !== State.Stopped) { + // Focus child session instead if it is stopped #112595 + const stopppedChildSession = debugService.getModel().getSessions().find(s => s.parentSession === session && s.state === State.Stopped); + if (stopppedChildSession) { + session = stopppedChildSession; } } + await debugService.focusStackFrame(undefined, undefined, session, { explicit: true }); } - }, -); - -registerAction2( - class extends ViewAction { - constructor() { - super({ - id: "workbench.debug.action.copyAll", - title: localize("copyAll", "Copy All"), - viewId: REPL_VIEW_ID, - menu: { - id: MenuId.DebugConsoleContext, - group: "2_cutcopypaste", - order: 20, - }, - }); - } + // Need to select the session in the view since the focussed session might not have changed + await view.selectSession(session); + } +}); - async runInView(accessor: ServicesAccessor, view: Repl): Promise { - const clipboardService = accessor.get(IClipboardService); - await clipboardService.writeText(view.getVisibleContent()); - } - }, -); - -registerAction2( - class extends Action2 { - constructor() { - super({ - id: "debug.replCopy", - title: localize("copy", "Copy"), - menu: { - id: MenuId.DebugConsoleContext, - group: "2_cutcopypaste", - order: 10, - }, - }); - } +registerAction2(class extends ViewAction { + constructor() { + super({ + id: 'workbench.debug.panel.action.clearReplAction', + viewId: REPL_VIEW_ID, + title: localize2('clearRepl', 'Clear Console'), + metadata: { + description: localize2('clearRepl.descriotion', 'Clears all program output from your debug REPL') + }, + f1: true, + icon: debugConsoleClearAll, + menu: [{ + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyExpr.equals('view', REPL_VIEW_ID), + order: 30 + }, { + id: MenuId.DebugConsoleContext, + group: 'z_commands', + order: 20 + }], + keybinding: [{ + primary: 0, + mac: { primary: KeyMod.CtrlCmd | KeyCode.KeyK }, + // Weight is higher than work workbench contributions so the keybinding remains + // highest priority when chords are registered afterwards + weight: KeybindingWeight.WorkbenchContrib + 1, + when: ContextKeyExpr.equals('focusedView', 'workbench.panel.repl.view') + }], + }); + } - async run( - accessor: ServicesAccessor, - element: IReplElement, - ): Promise { - const clipboardService = accessor.get(IClipboardService); + runInView(_accessor: ServicesAccessor, view: Repl): void { + const accessibilitySignalService = _accessor.get(IAccessibilitySignalService); + view.clearRepl(); + accessibilitySignalService.playSignal(AccessibilitySignal.clear); + } +}); - const debugService = accessor.get(IDebugService); +registerAction2(class extends ViewAction { + constructor() { + super({ + id: 'debug.collapseRepl', + title: localize('collapse', "Collapse All"), + viewId: REPL_VIEW_ID, + menu: { + id: MenuId.DebugConsoleContext, + group: 'z_commands', + order: 10 + } + }); + } - const nativeSelection = dom.getActiveWindow().getSelection(); + runInView(_accessor: ServicesAccessor, view: Repl): void { + view.collapseAll(); + view.focus(); + } +}); - const selectedText = nativeSelection?.toString(); +registerAction2(class extends ViewAction { + constructor() { + super({ + id: 'debug.replPaste', + title: localize('paste', "Paste"), + viewId: REPL_VIEW_ID, + precondition: CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Inactive)), + menu: { + id: MenuId.DebugConsoleContext, + group: '2_cutcopypaste', + order: 30 + } + }); + } - if (selectedText && selectedText.length > 0) { - return clipboardService.writeText(selectedText); - } else if (element) { - return clipboardService.writeText( - (await this.tryEvaluateAndCopy(debugService, element)) || - element.toString(), - ); + async runInView(accessor: ServicesAccessor, view: Repl): Promise { + const clipboardService = accessor.get(IClipboardService); + const clipboardText = await clipboardService.readText(); + if (clipboardText) { + const replInput = view.getReplInput(); + replInput.setValue(replInput.getValue().concat(clipboardText)); + view.focus(); + const model = replInput.getModel(); + const lineNumber = model ? model.getLineCount() : 0; + const column = model?.getLineMaxColumn(lineNumber); + if (typeof lineNumber === 'number' && typeof column === 'number') { + replInput.setPosition({ lineNumber, column }); } } + } +}); - private async tryEvaluateAndCopy( - debugService: IDebugService, - element: IReplElement, - ): Promise { - // todo: we should expand DAP to allow copying more types here (#187784) - if (!(element instanceof ReplEvaluationResult)) { - return; +registerAction2(class extends ViewAction { + constructor() { + super({ + id: 'workbench.debug.action.copyAll', + title: localize('copyAll', "Copy All"), + viewId: REPL_VIEW_ID, + menu: { + id: MenuId.DebugConsoleContext, + group: '2_cutcopypaste', + order: 20 } + }); + } - const stackFrame = debugService.getViewModel().focusedStackFrame; - - const session = debugService.getViewModel().focusedSession; + async runInView(accessor: ServicesAccessor, view: Repl): Promise { + const clipboardService = accessor.get(IClipboardService); + await clipboardService.writeText(view.getVisibleContent()); + } +}); - if ( - !stackFrame || - !session || - !session.capabilities.supportsClipboardContext - ) { - return; +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'debug.replCopy', + title: localize('copy', "Copy"), + menu: { + id: MenuId.DebugConsoleContext, + group: '2_cutcopypaste', + order: 10 } + }); + } - try { - const evaluation = await session.evaluate( - element.originalExpression, - stackFrame.frameId, - "clipboard", - ); + async run(accessor: ServicesAccessor, element: IReplElement): Promise { + const clipboardService = accessor.get(IClipboardService); + const debugService = accessor.get(IDebugService); + const nativeSelection = dom.getActiveWindow().getSelection(); + const selectedText = nativeSelection?.toString(); + if (selectedText && selectedText.length > 0) { + return clipboardService.writeText(selectedText); + } else if (element) { + return clipboardService.writeText(await this.tryEvaluateAndCopy(debugService, element) || element.toString()); + } + } - return evaluation?.body.result; - } catch (e) { - return; - } + private async tryEvaluateAndCopy(debugService: IDebugService, element: IReplElement): Promise { + // todo: we should expand DAP to allow copying more types here (#187784) + if (!(element instanceof ReplEvaluationResult)) { + return; } - }, -); + + const stackFrame = debugService.getViewModel().focusedStackFrame; + const session = debugService.getViewModel().focusedSession; + if (!stackFrame || !session || !session.capabilities.supportsClipboardContext) { + return; + } + + try { + const evaluation = await session.evaluate(element.originalExpression, stackFrame.frameId, 'clipboard'); + return evaluation?.body.result; + } catch (e) { + return; + } + } +}); diff --git a/Source/vs/workbench/contrib/debug/browser/replFilter.ts b/Source/vs/workbench/contrib/debug/browser/replFilter.ts index 96f0181c82729..9970017a8831a 100644 --- a/Source/vs/workbench/contrib/debug/browser/replFilter.ts +++ b/Source/vs/workbench/contrib/debug/browser/replFilter.ts @@ -2,88 +2,66 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { - ITreeFilter, - TreeFilterResult, - TreeVisibility, -} from "../../../../base/browser/ui/tree/tree.js"; -import { matchesFuzzy } from "../../../../base/common/filters.js"; -import { splitGlobAware } from "../../../../base/common/glob.js"; -import { IReplElement } from "../common/debug.js"; -import { Variable } from "../common/debugModel.js"; -import { - ReplEvaluationInput, - ReplEvaluationResult, -} from "../common/replModel.js"; + +import { FuzzyScore, matchesFuzzy } from '../../../../base/common/filters.js'; +import { splitGlobAware } from '../../../../base/common/glob.js'; +import { ITreeFilter, TreeVisibility, TreeFilterResult } from '../../../../base/browser/ui/tree/tree.js'; +import { IReplElement } from '../common/debug.js'; +import { ReplEvaluationResult, ReplEvaluationInput } from '../common/replModel.js'; +import { Variable } from '../common/debugModel.js'; + type ParsedQuery = { - type: "include" | "exclude"; + type: 'include' | 'exclude'; query: string; }; -export class ReplFilter implements ITreeFilter { + +export class ReplFilter implements ITreeFilter { + static matchQuery = matchesFuzzy; - private _parsedQueries: ParsedQuery[] = []; + private _parsedQueries: ParsedQuery[] = []; set filterQuery(query: string) { this._parsedQueries = []; query = query.trim(); - if (query && query !== "") { - const filters = splitGlobAware(query, ",") - .map((s) => s.trim()) - .filter((s) => !!s.length); - + if (query && query !== '') { + const filters = splitGlobAware(query, ',').map(s => s.trim()).filter(s => !!s.length); for (const f of filters) { - if (f.startsWith("\\")) { - this._parsedQueries.push({ - type: "include", - query: f.slice(1), - }); - } else if (f.startsWith("!")) { - this._parsedQueries.push({ - type: "exclude", - query: f.slice(1), - }); + if (f.startsWith('\\')) { + this._parsedQueries.push({ type: 'include', query: f.slice(1) }); + } else if (f.startsWith('!')) { + this._parsedQueries.push({ type: 'exclude', query: f.slice(1) }); } else { - this._parsedQueries.push({ type: "include", query: f }); + this._parsedQueries.push({ type: 'include', query: f }); } } } } - filter( - element: IReplElement, - parentVisibility: TreeVisibility, - ): TreeFilterResult { - if ( - element instanceof ReplEvaluationInput || - element instanceof ReplEvaluationResult || - element instanceof Variable - ) { + + filter(element: IReplElement, parentVisibility: TreeVisibility): TreeFilterResult { + if (element instanceof ReplEvaluationInput || element instanceof ReplEvaluationResult || element instanceof Variable) { // Only filter the output events, everything else is visible https://github.com/microsoft/vscode/issues/105863 return TreeVisibility.Visible; } - let includeQueryPresent = false; + let includeQueryPresent = false; let includeQueryMatched = false; const text = element.toString(true); for (const { type, query } of this._parsedQueries) { - if (type === "exclude" && ReplFilter.matchQuery(query, text)) { + if (type === 'exclude' && ReplFilter.matchQuery(query, text)) { // If exclude query matches, ignore all other queries and hide return false; - } else if (type === "include") { + } else if (type === 'include') { includeQueryPresent = true; - if (ReplFilter.matchQuery(query, text)) { includeQueryMatched = true; } } } - return includeQueryPresent - ? includeQueryMatched - : typeof parentVisibility !== "undefined" - ? parentVisibility - : TreeVisibility.Visible; + + return includeQueryPresent ? includeQueryMatched : (typeof parentVisibility !== 'undefined' ? parentVisibility : TreeVisibility.Visible); } } diff --git a/Source/vs/workbench/contrib/debug/browser/variablesView.ts b/Source/vs/workbench/contrib/debug/browser/variablesView.ts index 878f4e99e2e60..222e0a7b57b65 100644 --- a/Source/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/Source/vs/workbench/contrib/debug/browser/variablesView.ts @@ -3,149 +3,69 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as dom from "../../../../base/browser/dom.js"; -import { ActionBar } from "../../../../base/browser/ui/actionbar/actionbar.js"; -import { - HighlightedLabel, - IHighlight, -} from "../../../../base/browser/ui/highlightedlabel/highlightedLabel.js"; -import { IListVirtualDelegate } from "../../../../base/browser/ui/list/list.js"; -import { IListAccessibilityProvider } from "../../../../base/browser/ui/list/listWidget.js"; -import { - AsyncDataTree, - IAsyncDataTreeViewState, -} from "../../../../base/browser/ui/tree/asyncDataTree.js"; -import { - ITreeContextMenuEvent, - ITreeMouseEvent, - ITreeNode, - ITreeRenderer, -} from "../../../../base/browser/ui/tree/tree.js"; -import { Action, IAction } from "../../../../base/common/actions.js"; -import { coalesce } from "../../../../base/common/arrays.js"; -import { RunOnceScheduler } from "../../../../base/common/async.js"; -import { - CancellationToken, - CancellationTokenSource, -} from "../../../../base/common/cancellation.js"; -import { Codicon } from "../../../../base/common/codicons.js"; -import { createMatches, FuzzyScore } from "../../../../base/common/filters.js"; -import { - IDisposable, - toDisposable, -} from "../../../../base/common/lifecycle.js"; -import { ThemeIcon } from "../../../../base/common/themables.js"; -import { localize } from "../../../../nls.js"; -import { getContextMenuActions } from "../../../../platform/actions/browser/menuEntryActionViewItem.js"; -import { - IMenuService, - MenuId, - registerAction2, -} from "../../../../platform/actions/common/actions.js"; -import { IClipboardService } from "../../../../platform/clipboard/common/clipboardService.js"; -import { CommandsRegistry } from "../../../../platform/commands/common/commands.js"; -import { IConfigurationService } from "../../../../platform/configuration/common/configuration.js"; -import { - ContextKeyExpr, - IContextKeyService, -} from "../../../../platform/contextkey/common/contextkey.js"; -import { - IContextMenuService, - IContextViewService, -} from "../../../../platform/contextview/browser/contextView.js"; -import { IHoverService } from "../../../../platform/hover/browser/hover.js"; -import { - IInstantiationService, - ServicesAccessor, -} from "../../../../platform/instantiation/common/instantiation.js"; -import { IKeybindingService } from "../../../../platform/keybinding/common/keybinding.js"; -import { WorkbenchAsyncDataTree } from "../../../../platform/list/browser/listService.js"; -import { INotificationService } from "../../../../platform/notification/common/notification.js"; -import { IOpenerService } from "../../../../platform/opener/common/opener.js"; -import { ProgressLocation } from "../../../../platform/progress/common/progress.js"; -import { ITelemetryService } from "../../../../platform/telemetry/common/telemetry.js"; -import { IThemeService } from "../../../../platform/theme/common/themeService.js"; -import { ViewAction, ViewPane } from "../../../browser/parts/views/viewPane.js"; -import { IViewletViewOptions } from "../../../browser/parts/views/viewsViewlet.js"; -import { IViewDescriptorService } from "../../../common/views.js"; -import { - IEditorService, - SIDE_GROUP, -} from "../../../services/editor/common/editorService.js"; -import { IExtensionService } from "../../../services/extensions/common/extensions.js"; -import { IExtensionsWorkbenchService } from "../../extensions/common/extensions.js"; -import { - CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, - CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, - CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, - CONTEXT_VARIABLES_FOCUSED, - DataBreakpointSetType, - DebugVisualizationType, - IDataBreakpointInfoResponse, - IDebugService, - IExpression, - IScope, - IStackFrame, - IViewModel, - VARIABLES_VIEW_ID, -} from "../common/debug.js"; -import { getContextForVariable } from "../common/debugContext.js"; -import { - ErrorScope, - Expression, - getUriForDebugMemory, - Scope, - StackFrame, - Variable, - VisualizedExpression, -} from "../common/debugModel.js"; -import { - DebugVisualizer, - IDebugVisualizerService, -} from "../common/debugVisualizers.js"; -import { - AbstractExpressionDataSource, - AbstractExpressionsRenderer, - IExpressionTemplateData, - IInputBoxOptions, - renderViewTree, -} from "./baseDebugView.js"; -import { - ADD_TO_WATCH_ID, - ADD_TO_WATCH_LABEL, - COPY_EVALUATE_PATH_ID, - COPY_EVALUATE_PATH_LABEL, - COPY_VALUE_ID, - COPY_VALUE_LABEL, -} from "./debugCommands.js"; -import { DebugExpressionRenderer } from "./debugExpressionRenderer.js"; +import * as dom from '../../../../base/browser/dom.js'; +import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js'; +import { HighlightedLabel, IHighlight } from '../../../../base/browser/ui/highlightedlabel/highlightedLabel.js'; +import { IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js'; +import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js'; +import { AsyncDataTree, IAsyncDataTreeViewState } from '../../../../base/browser/ui/tree/asyncDataTree.js'; +import { ITreeContextMenuEvent, ITreeMouseEvent, ITreeNode, ITreeRenderer } from '../../../../base/browser/ui/tree/tree.js'; +import { Action, IAction } from '../../../../base/common/actions.js'; +import { coalesce } from '../../../../base/common/arrays.js'; +import { RunOnceScheduler } from '../../../../base/common/async.js'; +import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { FuzzyScore, createMatches } from '../../../../base/common/filters.js'; +import { IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { localize } from '../../../../nls.js'; +import { getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { IMenuService, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; +import { CommandsRegistry } from '../../../../platform/commands/common/commands.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextMenuService, IContextViewService } from '../../../../platform/contextview/browser/contextView.js'; +import { IHoverService } from '../../../../platform/hover/browser/hover.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { WorkbenchAsyncDataTree } from '../../../../platform/list/browser/listService.js'; +import { INotificationService } from '../../../../platform/notification/common/notification.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; +import { ProgressLocation } from '../../../../platform/progress/common/progress.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { ViewAction, ViewPane } from '../../../browser/parts/views/viewPane.js'; +import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js'; +import { IViewDescriptorService } from '../../../common/views.js'; +import { IEditorService, SIDE_GROUP } from '../../../services/editor/common/editorService.js'; +import { IExtensionService } from '../../../services/extensions/common/extensions.js'; +import { IExtensionsWorkbenchService } from '../../extensions/common/extensions.js'; +import { CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_VARIABLES_FOCUSED, DataBreakpointSetType, DebugVisualizationType, IDataBreakpointInfoResponse, IDebugService, IExpression, IScope, IStackFrame, IViewModel, VARIABLES_VIEW_ID } from '../common/debug.js'; +import { getContextForVariable } from '../common/debugContext.js'; +import { ErrorScope, Expression, Scope, StackFrame, Variable, VisualizedExpression, getUriForDebugMemory } from '../common/debugModel.js'; +import { DebugVisualizer, IDebugVisualizerService } from '../common/debugVisualizers.js'; +import { AbstractExpressionDataSource, AbstractExpressionsRenderer, IExpressionTemplateData, IInputBoxOptions, renderViewTree } from './baseDebugView.js'; +import { ADD_TO_WATCH_ID, ADD_TO_WATCH_LABEL, COPY_EVALUATE_PATH_ID, COPY_EVALUATE_PATH_LABEL, COPY_VALUE_ID, COPY_VALUE_LABEL } from './debugCommands.js'; +import { DebugExpressionRenderer } from './debugExpressionRenderer.js'; const $ = dom.$; - let forgetScopes = true; let variableInternalContext: Variable | undefined; - let dataBreakpointInfoResponse: IDataBreakpointInfoResponse | undefined; interface IVariablesContext { sessionId: string | undefined; - container: - | DebugProtocol.Variable - | DebugProtocol.Scope - | DebugProtocol.EvaluateArguments; - + container: DebugProtocol.Variable | DebugProtocol.Scope | DebugProtocol.EvaluateArguments; variable: DebugProtocol.Variable; } export class VariablesView extends ViewPane { + private updateTreeScheduler: RunOnceScheduler; private needsRefresh = false; - private tree!: WorkbenchAsyncDataTree< - IStackFrame | null, - IExpression | IScope, - FuzzyScore - >; + private tree!: WorkbenchAsyncDataTree; private savedViewState = new Map(); private autoExpandedScopes = new Set(); @@ -162,40 +82,21 @@ export class VariablesView extends ViewPane { @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, @IHoverService hoverService: IHoverService, - @IMenuService private readonly menuService: IMenuService, + @IMenuService private readonly menuService: IMenuService ) { - super( - options, - keybindingService, - contextMenuService, - configurationService, - contextKeyService, - viewDescriptorService, - instantiationService, - openerService, - themeService, - telemetryService, - hoverService, - ); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService); // Use scheduler to prevent unnecessary flashing this.updateTreeScheduler = new RunOnceScheduler(async () => { - const stackFrame = - this.debugService.getViewModel().focusedStackFrame; + const stackFrame = this.debugService.getViewModel().focusedStackFrame; this.needsRefresh = false; - const input = this.tree.getInput(); - if (input) { - this.savedViewState.set( - input.getId(), - this.tree.getViewState(), - ); + this.savedViewState.set(input.getId(), this.tree.getViewState()); } if (!stackFrame) { await this.tree.setInput(null); - return; } @@ -204,8 +105,7 @@ export class VariablesView extends ViewPane { // Automatically expand the first non-expensive scope const scopes = await stackFrame.getScopes(); - - const toExpand = scopes.find((s) => !s.expensive); + const toExpand = scopes.find(s => !s.expensive); // A race condition could be present causing the scopes here to be different from the scopes that the tree just retrieved. // If that happened, don't try to reveal anything, it will be straightened out on the next update @@ -219,143 +119,82 @@ export class VariablesView extends ViewPane { protected override renderBody(container: HTMLElement): void { super.renderBody(container); - this.element.classList.add("debug-pane"); - container.classList.add("debug-variables"); - + this.element.classList.add('debug-pane'); + container.classList.add('debug-variables'); const treeContainer = renderViewTree(container); - - const expressionRenderer = this.instantiationService.createInstance( - DebugExpressionRenderer, - ); - this.tree = < - WorkbenchAsyncDataTree< - IStackFrame | null, - IExpression | IScope, - FuzzyScore - > - >this.instantiationService.createInstance( - WorkbenchAsyncDataTree, - "VariablesView", - treeContainer, - new VariablesDelegate(), + const expressionRenderer = this.instantiationService.createInstance(DebugExpressionRenderer); + this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'VariablesView', treeContainer, new VariablesDelegate(), [ - this.instantiationService.createInstance( - VariablesRenderer, - expressionRenderer, - ), - this.instantiationService.createInstance( - VisualizedVariableRenderer, - expressionRenderer, - ), + this.instantiationService.createInstance(VariablesRenderer, expressionRenderer), + this.instantiationService.createInstance(VisualizedVariableRenderer, expressionRenderer), new ScopesRenderer(), new ScopeErrorRenderer(), ], - this.instantiationService.createInstance(VariablesDataSource), - { - accessibilityProvider: new VariablesAccessibilityProvider(), - identityProvider: { - getId: (element: IExpression | IScope) => element.getId(), - }, - keyboardNavigationLabelProvider: { - getKeyboardNavigationLabel: (e: IExpression | IScope) => - e.name, - }, - overrideStyles: - this.getLocationBasedColors().listOverrideStyles, - }, - ); - - this._register( - VisualizedVariableRenderer.rendererOnVisualizationRange( - this.debugService.getViewModel(), - this.tree, - ), - ); - this.tree.setInput( - this.debugService.getViewModel().focusedStackFrame ?? null, - ); + this.instantiationService.createInstance(VariablesDataSource), { + accessibilityProvider: new VariablesAccessibilityProvider(), + identityProvider: { getId: (element: IExpression | IScope) => element.getId() }, + keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IExpression | IScope) => e.name }, + overrideStyles: this.getLocationBasedColors().listOverrideStyles + }); - CONTEXT_VARIABLES_FOCUSED.bindTo(this.tree.contextKeyService); + this._register(VisualizedVariableRenderer.rendererOnVisualizationRange(this.debugService.getViewModel(), this.tree)); + this.tree.setInput(this.debugService.getViewModel().focusedStackFrame ?? null); - this._register( - this.debugService.getViewModel().onDidFocusStackFrame((sf) => { - if (!this.isBodyVisible()) { - this.needsRefresh = true; + CONTEXT_VARIABLES_FOCUSED.bindTo(this.tree.contextKeyService); - return; - } + this._register(this.debugService.getViewModel().onDidFocusStackFrame(sf => { + if (!this.isBodyVisible()) { + this.needsRefresh = true; + return; + } - // Refresh the tree immediately if the user explictly changed stack frames. - // Otherwise postpone the refresh until user stops stepping. - const timeout = sf.explicit ? 0 : undefined; - this.updateTreeScheduler.schedule(timeout); - }), - ); - this._register( - this.debugService.getViewModel().onWillUpdateViews(() => { - const stackFrame = - this.debugService.getViewModel().focusedStackFrame; - - if (stackFrame && forgetScopes) { - stackFrame.forgetScopes(); - } - forgetScopes = true; - this.tree.updateChildren(); - }), - ); + // Refresh the tree immediately if the user explictly changed stack frames. + // Otherwise postpone the refresh until user stops stepping. + const timeout = sf.explicit ? 0 : undefined; + this.updateTreeScheduler.schedule(timeout); + })); + this._register(this.debugService.getViewModel().onWillUpdateViews(() => { + const stackFrame = this.debugService.getViewModel().focusedStackFrame; + if (stackFrame && forgetScopes) { + stackFrame.forgetScopes(); + } + forgetScopes = true; + this.tree.updateChildren(); + })); this._register(this.tree); - this._register( - this.tree.onMouseDblClick((e) => this.onMouseDblClick(e)), - ); - this._register( - this.tree.onContextMenu(async (e) => await this.onContextMenu(e)), - ); - - this._register( - this.onDidChangeBodyVisibility((visible) => { - if (visible && this.needsRefresh) { - this.updateTreeScheduler.schedule(); - } - }), - ); + this._register(this.tree.onMouseDblClick(e => this.onMouseDblClick(e))); + this._register(this.tree.onContextMenu(async e => await this.onContextMenu(e))); + this._register(this.onDidChangeBodyVisibility(visible => { + if (visible && this.needsRefresh) { + this.updateTreeScheduler.schedule(); + } + })); let horizontalScrolling: boolean | undefined; - this._register( - this.debugService.getViewModel().onDidSelectExpression((e) => { - const variable = e?.expression; - - if (variable && this.tree.hasNode(variable)) { - horizontalScrolling = this.tree.options.horizontalScrolling; - - if (horizontalScrolling) { - this.tree.updateOptions({ horizontalScrolling: false }); - } - - this.tree.rerender(variable); - } else if (!e && horizontalScrolling !== undefined) { - this.tree.updateOptions({ - horizontalScrolling: horizontalScrolling, - }); - horizontalScrolling = undefined; + this._register(this.debugService.getViewModel().onDidSelectExpression(e => { + const variable = e?.expression; + if (variable && this.tree.hasNode(variable)) { + horizontalScrolling = this.tree.options.horizontalScrolling; + if (horizontalScrolling) { + this.tree.updateOptions({ horizontalScrolling: false }); } - }), - ); - this._register( - this.debugService - .getViewModel() - .onDidEvaluateLazyExpression(async (e) => { - if (e instanceof Variable && this.tree.hasNode(e)) { - await this.tree.updateChildren(e, false, true); - await this.tree.expand(e); - } - }), - ); - this._register( - this.debugService.onDidEndSession(() => { - this.savedViewState.clear(); - this.autoExpandedScopes.clear(); - }), - ); + + this.tree.rerender(variable); + } else if (!e && horizontalScrolling !== undefined) { + this.tree.updateOptions({ horizontalScrolling: horizontalScrolling }); + horizontalScrolling = undefined; + } + })); + this._register(this.debugService.getViewModel().onDidEvaluateLazyExpression(async e => { + if (e instanceof Variable && this.tree.hasNode(e)) { + await this.tree.updateChildren(e, false, true); + await this.tree.expand(e); + } + })); + this._register(this.debugService.onDidEndSession(() => { + this.savedViewState.clear(); + this.autoExpandedScopes.clear(); + })); } protected override layoutBody(width: number, height: number): void { @@ -374,17 +213,12 @@ export class VariablesView extends ViewPane { private onMouseDblClick(e: ITreeMouseEvent): void { if (this.canSetExpressionValue(e.element)) { - this.debugService - .getViewModel() - .setSelectedExpression(e.element, false); + this.debugService.getViewModel().setSelectedExpression(e.element, false); } } - private canSetExpressionValue( - e: IExpression | IScope | null, - ): e is IExpression { + private canSetExpressionValue(e: IExpression | IScope | null): e is IExpression { const session = this.debugService.getViewModel().focusedSession; - if (!session) { return false; } @@ -393,126 +227,71 @@ export class VariablesView extends ViewPane { return !!e.treeItem.canEdit; } - return ( - e instanceof Variable && - !e.presentationHint?.attributes?.includes("readOnly") && - !e.presentationHint?.lazy - ); + return e instanceof Variable && !e.presentationHint?.attributes?.includes('readOnly') && !e.presentationHint?.lazy; } - private async onContextMenu( - e: ITreeContextMenuEvent, - ): Promise { + private async onContextMenu(e: ITreeContextMenuEvent): Promise { const variable = e.element; - if (!(variable instanceof Variable) || !variable.value) { return; } - return openContextMenuForVariableTreeElement( - this.contextKeyService, - this.menuService, - this.contextMenuService, - MenuId.DebugVariablesContext, - e, - ); + return openContextMenuForVariableTreeElement(this.contextKeyService, this.menuService, this.contextMenuService, MenuId.DebugVariablesContext, e); } } -export async function openContextMenuForVariableTreeElement( - parentContextKeyService: IContextKeyService, - menuService: IMenuService, - contextMenuService: IContextMenuService, - menuId: MenuId, - e: ITreeContextMenuEvent, -) { +export async function openContextMenuForVariableTreeElement(parentContextKeyService: IContextKeyService, menuService: IMenuService, contextMenuService: IContextMenuService, menuId: MenuId, e: ITreeContextMenuEvent) { const variable = e.element; - if (!(variable instanceof Variable) || !variable.value) { return; } - const contextKeyService = await getContextForVariableMenuWithDataAccess( - parentContextKeyService, - variable, - ); - + const contextKeyService = await getContextForVariableMenuWithDataAccess(parentContextKeyService, variable); const context: IVariablesContext = getVariablesContext(variable); + const menu = menuService.getMenuActions(menuId, contextKeyService, { arg: context, shouldForwardArgs: false }); - const menu = menuService.getMenuActions(menuId, contextKeyService, { - arg: context, - shouldForwardArgs: false, - }); - - const { secondary } = getContextMenuActions(menu, "inline"); + const { secondary } = getContextMenuActions(menu, 'inline'); contextMenuService.showContextMenu({ getAnchor: () => e.anchor, - getActions: () => secondary, + getActions: () => secondary }); } const getVariablesContext = (variable: Variable): IVariablesContext => ({ sessionId: variable.getSession()?.getId(), - container: - variable.parent instanceof Expression - ? { expression: variable.parent.name } - : (variable.parent as Variable | Scope).toDebugProtocolObject(), - variable: variable.toDebugProtocolObject(), + container: variable.parent instanceof Expression + ? { expression: variable.parent.name } + : (variable.parent as (Variable | Scope)).toDebugProtocolObject(), + variable: variable.toDebugProtocolObject() }); /** * Gets a context key overlay that has context for the given variable, including data access info. */ -async function getContextForVariableMenuWithDataAccess( - parentContext: IContextKeyService, - variable: Variable, -) { +async function getContextForVariableMenuWithDataAccess(parentContext: IContextKeyService, variable: Variable) { const session = variable.getSession(); - if (!session || !session.capabilities.supportsDataBreakpoints) { return getContextForVariableMenuBase(parentContext, variable); } const contextKeys: [string, unknown][] = []; - dataBreakpointInfoResponse = await session.dataBreakpointInfo( - variable.name, - variable.parent.reference, - ); - + dataBreakpointInfoResponse = await session.dataBreakpointInfo(variable.name, variable.parent.reference); const dataBreakpointId = dataBreakpointInfoResponse?.dataId; - const dataBreakpointAccessTypes = dataBreakpointInfoResponse?.accessTypes; if (!dataBreakpointAccessTypes) { - contextKeys.push([ - CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED.key, - !!dataBreakpointId, - ]); + contextKeys.push([CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED.key, !!dataBreakpointId]); } else { for (const accessType of dataBreakpointAccessTypes) { switch (accessType) { - case "read": - contextKeys.push([ - CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED.key, - !!dataBreakpointId, - ]); - + case 'read': + contextKeys.push([CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED.key, !!dataBreakpointId]); break; - - case "write": - contextKeys.push([ - CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED.key, - !!dataBreakpointId, - ]); - + case 'write': + contextKeys.push([CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED.key, !!dataBreakpointId]); break; - - case "readWrite": - contextKeys.push([ - CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED.key, - !!dataBreakpointId, - ]); - + case 'readWrite': + contextKeys.push([CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED.key, !!dataBreakpointId]); break; } } @@ -524,13 +303,8 @@ async function getContextForVariableMenuWithDataAccess( /** * Gets a context key overlay that has context for the given variable. */ -function getContextForVariableMenuBase( - parentContext: IContextKeyService, - variable: Variable, - additionalContext: [string, unknown][] = [], -) { +function getContextForVariableMenuBase(parentContext: IContextKeyService, variable: Variable, additionalContext: [string, unknown][] = []) { variableInternalContext = variable; - return getContextForVariable(parentContext, variable, additionalContext); } @@ -538,13 +312,9 @@ function isStackFrame(obj: any): obj is IStackFrame { return obj instanceof StackFrame; } -class VariablesDataSource extends AbstractExpressionDataSource< - IStackFrame | null, - IExpression | IScope -> { - public override hasChildren( - element: IStackFrame | null | IExpression | IScope, - ): boolean { +class VariablesDataSource extends AbstractExpressionDataSource { + + public override hasChildren(element: IStackFrame | null | IExpression | IScope): boolean { if (!element) { return false; } @@ -555,9 +325,7 @@ class VariablesDataSource extends AbstractExpressionDataSource< return element.hasChildren; } - protected override doGetChildren( - element: IStackFrame | IExpression | IScope, - ): Promise<(IExpression | IScope)[]> { + protected override doGetChildren(element: IStackFrame | IExpression | IScope): Promise<(IExpression | IScope)[]> { if (isStackFrame(element)) { return element.getScopes(); } @@ -572,6 +340,7 @@ interface IScopeTemplateData { } class VariablesDelegate implements IListVirtualDelegate { + getHeight(element: IExpression | IScope): number { return 22; } @@ -593,32 +362,23 @@ class VariablesDelegate implements IListVirtualDelegate { } } -class ScopesRenderer - implements ITreeRenderer -{ - static readonly ID = "scope"; +class ScopesRenderer implements ITreeRenderer { + + static readonly ID = 'scope'; get templateId(): string { return ScopesRenderer.ID; } renderTemplate(container: HTMLElement): IScopeTemplateData { - const name = dom.append(container, $(".scope")); - + const name = dom.append(container, $('.scope')); const label = new HighlightedLabel(name); return { name, label }; } - renderElement( - element: ITreeNode, - index: number, - templateData: IScopeTemplateData, - ): void { - templateData.label.set( - element.element.name, - createMatches(element.filterData), - ); + renderElement(element: ITreeNode, index: number, templateData: IScopeTemplateData): void { + templateData.label.set(element.element.name, createMatches(element.filterData)); } disposeTemplate(templateData: IScopeTemplateData): void { @@ -630,28 +390,21 @@ interface IScopeErrorTemplateData { error: HTMLElement; } -class ScopeErrorRenderer - implements ITreeRenderer -{ - static readonly ID = "scopeError"; +class ScopeErrorRenderer implements ITreeRenderer { + + static readonly ID = 'scopeError'; get templateId(): string { return ScopeErrorRenderer.ID; } renderTemplate(container: HTMLElement): IScopeErrorTemplateData { - const wrapper = dom.append(container, $(".scope")); - - const error = dom.append(wrapper, $(".error")); - + const wrapper = dom.append(container, $('.scope')); + const error = dom.append(wrapper, $('.error')); return { error }; } - renderElement( - element: ITreeNode, - index: number, - templateData: IScopeErrorTemplateData, - ): void { + renderElement(element: ITreeNode, index: number, templateData: IScopeErrorTemplateData): void { templateData.error.innerText = element.element.name; } @@ -661,16 +414,13 @@ class ScopeErrorRenderer } export class VisualizedVariableRenderer extends AbstractExpressionsRenderer { - public static readonly ID = "viz"; + public static readonly ID = 'viz'; /** * Registers a helper that rerenders the tree when visualization is requested * or cancelled./ */ - public static rendererOnVisualizationRange( - model: IViewModel, - tree: AsyncDataTree, - ): IDisposable { + public static rendererOnVisualizationRange(model: IViewModel, tree: AsyncDataTree): IDisposable { return model.onDidChangeVisualization(({ original }) => { if (!tree.hasNode(original)) { return; @@ -679,6 +429,7 @@ export class VisualizedVariableRenderer extends AbstractExpressionsRenderer { const parent: IExpression = tree.getParentElement(original); tree.updateChildren(parent, false, false); }); + } constructor( @@ -687,8 +438,7 @@ export class VisualizedVariableRenderer extends AbstractExpressionsRenderer { @IContextViewService contextViewService: IContextViewService, @IHoverService hoverService: IHoverService, @IMenuService private readonly menuService: IMenuService, - @IContextKeyService - private readonly contextKeyService: IContextKeyService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { super(debugService, contextViewService, hoverService); } @@ -697,57 +447,37 @@ export class VisualizedVariableRenderer extends AbstractExpressionsRenderer { return VisualizedVariableRenderer.ID; } - public override renderElement( - node: ITreeNode, - index: number, - data: IExpressionTemplateData, - ): void { + public override renderElement(node: ITreeNode, index: number, data: IExpressionTemplateData): void { data.elementDisposable.clear(); - super.renderExpressionElement(node.element, node, data); } - protected override renderExpression( - expression: IExpression, - data: IExpressionTemplateData, - highlights: IHighlight[], - ): void { + protected override renderExpression(expression: IExpression, data: IExpressionTemplateData, highlights: IHighlight[]): void { const viz = expression as VisualizedExpression; let text = viz.name; - - if (viz.value && typeof viz.name === "string") { - text += ":"; + if (viz.value && typeof viz.name === 'string') { + text += ':'; } data.label.set(text, highlights, viz.name); - data.elementDisposable.add( - this.expressionRenderer.renderValue(data.value, viz, { - showChanged: false, - maxValueLength: 1024, - colorize: true, - session: expression.getSession(), - }), - ); + data.elementDisposable.add(this.expressionRenderer.renderValue(data.value, viz, { + showChanged: false, + maxValueLength: 1024, + colorize: true, + session: expression.getSession(), + })); } - protected override getInputBoxOptions( - expression: IExpression, - ): IInputBoxOptions | undefined { + protected override getInputBoxOptions(expression: IExpression): IInputBoxOptions | undefined { const viz = expression; - return { initialValue: expression.value, - ariaLabel: localize( - "variableValueAriaLabel", - "Type new variable value", - ), + ariaLabel: localize('variableValueAriaLabel', "Type new variable value"), validationOptions: { - validation: () => - viz.errorMessage ? { content: viz.errorMessage } : null, + validation: () => viz.errorMessage ? ({ content: viz.errorMessage }) : null }, onFinish: (value: string, success: boolean) => { viz.errorMessage = undefined; - if (success) { viz.edit(value).then(() => { // Do not refresh scopes due to a node limitation #15520 @@ -755,50 +485,23 @@ export class VisualizedVariableRenderer extends AbstractExpressionsRenderer { this.debugService.getViewModel().updateViews(); }); } - }, + } }; } - protected override renderActionBar( - actionBar: ActionBar, - expression: IExpression, - _data: IExpressionTemplateData, - ) { + protected override renderActionBar(actionBar: ActionBar, expression: IExpression, _data: IExpressionTemplateData) { const viz = expression as VisualizedExpression; + const contextKeyService = viz.original ? getContextForVariableMenuBase(this.contextKeyService, viz.original) : this.contextKeyService; + const context = viz.original ? getVariablesContext(viz.original) : undefined; + const menu = this.menuService.getMenuActions(MenuId.DebugVariablesContext, contextKeyService, { arg: context, shouldForwardArgs: false }); - const contextKeyService = viz.original - ? getContextForVariableMenuBase( - this.contextKeyService, - viz.original, - ) - : this.contextKeyService; - - const context = viz.original - ? getVariablesContext(viz.original) - : undefined; - - const menu = this.menuService.getMenuActions( - MenuId.DebugVariablesContext, - contextKeyService, - { arg: context, shouldForwardArgs: false }, - ); - - const { primary } = getContextMenuActions(menu, "inline"); + const { primary } = getContextMenuActions(menu, 'inline'); if (viz.original) { - const action = new Action( - "debugViz", - localize("removeVisualizer", "Remove Visualizer"), - ThemeIcon.asClassName(Codicon.eye), - true, - () => - this.debugService - .getViewModel() - .setVisualizedExpression(viz.original!, undefined), - ); + const action = new Action('debugViz', localize('removeVisualizer', 'Remove Visualizer'), ThemeIcon.asClassName(Codicon.eye), true, () => this.debugService.getViewModel().setVisualizedExpression(viz.original!, undefined)); action.checked = true; primary.push(action); - actionBar.domNode.style.display = "initial"; + actionBar.domNode.style.display = 'initial'; } actionBar.clear(); actionBar.context = context; @@ -807,17 +510,15 @@ export class VisualizedVariableRenderer extends AbstractExpressionsRenderer { } export class VariablesRenderer extends AbstractExpressionsRenderer { - static readonly ID = "variable"; + + static readonly ID = 'variable'; constructor( private readonly expressionRenderer: DebugExpressionRenderer, @IMenuService private readonly menuService: IMenuService, - @IContextKeyService - private readonly contextKeyService: IContextKeyService, - @IDebugVisualizerService - private readonly visualization: IDebugVisualizerService, - @IContextMenuService - private readonly contextMenuService: IContextMenuService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IDebugVisualizerService private readonly visualization: IDebugVisualizerService, + @IContextMenuService private readonly contextMenuService: IContextMenuService, @IDebugService debugService: IDebugService, @IContextViewService contextViewService: IContextViewService, @IHoverService hoverService: IHoverService, @@ -829,57 +530,31 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { return VariablesRenderer.ID; } - protected renderExpression( - expression: IExpression, - data: IExpressionTemplateData, - highlights: IHighlight[], - ): void { - data.elementDisposable.add( - this.expressionRenderer.renderVariable( - data, - expression as Variable, - { - highlights, - showChanged: true, - }, - ), - ); + protected renderExpression(expression: IExpression, data: IExpressionTemplateData, highlights: IHighlight[]): void { + data.elementDisposable.add(this.expressionRenderer.renderVariable(data, expression as Variable, { + highlights, + showChanged: true, + })); } - public override renderElement( - node: ITreeNode, - index: number, - data: IExpressionTemplateData, - ): void { + public override renderElement(node: ITreeNode, index: number, data: IExpressionTemplateData): void { data.elementDisposable.clear(); - super.renderExpressionElement(node.element, node, data); } protected getInputBoxOptions(expression: IExpression): IInputBoxOptions { const variable = expression; - return { initialValue: expression.value, - ariaLabel: localize( - "variableValueAriaLabel", - "Type new variable value", - ), + ariaLabel: localize('variableValueAriaLabel', "Type new variable value"), validationOptions: { - validation: () => - variable.errorMessage - ? { content: variable.errorMessage } - : null, + validation: () => variable.errorMessage ? ({ content: variable.errorMessage }) : null }, onFinish: (value: string, success: boolean) => { variable.errorMessage = undefined; - - const focusedStackFrame = - this.debugService.getViewModel().focusedStackFrame; - + const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame; if (success && variable.value !== value && focusedStackFrame) { - variable - .setVariable(value, focusedStackFrame) + variable.setVariable(value, focusedStackFrame) // Need to force watch expressions and variables to update since a variable change can have an effect on both .then(() => { // Do not refresh scopes due to a node limitation #15520 @@ -887,31 +562,17 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { this.debugService.getViewModel().updateViews(); }); } - }, + } }; } - protected override renderActionBar( - actionBar: ActionBar, - expression: IExpression, - data: IExpressionTemplateData, - ) { + protected override renderActionBar(actionBar: ActionBar, expression: IExpression, data: IExpressionTemplateData) { const variable = expression as Variable; - - const contextKeyService = getContextForVariableMenuBase( - this.contextKeyService, - variable, - ); + const contextKeyService = getContextForVariableMenuBase(this.contextKeyService, variable); const context = getVariablesContext(variable); - - const menu = this.menuService.getMenuActions( - MenuId.DebugVariablesContext, - contextKeyService, - { arg: context, shouldForwardArgs: false }, - ); - - const { primary } = getContextMenuActions(menu, "inline"); + const menu = this.menuService.getMenuActions(MenuId.DebugVariablesContext, contextKeyService, { arg: context, shouldForwardArgs: false }); + const { primary } = getContextMenuActions(menu, 'inline'); actionBar.clear(); actionBar.context = context; @@ -919,74 +580,31 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { const cts = new CancellationTokenSource(); data.elementDisposable.add(toDisposable(() => cts.dispose(true))); - this.visualization - .getApplicableFor(expression, cts.token) - .then((result) => { - data.elementDisposable.add(result); - - const originalExpression = - (expression instanceof VisualizedExpression && - expression.original) || - expression; - - const actions = result.object.map( - (v) => - new Action( - "debugViz", - v.name, - v.iconClass || "debug-viz-icon", - undefined, - this.useVisualizer( - v, - originalExpression, - cts.token, - ), - ), - ); - - if (actions.length === 0) { - // no-op - } else if (actions.length === 1) { - actionBar.push(actions[0], { icon: true, label: false }); - } else { - actionBar.push( - new Action( - "debugViz", - localize("useVisualizer", "Visualize Variable..."), - ThemeIcon.asClassName(Codicon.eye), - undefined, - () => - this.pickVisualizer( - actions, - originalExpression, - data, - ), - ), - { icon: true, label: false }, - ); - } - }); + this.visualization.getApplicableFor(expression, cts.token).then(result => { + data.elementDisposable.add(result); + + const originalExpression = (expression instanceof VisualizedExpression && expression.original) || expression; + const actions = result.object.map(v => new Action('debugViz', v.name, v.iconClass || 'debug-viz-icon', undefined, this.useVisualizer(v, originalExpression, cts.token))); + if (actions.length === 0) { + // no-op + } else if (actions.length === 1) { + actionBar.push(actions[0], { icon: true, label: false }); + } else { + actionBar.push(new Action('debugViz', localize('useVisualizer', 'Visualize Variable...'), ThemeIcon.asClassName(Codicon.eye), undefined, () => this.pickVisualizer(actions, originalExpression, data)), { icon: true, label: false }); + } + }); } - private pickVisualizer( - actions: IAction[], - expression: IExpression, - data: IExpressionTemplateData, - ) { + private pickVisualizer(actions: IAction[], expression: IExpression, data: IExpressionTemplateData) { this.contextMenuService.showContextMenu({ getAnchor: () => data.actionBar!.getContainer(), getActions: () => actions, }); } - private useVisualizer( - viz: DebugVisualizer, - expression: IExpression, - token: CancellationToken, - ) { + private useVisualizer(viz: DebugVisualizer, expression: IExpression, token: CancellationToken) { return async () => { const resolved = await viz.resolve(token); - if (token.isCancellationRequested) { return; } @@ -994,64 +612,40 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { if (resolved.type === DebugVisualizationType.Command) { viz.execute(); } else { - const replacement = - await this.visualization.getVisualizedNodeFor( - resolved.id, - expression, - ); - + const replacement = await this.visualization.getVisualizedNodeFor(resolved.id, expression); if (replacement) { - this.debugService - .getViewModel() - .setVisualizedExpression(expression, replacement); + this.debugService.getViewModel().setVisualizedExpression(expression, replacement); } } }; } } -class VariablesAccessibilityProvider - implements IListAccessibilityProvider -{ +class VariablesAccessibilityProvider implements IListAccessibilityProvider { + getWidgetAriaLabel(): string { - return localize("variablesAriaTreeLabel", "Debug Variables"); + return localize('variablesAriaTreeLabel', "Debug Variables"); } getAriaLabel(element: IExpression | IScope): string | null { if (element instanceof Scope) { - return localize( - "variableScopeAriaLabel", - "Scope {0}", - element.name, - ); + return localize('variableScopeAriaLabel', "Scope {0}", element.name); } if (element instanceof Variable) { - return localize( - { - key: "variableAriaLabel", - comment: [ - "Placeholders are variable name and variable value respectivly. They should not be translated.", - ], - }, - "{0}, value {1}", - element.name, - element.value, - ); + return localize({ key: 'variableAriaLabel', comment: ['Placeholders are variable name and variable value respectivly. They should not be translated.'] }, "{0}, value {1}", element.name, element.value); } return null; } } -export const SET_VARIABLE_ID = "debug.setVariable"; +export const SET_VARIABLE_ID = 'debug.setVariable'; CommandsRegistry.registerCommand({ id: SET_VARIABLE_ID, handler: (accessor: ServicesAccessor) => { const debugService = accessor.get(IDebugService); - debugService - .getViewModel() - .setSelectedExpression(variableInternalContext, false); - }, + debugService.getViewModel().setSelectedExpression(variableInternalContext, false); + } }); CommandsRegistry.registerCommand({ @@ -1059,99 +653,63 @@ CommandsRegistry.registerCommand({ description: COPY_VALUE_LABEL, }, id: COPY_VALUE_ID, - handler: async ( - accessor: ServicesAccessor, - arg: Variable | Expression | IVariablesContext, - ctx?: (Variable | Expression)[], - ) => { + handler: async (accessor: ServicesAccessor, arg: Variable | Expression | IVariablesContext, ctx?: (Variable | Expression)[]) => { const debugService = accessor.get(IDebugService); - const clipboardService = accessor.get(IClipboardService); - - let elementContext = ""; - + let elementContext = ''; let elements: (Variable | Expression)[]; - if (arg instanceof Variable || arg instanceof Expression) { - elementContext = "watch"; + elementContext = 'watch'; elements = ctx ? ctx : []; } else { - elementContext = "variables"; + elementContext = 'variables'; elements = variableInternalContext ? [variableInternalContext] : []; } const stackFrame = debugService.getViewModel().focusedStackFrame; - const session = debugService.getViewModel().focusedSession; - if (!stackFrame || !session || elements.length === 0) { return; } - const evalContext = session.capabilities.supportsClipboardContext - ? "clipboard" - : elementContext; - - const toEvaluate = elements.map((element) => - element instanceof Variable - ? element.evaluateName || element.value - : element.name, - ); + const evalContext = session.capabilities.supportsClipboardContext ? 'clipboard' : elementContext; + const toEvaluate = elements.map(element => element instanceof Variable ? (element.evaluateName || element.value) : element.name); try { - const evaluations = await Promise.all( - toEvaluate.map((expr) => - session.evaluate(expr, stackFrame.frameId, evalContext), - ), - ); - - const result = coalesce(evaluations).map( - (evaluation) => evaluation.body.result, - ); - + const evaluations = await Promise.all(toEvaluate.map(expr => session.evaluate(expr, stackFrame.frameId, evalContext))); + const result = coalesce(evaluations).map(evaluation => evaluation.body.result); if (result.length) { - clipboardService.writeText(result.join("\n")); + clipboardService.writeText(result.join('\n')); } } catch (e) { - const result = elements.map((element) => element.value); - clipboardService.writeText(result.join("\n")); + const result = elements.map(element => element.value); + clipboardService.writeText(result.join('\n')); } - }, + } }); -export const VIEW_MEMORY_ID = "workbench.debug.viewlet.action.viewMemory"; - -const HEX_EDITOR_EXTENSION_ID = "ms-vscode.hexeditor"; +export const VIEW_MEMORY_ID = 'workbench.debug.viewlet.action.viewMemory'; -const HEX_EDITOR_EDITOR_ID = "hexEditor.hexedit"; +const HEX_EDITOR_EXTENSION_ID = 'ms-vscode.hexeditor'; +const HEX_EDITOR_EDITOR_ID = 'hexEditor.hexedit'; CommandsRegistry.registerCommand({ id: VIEW_MEMORY_ID, - handler: async ( - accessor: ServicesAccessor, - arg: IVariablesContext | IExpression, - ctx?: (Variable | Expression)[], - ) => { + handler: async (accessor: ServicesAccessor, arg: IVariablesContext | IExpression, ctx?: (Variable | Expression)[]) => { const debugService = accessor.get(IDebugService); - let sessionId: string; - let memoryReference: string; - - if ("sessionId" in arg) { - // IVariablesContext + if ('sessionId' in arg) { // IVariablesContext if (!arg.sessionId || !arg.variable.memoryReference) { return; } sessionId = arg.sessionId; memoryReference = arg.variable.memoryReference; - } else { - // IExpression + } else { // IExpression if (!arg.memoryReference) { return; } const focused = debugService.getViewModel().focusedSession; - if (!focused) { return; } @@ -1160,140 +718,79 @@ CommandsRegistry.registerCommand({ memoryReference = arg.memoryReference; } - const extensionsWorkbenchService = accessor.get( - IExtensionsWorkbenchService, - ); - + const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); const editorService = accessor.get(IEditorService); - const notificationService = accessor.get(INotificationService); - const extensionService = accessor.get(IExtensionService); - const telemetryService = accessor.get(ITelemetryService); - const ext = await extensionService.getExtension( - HEX_EDITOR_EXTENSION_ID, - ); - - if ( - ext || - (await tryInstallHexEditor( - extensionsWorkbenchService, - notificationService, - )) - ) { + const ext = await extensionService.getExtension(HEX_EDITOR_EXTENSION_ID); + if (ext || await tryInstallHexEditor(extensionsWorkbenchService, notificationService)) { /* __GDPR__ "debug/didViewMemory" : { "owner": "connor4312", "debugType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ - telemetryService.publicLog("debug/didViewMemory", { - debugType: debugService.getModel().getSession(sessionId) - ?.configuration.type, + telemetryService.publicLog('debug/didViewMemory', { + debugType: debugService.getModel().getSession(sessionId)?.configuration.type, }); - await editorService.openEditor( - { - resource: getUriForDebugMemory(sessionId, memoryReference), - options: { - revealIfOpened: true, - override: HEX_EDITOR_EDITOR_ID, - }, + await editorService.openEditor({ + resource: getUriForDebugMemory(sessionId, memoryReference), + options: { + revealIfOpened: true, + override: HEX_EDITOR_EDITOR_ID, }, - SIDE_GROUP, - ); + }, SIDE_GROUP); } - }, + } }); -async function tryInstallHexEditor( - extensionsWorkbenchService: IExtensionsWorkbenchService, - notificationService: INotificationService, -): Promise { +async function tryInstallHexEditor(extensionsWorkbenchService: IExtensionsWorkbenchService, notificationService: INotificationService): Promise { try { - await extensionsWorkbenchService.install( - HEX_EDITOR_EXTENSION_ID, - { - justification: localize( - "viewMemory.prompt", - "Inspecting binary data requires this extension.", - ), - enable: true, - }, - ProgressLocation.Notification, - ); - + await extensionsWorkbenchService.install(HEX_EDITOR_EXTENSION_ID, { + justification: localize("viewMemory.prompt", "Inspecting binary data requires this extension."), + enable: true + }, ProgressLocation.Notification); return true; } catch (error) { notificationService.error(error); - return false; } } -export const BREAK_WHEN_VALUE_CHANGES_ID = "debug.breakWhenValueChanges"; +export const BREAK_WHEN_VALUE_CHANGES_ID = 'debug.breakWhenValueChanges'; CommandsRegistry.registerCommand({ id: BREAK_WHEN_VALUE_CHANGES_ID, handler: async (accessor: ServicesAccessor) => { const debugService = accessor.get(IDebugService); - if (dataBreakpointInfoResponse) { - await debugService.addDataBreakpoint({ - description: dataBreakpointInfoResponse.description, - src: { - type: DataBreakpointSetType.Variable, - dataId: dataBreakpointInfoResponse.dataId!, - }, - canPersist: !!dataBreakpointInfoResponse.canPersist, - accessTypes: dataBreakpointInfoResponse.accessTypes, - accessType: "write", - }); + await debugService.addDataBreakpoint({ description: dataBreakpointInfoResponse.description, src: { type: DataBreakpointSetType.Variable, dataId: dataBreakpointInfoResponse.dataId! }, canPersist: !!dataBreakpointInfoResponse.canPersist, accessTypes: dataBreakpointInfoResponse.accessTypes, accessType: 'write' }); } - }, + } }); -export const BREAK_WHEN_VALUE_IS_ACCESSED_ID = "debug.breakWhenValueIsAccessed"; +export const BREAK_WHEN_VALUE_IS_ACCESSED_ID = 'debug.breakWhenValueIsAccessed'; CommandsRegistry.registerCommand({ id: BREAK_WHEN_VALUE_IS_ACCESSED_ID, handler: async (accessor: ServicesAccessor) => { const debugService = accessor.get(IDebugService); - if (dataBreakpointInfoResponse) { - await debugService.addDataBreakpoint({ - description: dataBreakpointInfoResponse.description, - src: { - type: DataBreakpointSetType.Variable, - dataId: dataBreakpointInfoResponse.dataId!, - }, - canPersist: !!dataBreakpointInfoResponse.canPersist, - accessTypes: dataBreakpointInfoResponse.accessTypes, - accessType: "readWrite", - }); + await debugService.addDataBreakpoint({ description: dataBreakpointInfoResponse.description, src: { type: DataBreakpointSetType.Variable, dataId: dataBreakpointInfoResponse.dataId! }, canPersist: !!dataBreakpointInfoResponse.canPersist, accessTypes: dataBreakpointInfoResponse.accessTypes, accessType: 'readWrite' }); } - }, + } }); -export const BREAK_WHEN_VALUE_IS_READ_ID = "debug.breakWhenValueIsRead"; +export const BREAK_WHEN_VALUE_IS_READ_ID = 'debug.breakWhenValueIsRead'; CommandsRegistry.registerCommand({ id: BREAK_WHEN_VALUE_IS_READ_ID, handler: async (accessor: ServicesAccessor) => { const debugService = accessor.get(IDebugService); - if (dataBreakpointInfoResponse) { - await debugService.addDataBreakpoint({ - description: dataBreakpointInfoResponse.description, - src: { - type: DataBreakpointSetType.Variable, - dataId: dataBreakpointInfoResponse.dataId!, - }, - canPersist: !!dataBreakpointInfoResponse.canPersist, - accessTypes: dataBreakpointInfoResponse.accessTypes, - accessType: "read", - }); + await debugService.addDataBreakpoint({ description: dataBreakpointInfoResponse.description, src: { type: DataBreakpointSetType.Variable, dataId: dataBreakpointInfoResponse.dataId! }, canPersist: !!dataBreakpointInfoResponse.canPersist, accessTypes: dataBreakpointInfoResponse.accessTypes, accessType: 'read' }); } - }, + } }); CommandsRegistry.registerCommand({ @@ -1304,7 +801,7 @@ CommandsRegistry.registerCommand({ handler: async (accessor: ServicesAccessor, context: IVariablesContext) => { const clipboardService = accessor.get(IClipboardService); await clipboardService.writeText(context.variable.evaluateName!); - }, + } }); CommandsRegistry.registerCommand({ @@ -1315,28 +812,26 @@ CommandsRegistry.registerCommand({ handler: async (accessor: ServicesAccessor, context: IVariablesContext) => { const debugService = accessor.get(IDebugService); debugService.addWatchExpression(context.variable.evaluateName); - }, + } }); -registerAction2( - class extends ViewAction { - constructor() { - super({ - id: "variables.collapse", - viewId: VARIABLES_VIEW_ID, - title: localize("collapse", "Collapse All"), - f1: false, - icon: Codicon.collapseAll, - menu: { - id: MenuId.ViewTitle, - group: "navigation", - when: ContextKeyExpr.equals("view", VARIABLES_VIEW_ID), - }, - }); - } +registerAction2(class extends ViewAction { + constructor() { + super({ + id: 'variables.collapse', + viewId: VARIABLES_VIEW_ID, + title: localize('collapse', "Collapse All"), + f1: false, + icon: Codicon.collapseAll, + menu: { + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyExpr.equals('view', VARIABLES_VIEW_ID) + } + }); + } - runInView(_accessor: ServicesAccessor, view: VariablesView) { - view.collapseAll(); - } - }, -); + runInView(_accessor: ServicesAccessor, view: VariablesView) { + view.collapseAll(); + } +}); diff --git a/Source/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/Source/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index d0c45d975d74f..b1b75a73bc0fc 100644 --- a/Source/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/Source/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -3,111 +3,48 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDragAndDropData } from "../../../../base/browser/dnd.js"; -import { ActionBar } from "../../../../base/browser/ui/actionbar/actionbar.js"; -import { IHighlight } from "../../../../base/browser/ui/highlightedlabel/highlightedLabel.js"; -import { - IListVirtualDelegate, - ListDragOverEffectPosition, - ListDragOverEffectType, -} from "../../../../base/browser/ui/list/list.js"; -import { - ElementsDragAndDropData, - ListViewTargetSector, -} from "../../../../base/browser/ui/list/listView.js"; -import { IListAccessibilityProvider } from "../../../../base/browser/ui/list/listWidget.js"; -import { - ITreeContextMenuEvent, - ITreeDragAndDrop, - ITreeDragOverReaction, - ITreeMouseEvent, - ITreeNode, -} from "../../../../base/browser/ui/tree/tree.js"; -import { RunOnceScheduler } from "../../../../base/common/async.js"; -import { Codicon } from "../../../../base/common/codicons.js"; -import { FuzzyScore } from "../../../../base/common/filters.js"; -import { localize } from "../../../../nls.js"; -import { - getContextMenuActions, - getFlatContextMenuActions, -} from "../../../../platform/actions/browser/menuEntryActionViewItem.js"; -import { - Action2, - IMenu, - IMenuService, - MenuId, - registerAction2, -} from "../../../../platform/actions/common/actions.js"; -import { IConfigurationService } from "../../../../platform/configuration/common/configuration.js"; -import { - ContextKeyExpr, - IContextKey, - IContextKeyService, -} from "../../../../platform/contextkey/common/contextkey.js"; -import { - IContextMenuService, - IContextViewService, -} from "../../../../platform/contextview/browser/contextView.js"; -import { IHoverService } from "../../../../platform/hover/browser/hover.js"; -import { - IInstantiationService, - ServicesAccessor, -} from "../../../../platform/instantiation/common/instantiation.js"; -import { IKeybindingService } from "../../../../platform/keybinding/common/keybinding.js"; -import { WorkbenchAsyncDataTree } from "../../../../platform/list/browser/listService.js"; -import { IOpenerService } from "../../../../platform/opener/common/opener.js"; -import { ITelemetryService } from "../../../../platform/telemetry/common/telemetry.js"; -import { IThemeService } from "../../../../platform/theme/common/themeService.js"; -import { ViewAction, ViewPane } from "../../../browser/parts/views/viewPane.js"; -import { IViewletViewOptions } from "../../../browser/parts/views/viewsViewlet.js"; -import { IViewDescriptorService } from "../../../common/views.js"; -import { - CONTEXT_CAN_VIEW_MEMORY, - CONTEXT_VARIABLE_IS_READONLY, - CONTEXT_WATCH_EXPRESSIONS_EXIST, - CONTEXT_WATCH_EXPRESSIONS_FOCUSED, - CONTEXT_WATCH_ITEM_TYPE, - IDebugConfiguration, - IDebugService, - IExpression, - WATCH_VIEW_ID, -} from "../common/debug.js"; -import { - Expression, - Variable, - VisualizedExpression, -} from "../common/debugModel.js"; -import { - AbstractExpressionDataSource, - AbstractExpressionsRenderer, - IExpressionTemplateData, - IInputBoxOptions, - renderViewTree, -} from "./baseDebugView.js"; -import { DebugExpressionRenderer } from "./debugExpressionRenderer.js"; -import { - watchExpressionsAdd, - watchExpressionsRemoveAll, -} from "./debugIcons.js"; -import { - VariablesRenderer, - VisualizedVariableRenderer, -} from "./variablesView.js"; +import { IDragAndDropData } from '../../../../base/browser/dnd.js'; +import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js'; +import { IHighlight } from '../../../../base/browser/ui/highlightedlabel/highlightedLabel.js'; +import { IListVirtualDelegate, ListDragOverEffectPosition, ListDragOverEffectType } from '../../../../base/browser/ui/list/list.js'; +import { ElementsDragAndDropData, ListViewTargetSector } from '../../../../base/browser/ui/list/listView.js'; +import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js'; +import { ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction, ITreeMouseEvent, ITreeNode } from '../../../../base/browser/ui/tree/tree.js'; +import { RunOnceScheduler } from '../../../../base/common/async.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { FuzzyScore } from '../../../../base/common/filters.js'; +import { localize } from '../../../../nls.js'; +import { getContextMenuActions, getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { Action2, IMenu, IMenuService, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { ContextKeyExpr, IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextMenuService, IContextViewService } from '../../../../platform/contextview/browser/contextView.js'; +import { IHoverService } from '../../../../platform/hover/browser/hover.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { WorkbenchAsyncDataTree } from '../../../../platform/list/browser/listService.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { ViewAction, ViewPane } from '../../../browser/parts/views/viewPane.js'; +import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js'; +import { IViewDescriptorService } from '../../../common/views.js'; +import { CONTEXT_CAN_VIEW_MEMORY, CONTEXT_VARIABLE_IS_READONLY, CONTEXT_WATCH_EXPRESSIONS_EXIST, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_WATCH_ITEM_TYPE, IDebugConfiguration, IDebugService, IExpression, WATCH_VIEW_ID } from '../common/debug.js'; +import { Expression, Variable, VisualizedExpression } from '../common/debugModel.js'; +import { AbstractExpressionDataSource, AbstractExpressionsRenderer, IExpressionTemplateData, IInputBoxOptions, renderViewTree } from './baseDebugView.js'; +import { DebugExpressionRenderer } from './debugExpressionRenderer.js'; +import { watchExpressionsAdd, watchExpressionsRemoveAll } from './debugIcons.js'; +import { VariablesRenderer, VisualizedVariableRenderer } from './variablesView.js'; const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; - let ignoreViewUpdates = false; - let useCachedEvaluation = false; export class WatchExpressionsView extends ViewPane { + private watchExpressionsUpdatedScheduler: RunOnceScheduler; private needsRefresh = false; - private tree!: WorkbenchAsyncDataTree< - IDebugService | IExpression, - IExpression, - FuzzyScore - >; + private tree!: WorkbenchAsyncDataTree; private watchExpressionsExist: IContextKey; private watchItemType: IContextKey; private variableReadonly: IContextKey; @@ -127,210 +64,122 @@ export class WatchExpressionsView extends ViewPane { @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, @IHoverService hoverService: IHoverService, - @IMenuService menuService: IMenuService, + @IMenuService menuService: IMenuService ) { - super( - options, - keybindingService, - contextMenuService, - configurationService, - contextKeyService, - viewDescriptorService, - instantiationService, - openerService, - themeService, - telemetryService, - hoverService, - ); - - this.menu = menuService.createMenu( - MenuId.DebugWatchContext, - contextKeyService, - ); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService); + + this.menu = menuService.createMenu(MenuId.DebugWatchContext, contextKeyService); this._register(this.menu); this.watchExpressionsUpdatedScheduler = new RunOnceScheduler(() => { this.needsRefresh = false; this.tree.updateChildren(); }, 50); - this.watchExpressionsExist = - CONTEXT_WATCH_EXPRESSIONS_EXIST.bindTo(contextKeyService); - this.variableReadonly = - CONTEXT_VARIABLE_IS_READONLY.bindTo(contextKeyService); - this.watchExpressionsExist.set( - this.debugService.getModel().getWatchExpressions().length > 0, - ); + this.watchExpressionsExist = CONTEXT_WATCH_EXPRESSIONS_EXIST.bindTo(contextKeyService); + this.variableReadonly = CONTEXT_VARIABLE_IS_READONLY.bindTo(contextKeyService); + this.watchExpressionsExist.set(this.debugService.getModel().getWatchExpressions().length > 0); this.watchItemType = CONTEXT_WATCH_ITEM_TYPE.bindTo(contextKeyService); - this.expressionRenderer = instantiationService.createInstance( - DebugExpressionRenderer, - ); + this.expressionRenderer = instantiationService.createInstance(DebugExpressionRenderer); } protected override renderBody(container: HTMLElement): void { super.renderBody(container); - this.element.classList.add("debug-pane"); - container.classList.add("debug-watch"); - + this.element.classList.add('debug-pane'); + container.classList.add('debug-watch'); const treeContainer = renderViewTree(container); - const expressionsRenderer = this.instantiationService.createInstance( - WatchExpressionsRenderer, - this.expressionRenderer, - ); - this.tree = < - WorkbenchAsyncDataTree< - IDebugService | IExpression, - IExpression, - FuzzyScore - > - >this.instantiationService.createInstance( - WorkbenchAsyncDataTree, - "WatchExpressions", - treeContainer, - new WatchExpressionsDelegate(), + const expressionsRenderer = this.instantiationService.createInstance(WatchExpressionsRenderer, this.expressionRenderer); + this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'WatchExpressions', treeContainer, new WatchExpressionsDelegate(), [ expressionsRenderer, - this.instantiationService.createInstance( - VariablesRenderer, - this.expressionRenderer, - ), - this.instantiationService.createInstance( - VisualizedVariableRenderer, - this.expressionRenderer, - ), + this.instantiationService.createInstance(VariablesRenderer, this.expressionRenderer), + this.instantiationService.createInstance(VisualizedVariableRenderer, this.expressionRenderer), ], - this.instantiationService.createInstance( - WatchExpressionsDataSource, - ), - { - accessibilityProvider: - new WatchExpressionsAccessibilityProvider(), - identityProvider: { - getId: (element: IExpression) => element.getId(), - }, - keyboardNavigationLabelProvider: { - getKeyboardNavigationLabel: (e: IExpression) => { - if ( - e === - this.debugService - .getViewModel() - .getSelectedExpression()?.expression - ) { - // Don't filter input box - return undefined; - } + this.instantiationService.createInstance(WatchExpressionsDataSource), { + accessibilityProvider: new WatchExpressionsAccessibilityProvider(), + identityProvider: { getId: (element: IExpression) => element.getId() }, + keyboardNavigationLabelProvider: { + getKeyboardNavigationLabel: (e: IExpression) => { + if (e === this.debugService.getViewModel().getSelectedExpression()?.expression) { + // Don't filter input box + return undefined; + } - return e.name; - }, - }, - dnd: new WatchExpressionsDragAndDrop(this.debugService), - overrideStyles: - this.getLocationBasedColors().listOverrideStyles, + return e.name; + } }, - ); + dnd: new WatchExpressionsDragAndDrop(this.debugService), + overrideStyles: this.getLocationBasedColors().listOverrideStyles + }); this._register(this.tree); this.tree.setInput(this.debugService); CONTEXT_WATCH_EXPRESSIONS_FOCUSED.bindTo(this.tree.contextKeyService); - this._register( - VisualizedVariableRenderer.rendererOnVisualizationRange( - this.debugService.getViewModel(), - this.tree, - ), - ); - this._register(this.tree.onContextMenu((e) => this.onContextMenu(e))); - this._register( - this.tree.onMouseDblClick((e) => this.onMouseDblClick(e)), - ); - this._register( - this.debugService - .getModel() - .onDidChangeWatchExpressions(async (we) => { - this.watchExpressionsExist.set( - this.debugService.getModel().getWatchExpressions() - .length > 0, - ); - - if (!this.isBodyVisible()) { - this.needsRefresh = true; - } else { - if (we && !we.name) { - // We are adding a new input box, no need to re-evaluate watch expressions - useCachedEvaluation = true; - } - await this.tree.updateChildren(); - useCachedEvaluation = false; - - if (we instanceof Expression) { - this.tree.reveal(we); - } - } - }), - ); - this._register( - this.debugService.getViewModel().onDidFocusStackFrame(() => { - if (!this.isBodyVisible()) { - this.needsRefresh = true; - - return; - } - - if (!this.watchExpressionsUpdatedScheduler.isScheduled()) { - this.watchExpressionsUpdatedScheduler.schedule(); + this._register(VisualizedVariableRenderer.rendererOnVisualizationRange(this.debugService.getViewModel(), this.tree)); + this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); + this._register(this.tree.onMouseDblClick(e => this.onMouseDblClick(e))); + this._register(this.debugService.getModel().onDidChangeWatchExpressions(async we => { + this.watchExpressionsExist.set(this.debugService.getModel().getWatchExpressions().length > 0); + if (!this.isBodyVisible()) { + this.needsRefresh = true; + } else { + if (we && !we.name) { + // We are adding a new input box, no need to re-evaluate watch expressions + useCachedEvaluation = true; } - }), - ); - this._register( - this.debugService.getViewModel().onWillUpdateViews(() => { - if (!ignoreViewUpdates) { - this.tree.updateChildren(); + await this.tree.updateChildren(); + useCachedEvaluation = false; + if (we instanceof Expression) { + this.tree.reveal(we); } - }), - ); + } + })); + this._register(this.debugService.getViewModel().onDidFocusStackFrame(() => { + if (!this.isBodyVisible()) { + this.needsRefresh = true; + return; + } - this._register( - this.onDidChangeBodyVisibility((visible) => { - if (visible && this.needsRefresh) { - this.watchExpressionsUpdatedScheduler.schedule(); - } - }), - ); + if (!this.watchExpressionsUpdatedScheduler.isScheduled()) { + this.watchExpressionsUpdatedScheduler.schedule(); + } + })); + this._register(this.debugService.getViewModel().onWillUpdateViews(() => { + if (!ignoreViewUpdates) { + this.tree.updateChildren(); + } + })); + this._register(this.onDidChangeBodyVisibility(visible => { + if (visible && this.needsRefresh) { + this.watchExpressionsUpdatedScheduler.schedule(); + } + })); let horizontalScrolling: boolean | undefined; - this._register( - this.debugService.getViewModel().onDidSelectExpression((e) => { - const expression = e?.expression; - - if (expression && this.tree.hasNode(expression)) { - horizontalScrolling = this.tree.options.horizontalScrolling; - - if (horizontalScrolling) { - this.tree.updateOptions({ horizontalScrolling: false }); - } + this._register(this.debugService.getViewModel().onDidSelectExpression(e => { + const expression = e?.expression; + if (expression && this.tree.hasNode(expression)) { + horizontalScrolling = this.tree.options.horizontalScrolling; + if (horizontalScrolling) { + this.tree.updateOptions({ horizontalScrolling: false }); + } - if (expression.name) { - // Only rerender if the input is already done since otherwise the tree is not yet aware of the new element - this.tree.rerender(expression); - } - } else if (!expression && horizontalScrolling !== undefined) { - this.tree.updateOptions({ - horizontalScrolling: horizontalScrolling, - }); - horizontalScrolling = undefined; + if (expression.name) { + // Only rerender if the input is already done since otherwise the tree is not yet aware of the new element + this.tree.rerender(expression); } - }), - ); - - this._register( - this.debugService - .getViewModel() - .onDidEvaluateLazyExpression(async (e) => { - if (e instanceof Variable && this.tree.hasNode(e)) { - await this.tree.updateChildren(e, false, true); - await this.tree.expand(e); - } - }), - ); + } else if (!expression && horizontalScrolling !== undefined) { + this.tree.updateOptions({ horizontalScrolling: horizontalScrolling }); + horizontalScrolling = undefined; + } + })); + + this._register(this.debugService.getViewModel().onDidEvaluateLazyExpression(async e => { + if (e instanceof Variable && this.tree.hasNode(e)) { + await this.tree.updateChildren(e, false, true); + await this.tree.expand(e); + } + })); } protected override layoutBody(height: number, width: number): void { @@ -348,30 +197,16 @@ export class WatchExpressionsView extends ViewPane { } private onMouseDblClick(e: ITreeMouseEvent): void { - if ( - (e.browserEvent.target as HTMLElement).className.indexOf( - "twistie", - ) >= 0 - ) { + if ((e.browserEvent.target as HTMLElement).className.indexOf('twistie') >= 0) { // Ignore double click events on twistie return; } const element = e.element; // double click on primitive value: open input box to be able to select and copy value. - const selectedExpression = this.debugService - .getViewModel() - .getSelectedExpression(); - - if ( - (element instanceof Expression && - element !== selectedExpression?.expression) || - (element instanceof VisualizedExpression && - element.treeItem.canEdit) - ) { - this.debugService - .getViewModel() - .setSelectedExpression(element, false); + const selectedExpression = this.debugService.getViewModel().getSelectedExpression(); + if ((element instanceof Expression && element !== selectedExpression?.expression) || (element instanceof VisualizedExpression && element.treeItem.canEdit)) { + this.debugService.getViewModel().setSelectedExpression(element, false); } else if (!element) { // Double click in watch panel triggers to add a new watch expression this.debugService.addWatchExpression(); @@ -380,43 +215,22 @@ export class WatchExpressionsView extends ViewPane { private onContextMenu(e: ITreeContextMenuEvent): void { const element = e.element; - const selection = this.tree.getSelection(); - this.watchItemType.set( - element instanceof Expression - ? "expression" - : element instanceof Variable - ? "variable" - : undefined, - ); - - const attributes = - element instanceof Variable - ? element.presentationHint?.attributes - : undefined; - this.variableReadonly.set( - (!!attributes && attributes.indexOf("readOnly") >= 0) || - !!element?.presentationHint?.lazy, - ); - - const actions = getFlatContextMenuActions( - this.menu.getActions({ arg: element, shouldForwardArgs: true }), - ); + this.watchItemType.set(element instanceof Expression ? 'expression' : element instanceof Variable ? 'variable' : undefined); + const attributes = element instanceof Variable ? element.presentationHint?.attributes : undefined; + this.variableReadonly.set(!!attributes && attributes.indexOf('readOnly') >= 0 || !!element?.presentationHint?.lazy); + const actions = getFlatContextMenuActions(this.menu.getActions({ arg: element, shouldForwardArgs: true })); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => actions, - getActionsContext: () => - element && selection.includes(element) - ? selection - : element - ? [element] - : [], + getActionsContext: () => element && selection.includes(element) ? selection : element ? [element] : [], }); } } class WatchExpressionsDelegate implements IListVirtualDelegate { + getHeight(_element: IExpression): number { return 22; } @@ -436,61 +250,42 @@ class WatchExpressionsDelegate implements IListVirtualDelegate { } function isDebugService(element: any): element is IDebugService { - return typeof element.getConfigurationManager === "function"; + return typeof element.getConfigurationManager === 'function'; } -class WatchExpressionsDataSource extends AbstractExpressionDataSource< - IDebugService, - IExpression -> { +class WatchExpressionsDataSource extends AbstractExpressionDataSource { + public override hasChildren(element: IExpression | IDebugService): boolean { return isDebugService(element) || element.hasChildren; } - protected override doGetChildren( - element: IDebugService | IExpression, - ): Promise> { + protected override doGetChildren(element: IDebugService | IExpression): Promise> { if (isDebugService(element)) { const debugService = element as IDebugService; - - const watchExpressions = debugService - .getModel() - .getWatchExpressions(); - + const watchExpressions = debugService.getModel().getWatchExpressions(); const viewModel = debugService.getViewModel(); - - return Promise.all( - watchExpressions.map((we) => - !!we.name && !useCachedEvaluation - ? we - .evaluate( - viewModel.focusedSession!, - viewModel.focusedStackFrame!, - "watch", - ) - .then(() => we) - : Promise.resolve(we), - ), - ); + return Promise.all(watchExpressions.map(we => !!we.name && !useCachedEvaluation + ? we.evaluate(viewModel.focusedSession!, viewModel.focusedStackFrame!, 'watch').then(() => we) + : Promise.resolve(we))); } return element.getChildren(); } } + export class WatchExpressionsRenderer extends AbstractExpressionsRenderer { - static readonly ID = "watchexpression"; + + static readonly ID = 'watchexpression'; constructor( private readonly expressionRenderer: DebugExpressionRenderer, @IMenuService private readonly menuService: IMenuService, - @IContextKeyService - private readonly contextKeyService: IContextKeyService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, @IDebugService debugService: IDebugService, @IContextViewService contextViewService: IContextViewService, @IHoverService hoverService: IHoverService, - @IConfigurationService - private configurationService: IConfigurationService, + @IConfigurationService private configurationService: IConfigurationService, ) { super(debugService, contextViewService, hoverService); } @@ -499,148 +294,90 @@ export class WatchExpressionsRenderer extends AbstractExpressionsRenderer { return WatchExpressionsRenderer.ID; } - public override renderElement( - node: ITreeNode, - index: number, - data: IExpressionTemplateData, - ): void { + public override renderElement(node: ITreeNode, index: number, data: IExpressionTemplateData): void { data.elementDisposable.clear(); - data.elementDisposable.add( - this.configurationService.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration("debug.showVariableTypes")) { - super.renderExpressionElement(node.element, node, data); - } - }), - ); - + data.elementDisposable.add(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('debug.showVariableTypes')) { + super.renderExpressionElement(node.element, node, data); + } + })); super.renderExpressionElement(node.element, node, data); } - protected renderExpression( - expression: IExpression, - data: IExpressionTemplateData, - highlights: IHighlight[], - ): void { + protected renderExpression(expression: IExpression, data: IExpressionTemplateData, highlights: IHighlight[]): void { let text: string; - data.type.textContent = ""; - - const showType = - this.configurationService.getValue( - "debug", - ).showVariableTypes; - + data.type.textContent = ''; + const showType = this.configurationService.getValue('debug').showVariableTypes; if (showType && expression.type) { - text = - typeof expression.value === "string" - ? `${expression.name}: ` - : expression.name; + text = typeof expression.value === 'string' ? `${expression.name}: ` : expression.name; //render type - data.type.textContent = expression.type + " ="; + data.type.textContent = expression.type + ' ='; } else { - text = - typeof expression.value === "string" - ? `${expression.name} =` - : expression.name; + text = typeof expression.value === 'string' ? `${expression.name} =` : expression.name; } let title: string; - if (expression.type) { if (showType) { title = `${expression.name}`; } else { - title = - expression.type === expression.value - ? expression.type - : `${expression.type}`; + title = expression.type === expression.value ? + expression.type : + `${expression.type}`; } } else { title = expression.value; } data.label.set(text, highlights, title); - data.elementDisposable.add( - this.expressionRenderer.renderValue(data.value, expression, { - showChanged: true, - maxValueLength: MAX_VALUE_RENDER_LENGTH_IN_VIEWLET, - colorize: true, - session: expression.getSession(), - }), - ); + data.elementDisposable.add(this.expressionRenderer.renderValue(data.value, expression, { + showChanged: true, + maxValueLength: MAX_VALUE_RENDER_LENGTH_IN_VIEWLET, + colorize: true, + session: expression.getSession(), + })); } - protected getInputBoxOptions( - expression: IExpression, - settingValue: boolean, - ): IInputBoxOptions { + protected getInputBoxOptions(expression: IExpression, settingValue: boolean): IInputBoxOptions { if (settingValue) { return { initialValue: expression.value, - ariaLabel: localize("typeNewValue", "Type new value"), + ariaLabel: localize('typeNewValue', "Type new value"), onFinish: async (value: string, success: boolean) => { if (success && value) { - const focusedFrame = - this.debugService.getViewModel().focusedStackFrame; - - if ( - focusedFrame && - (expression instanceof Variable || - expression instanceof Expression) - ) { + const focusedFrame = this.debugService.getViewModel().focusedStackFrame; + if (focusedFrame && (expression instanceof Variable || expression instanceof Expression)) { await expression.setExpression(value, focusedFrame); this.debugService.getViewModel().updateViews(); } } - }, + } }; } return { - initialValue: expression.name ? expression.name : "", - ariaLabel: localize( - "watchExpressionInputAriaLabel", - "Type watch expression", - ), - placeholder: localize( - "watchExpressionPlaceholder", - "Expression to watch", - ), + initialValue: expression.name ? expression.name : '', + ariaLabel: localize('watchExpressionInputAriaLabel', "Type watch expression"), + placeholder: localize('watchExpressionPlaceholder', "Expression to watch"), onFinish: (value: string, success: boolean) => { if (success && value) { - this.debugService.renameWatchExpression( - expression.getId(), - value, - ); + this.debugService.renameWatchExpression(expression.getId(), value); ignoreViewUpdates = true; this.debugService.getViewModel().updateViews(); ignoreViewUpdates = false; } else if (!expression.name) { - this.debugService.removeWatchExpressions( - expression.getId(), - ); + this.debugService.removeWatchExpressions(expression.getId()); } - }, + } }; } - protected override renderActionBar( - actionBar: ActionBar, - expression: IExpression, - ) { - const contextKeyService = getContextForWatchExpressionMenu( - this.contextKeyService, - expression, - ); - + protected override renderActionBar(actionBar: ActionBar, expression: IExpression) { + const contextKeyService = getContextForWatchExpressionMenu(this.contextKeyService, expression); const context = expression; + const menu = this.menuService.getMenuActions(MenuId.DebugWatchContext, contextKeyService, { arg: context, shouldForwardArgs: false }); - const menu = this.menuService.getMenuActions( - MenuId.DebugWatchContext, - contextKeyService, - { arg: context, shouldForwardArgs: false }, - ); - - const { primary } = getContextMenuActions(menu, "inline"); + const { primary } = getContextMenuActions(menu, 'inline'); actionBar.clear(); actionBar.context = context; @@ -651,73 +388,44 @@ export class WatchExpressionsRenderer extends AbstractExpressionsRenderer { /** * Gets a context key overlay that has context for the given expression. */ -function getContextForWatchExpressionMenu( - parentContext: IContextKeyService, - expression: IExpression, -) { +function getContextForWatchExpressionMenu(parentContext: IContextKeyService, expression: IExpression) { return parentContext.createOverlay([ [CONTEXT_CAN_VIEW_MEMORY.key, expression.memoryReference !== undefined], - [CONTEXT_WATCH_ITEM_TYPE.key, "expression"], + [CONTEXT_WATCH_ITEM_TYPE.key, 'expression'] ]); } -class WatchExpressionsAccessibilityProvider - implements IListAccessibilityProvider -{ +class WatchExpressionsAccessibilityProvider implements IListAccessibilityProvider { + getWidgetAriaLabel(): string { - return localize( - { - comment: ["Debug is a noun in this context, not a verb."], - key: "watchAriaTreeLabel", - }, - "Debug Watch Expressions", - ); + return localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'watchAriaTreeLabel' }, "Debug Watch Expressions"); } getAriaLabel(element: IExpression): string { if (element instanceof Expression) { - return localize( - "watchExpressionAriaLabel", - "{0}, value {1}", - (element).name, - (element).value, - ); + return localize('watchExpressionAriaLabel', "{0}, value {1}", (element).name, (element).value); } // Variable - return localize( - "watchVariableAriaLabel", - "{0}, value {1}", - (element).name, - (element).value, - ); + return localize('watchVariableAriaLabel', "{0}, value {1}", (element).name, (element).value); } } class WatchExpressionsDragAndDrop implements ITreeDragAndDrop { - constructor(private debugService: IDebugService) {} - - onDragOver( - data: IDragAndDropData, - targetElement: IExpression | undefined, - targetIndex: number | undefined, - targetSector: ListViewTargetSector | undefined, - originalEvent: DragEvent, - ): boolean | ITreeDragOverReaction { + + constructor(private debugService: IDebugService) { } + + onDragOver(data: IDragAndDropData, targetElement: IExpression | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction { if (!(data instanceof ElementsDragAndDropData)) { return false; } - const expressions = (data as ElementsDragAndDropData) - .elements; - + const expressions = (data as ElementsDragAndDropData).elements; if (!(expressions.length > 0 && expressions[0] instanceof Expression)) { return false; } - let dropEffectPosition: ListDragOverEffectPosition | undefined = - undefined; - + let dropEffectPosition: ListDragOverEffectPosition | undefined = undefined; if (targetIndex === undefined) { // Hovering over the list dropEffectPosition = ListDragOverEffectPosition.After; @@ -727,33 +435,18 @@ class WatchExpressionsDragAndDrop implements ITreeDragAndDrop { switch (targetSector) { case ListViewTargetSector.TOP: case ListViewTargetSector.CENTER_TOP: - dropEffectPosition = ListDragOverEffectPosition.Before; - break; - + dropEffectPosition = ListDragOverEffectPosition.Before; break; case ListViewTargetSector.CENTER_BOTTOM: case ListViewTargetSector.BOTTOM: - dropEffectPosition = ListDragOverEffectPosition.After; - break; + dropEffectPosition = ListDragOverEffectPosition.After; break; } } - return { - accept: true, - effect: { - type: ListDragOverEffectType.Move, - position: dropEffectPosition, - }, - feedback: [targetIndex], - } satisfies ITreeDragOverReaction; + return { accept: true, effect: { type: ListDragOverEffectType.Move, position: dropEffectPosition }, feedback: [targetIndex] } satisfies ITreeDragOverReaction; } getDragURI(element: IExpression): string | null { - if ( - !(element instanceof Expression) || - element === - this.debugService.getViewModel().getSelectedExpression() - ?.expression - ) { + if (!(element instanceof Expression) || element === this.debugService.getViewModel().getSelectedExpression()?.expression) { return null; } @@ -768,38 +461,27 @@ class WatchExpressionsDragAndDrop implements ITreeDragAndDrop { return undefined; } - drop( - data: IDragAndDropData, - targetElement: IExpression, - targetIndex: number | undefined, - targetSector: ListViewTargetSector | undefined, - originalEvent: DragEvent, - ): void { + drop(data: IDragAndDropData, targetElement: IExpression, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): void { if (!(data instanceof ElementsDragAndDropData)) { return; } - const draggedElement = (data as ElementsDragAndDropData) - .elements[0]; - + const draggedElement = (data as ElementsDragAndDropData).elements[0]; if (!(draggedElement instanceof Expression)) { - throw new Error("Invalid dragged element"); + throw new Error('Invalid dragged element'); } const watches = this.debugService.getModel().getWatchExpressions(); - const sourcePosition = watches.indexOf(draggedElement); let targetPosition; - if (targetElement instanceof Expression) { targetPosition = watches.indexOf(targetElement); switch (targetSector) { case ListViewTargetSector.BOTTOM: case ListViewTargetSector.CENTER_BOTTOM: - targetPosition++; - break; + targetPosition++; break; } if (sourcePosition < targetPosition) { @@ -809,93 +491,80 @@ class WatchExpressionsDragAndDrop implements ITreeDragAndDrop { targetPosition = watches.length - 1; } - this.debugService.moveWatchExpression( - draggedElement.getId(), - targetPosition, - ); + this.debugService.moveWatchExpression(draggedElement.getId(), targetPosition); } - dispose(): void {} + dispose(): void { } } -registerAction2( - class Collapse extends ViewAction { - constructor() { - super({ - id: "watch.collapse", - viewId: WATCH_VIEW_ID, - title: localize("collapse", "Collapse All"), - f1: false, - icon: Codicon.collapseAll, - precondition: CONTEXT_WATCH_EXPRESSIONS_EXIST, - menu: { - id: MenuId.ViewTitle, - order: 30, - group: "navigation", - when: ContextKeyExpr.equals("view", WATCH_VIEW_ID), - }, - }); - } +registerAction2(class Collapse extends ViewAction { + constructor() { + super({ + id: 'watch.collapse', + viewId: WATCH_VIEW_ID, + title: localize('collapse', "Collapse All"), + f1: false, + icon: Codicon.collapseAll, + precondition: CONTEXT_WATCH_EXPRESSIONS_EXIST, + menu: { + id: MenuId.ViewTitle, + order: 30, + group: 'navigation', + when: ContextKeyExpr.equals('view', WATCH_VIEW_ID) + } + }); + } - runInView(_accessor: ServicesAccessor, view: WatchExpressionsView) { - view.collapseAll(); - } - }, -); - -export const ADD_WATCH_ID = "workbench.debug.viewlet.action.addWatchExpression"; // Use old and long id for backwards compatibility -export const ADD_WATCH_LABEL = localize("addWatchExpression", "Add Expression"); - -registerAction2( - class AddWatchExpressionAction extends Action2 { - constructor() { - super({ - id: ADD_WATCH_ID, - title: ADD_WATCH_LABEL, - f1: false, - icon: watchExpressionsAdd, - menu: { - id: MenuId.ViewTitle, - group: "navigation", - when: ContextKeyExpr.equals("view", WATCH_VIEW_ID), - }, - }); - } + runInView(_accessor: ServicesAccessor, view: WatchExpressionsView) { + view.collapseAll(); + } +}); + +export const ADD_WATCH_ID = 'workbench.debug.viewlet.action.addWatchExpression'; // Use old and long id for backwards compatibility +export const ADD_WATCH_LABEL = localize('addWatchExpression', "Add Expression"); + +registerAction2(class AddWatchExpressionAction extends Action2 { + constructor() { + super({ + id: ADD_WATCH_ID, + title: ADD_WATCH_LABEL, + f1: false, + icon: watchExpressionsAdd, + menu: { + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyExpr.equals('view', WATCH_VIEW_ID) + } + }); + } - run(accessor: ServicesAccessor): void { - const debugService = accessor.get(IDebugService); - debugService.addWatchExpression(); - } - }, -); - -export const REMOVE_WATCH_EXPRESSIONS_COMMAND_ID = - "workbench.debug.viewlet.action.removeAllWatchExpressions"; -export const REMOVE_WATCH_EXPRESSIONS_LABEL = localize( - "removeAllWatchExpressions", - "Remove All Expressions", -); -registerAction2( - class RemoveAllWatchExpressionsAction extends Action2 { - constructor() { - super({ - id: REMOVE_WATCH_EXPRESSIONS_COMMAND_ID, // Use old and long id for backwards compatibility - title: REMOVE_WATCH_EXPRESSIONS_LABEL, - f1: false, - icon: watchExpressionsRemoveAll, - precondition: CONTEXT_WATCH_EXPRESSIONS_EXIST, - menu: { - id: MenuId.ViewTitle, - order: 20, - group: "navigation", - when: ContextKeyExpr.equals("view", WATCH_VIEW_ID), - }, - }); - } + run(accessor: ServicesAccessor): void { + const debugService = accessor.get(IDebugService); + debugService.addWatchExpression(); + } +}); + +export const REMOVE_WATCH_EXPRESSIONS_COMMAND_ID = 'workbench.debug.viewlet.action.removeAllWatchExpressions'; +export const REMOVE_WATCH_EXPRESSIONS_LABEL = localize('removeAllWatchExpressions', "Remove All Expressions"); +registerAction2(class RemoveAllWatchExpressionsAction extends Action2 { + constructor() { + super({ + id: REMOVE_WATCH_EXPRESSIONS_COMMAND_ID, // Use old and long id for backwards compatibility + title: REMOVE_WATCH_EXPRESSIONS_LABEL, + f1: false, + icon: watchExpressionsRemoveAll, + precondition: CONTEXT_WATCH_EXPRESSIONS_EXIST, + menu: { + id: MenuId.ViewTitle, + order: 20, + group: 'navigation', + when: ContextKeyExpr.equals('view', WATCH_VIEW_ID) + } + }); + } - run(accessor: ServicesAccessor): void { - const debugService = accessor.get(IDebugService); - debugService.removeWatchExpressions(); - } - }, -); + run(accessor: ServicesAccessor): void { + const debugService = accessor.get(IDebugService); + debugService.removeWatchExpressions(); + } +}); diff --git a/Source/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts b/Source/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts index d8ea137fe2eca..308bf216205c1 100644 --- a/Source/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts +++ b/Source/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts @@ -3,89 +3,48 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { - $, - addDisposableListener, - append, - clearNode, - Dimension, -} from "../../../../base/browser/dom.js"; -import { ActionBar } from "../../../../base/browser/ui/actionbar/actionbar.js"; -import { getDefaultHoverDelegate } from "../../../../base/browser/ui/hover/hoverDelegateFactory.js"; -import { renderLabelWithIcons } from "../../../../base/browser/ui/iconLabel/iconLabels.js"; -import { - IListRenderer, - IListVirtualDelegate, -} from "../../../../base/browser/ui/list/list.js"; -import { IListAccessibilityProvider } from "../../../../base/browser/ui/list/listWidget.js"; -import { Action, IAction, Separator } from "../../../../base/common/actions.js"; -import { isNonEmptyArray } from "../../../../base/common/arrays.js"; -import { RunOnceScheduler } from "../../../../base/common/async.js"; -import { fromNow } from "../../../../base/common/date.js"; -import { dispose, IDisposable } from "../../../../base/common/lifecycle.js"; -import { Schemas } from "../../../../base/common/network.js"; -import * as nls from "../../../../nls.js"; -import { Categories } from "../../../../platform/action/common/actionCommonCategories.js"; -import { getContextMenuActions } from "../../../../platform/actions/browser/menuEntryActionViewItem.js"; -import { - Action2, - IMenuService, - MenuId, -} from "../../../../platform/actions/common/actions.js"; -import { IClipboardService } from "../../../../platform/clipboard/common/clipboardService.js"; -import { - ContextKeyExpr, - IContextKeyService, -} from "../../../../platform/contextkey/common/contextkey.js"; -import { IContextMenuService } from "../../../../platform/contextview/browser/contextView.js"; -import { - ExtensionIdentifier, - ExtensionIdentifierMap, - IExtensionDescription, -} from "../../../../platform/extensions/common/extensions.js"; -import { IHoverService } from "../../../../platform/hover/browser/hover.js"; -import { - IInstantiationService, - ServicesAccessor, -} from "../../../../platform/instantiation/common/instantiation.js"; -import { ILabelService } from "../../../../platform/label/common/label.js"; -import { WorkbenchList } from "../../../../platform/list/browser/listService.js"; -import { - INotificationService, - Severity, -} from "../../../../platform/notification/common/notification.js"; -import { Registry } from "../../../../platform/registry/common/platform.js"; -import { IStorageService } from "../../../../platform/storage/common/storage.js"; -import { ITelemetryService } from "../../../../platform/telemetry/common/telemetry.js"; -import { editorBackground } from "../../../../platform/theme/common/colorRegistry.js"; -import { IThemeService } from "../../../../platform/theme/common/themeService.js"; -import { EditorPane } from "../../../browser/parts/editor/editorPane.js"; -import { IEditorGroup } from "../../../services/editor/common/editorGroupsService.js"; -import { IEditorService } from "../../../services/editor/common/editorService.js"; -import { IWorkbenchEnvironmentService } from "../../../services/environment/common/environmentService.js"; -import { - Extensions, - IExtensionFeaturesManagementService, - IExtensionFeaturesRegistry, -} from "../../../services/extensionManagement/common/extensionFeatures.js"; -import { - DefaultIconPath, - EnablementState, -} from "../../../services/extensionManagement/common/extensionManagement.js"; -import { LocalWebWorkerRunningLocation } from "../../../services/extensions/common/extensionRunningLocation.js"; -import { - IExtensionHostProfile, - IExtensionService, - IExtensionsStatus, -} from "../../../services/extensions/common/extensions.js"; -import { - IExtension, - IExtensionsWorkbenchService, -} from "../common/extensions.js"; -import { RuntimeExtensionsInput } from "../common/runtimeExtensionsInput.js"; -import { errorIcon, warningIcon } from "./extensionsIcons.js"; - -import "./media/runtimeExtensionsEditor.css"; +import { $, Dimension, addDisposableListener, append, clearNode } from '../../../../base/browser/dom.js'; +import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js'; +import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; +import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; +import { IListRenderer, IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js'; +import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js'; +import { Action, IAction, Separator } from '../../../../base/common/actions.js'; +import { isNonEmptyArray } from '../../../../base/common/arrays.js'; +import { RunOnceScheduler } from '../../../../base/common/async.js'; +import { fromNow } from '../../../../base/common/date.js'; +import { IDisposable, dispose } from '../../../../base/common/lifecycle.js'; +import { Schemas } from '../../../../base/common/network.js'; +import * as nls from '../../../../nls.js'; +import { Categories } from '../../../../platform/action/common/actionCommonCategories.js'; +import { getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { Action2, IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; +import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; +import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; +import { ExtensionIdentifier, ExtensionIdentifierMap, IExtensionDescription } from '../../../../platform/extensions/common/extensions.js'; +import { IHoverService } from '../../../../platform/hover/browser/hover.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { ILabelService } from '../../../../platform/label/common/label.js'; +import { WorkbenchList } from '../../../../platform/list/browser/listService.js'; +import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { IStorageService } from '../../../../platform/storage/common/storage.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { editorBackground } from '../../../../platform/theme/common/colorRegistry.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { EditorPane } from '../../../browser/parts/editor/editorPane.js'; +import { IEditorGroup } from '../../../services/editor/common/editorGroupsService.js'; +import { IEditorService } from '../../../services/editor/common/editorService.js'; +import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js'; +import { Extensions, IExtensionFeaturesManagementService, IExtensionFeaturesRegistry } from '../../../services/extensionManagement/common/extensionFeatures.js'; +import { DefaultIconPath, EnablementState } from '../../../services/extensionManagement/common/extensionManagement.js'; +import { LocalWebWorkerRunningLocation } from '../../../services/extensions/common/extensionRunningLocation.js'; +import { IExtensionHostProfile, IExtensionService, IExtensionsStatus } from '../../../services/extensions/common/extensions.js'; +import { IExtension, IExtensionsWorkbenchService } from '../common/extensions.js'; +import { RuntimeExtensionsInput } from '../common/runtimeExtensionsInput.js'; +import { errorIcon, warningIcon } from './extensionsIcons.js'; +import './media/runtimeExtensionsEditor.css'; interface IExtensionProfileInformation { /** @@ -111,7 +70,8 @@ export interface IRuntimeExtension { } export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { - public static readonly ID: string = "workbench.editor.runtimeExtensions"; + + public static readonly ID: string = 'workbench.editor.runtimeExtensions'; private _list: WorkbenchList | null; private _elements: IRuntimeExtension[] | null; @@ -121,53 +81,28 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { group: IEditorGroup, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, - @IContextKeyService - private readonly contextKeyService: IContextKeyService, - @IExtensionsWorkbenchService - private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionService - private readonly _extensionService: IExtensionService, - @INotificationService - private readonly _notificationService: INotificationService, - @IContextMenuService - private readonly _contextMenuService: IContextMenuService, - @IInstantiationService - protected readonly _instantiationService: IInstantiationService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IExtensionsWorkbenchService private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionService private readonly _extensionService: IExtensionService, + @INotificationService private readonly _notificationService: INotificationService, + @IContextMenuService private readonly _contextMenuService: IContextMenuService, + @IInstantiationService protected readonly _instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, @ILabelService private readonly _labelService: ILabelService, - @IWorkbenchEnvironmentService - private readonly _environmentService: IWorkbenchEnvironmentService, - @IClipboardService - private readonly _clipboardService: IClipboardService, - @IExtensionFeaturesManagementService - private readonly _extensionFeaturesManagementService: IExtensionFeaturesManagementService, + @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, + @IClipboardService private readonly _clipboardService: IClipboardService, + @IExtensionFeaturesManagementService private readonly _extensionFeaturesManagementService: IExtensionFeaturesManagementService, @IHoverService private readonly _hoverService: IHoverService, @IMenuService private readonly _menuService: IMenuService, ) { - super( - AbstractRuntimeExtensionsEditor.ID, - group, - telemetryService, - themeService, - storageService, - ); + super(AbstractRuntimeExtensionsEditor.ID, group, telemetryService, themeService, storageService); this._list = null; this._elements = null; - this._updateSoon = this._register( - new RunOnceScheduler(() => this._updateExtensions(), 200), - ); - - this._register( - this._extensionService.onDidChangeExtensionsStatus(() => - this._updateSoon.schedule(), - ), - ); - this._register( - this._extensionFeaturesManagementService.onDidChangeAccessData(() => - this._updateSoon.schedule(), - ), - ); + this._updateSoon = this._register(new RunOnceScheduler(() => this._updateExtensions(), 200)); + + this._register(this._extensionService.onDidChangeExtensionsStatus(() => this._updateSoon.schedule())); + this._register(this._extensionFeaturesManagementService.onDidChangeAccessData(() => this._updateSoon.schedule())); this._updateExtensions(); } @@ -179,18 +114,11 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { private async _resolveExtensions(): Promise { // We only deal with extensions with source code! await this._extensionService.whenInstalledExtensionsRegistered(); - - const extensionsDescriptions = this._extensionService.extensions.filter( - (extension) => { - return Boolean(extension.main) || Boolean(extension.browser); - }, - ); - + const extensionsDescriptions = this._extensionService.extensions.filter((extension) => { + return Boolean(extension.main) || Boolean(extension.browser); + }); const marketplaceMap = new ExtensionIdentifierMap(); - - const marketPlaceExtensions = - await this._extensionsWorkbenchService.queryLocal(); - + const marketPlaceExtensions = await this._extensionsWorkbenchService.queryLocal(); for (const extension of marketPlaceExtensions) { marketplaceMap.set(extension.identifier.id, extension); } @@ -201,17 +129,13 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { const segments = new ExtensionIdentifierMap(); const profileInfo = this._getProfileInfo(); - if (profileInfo) { let currentStartTime = profileInfo.startTime; - for (let i = 0, len = profileInfo.deltas.length; i < len; i++) { const id = profileInfo.ids[i]; - const delta = profileInfo.deltas[i]; let extensionSegments = segments.get(id); - if (!extensionSegments) { extensionSegments = []; segments.set(id, extensionSegments); @@ -224,49 +148,35 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { } let result: IRuntimeExtension[] = []; - for (let i = 0, len = extensionsDescriptions.length; i < len; i++) { const extensionDescription = extensionsDescriptions[i]; let extProfileInfo: IExtensionProfileInformation | null = null; - if (profileInfo) { - const extensionSegments = - segments.get(extensionDescription.identifier) || []; - + const extensionSegments = segments.get(extensionDescription.identifier) || []; let extensionTotalTime = 0; - - for ( - let j = 0, lenJ = extensionSegments.length / 2; - j < lenJ; - j++ - ) { + for (let j = 0, lenJ = extensionSegments.length / 2; j < lenJ; j++) { const startTime = extensionSegments[2 * j]; - const endTime = extensionSegments[2 * j + 1]; - extensionTotalTime += endTime - startTime; + extensionTotalTime += (endTime - startTime); } extProfileInfo = { segments: extensionSegments, - totalTime: extensionTotalTime, + totalTime: extensionTotalTime }; } result[i] = { originalIndex: i, description: extensionDescription, - marketplaceInfo: marketplaceMap.get( - extensionDescription.identifier, - ), + marketplaceInfo: marketplaceMap.get(extensionDescription.identifier), status: statusMap[extensionDescription.identifier.value], profileInfo: extProfileInfo || undefined, - unresponsiveProfile: this._getUnresponsiveProfile( - extensionDescription.identifier, - ), + unresponsiveProfile: this._getUnresponsiveProfile(extensionDescription.identifier) }; } - result = result.filter((element) => element.status.activationStarted); + result = result.filter(element => element.status.activationStarted); // bubble up extensions that have caused slowness @@ -295,20 +205,18 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { } protected createEditor(parent: HTMLElement): void { - parent.classList.add("runtime-extensions-editor"); + parent.classList.add('runtime-extensions-editor'); - const TEMPLATE_ID = "runtimeExtensionElementTemplate"; + const TEMPLATE_ID = 'runtimeExtensionElementTemplate'; - const delegate = new (class - implements IListVirtualDelegate - { + const delegate = new class implements IListVirtualDelegate { getHeight(element: IRuntimeExtension): number { return 70; } getTemplateId(element: IRuntimeExtension): string { return TEMPLATE_ID; } - })(); + }; interface IRuntimeExtensionTemplateData { root: HTMLElement; @@ -324,52 +232,27 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { elementDisposables: IDisposable[]; } - const renderer: IListRenderer< - IRuntimeExtension, - IRuntimeExtensionTemplateData - > = { + const renderer: IListRenderer = { templateId: TEMPLATE_ID, - renderTemplate: ( - root: HTMLElement, - ): IRuntimeExtensionTemplateData => { - const element = append(root, $(".extension")); - - const iconContainer = append(element, $(".icon-container")); - - const icon = append( - iconContainer, - $("img.icon"), - ); - - const desc = append(element, $("div.desc")); - - const headerContainer = append(desc, $(".header-container")); - - const header = append(headerContainer, $(".header")); - - const name = append(header, $("div.name")); + renderTemplate: (root: HTMLElement): IRuntimeExtensionTemplateData => { + const element = append(root, $('.extension')); + const iconContainer = append(element, $('.icon-container')); + const icon = append(iconContainer, $('img.icon')); - const version = append(header, $("span.version")); + const desc = append(element, $('div.desc')); + const headerContainer = append(desc, $('.header-container')); + const header = append(headerContainer, $('.header')); + const name = append(header, $('div.name')); + const version = append(header, $('span.version')); - const msgContainer = append(desc, $("div.msg")); + const msgContainer = append(desc, $('div.msg')); const actionbar = new ActionBar(desc); - actionbar.onDidRun( - ({ error }) => - error && this._notificationService.error(error), - ); + actionbar.onDidRun(({ error }) => error && this._notificationService.error(error)); - const timeContainer = append(element, $(".time")); - - const activationTime = append( - timeContainer, - $("div.activation-time"), - ); - - const profileTime = append( - timeContainer, - $("div.profile-time"), - ); + const timeContainer = append(element, $('.time')); + const activationTime = append(timeContainer, $('div.activation-time')); + const profileTime = append(timeContainer, $('div.profile-time')); const disposables = [actionbar]; @@ -388,347 +271,162 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { }; }, - renderElement: ( - element: IRuntimeExtension, - index: number, - data: IRuntimeExtensionTemplateData, - ): void => { + renderElement: (element: IRuntimeExtension, index: number, data: IRuntimeExtensionTemplateData): void => { + data.elementDisposables = dispose(data.elementDisposables); - data.root.classList.toggle("odd", index % 2 === 1); - - data.elementDisposables.push( - addDisposableListener( - data.icon, - "error", - () => - (data.icon.src = - element.marketplaceInfo?.iconUrlFallback || - DefaultIconPath), - { once: true }, - ), - ); - data.icon.src = - element.marketplaceInfo?.iconUrl || DefaultIconPath; + data.root.classList.toggle('odd', index % 2 === 1); + + data.elementDisposables.push(addDisposableListener(data.icon, 'error', () => data.icon.src = element.marketplaceInfo?.iconUrlFallback || DefaultIconPath, { once: true })); + data.icon.src = element.marketplaceInfo?.iconUrl || DefaultIconPath; if (!data.icon.complete) { - data.icon.style.visibility = "hidden"; - data.icon.onload = () => - (data.icon.style.visibility = "inherit"); + data.icon.style.visibility = 'hidden'; + data.icon.onload = () => data.icon.style.visibility = 'inherit'; } else { - data.icon.style.visibility = "inherit"; + data.icon.style.visibility = 'inherit'; } - data.name.textContent = ( - element.marketplaceInfo?.displayName || - element.description.identifier.value - ).substr(0, 50); + data.name.textContent = (element.marketplaceInfo?.displayName || element.description.identifier.value).substr(0, 50); data.version.textContent = element.description.version; const activationTimes = element.status.activationTimes; - if (activationTimes) { - const syncTime = - activationTimes.codeLoadingTime + - activationTimes.activateCallTime; - data.activationTime.textContent = activationTimes - .activationReason.startup - ? `Startup Activation: ${syncTime}ms` - : `Activation: ${syncTime}ms`; + const syncTime = activationTimes.codeLoadingTime + activationTimes.activateCallTime; + data.activationTime.textContent = activationTimes.activationReason.startup ? `Startup Activation: ${syncTime}ms` : `Activation: ${syncTime}ms`; } else { data.activationTime.textContent = `Activating...`; } data.actionbar.clear(); - - const slowExtensionAction = - this._createSlowExtensionAction(element); - + const slowExtensionAction = this._createSlowExtensionAction(element); if (slowExtensionAction) { - data.actionbar.push(slowExtensionAction, { - icon: false, - label: true, - }); + data.actionbar.push(slowExtensionAction, { icon: false, label: true }); } if (isNonEmptyArray(element.status.runtimeErrors)) { - const reportExtensionIssueAction = - this._createReportExtensionIssueAction(element); - + const reportExtensionIssueAction = this._createReportExtensionIssueAction(element); if (reportExtensionIssueAction) { - data.actionbar.push(reportExtensionIssueAction, { - icon: false, - label: true, - }); + data.actionbar.push(reportExtensionIssueAction, { icon: false, label: true }); } } let title: string; - if (activationTimes) { - const activationId = - activationTimes.activationReason.extensionId.value; - - const activationEvent = - activationTimes.activationReason.activationEvent; - - if (activationEvent === "*") { - title = nls.localize( - { - key: "starActivation", - comment: [ - "{0} will be an extension identifier", - ], - }, - "Activated by {0} on start-up", - activationId, - ); + const activationId = activationTimes.activationReason.extensionId.value; + const activationEvent = activationTimes.activationReason.activationEvent; + if (activationEvent === '*') { + title = nls.localize({ + key: 'starActivation', + comment: [ + '{0} will be an extension identifier' + ] + }, "Activated by {0} on start-up", activationId); } else if (/^workspaceContains:/.test(activationEvent)) { - const fileNameOrGlob = activationEvent.substr( - "workspaceContains:".length, - ); - - if ( - fileNameOrGlob.indexOf("*") >= 0 || - fileNameOrGlob.indexOf("?") >= 0 - ) { - title = nls.localize( - { - key: "workspaceContainsGlobActivation", - comment: [ - "{0} will be a glob pattern", - "{1} will be an extension identifier", - ], - }, - "Activated by {1} because a file matching {0} exists in your workspace", - fileNameOrGlob, - activationId, - ); - } else { - title = nls.localize( - { - key: "workspaceContainsFileActivation", - comment: [ - "{0} will be a file name", - "{1} will be an extension identifier", - ], - }, - "Activated by {1} because file {0} exists in your workspace", - fileNameOrGlob, - activationId, - ); - } - } else if ( - /^workspaceContainsTimeout:/.test(activationEvent) - ) { - const glob = activationEvent.substr( - "workspaceContainsTimeout:".length, - ); - title = nls.localize( - { - key: "workspaceContainsTimeout", + const fileNameOrGlob = activationEvent.substr('workspaceContains:'.length); + if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0) { + title = nls.localize({ + key: 'workspaceContainsGlobActivation', comment: [ - "{0} will be a glob pattern", - "{1} will be an extension identifier", - ], - }, - "Activated by {1} because searching for {0} took too long", - glob, - activationId, - ); - } else if (activationEvent === "onStartupFinished") { - title = nls.localize( - { - key: "startupFinishedActivation", + '{0} will be a glob pattern', + '{1} will be an extension identifier' + ] + }, "Activated by {1} because a file matching {0} exists in your workspace", fileNameOrGlob, activationId); + } else { + title = nls.localize({ + key: 'workspaceContainsFileActivation', comment: [ - "This refers to an extension. {0} will be an activation event.", - ], - }, - "Activated by {0} after start-up finished", - activationId, - ); + '{0} will be a file name', + '{1} will be an extension identifier' + ] + }, "Activated by {1} because file {0} exists in your workspace", fileNameOrGlob, activationId); + } + } else if (/^workspaceContainsTimeout:/.test(activationEvent)) { + const glob = activationEvent.substr('workspaceContainsTimeout:'.length); + title = nls.localize({ + key: 'workspaceContainsTimeout', + comment: [ + '{0} will be a glob pattern', + '{1} will be an extension identifier' + ] + }, "Activated by {1} because searching for {0} took too long", glob, activationId); + } else if (activationEvent === 'onStartupFinished') { + title = nls.localize({ + key: 'startupFinishedActivation', + comment: [ + 'This refers to an extension. {0} will be an activation event.' + ] + }, "Activated by {0} after start-up finished", activationId); } else if (/^onLanguage:/.test(activationEvent)) { - const language = activationEvent.substr( - "onLanguage:".length, - ); - title = nls.localize( - "languageActivation", - "Activated by {1} because you opened a {0} file", - language, - activationId, - ); + const language = activationEvent.substr('onLanguage:'.length); + title = nls.localize('languageActivation', "Activated by {1} because you opened a {0} file", language, activationId); } else { - title = nls.localize( - { - key: "workspaceGenericActivation", - comment: [ - "{0} will be an activation event, like e.g. 'language:typescript', 'debug', etc.", - "{1} will be an extension identifier", - ], - }, - "Activated by {1} on {0}", - activationEvent, - activationId, - ); + title = nls.localize({ + key: 'workspaceGenericActivation', + comment: [ + '{0} will be an activation event, like e.g. \'language:typescript\', \'debug\', etc.', + '{1} will be an extension identifier' + ] + }, "Activated by {1} on {0}", activationEvent, activationId); } } else { - title = nls.localize( - "extensionActivating", - "Extension is activating...", - ); + title = nls.localize('extensionActivating', "Extension is activating..."); } - data.elementDisposables.push( - this._hoverService.setupManagedHover( - getDefaultHoverDelegate("mouse"), - data.activationTime, - title, - ), - ); + data.elementDisposables.push(this._hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.activationTime, title)); clearNode(data.msgContainer); - if ( - this._getUnresponsiveProfile(element.description.identifier) - ) { - const el = $( - "span", - undefined, - ...renderLabelWithIcons(` $(alert) Unresponsive`), - ); - - const extensionHostFreezTitle = nls.localize( - "unresponsive.title", - "Extension has caused the extension host to freeze.", - ); - data.elementDisposables.push( - this._hoverService.setupManagedHover( - getDefaultHoverDelegate("mouse"), - el, - extensionHostFreezTitle, - ), - ); + if (this._getUnresponsiveProfile(element.description.identifier)) { + const el = $('span', undefined, ...renderLabelWithIcons(` $(alert) Unresponsive`)); + const extensionHostFreezTitle = nls.localize('unresponsive.title', "Extension has caused the extension host to freeze."); + data.elementDisposables.push(this._hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), el, extensionHostFreezTitle)); data.msgContainer.appendChild(el); } if (isNonEmptyArray(element.status.runtimeErrors)) { - const el = $( - "span", - undefined, - ...renderLabelWithIcons( - `$(bug) ${nls.localize("errors", "{0} uncaught errors", element.status.runtimeErrors.length)}`, - ), - ); + const el = $('span', undefined, ...renderLabelWithIcons(`$(bug) ${nls.localize('errors', "{0} uncaught errors", element.status.runtimeErrors.length)}`)); data.msgContainer.appendChild(el); } - if ( - element.status.messages && - element.status.messages.length > 0 - ) { - const el = $( - "span", - undefined, - ...renderLabelWithIcons( - `$(alert) ${element.status.messages[0].message}`, - ), - ); + if (element.status.messages && element.status.messages.length > 0) { + const el = $('span', undefined, ...renderLabelWithIcons(`$(alert) ${element.status.messages[0].message}`)); data.msgContainer.appendChild(el); } let extraLabel: string | null = null; - - if ( - element.status.runningLocation && - element.status.runningLocation.equals( - new LocalWebWorkerRunningLocation(0), - ) - ) { + if (element.status.runningLocation && element.status.runningLocation.equals(new LocalWebWorkerRunningLocation(0))) { extraLabel = `$(globe) web worker`; - } else if ( - element.description.extensionLocation.scheme === - Schemas.vscodeRemote - ) { - const hostLabel = this._labelService.getHostLabel( - Schemas.vscodeRemote, - this._environmentService.remoteAuthority, - ); - + } else if (element.description.extensionLocation.scheme === Schemas.vscodeRemote) { + const hostLabel = this._labelService.getHostLabel(Schemas.vscodeRemote, this._environmentService.remoteAuthority); if (hostLabel) { extraLabel = `$(remote) ${hostLabel}`; } else { extraLabel = `$(remote) ${element.description.extensionLocation.authority}`; } - } else if ( - element.status.runningLocation && - element.status.runningLocation.affinity > 0 - ) { - extraLabel = - element.status.runningLocation instanceof - LocalWebWorkerRunningLocation - ? `$(globe) web worker ${element.status.runningLocation.affinity + 1}` - : `$(server-process) local process ${element.status.runningLocation.affinity + 1}`; + } else if (element.status.runningLocation && element.status.runningLocation.affinity > 0) { + extraLabel = element.status.runningLocation instanceof LocalWebWorkerRunningLocation + ? `$(globe) web worker ${element.status.runningLocation.affinity + 1}` + : `$(server-process) local process ${element.status.runningLocation.affinity + 1}`; } if (extraLabel) { - const el = $( - "span", - undefined, - ...renderLabelWithIcons(extraLabel), - ); + const el = $('span', undefined, ...renderLabelWithIcons(extraLabel)); data.msgContainer.appendChild(el); } - const features = Registry.as( - Extensions.ExtensionFeaturesRegistry, - ).getExtensionFeatures(); - + const features = Registry.as(Extensions.ExtensionFeaturesRegistry).getExtensionFeatures(); for (const feature of features) { - const accessData = - this._extensionFeaturesManagementService.getAccessData( - element.description.identifier, - feature.id, - ); - + const accessData = this._extensionFeaturesManagementService.getAccessData(element.description.identifier, feature.id); if (accessData) { const status = accessData?.current?.status; - if (status) { - data.msgContainer.appendChild( - $("span", undefined, `${feature.label}: `), - ); - data.msgContainer.appendChild( - $( - "span", - undefined, - ...renderLabelWithIcons( - `$(${status.severity === Severity.Error ? errorIcon.id : warningIcon.id}) ${status.message}`, - ), - ), - ); + data.msgContainer.appendChild($('span', undefined, `${feature.label}: `)); + data.msgContainer.appendChild($('span', undefined, ...renderLabelWithIcons(`$(${status.severity === Severity.Error ? errorIcon.id : warningIcon.id}) ${status.message}`))); } if (accessData?.totalCount > 0) { - const element = $( - "span", - undefined, - `${nls.localize("requests count", "{0} Requests: {1} (Overall)", feature.label, accessData.totalCount)}${accessData.current ? nls.localize("session requests count", ", {0} (Session)", accessData.current.count) : ""}`, - ); - + const element = $('span', undefined, `${nls.localize('requests count', "{0} Requests: {1} (Overall)", feature.label, accessData.totalCount)}${accessData.current ? nls.localize('session requests count', ", {0} (Session)", accessData.current.count) : ''}`); if (accessData.current) { - const title = nls.localize( - "requests count title", - "Last request was {0}.", - fromNow( - accessData.current.lastAccessed, - true, - true, - ), - ); - data.elementDisposables.push( - this._hoverService.setupManagedHover( - getDefaultHoverDelegate("mouse"), - element, - title, - ), - ); + const title = nls.localize('requests count title', "Last request was {0}.", fromNow(accessData.current.lastAccessed, true, true)); + data.elementDisposables.push(this._hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), element, title)); } data.msgContainer.appendChild(element); @@ -739,48 +437,34 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { if (element.profileInfo) { data.profileTime.textContent = `Profile: ${(element.profileInfo.totalTime / 1000).toFixed(2)}ms`; } else { - data.profileTime.textContent = ""; + data.profileTime.textContent = ''; } + }, disposeTemplate: (data: IRuntimeExtensionTemplateData): void => { data.disposables = dispose(data.disposables); - }, + } }; - this._list = >( - this._instantiationService.createInstance( - WorkbenchList, - "RuntimeExtensions", - parent, - delegate, - [renderer], - { - multipleSelectionSupport: false, - setRowLineHeight: false, - horizontalScrolling: false, - overrideStyles: { - listBackground: editorBackground, - }, - accessibilityProvider: new (class - implements - IListAccessibilityProvider - { - getWidgetAriaLabel(): string { - return nls.localize( - "runtimeExtensions", - "Runtime Extensions", - ); - } - getAriaLabel( - element: IRuntimeExtension, - ): string | null { - return element.description.name; - } - })(), - }, - ) - ); + this._list = this._instantiationService.createInstance(WorkbenchList, + 'RuntimeExtensions', + parent, delegate, [renderer], { + multipleSelectionSupport: false, + setRowLineHeight: false, + horizontalScrolling: false, + overrideStyles: { + listBackground: editorBackground + }, + accessibilityProvider: new class implements IListAccessibilityProvider { + getWidgetAriaLabel(): string { + return nls.localize('runtimeExtensions', "Runtime Extensions"); + } + getAriaLabel(element: IRuntimeExtension): string | null { + return element.description.name; + } + } + }); this._list.splice(0, this._list.length, this._elements || undefined); @@ -791,74 +475,34 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { const actions: IAction[] = []; - actions.push( - new Action( - "runtimeExtensionsEditor.action.copyId", - nls.localize( - "copy id", - "Copy id ({0})", - e.element.description.identifier.value, - ), - undefined, - true, - () => { - this._clipboardService.writeText( - e.element!.description.identifier.value, - ); - }, - ), - ); - - const reportExtensionIssueAction = - this._createReportExtensionIssueAction(e.element); + actions.push(new Action( + 'runtimeExtensionsEditor.action.copyId', + nls.localize('copy id', "Copy id ({0})", e.element.description.identifier.value), + undefined, + true, + () => { + this._clipboardService.writeText(e.element!.description.identifier.value); + } + )); + const reportExtensionIssueAction = this._createReportExtensionIssueAction(e.element); if (reportExtensionIssueAction) { actions.push(reportExtensionIssueAction); } actions.push(new Separator()); if (e.element.marketplaceInfo) { - actions.push( - new Action( - "runtimeExtensionsEditor.action.disableWorkspace", - nls.localize( - "disable workspace", - "Disable (Workspace)", - ), - undefined, - true, - () => - this._extensionsWorkbenchService.setEnablement( - e.element!.marketplaceInfo!, - EnablementState.DisabledWorkspace, - ), - ), - ); - actions.push( - new Action( - "runtimeExtensionsEditor.action.disable", - nls.localize("disable", "Disable"), - undefined, - true, - () => - this._extensionsWorkbenchService.setEnablement( - e.element!.marketplaceInfo!, - EnablementState.DisabledGlobally, - ), - ), - ); + actions.push(new Action('runtimeExtensionsEditor.action.disableWorkspace', nls.localize('disable workspace', "Disable (Workspace)"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo!, EnablementState.DisabledWorkspace))); + actions.push(new Action('runtimeExtensionsEditor.action.disable', nls.localize('disable', "Disable"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo!, EnablementState.DisabledGlobally))); } actions.push(new Separator()); - const menuActions = this._menuService.getMenuActions( - MenuId.ExtensionEditorContextMenu, - this.contextKeyService, - ); - actions.push(...getContextMenuActions(menuActions).secondary); + const menuActions = this._menuService.getMenuActions(MenuId.ExtensionEditorContextMenu, this.contextKeyService); + actions.push(...getContextMenuActions(menuActions,).secondary); this._contextMenuService.showContextMenu({ getAnchor: () => e.anchor, - getActions: () => actions, + getActions: () => actions }); }); } @@ -868,42 +512,29 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { } protected abstract _getProfileInfo(): IExtensionHostProfile | null; - protected abstract _getUnresponsiveProfile( - extensionId: ExtensionIdentifier, - ): IExtensionHostProfile | undefined; - protected abstract _createSlowExtensionAction( - element: IRuntimeExtension, - ): Action | null; - protected abstract _createReportExtensionIssueAction( - element: IRuntimeExtension, - ): Action | null; + protected abstract _getUnresponsiveProfile(extensionId: ExtensionIdentifier): IExtensionHostProfile | undefined; + protected abstract _createSlowExtensionAction(element: IRuntimeExtension): Action | null; + protected abstract _createReportExtensionIssueAction(element: IRuntimeExtension): Action | null; } export class ShowRuntimeExtensionsAction extends Action2 { + constructor() { super({ - id: "workbench.action.showRuntimeExtensions", - title: nls.localize2( - "showRuntimeExtensions", - "Show Running Extensions", - ), + id: 'workbench.action.showRuntimeExtensions', + title: nls.localize2('showRuntimeExtensions', "Show Running Extensions"), category: Categories.Developer, f1: true, menu: { id: MenuId.ViewContainerTitle, - when: ContextKeyExpr.equals( - "viewContainer", - "workbench.view.extensions", - ), - group: "2_enablement", - order: 3, - }, + when: ContextKeyExpr.equals('viewContainer', 'workbench.view.extensions'), + group: '2_enablement', + order: 3 + } }); } async run(accessor: ServicesAccessor): Promise { - await accessor - .get(IEditorService) - .openEditor(RuntimeExtensionsInput.instance, { pinned: true }); + await accessor.get(IEditorService).openEditor(RuntimeExtensionsInput.instance, { pinned: true }); } } diff --git a/Source/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/Source/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index b9604a8614979..575eebe161823 100644 --- a/Source/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/Source/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -3,280 +3,114 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAction } from "../../../../base/common/actions.js"; -import { CancellationToken } from "../../../../base/common/cancellation.js"; -import { IStringDictionary } from "../../../../base/common/collections.js"; -import { onUnexpectedError } from "../../../../base/common/errors.js"; -import { Event } from "../../../../base/common/event.js"; -import { KeyCode, KeyMod } from "../../../../base/common/keyCodes.js"; -import { mnemonicButtonLabel } from "../../../../base/common/labels.js"; -import { - Disposable, - DisposableStore, - IDisposable, - isDisposable, -} from "../../../../base/common/lifecycle.js"; -import { Schemas } from "../../../../base/common/network.js"; -import { isLinux, isNative, isWeb } from "../../../../base/common/platform.js"; -import { URI, UriComponents } from "../../../../base/common/uri.js"; -import { MultiCommand } from "../../../../editor/browser/editorExtensions.js"; -import { - CopyAction, - CutAction, - PasteAction, -} from "../../../../editor/contrib/clipboard/browser/clipboard.js"; -import { localize, localize2 } from "../../../../nls.js"; -import { Categories } from "../../../../platform/action/common/actionCommonCategories.js"; -import { - Action2, - IAction2Options, - IMenuItem, - MenuId, - MenuRegistry, - registerAction2, -} from "../../../../platform/actions/common/actions.js"; -import { IClipboardService } from "../../../../platform/clipboard/common/clipboardService.js"; -import { - CommandsRegistry, - ICommandService, -} from "../../../../platform/commands/common/commands.js"; -import { - Extensions as ConfigurationExtensions, - ConfigurationScope, - IConfigurationRegistry, -} from "../../../../platform/configuration/common/configurationRegistry.js"; -import { - ContextKeyExpr, - IContextKeyService, - RawContextKey, -} from "../../../../platform/contextkey/common/contextkey.js"; -import { - IDialogService, - IFileDialogService, -} from "../../../../platform/dialogs/common/dialogs.js"; -import { - AllowedExtensionsConfigKey, - EXTENSION_INSTALL_SOURCE_CONTEXT, - ExtensionInstallSource, - ExtensionsLocalizedLabel, - IExtensionGalleryService, - IExtensionManagementService, - PreferencesLocalizedLabel, - UseUnpkgResourceApiConfigKey, -} from "../../../../platform/extensionManagement/common/extensionManagement.js"; -import { - areSameExtensions, - getIdAndVersion, -} from "../../../../platform/extensionManagement/common/extensionManagementUtil.js"; -import { ExtensionStorageService } from "../../../../platform/extensionManagement/common/extensionStorage.js"; -import { IExtensionRecommendationNotificationService } from "../../../../platform/extensionRecommendations/common/extensionRecommendations.js"; -import { EXTENSION_CATEGORIES } from "../../../../platform/extensions/common/extensions.js"; -import { SyncDescriptor } from "../../../../platform/instantiation/common/descriptors.js"; -import { - InstantiationType, - registerSingleton, -} from "../../../../platform/instantiation/common/extensions.js"; -import { - IInstantiationService, - ServicesAccessor, -} from "../../../../platform/instantiation/common/instantiation.js"; -import * as jsonContributionRegistry from "../../../../platform/jsonschemas/common/jsonContributionRegistry.js"; -import { - INotificationService, - Severity, -} from "../../../../platform/notification/common/notification.js"; -import { IProductService } from "../../../../platform/product/common/productService.js"; -import { ProgressLocation } from "../../../../platform/progress/common/progress.js"; -import { - Extensions, - IQuickAccessRegistry, -} from "../../../../platform/quickinput/common/quickAccess.js"; -import { IQuickInputService } from "../../../../platform/quickinput/common/quickInput.js"; -import { Registry } from "../../../../platform/registry/common/platform.js"; -import { IStorageService } from "../../../../platform/storage/common/storage.js"; -import { IUriIdentityService } from "../../../../platform/uriIdentity/common/uriIdentity.js"; -import { - EditorPaneDescriptor, - IEditorPaneRegistry, -} from "../../../browser/editor.js"; -import { - Extensions as ConfigurationMigrationExtensions, - IConfigurationMigrationRegistry, -} from "../../../common/configuration.js"; -import { - ResourceContextKey, - WorkbenchStateContext, -} from "../../../common/contextkeys.js"; -import { - IWorkbenchContribution, - IWorkbenchContributionsRegistry, - Extensions as WorkbenchExtensions, -} from "../../../common/contributions.js"; -import { EditorExtensions } from "../../../common/editor.js"; -import { - IViewContainersRegistry, - Extensions as ViewContainerExtensions, - ViewContainerLocation, -} from "../../../common/views.js"; -import { IEditorService } from "../../../services/editor/common/editorService.js"; -import { - EnablementState, - extensionsConfigurationNodeBase, - IExtensionManagementServerService, - IWorkbenchExtensionEnablementService, - IWorkbenchExtensionManagementService, -} from "../../../services/extensionManagement/common/extensionManagement.js"; -import { - IExtensionIgnoredRecommendationsService, - IExtensionRecommendationsService, -} from "../../../services/extensionRecommendations/common/extensionRecommendations.js"; -import { IWorkspaceExtensionsConfigService } from "../../../services/extensionRecommendations/common/workspaceExtensionsConfig.js"; -import { IHostService } from "../../../services/host/browser/host.js"; -import { LifecyclePhase } from "../../../services/lifecycle/common/lifecycle.js"; -import { IPreferencesService } from "../../../services/preferences/common/preferences.js"; -import { CONTEXT_SYNC_ENABLEMENT } from "../../../services/userDataSync/common/userDataSync.js"; -import { IViewsService } from "../../../services/views/common/viewsService.js"; -import { WORKSPACE_TRUST_EXTENSION_SUPPORT } from "../../../services/workspaces/common/workspaceTrust.js"; -import { CONTEXT_KEYBINDINGS_EDITOR } from "../../preferences/common/preferences.js"; -import { IWebview } from "../../webview/browser/webview.js"; -import { Query } from "../common/extensionQuery.js"; -import { - AutoUpdateConfigurationKey, - CONTEXT_HAS_GALLERY, - ExtensionEditorTab, - ExtensionRuntimeActionType, - extensionsSearchActionsMenu, - HasOutdatedExtensionsContext, - IExtensionArg, - IExtensionsViewPaneContainer, - IExtensionsWorkbenchService, - INSTALL_ACTIONS_GROUP, - INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, - IWorkspaceRecommendedExtensionsView, - LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, - OUTDATED_EXTENSIONS_VIEW_ID, - SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, - THEME_ACTIONS_GROUP, - TOGGLE_IGNORE_EXTENSION_ACTION_ID, - UPDATE_ACTIONS_GROUP, - VIEWLET_ID, - WORKSPACE_RECOMMENDATIONS_VIEW_ID, -} from "../common/extensions.js"; -import { - ExtensionsConfigurationSchema, - ExtensionsConfigurationSchemaId, -} from "../common/extensionsFileTemplate.js"; -import { ExtensionsInput } from "../common/extensionsInput.js"; -import { KeymapExtensions } from "../common/extensionsUtils.js"; -import { ShowRuntimeExtensionsAction } from "./abstractRuntimeExtensionsEditor.js"; -import { ExtensionEditor } from "./extensionEditor.js"; -import { ExtensionEnablementWorkspaceTrustTransitionParticipant } from "./extensionEnablementWorkspaceTrustTransitionParticipant.js"; -import { ExtensionRecommendationNotificationService } from "./extensionRecommendationNotificationService.js"; -import { ExtensionRecommendationsService } from "./extensionRecommendationsService.js"; -import { - ClearLanguageAction, - ConfigureWorkspaceFolderRecommendedExtensionsAction, - ConfigureWorkspaceRecommendedExtensionsAction, - InstallAction, - InstallAnotherVersionAction, - InstallSpecificVersionOfExtensionAction, - ReinstallAction, - SetColorThemeAction, - SetFileIconThemeAction, - SetProductIconThemeAction, - ToggleAutoUpdateForExtensionAction, - ToggleAutoUpdatesForPublisherAction, - TogglePreReleaseExtensionAction, -} from "./extensionsActions.js"; -import { ExtensionActivationProgress } from "./extensionsActivationProgress.js"; -import { ExtensionsCompletionItemsProvider } from "./extensionsCompletionItemsProvider.js"; -import { ExtensionDependencyChecker } from "./extensionsDependencyChecker.js"; -import { - clearSearchResultsIcon, - configureRecommendedIcon, - extensionsViewIcon, - filterIcon, - installWorkspaceRecommendedIcon, - refreshIcon, -} from "./extensionsIcons.js"; -import { - InstallExtensionQuickAccessProvider, - ManageExtensionsQuickAccessProvider, -} from "./extensionsQuickAccess.js"; -import { - BuiltInExtensionsContext, - DefaultViewsContext, - ExtensionsSortByContext, - ExtensionsViewletViewsContribution, - ExtensionsViewPaneContainer, - MaliciousExtensionChecker, - RecommendedExtensionsContext, - SearchHasTextContext, - SearchMarketplaceExtensionsContext, - StatusUpdater, -} from "./extensionsViewlet.js"; -import { ExtensionsWorkbenchService } from "./extensionsWorkbenchService.js"; -import { UnsupportedExtensionsMigrationContrib } from "./unsupportedExtensionsMigrationContribution.js"; +import { localize, localize2 } from '../../../../nls.js'; +import { KeyMod, KeyCode } from '../../../../base/common/keyCodes.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { MenuRegistry, MenuId, registerAction2, Action2, IMenuItem, IAction2Options } from '../../../../platform/actions/common/actions.js'; +import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; +import { ExtensionsLocalizedLabel, IExtensionManagementService, IExtensionGalleryService, PreferencesLocalizedLabel, EXTENSION_INSTALL_SOURCE_CONTEXT, ExtensionInstallSource, UseUnpkgResourceApiConfigKey, AllowedExtensionsConfigKey } from '../../../../platform/extensionManagement/common/extensionManagement.js'; +import { EnablementState, IExtensionManagementServerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from '../../../services/extensionManagement/common/extensionManagement.js'; +import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from '../../../services/extensionRecommendations/common/extensionRecommendations.js'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from '../../../common/contributions.js'; +import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; +import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, WORKSPACE_RECOMMENDATIONS_VIEW_ID, IWorkspaceRecommendedExtensionsView, AutoUpdateConfigurationKey, HasOutdatedExtensionsContext, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, ExtensionEditorTab, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP, OUTDATED_EXTENSIONS_VIEW_ID, CONTEXT_HAS_GALLERY, extensionsSearchActionsMenu, UPDATE_ACTIONS_GROUP, IExtensionArg, ExtensionRuntimeActionType } from '../common/extensions.js'; +import { ReinstallAction, InstallSpecificVersionOfExtensionAction, ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, SetColorThemeAction, SetFileIconThemeAction, SetProductIconThemeAction, ClearLanguageAction, ToggleAutoUpdateForExtensionAction, ToggleAutoUpdatesForPublisherAction, TogglePreReleaseExtensionAction, InstallAnotherVersionAction, InstallAction } from './extensionsActions.js'; +import { ExtensionsInput } from '../common/extensionsInput.js'; +import { ExtensionEditor } from './extensionEditor.js'; +import { StatusUpdater, MaliciousExtensionChecker, ExtensionsViewletViewsContribution, ExtensionsViewPaneContainer, BuiltInExtensionsContext, SearchMarketplaceExtensionsContext, RecommendedExtensionsContext, DefaultViewsContext, ExtensionsSortByContext, SearchHasTextContext } from './extensionsViewlet.js'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from '../../../../platform/configuration/common/configurationRegistry.js'; +import * as jsonContributionRegistry from '../../../../platform/jsonschemas/common/jsonContributionRegistry.js'; +import { ExtensionsConfigurationSchema, ExtensionsConfigurationSchemaId } from '../common/extensionsFileTemplate.js'; +import { CommandsRegistry, ICommandService } from '../../../../platform/commands/common/commands.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { KeymapExtensions } from '../common/extensionsUtils.js'; +import { areSameExtensions, getIdAndVersion } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js'; +import { EditorPaneDescriptor, IEditorPaneRegistry } from '../../../browser/editor.js'; +import { LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js'; +import { URI, UriComponents } from '../../../../base/common/uri.js'; +import { ExtensionActivationProgress } from './extensionsActivationProgress.js'; +import { onUnexpectedError } from '../../../../base/common/errors.js'; +import { ExtensionDependencyChecker } from './extensionsDependencyChecker.js'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions } from '../../../common/views.js'; +import { IViewsService } from '../../../services/views/common/viewsService.js'; +import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; +import { IPreferencesService } from '../../../services/preferences/common/preferences.js'; +import { ContextKeyExpr, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { IQuickAccessRegistry, Extensions } from '../../../../platform/quickinput/common/quickAccess.js'; +import { InstallExtensionQuickAccessProvider, ManageExtensionsQuickAccessProvider } from './extensionsQuickAccess.js'; +import { ExtensionRecommendationsService } from './extensionRecommendationsService.js'; +import { CONTEXT_SYNC_ENABLEMENT } from '../../../services/userDataSync/common/userDataSync.js'; +import { CopyAction, CutAction, PasteAction } from '../../../../editor/contrib/clipboard/browser/clipboard.js'; +import { IEditorService } from '../../../services/editor/common/editorService.js'; +import { MultiCommand } from '../../../../editor/browser/editorExtensions.js'; +import { IWebview } from '../../webview/browser/webview.js'; +import { ExtensionsWorkbenchService } from './extensionsWorkbenchService.js'; +import { Categories } from '../../../../platform/action/common/actionCommonCategories.js'; +import { IExtensionRecommendationNotificationService } from '../../../../platform/extensionRecommendations/common/extensionRecommendations.js'; +import { ExtensionRecommendationNotificationService } from './extensionRecommendationNotificationService.js'; +import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js'; +import { IHostService } from '../../../services/host/browser/host.js'; +import { ResourceContextKey, WorkbenchStateContext } from '../../../common/contextkeys.js'; +import { IAction } from '../../../../base/common/actions.js'; +import { IWorkspaceExtensionsConfigService } from '../../../services/extensionRecommendations/common/workspaceExtensionsConfig.js'; +import { Schemas } from '../../../../base/common/network.js'; +import { ShowRuntimeExtensionsAction } from './abstractRuntimeExtensionsEditor.js'; +import { ExtensionEnablementWorkspaceTrustTransitionParticipant } from './extensionEnablementWorkspaceTrustTransitionParticipant.js'; +import { clearSearchResultsIcon, configureRecommendedIcon, extensionsViewIcon, filterIcon, installWorkspaceRecommendedIcon, refreshIcon } from './extensionsIcons.js'; +import { EXTENSION_CATEGORIES } from '../../../../platform/extensions/common/extensions.js'; +import { Disposable, DisposableStore, IDisposable, isDisposable } from '../../../../base/common/lifecycle.js'; +import { IDialogService, IFileDialogService } from '../../../../platform/dialogs/common/dialogs.js'; +import { mnemonicButtonLabel } from '../../../../base/common/labels.js'; +import { Query } from '../common/extensionQuery.js'; +import { EditorExtensions } from '../../../common/editor.js'; +import { WORKSPACE_TRUST_EXTENSION_SUPPORT } from '../../../services/workspaces/common/workspaceTrust.js'; +import { ExtensionsCompletionItemsProvider } from './extensionsCompletionItemsProvider.js'; +import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js'; +import { Event } from '../../../../base/common/event.js'; +import { UnsupportedExtensionsMigrationContrib } from './unsupportedExtensionsMigrationContribution.js'; +import { isLinux, isNative, isWeb } from '../../../../base/common/platform.js'; +import { ExtensionStorageService } from '../../../../platform/extensionManagement/common/extensionStorage.js'; +import { IStorageService } from '../../../../platform/storage/common/storage.js'; +import { IStringDictionary } from '../../../../base/common/collections.js'; +import { CONTEXT_KEYBINDINGS_EDITOR } from '../../preferences/common/preferences.js'; +import { ProgressLocation } from '../../../../platform/progress/common/progress.js'; +import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; +import { IConfigurationMigrationRegistry, Extensions as ConfigurationMigrationExtensions } from '../../../common/configuration.js'; +import { IProductService } from '../../../../platform/product/common/productService.js'; // Singletons -registerSingleton( - IExtensionsWorkbenchService, - ExtensionsWorkbenchService, - InstantiationType.Eager /* Auto updates extensions */, -); -registerSingleton( - IExtensionRecommendationNotificationService, - ExtensionRecommendationNotificationService, - InstantiationType.Delayed, -); -registerSingleton( - IExtensionRecommendationsService, - ExtensionRecommendationsService, - InstantiationType.Eager /* Prompts recommendations in the background */, -); +registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService, InstantiationType.Eager /* Auto updates extensions */); +registerSingleton(IExtensionRecommendationNotificationService, ExtensionRecommendationNotificationService, InstantiationType.Delayed); +registerSingleton(IExtensionRecommendationsService, ExtensionRecommendationsService, InstantiationType.Eager /* Prompts recommendations in the background */); // Quick Access -Registry.as( - Extensions.Quickaccess, -).registerQuickAccessProvider({ +Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ ctor: ManageExtensionsQuickAccessProvider, prefix: ManageExtensionsQuickAccessProvider.PREFIX, - placeholder: localize( - "manageExtensionsQuickAccessPlaceholder", - "Press Enter to manage extensions.", - ), - helpEntries: [ - { description: localize("manageExtensionsHelp", "Manage Extensions") }, - ], + placeholder: localize('manageExtensionsQuickAccessPlaceholder', "Press Enter to manage extensions."), + helpEntries: [{ description: localize('manageExtensionsHelp', "Manage Extensions") }] }); // Editor -Registry.as( - EditorExtensions.EditorPane, -).registerEditorPane( +Registry.as(EditorExtensions.EditorPane).registerEditorPane( EditorPaneDescriptor.create( ExtensionEditor, ExtensionEditor.ID, - localize("extension", "Extension"), + localize('extension', "Extension") ), - [new SyncDescriptor(ExtensionsInput)], -); + [ + new SyncDescriptor(ExtensionsInput) + ]); -Registry.as( - ViewContainerExtensions.ViewContainersRegistry, -).registerViewContainer( +Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer( { id: VIEWLET_ID, - title: localize2("extensions", "Extensions"), + title: localize2('extensions', "Extensions"), openCommandActionDescriptor: { id: VIEWLET_ID, - mnemonicTitle: localize( - { key: "miViewExtensions", comment: ["&& denotes a mnemonic"] }, - "E&&xtensions", - ), - keybindings: { - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyX, - }, + mnemonicTitle: localize({ key: 'miViewExtensions', comment: ['&& denotes a mnemonic'] }, "E&&xtensions"), + keybindings: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyX }, order: 4, }, ctorDescriptor: new SyncDescriptor(ExtensionsViewPaneContainer), @@ -284,493 +118,298 @@ Registry.as( order: 4, rejectAddedViews: true, alwaysUseContainerInfo: true, - }, - ViewContainerLocation.Sidebar, -); - -Registry.as( - ConfigurationExtensions.Configuration, -).registerConfiguration({ - ...extensionsConfigurationNodeBase, - properties: { - "extensions.autoUpdate": { - enum: [true, "onlyEnabledExtensions", false], - enumItemLabels: [ - localize("all", "All Extensions"), - localize("enabled", "Only Enabled Extensions"), - localize("none", "None"), - ], - enumDescriptions: [ - localize( - "extensions.autoUpdate.true", - "Download and install updates automatically for all extensions.", - ), - localize( - "extensions.autoUpdate.enabled", - "Download and install updates automatically only for enabled extensions.", - ), - localize( - "extensions.autoUpdate.false", - "Extensions are not automatically updated.", - ), - ], - description: localize( - "extensions.autoUpdate", - "Controls the automatic update behavior of extensions. The updates are fetched from a Microsoft online service.", - ), - default: true, - scope: ConfigurationScope.APPLICATION, - tags: ["usesOnlineServices"], - }, - "extensions.autoCheckUpdates": { - type: "boolean", - description: localize( - "extensionsCheckUpdates", - "When enabled, automatically checks extensions for updates. If an extension has an update, it is marked as outdated in the Extensions view. The updates are fetched from a Microsoft online service.", - ), - default: true, - scope: ConfigurationScope.APPLICATION, - tags: ["usesOnlineServices"], - }, - "extensions.ignoreRecommendations": { - type: "boolean", - description: localize( - "extensionsIgnoreRecommendations", - "When enabled, the notifications for extension recommendations will not be shown.", - ), - default: false, - }, - "extensions.showRecommendationsOnlyOnDemand": { - type: "boolean", - deprecationMessage: localize( - "extensionsShowRecommendationsOnlyOnDemand_Deprecated", - "This setting is deprecated. Use extensions.ignoreRecommendations setting to control recommendation notifications. Use Extensions view's visibility actions to hide Recommended view by default.", - ), - default: false, - tags: ["usesOnlineServices"], - }, - "extensions.closeExtensionDetailsOnViewChange": { - type: "boolean", - description: localize( - "extensionsCloseExtensionDetailsOnViewChange", - "When enabled, editors with extension details will be automatically closed upon navigating away from the Extensions View.", - ), - default: false, - }, - "extensions.confirmedUriHandlerExtensionIds": { - type: "array", - items: { - type: "string", - }, - description: localize( - "handleUriConfirmedExtensions", - "When an extension is listed here, a confirmation prompt will not be shown when that extension handles a URI.", - ), - default: [], - scope: ConfigurationScope.APPLICATION, - }, - "extensions.webWorker": { - type: ["boolean", "string"], - enum: [true, false, "auto"], - enumDescriptions: [ - localize( - "extensionsWebWorker.true", - "The Web Worker Extension Host will always be launched.", - ), - localize( - "extensionsWebWorker.false", - "The Web Worker Extension Host will never be launched.", - ), - localize( - "extensionsWebWorker.auto", - "The Web Worker Extension Host will be launched when a web extension needs it.", - ), - ], - description: localize( - "extensionsWebWorker", - "Enable web worker extension host.", - ), - default: "auto", - }, - "extensions.supportVirtualWorkspaces": { - type: "object", - markdownDescription: localize( - "extensions.supportVirtualWorkspaces", - "Override the virtual workspaces support of an extension.", - ), - patternProperties: { - "([a-z0-9A-Z][a-z0-9-A-Z]*)\\.([a-z0-9A-Z][a-z0-9-A-Z]*)$": { - type: "boolean", - default: false, - }, - }, - additionalProperties: false, - default: {}, - defaultSnippets: [ - { - "body": { - "pub.name": false, - }, - }, - ], - }, - "extensions.experimental.affinity": { - type: "object", - markdownDescription: localize( - "extensions.affinity", - "Configure an extension to execute in a different extension host process.", - ), - patternProperties: { - "([a-z0-9A-Z][a-z0-9-A-Z]*)\\.([a-z0-9A-Z][a-z0-9-A-Z]*)$": { - type: "integer", - default: 1, + }, ViewContainerLocation.Sidebar); + +Registry.as(ConfigurationExtensions.Configuration) + .registerConfiguration({ + id: 'extensions', + order: 30, + title: localize('extensionsConfigurationTitle', "Extensions"), + type: 'object', + properties: { + 'extensions.autoUpdate': { + enum: [true, 'onlyEnabledExtensions', false,], + enumItemLabels: [ + localize('all', "All Extensions"), + localize('enabled', "Only Enabled Extensions"), + localize('none', "None"), + ], + enumDescriptions: [ + localize('extensions.autoUpdate.true', 'Download and install updates automatically for all extensions.'), + localize('extensions.autoUpdate.enabled', 'Download and install updates automatically only for enabled extensions.'), + localize('extensions.autoUpdate.false', 'Extensions are not automatically updated.'), + ], + description: localize('extensions.autoUpdate', "Controls the automatic update behavior of extensions. The updates are fetched from a Microsoft online service."), + default: true, + scope: ConfigurationScope.APPLICATION, + tags: ['usesOnlineServices'] + }, + 'extensions.autoCheckUpdates': { + type: 'boolean', + description: localize('extensionsCheckUpdates', "When enabled, automatically checks extensions for updates. If an extension has an update, it is marked as outdated in the Extensions view. The updates are fetched from a Microsoft online service."), + default: true, + scope: ConfigurationScope.APPLICATION, + tags: ['usesOnlineServices'] + }, + 'extensions.ignoreRecommendations': { + type: 'boolean', + description: localize('extensionsIgnoreRecommendations', "When enabled, the notifications for extension recommendations will not be shown."), + default: false + }, + 'extensions.showRecommendationsOnlyOnDemand': { + type: 'boolean', + deprecationMessage: localize('extensionsShowRecommendationsOnlyOnDemand_Deprecated', "This setting is deprecated. Use extensions.ignoreRecommendations setting to control recommendation notifications. Use Extensions view's visibility actions to hide Recommended view by default."), + default: false, + tags: ['usesOnlineServices'] + }, + 'extensions.closeExtensionDetailsOnViewChange': { + type: 'boolean', + description: localize('extensionsCloseExtensionDetailsOnViewChange', "When enabled, editors with extension details will be automatically closed upon navigating away from the Extensions View."), + default: false + }, + 'extensions.confirmedUriHandlerExtensionIds': { + type: 'array', + items: { + type: 'string' }, - }, - additionalProperties: false, - default: {}, - defaultSnippets: [ - { - "body": { - "pub.name": 1, - }, + description: localize('handleUriConfirmedExtensions', "When an extension is listed here, a confirmation prompt will not be shown when that extension handles a URI."), + default: [], + scope: ConfigurationScope.APPLICATION + }, + 'extensions.webWorker': { + type: ['boolean', 'string'], + enum: [true, false, 'auto'], + enumDescriptions: [ + localize('extensionsWebWorker.true', "The Web Worker Extension Host will always be launched."), + localize('extensionsWebWorker.false', "The Web Worker Extension Host will never be launched."), + localize('extensionsWebWorker.auto', "The Web Worker Extension Host will be launched when a web extension needs it."), + ], + description: localize('extensionsWebWorker', "Enable web worker extension host."), + default: 'auto' + }, + 'extensions.supportVirtualWorkspaces': { + type: 'object', + markdownDescription: localize('extensions.supportVirtualWorkspaces', "Override the virtual workspaces support of an extension."), + patternProperties: { + '([a-z0-9A-Z][a-z0-9-A-Z]*)\\.([a-z0-9A-Z][a-z0-9-A-Z]*)$': { + type: 'boolean', + default: false + } }, - ], - }, - [WORKSPACE_TRUST_EXTENSION_SUPPORT]: { - type: "object", - scope: ConfigurationScope.APPLICATION, - markdownDescription: localize( - "extensions.supportUntrustedWorkspaces", - "Override the untrusted workspace support of an extension. Extensions using `true` will always be enabled. Extensions using `limited` will always be enabled, and the extension will hide functionality that requires trust. Extensions using `false` will only be enabled only when the workspace is trusted.", - ), - patternProperties: { - "([a-z0-9A-Z][a-z0-9-A-Z]*)\\.([a-z0-9A-Z][a-z0-9-A-Z]*)$": { - type: "object", - properties: { - "supported": { - type: ["boolean", "string"], - enum: [true, false, "limited"], - enumDescriptions: [ - localize( - "extensions.supportUntrustedWorkspaces.true", - "Extension will always be enabled.", - ), - localize( - "extensions.supportUntrustedWorkspaces.false", - "Extension will only be enabled only when the workspace is trusted.", - ), - localize( - "extensions.supportUntrustedWorkspaces.limited", - "Extension will always be enabled, and the extension will hide functionality requiring trust.", - ), - ], - description: localize( - "extensions.supportUntrustedWorkspaces.supported", - "Defines the untrusted workspace support setting for the extension.", - ), - }, - "version": { - type: "string", - description: localize( - "extensions.supportUntrustedWorkspaces.version", - "Defines the version of the extension for which the override should be applied. If not specified, the override will be applied independent of the extension version.", - ), - }, - }, + additionalProperties: false, + default: {}, + defaultSnippets: [{ + 'body': { + 'pub.name': false + } + }] + }, + 'extensions.experimental.affinity': { + type: 'object', + markdownDescription: localize('extensions.affinity', "Configure an extension to execute in a different extension host process."), + patternProperties: { + '([a-z0-9A-Z][a-z0-9-A-Z]*)\\.([a-z0-9A-Z][a-z0-9-A-Z]*)$': { + type: 'integer', + default: 1 + } }, + additionalProperties: false, + default: {}, + defaultSnippets: [{ + 'body': { + 'pub.name': 1 + } + }] + }, + [WORKSPACE_TRUST_EXTENSION_SUPPORT]: { + type: 'object', + scope: ConfigurationScope.APPLICATION, + markdownDescription: localize('extensions.supportUntrustedWorkspaces', "Override the untrusted workspace support of an extension. Extensions using `true` will always be enabled. Extensions using `limited` will always be enabled, and the extension will hide functionality that requires trust. Extensions using `false` will only be enabled only when the workspace is trusted."), + patternProperties: { + '([a-z0-9A-Z][a-z0-9-A-Z]*)\\.([a-z0-9A-Z][a-z0-9-A-Z]*)$': { + type: 'object', + properties: { + 'supported': { + type: ['boolean', 'string'], + enum: [true, false, 'limited'], + enumDescriptions: [ + localize('extensions.supportUntrustedWorkspaces.true', "Extension will always be enabled."), + localize('extensions.supportUntrustedWorkspaces.false', "Extension will only be enabled only when the workspace is trusted."), + localize('extensions.supportUntrustedWorkspaces.limited', "Extension will always be enabled, and the extension will hide functionality requiring trust."), + ], + description: localize('extensions.supportUntrustedWorkspaces.supported', "Defines the untrusted workspace support setting for the extension."), + }, + 'version': { + type: 'string', + description: localize('extensions.supportUntrustedWorkspaces.version', "Defines the version of the extension for which the override should be applied. If not specified, the override will be applied independent of the extension version."), + } + } + } + } }, - }, - "extensions.experimental.deferredStartupFinishedActivation": { - type: "boolean", - description: localize( - "extensionsDeferredStartupFinishedActivation", - "When enabled, extensions which declare the `onStartupFinished` activation event will be activated after a timeout.", - ), - default: false, - }, - "extensions.experimental.issueQuickAccess": { - type: "boolean", - description: localize( - "extensionsInQuickAccess", - "When enabled, extensions can be searched for via Quick Access and report issues from there.", - ), - default: true, - }, - "extensions.verifySignature": { - type: "boolean", - description: localize( - "extensions.verifySignature", - "When enabled, extensions are verified to be signed before getting installed.", - ), - default: true, - scope: ConfigurationScope.APPLICATION, - included: isNative && !isLinux, - }, - [UseUnpkgResourceApiConfigKey]: { - type: "boolean", - description: localize( - "extensions.gallery.useUnpkgResourceApi", - "When enabled, extensions to update are fetched from Unpkg service.", - ), - default: true, - scope: ConfigurationScope.APPLICATION, - tags: ["onExp", "usesOnlineServices"], - }, - [AllowedExtensionsConfigKey]: { - // Note: Type is set only to object because to support policies generation during build time, where single type is expected. - type: "object", - description: localize( - "extensions.allowed", - "List of extensions that are allowed.", - ), - default: "*", - defaultSnippets: [ - { + 'extensions.experimental.deferredStartupFinishedActivation': { + type: 'boolean', + description: localize('extensionsDeferredStartupFinishedActivation', "When enabled, extensions which declare the `onStartupFinished` activation event will be activated after a timeout."), + default: false + }, + 'extensions.experimental.issueQuickAccess': { + type: 'boolean', + description: localize('extensionsInQuickAccess', "When enabled, extensions can be searched for via Quick Access and report issues from there."), + default: true + }, + 'extensions.verifySignature': { + type: 'boolean', + description: localize('extensions.verifySignature', "When enabled, extensions are verified to be signed before getting installed."), + default: true, + scope: ConfigurationScope.APPLICATION, + included: isNative && !isLinux + }, + [UseUnpkgResourceApiConfigKey]: { + type: 'boolean', + description: localize('extensions.gallery.useUnpkgResourceApi', "When enabled, extensions to update are fetched from Unpkg service."), + default: true, + scope: ConfigurationScope.APPLICATION, + tags: ['onExp', 'usesOnlineServices'] + }, + [AllowedExtensionsConfigKey]: { + // Note: Type is set only to object because to support policies generation during build time, where single type is expected. + type: 'object', + description: localize('extensions.allowed', "List of extensions that are allowed."), + default: '*', + defaultSnippets: [{ body: {}, - description: localize( - "extensions.allowed.none", - "No extensions are allowed.", - ), - }, - { + description: localize('extensions.allowed.none', "No extensions are allowed."), + }, { body: { - "*": true, + '*': true }, - description: localize( - "extensions.allowed.all", - "All extensions are allowed.", - ), - }, - ], - scope: ConfigurationScope.APPLICATION, - policy: { - name: "AllowedExtensions", - minimumVersion: "1.96", - }, - additionalProperties: false, - patternProperties: { - "([a-z0-9A-Z][a-z0-9-A-Z]*)\\.([a-z0-9A-Z][a-z0-9-A-Z]*)$": { - anyOf: [ - { - type: ["boolean", "string"], - enum: [true, false, "stable"], - description: localize( - "extensions.allow.description", - "Allow or disallow the extension.", - ), - enumDescriptions: [ - localize( - "extensions.allowed.enable.desc", - "Extension is allowed.", - ), - localize( - "extensions.allowed.disable.desc", - "Extension is not allowed.", - ), - localize( - "extensions.allowed.disable.stable.desc", - "Allow only stable versions of the extension.", - ), - ], - }, - { - type: "array", - description: localize( - "extensions.allow.version.description", - "Allow or disallow specific versions of the extension. To specifcy a platform specific version, use the format `platform@1.2.3`, e.g. `win32-x64@1.2.3`. Supported platforms are `win32-x64`, `win32-arm64`, `linux-x64`, `linux-arm64`, `linux-armhf`, `alpine-x64`, `alpine-arm64`, `darwin-x64`, `darwin-arm64`", - ), - }, - ], - }, - "([a-z0-9A-Z][a-z0-9-A-Z]*)$": { - type: ["boolean", "string"], - enum: [true, false, "stable"], - description: localize( - "extension.publisher.allow.description", - "Allow or disallow all extensions from the publisher.", - ), - enumDescriptions: [ - localize( - "extensions.publisher.allowed.enable.desc", - "All extensions from the publisher are allowed.", - ), - localize( - "extensions.publisher.allowed.disable.desc", - "All extensions from the publisher are not allowed.", - ), - localize( - "extensions.publisher.allowed.disable.stable.desc", - "Allow only stable versions of the extensions from the publisher.", - ), - ], - }, - "\\*": { - type: "boolean", - enum: [true, false], - description: localize( - "extensions.allow.all.description", - "Allow or disallow all extensions.", - ), - enumDescriptions: [ - localize( - "extensions.allow.all.enable", - "Allow all extensions.", - ), - localize( - "extensions.allow.all.disable", - "Disallow all extensions.", - ), - ], + description: localize('extensions.allowed.all', "All extensions are allowed."), + }], + scope: ConfigurationScope.APPLICATION, + policy: { + name: 'AllowedExtensions', + minimumVersion: '1.96', }, - }, - }, - }, -}); + additionalProperties: false, + patternProperties: { + '([a-z0-9A-Z][a-z0-9-A-Z]*)\\.([a-z0-9A-Z][a-z0-9-A-Z]*)$': { + anyOf: [ + { + type: ['boolean', 'string'], + enum: [true, false, 'stable'], + description: localize('extensions.allow.description', "Allow or disallow the extension."), + enumDescriptions: [ + localize('extensions.allowed.enable.desc', "Extension is allowed."), + localize('extensions.allowed.disable.desc', "Extension is not allowed."), + localize('extensions.allowed.disable.stable.desc', "Allow only stable versions of the extension."), + ], + }, + { + type: 'array', + description: localize('extensions.allow.version.description', "Allow or disallow specific versions of the extension. To specifcy a platform specific version, use the format `platform@1.2.3`, e.g. `win32-x64@1.2.3`. Supported platforms are `win32-x64`, `win32-arm64`, `linux-x64`, `linux-arm64`, `linux-armhf`, `alpine-x64`, `alpine-arm64`, `darwin-x64`, `darwin-arm64`"), + }, + ] + }, + '([a-z0-9A-Z][a-z0-9-A-Z]*)$': { + type: ['boolean', 'string'], + enum: [true, false, 'stable'], + description: localize('extension.publisher.allow.description', "Allow or disallow all extensions from the publisher."), + enumDescriptions: [ + localize('extensions.publisher.allowed.enable.desc', "All extensions from the publisher are allowed."), + localize('extensions.publisher.allowed.disable.desc', "All extensions from the publisher are not allowed."), + localize('extensions.publisher.allowed.disable.stable.desc', "Allow only stable versions of the extensions from the publisher."), + ], + }, + '\\*': { + type: 'boolean', + enum: [true, false], + description: localize('extensions.allow.all.description', "Allow or disallow all extensions."), + enumDescriptions: [ + localize('extensions.allow.all.enable', "Allow all extensions."), + localize('extensions.allow.all.disable', "Disallow all extensions.") + ], + } + } + } + } + }); -const jsonRegistry = ( - Registry.as(jsonContributionRegistry.Extensions.JSONContribution) -); -jsonRegistry.registerSchema( - ExtensionsConfigurationSchemaId, - ExtensionsConfigurationSchema, -); +const jsonRegistry = Registry.as(jsonContributionRegistry.Extensions.JSONContribution); +jsonRegistry.registerSchema(ExtensionsConfigurationSchemaId, ExtensionsConfigurationSchema); // Register Commands -CommandsRegistry.registerCommand( - "_extensions.manage", - ( - accessor: ServicesAccessor, - extensionId: string, - tab?: ExtensionEditorTab, - preserveFocus?: boolean, - feature?: string, - ) => { - const extensionService = accessor.get(IExtensionsWorkbenchService); - const extension = extensionService.local.find((e) => - areSameExtensions(e.identifier, { id: extensionId }), - ); - if (extension) { - extensionService.open(extension, { tab, preserveFocus, feature }); - } else { - throw new Error( - localize("notFound", "Extension '{0}' not found.", extensionId), - ); - } - }, -); - -CommandsRegistry.registerCommand( - "extension.open", - async ( - accessor: ServicesAccessor, - extensionId: string, - tab?: ExtensionEditorTab, - preserveFocus?: boolean, - feature?: string, - sideByside?: boolean, - ) => { - const extensionService = accessor.get(IExtensionsWorkbenchService); - const commandService = accessor.get(ICommandService); - - const [extension] = await extensionService.getExtensions( - [{ id: extensionId }], - CancellationToken.None, - ); - if (extension) { - return extensionService.open(extension, { - tab, - preserveFocus, - feature, - sideByside, - }); - } +CommandsRegistry.registerCommand('_extensions.manage', (accessor: ServicesAccessor, extensionId: string, tab?: ExtensionEditorTab, preserveFocus?: boolean, feature?: string) => { + const extensionService = accessor.get(IExtensionsWorkbenchService); + const extension = extensionService.local.find(e => areSameExtensions(e.identifier, { id: extensionId })); + if (extension) { + extensionService.open(extension, { tab, preserveFocus, feature }); + } else { + throw new Error(localize('notFound', "Extension '{0}' not found.", extensionId)); + } +}); - return commandService.executeCommand( - "_extensions.manage", - extensionId, - tab, - preserveFocus, - feature, - ); - }, -); +CommandsRegistry.registerCommand('extension.open', async (accessor: ServicesAccessor, extensionId: string, tab?: ExtensionEditorTab, preserveFocus?: boolean, feature?: string, sideByside?: boolean) => { + const extensionService = accessor.get(IExtensionsWorkbenchService); + const commandService = accessor.get(ICommandService); + + const [extension] = await extensionService.getExtensions([{ id: extensionId }], CancellationToken.None); + if (extension) { + return extensionService.open(extension, { tab, preserveFocus, feature, sideByside }); + } + + return commandService.executeCommand('_extensions.manage', extensionId, tab, preserveFocus, feature); +}); CommandsRegistry.registerCommand({ - id: "workbench.extensions.installExtension", + id: 'workbench.extensions.installExtension', metadata: { - description: localize( - "workbench.extensions.installExtension.description", - "Install the given extension", - ), + description: localize('workbench.extensions.installExtension.description', "Install the given extension"), args: [ { - name: "extensionIdOrVSIXUri", - description: localize( - "workbench.extensions.installExtension.arg.decription", - "Extension id or VSIX resource uri", - ), - constraint: (value: any) => - typeof value === "string" || value instanceof URI, + name: 'extensionIdOrVSIXUri', + description: localize('workbench.extensions.installExtension.arg.decription', "Extension id or VSIX resource uri"), + constraint: (value: any) => typeof value === 'string' || value instanceof URI, }, { - name: "options", - description: - "(optional) Options for installing the extension. Object with the following properties: " + - "`installOnlyNewlyAddedFromExtensionPackVSIX`: When enabled, VS Code installs only newly added extensions from the extension pack VSIX. This option is considered only when installing VSIX. ", + name: 'options', + description: '(optional) Options for installing the extension. Object with the following properties: ' + + '`installOnlyNewlyAddedFromExtensionPackVSIX`: When enabled, VS Code installs only newly added extensions from the extension pack VSIX. This option is considered only when installing VSIX. ', isOptional: true, schema: { - "type": "object", - "properties": { - "installOnlyNewlyAddedFromExtensionPackVSIX": { - "type": "boolean", - "description": localize( - "workbench.extensions.installExtension.option.installOnlyNewlyAddedFromExtensionPackVSIX", - "When enabled, VS Code installs only newly added extensions from the extension pack VSIX. This option is considered only while installing a VSIX.", - ), - default: false, - }, - "installPreReleaseVersion": { - "type": "boolean", - "description": localize( - "workbench.extensions.installExtension.option.installPreReleaseVersion", - "When enabled, VS Code installs the pre-release version of the extension if available.", - ), - default: false, + 'type': 'object', + 'properties': { + 'installOnlyNewlyAddedFromExtensionPackVSIX': { + 'type': 'boolean', + 'description': localize('workbench.extensions.installExtension.option.installOnlyNewlyAddedFromExtensionPackVSIX', "When enabled, VS Code installs only newly added extensions from the extension pack VSIX. This option is considered only while installing a VSIX."), + default: false }, - "donotSync": { - "type": "boolean", - "description": localize( - "workbench.extensions.installExtension.option.donotSync", - "When enabled, VS Code do not sync this extension when Settings Sync is on.", - ), - default: false, + 'installPreReleaseVersion': { + 'type': 'boolean', + 'description': localize('workbench.extensions.installExtension.option.installPreReleaseVersion', "When enabled, VS Code installs the pre-release version of the extension if available."), + default: false }, - "justification": { - "type": ["string", "object"], - "description": localize( - "workbench.extensions.installExtension.option.justification", - "Justification for installing the extension. This is a string or an object that can be used to pass any information to the installation handlers. i.e. `{reason: 'This extension wants to open a URI', action: 'Open URI'}` will show a message box with the reason and action upon install.", - ), + 'donotSync': { + 'type': 'boolean', + 'description': localize('workbench.extensions.installExtension.option.donotSync', "When enabled, VS Code do not sync this extension when Settings Sync is on."), + default: false }, - "enable": { - "type": "boolean", - "description": localize( - "workbench.extensions.installExtension.option.enable", - "When enabled, the extension will be enabled if it is installed but disabled. If the extension is already enabled, this has no effect.", - ), - default: false, + 'justification': { + 'type': ['string', 'object'], + 'description': localize('workbench.extensions.installExtension.option.justification', "Justification for installing the extension. This is a string or an object that can be used to pass any information to the installation handlers. i.e. `{reason: 'This extension wants to open a URI', action: 'Open URI'}` will show a message box with the reason and action upon install."), }, - "context": { - "type": "object", - "description": localize( - "workbench.extensions.installExtension.option.context", - "Context for the installation. This is a JSON object that can be used to pass any information to the installation handlers. i.e. `{skipWalkthrough: true}` will skip opening the walkthrough upon install.", - ), + 'enable': { + 'type': 'boolean', + 'description': localize('workbench.extensions.installExtension.option.enable', "When enabled, the extension will be enabled if it is installed but disabled. If the extension is already enabled, this has no effect."), + default: false }, - }, - }, - }, - ], + 'context': { + 'type': 'object', + 'description': localize('workbench.extensions.installExtension.option.context', "Context for the installation. This is a JSON object that can be used to pass any information to the installation handlers. i.e. `{skipWalkthrough: true}` will skip opening the walkthrough upon install."), + } + } + } + } + ] }, handler: async ( accessor, @@ -782,144 +421,71 @@ CommandsRegistry.registerCommand({ justification?: string | { reason: string; action: string }; enable?: boolean; context?: IStringDictionary; - }, - ) => { - const extensionsWorkbenchService = accessor.get( - IExtensionsWorkbenchService, - ); - const extensionManagementService = accessor.get( - IWorkbenchExtensionManagementService, - ); + }) => { + const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); + const extensionManagementService = accessor.get(IWorkbenchExtensionManagementService); const extensionGalleryService = accessor.get(IExtensionGalleryService); try { - if (typeof arg === "string") { + if (typeof arg === 'string') { const [id, version] = getIdAndVersion(arg); - const extension = extensionsWorkbenchService.local.find((e) => - areSameExtensions(e.identifier, { id, uuid: version }), - ); - if ( - extension?.enablementState === - EnablementState.DisabledByExtensionKind - ) { - const [gallery] = - await extensionGalleryService.getExtensions( - [ - { - id, - preRelease: - options?.installPreReleaseVersion, - }, - ], - CancellationToken.None, - ); + const extension = extensionsWorkbenchService.local.find(e => areSameExtensions(e.identifier, { id, uuid: version })); + if (extension?.enablementState === EnablementState.DisabledByExtensionKind) { + const [gallery] = await extensionGalleryService.getExtensions([{ id, preRelease: options?.installPreReleaseVersion }], CancellationToken.None); if (!gallery) { - throw new Error( - localize( - "notFound", - "Extension '{0}' not found.", - arg, - ), - ); + throw new Error(localize('notFound', "Extension '{0}' not found.", arg)); } - await extensionManagementService.installFromGallery( - gallery, - { - isMachineScoped: options?.donotSync - ? true - : undefined /* do not allow syncing extensions automatically while installing through the command */, - installPreReleaseVersion: - options?.installPreReleaseVersion, - installGivenVersion: !!version, - context: { - ...options?.context, - [EXTENSION_INSTALL_SOURCE_CONTEXT]: - ExtensionInstallSource.COMMAND, - }, - }, - ); + await extensionManagementService.installFromGallery(gallery, { + isMachineScoped: options?.donotSync ? true : undefined, /* do not allow syncing extensions automatically while installing through the command */ + installPreReleaseVersion: options?.installPreReleaseVersion, + installGivenVersion: !!version, + context: { ...options?.context, [EXTENSION_INSTALL_SOURCE_CONTEXT]: ExtensionInstallSource.COMMAND }, + }); } else { - await extensionsWorkbenchService.install( - arg, - { - version, - installPreReleaseVersion: - options?.installPreReleaseVersion, - context: { - ...options?.context, - [EXTENSION_INSTALL_SOURCE_CONTEXT]: - ExtensionInstallSource.COMMAND, - }, - justification: options?.justification, - enable: options?.enable, - isMachineScoped: options?.donotSync - ? true - : undefined /* do not allow syncing extensions automatically while installing through the command */, - }, - ProgressLocation.Notification, - ); + await extensionsWorkbenchService.install(arg, { + version, + installPreReleaseVersion: options?.installPreReleaseVersion, + context: { ...options?.context, [EXTENSION_INSTALL_SOURCE_CONTEXT]: ExtensionInstallSource.COMMAND }, + justification: options?.justification, + enable: options?.enable, + isMachineScoped: options?.donotSync ? true : undefined, /* do not allow syncing extensions automatically while installing through the command */ + }, ProgressLocation.Notification); } } else { const vsix = URI.revive(arg); - await extensionsWorkbenchService.install(vsix, { - installOnlyNewlyAddedFromExtensionPack: - options?.installOnlyNewlyAddedFromExtensionPackVSIX, - installGivenVersion: true, - }); + await extensionsWorkbenchService.install(vsix, { installOnlyNewlyAddedFromExtensionPack: options?.installOnlyNewlyAddedFromExtensionPackVSIX, installGivenVersion: true }); } } catch (e) { onUnexpectedError(e); throw e; } - }, + } }); CommandsRegistry.registerCommand({ - id: "workbench.extensions.uninstallExtension", + id: 'workbench.extensions.uninstallExtension', metadata: { - description: localize( - "workbench.extensions.uninstallExtension.description", - "Uninstall the given extension", - ), + description: localize('workbench.extensions.uninstallExtension.description', "Uninstall the given extension"), args: [ { - name: localize( - "workbench.extensions.uninstallExtension.arg.name", - "Id of the extension to uninstall", - ), + name: localize('workbench.extensions.uninstallExtension.arg.name', "Id of the extension to uninstall"), schema: { - "type": "string", - }, - }, - ], + 'type': 'string' + } + } + ] }, handler: async (accessor, id: string) => { if (!id) { - throw new Error(localize("id required", "Extension id required.")); + throw new Error(localize('id required', "Extension id required.")); } - const extensionManagementService = accessor.get( - IExtensionManagementService, - ); + const extensionManagementService = accessor.get(IExtensionManagementService); const installed = await extensionManagementService.getInstalled(); - const [extensionToUninstall] = installed.filter((e) => - areSameExtensions(e.identifier, { id }), - ); + const [extensionToUninstall] = installed.filter(e => areSameExtensions(e.identifier, { id })); if (!extensionToUninstall) { - throw new Error( - localize( - "notInstalled", - "Extension '{0}' is not installed. Make sure you use the full extension ID, including the publisher, e.g.: ms-dotnettools.csharp.", - id, - ), - ); + throw new Error(localize('notInstalled', "Extension '{0}' is not installed. Make sure you use the full extension ID, including the publisher, e.g.: ms-dotnettools.csharp.", id)); } if (extensionToUninstall.isBuiltin) { - throw new Error( - localize( - "builtin", - "Extension '{0}' is a Built-in extension and cannot be installed", - id, - ), - ); + throw new Error(localize('builtin', "Extension '{0}' is a Built-in extension and cannot be installed", id)); } try { @@ -928,36 +494,27 @@ CommandsRegistry.registerCommand({ onUnexpectedError(e); throw e; } - }, + } }); CommandsRegistry.registerCommand({ - id: "workbench.extensions.search", + id: 'workbench.extensions.search', metadata: { - description: localize( - "workbench.extensions.search.description", - "Search for a specific extension", - ), + description: localize('workbench.extensions.search.description', "Search for a specific extension"), args: [ { - name: localize( - "workbench.extensions.search.arg.name", - "Query to use in search", - ), - schema: { "type": "string" }, - }, - ], + name: localize('workbench.extensions.search.arg.name', "Query to use in search"), + schema: { 'type': 'string' } + } + ] }, - handler: async (accessor, query: string = "") => { + handler: async (accessor, query: string = '') => { return accessor.get(IExtensionsWorkbenchService).openSearch(query); - }, + } }); -function overrideActionForActiveExtensionEditorWebview( - command: MultiCommand | undefined, - f: (webview: IWebview) => void, -) { - command?.addImplementation(105, "extensions-editor", (accessor) => { +function overrideActionForActiveExtensionEditorWebview(command: MultiCommand | undefined, f: (webview: IWebview) => void) { + command?.addImplementation(105, 'extensions-editor', (accessor) => { const editorService = accessor.get(IEditorService); const editor = editorService.activeEditorPane; if (editor instanceof ExtensionEditor) { @@ -970,29 +527,14 @@ function overrideActionForActiveExtensionEditorWebview( }); } -overrideActionForActiveExtensionEditorWebview(CopyAction, (webview) => - webview.copy(), -); -overrideActionForActiveExtensionEditorWebview(CutAction, (webview) => - webview.cut(), -); -overrideActionForActiveExtensionEditorWebview(PasteAction, (webview) => - webview.paste(), -); +overrideActionForActiveExtensionEditorWebview(CopyAction, webview => webview.copy()); +overrideActionForActiveExtensionEditorWebview(CutAction, webview => webview.cut()); +overrideActionForActiveExtensionEditorWebview(PasteAction, webview => webview.paste()); // Contexts -export const CONTEXT_HAS_LOCAL_SERVER = new RawContextKey( - "hasLocalServer", - false, -); -export const CONTEXT_HAS_REMOTE_SERVER = new RawContextKey( - "hasRemoteServer", - false, -); -export const CONTEXT_HAS_WEB_SERVER = new RawContextKey( - "hasWebServer", - false, -); +export const CONTEXT_HAS_LOCAL_SERVER = new RawContextKey('hasLocalServer', false); +export const CONTEXT_HAS_REMOTE_SERVER = new RawContextKey('hasRemoteServer', false); +export const CONTEXT_HAS_WEB_SERVER = new RawContextKey('hasWebServer', false); async function runAction(action: IAction): Promise { try { @@ -1009,23 +551,16 @@ type IExtensionActionOptions = IAction2Options & { run(accessor: ServicesAccessor, ...args: any[]): Promise; }; -class ExtensionsContributions - extends Disposable - implements IWorkbenchContribution -{ +class ExtensionsContributions extends Disposable implements IWorkbenchContribution { + constructor( - @IExtensionManagementServerService - private readonly extensionManagementServerService: IExtensionManagementServerService, - @IExtensionGalleryService - extensionGalleryService: IExtensionGalleryService, + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, + @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, @IContextKeyService contextKeyService: IContextKeyService, @IViewsService private readonly viewsService: IViewsService, - @IExtensionsWorkbenchService - private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IWorkbenchExtensionEnablementService - private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, - @IInstantiationService - private readonly instantiationService: IInstantiationService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, + @IInstantiationService private readonly instantiationService: IInstantiationService, @IDialogService private readonly dialogService: IDialogService, @ICommandService private readonly commandService: ICommandService, @IProductService private readonly productService: IProductService, @@ -1036,28 +571,18 @@ class ExtensionsContributions hasGalleryContext.set(true); } - const hasLocalServerContext = - CONTEXT_HAS_LOCAL_SERVER.bindTo(contextKeyService); - if ( - this.extensionManagementServerService.localExtensionManagementServer - ) { + const hasLocalServerContext = CONTEXT_HAS_LOCAL_SERVER.bindTo(contextKeyService); + if (this.extensionManagementServerService.localExtensionManagementServer) { hasLocalServerContext.set(true); } - const hasRemoteServerContext = - CONTEXT_HAS_REMOTE_SERVER.bindTo(contextKeyService); - if ( - this.extensionManagementServerService - .remoteExtensionManagementServer - ) { + const hasRemoteServerContext = CONTEXT_HAS_REMOTE_SERVER.bindTo(contextKeyService); + if (this.extensionManagementServerService.remoteExtensionManagementServer) { hasRemoteServerContext.set(true); } - const hasWebServerContext = - CONTEXT_HAS_WEB_SERVER.bindTo(contextKeyService); - if ( - this.extensionManagementServerService.webExtensionManagementServer - ) { + const hasWebServerContext = CONTEXT_HAS_WEB_SERVER.bindTo(contextKeyService); + if (this.extensionManagementServerService.webExtensionManagementServer) { hasWebServerContext.set(true); } @@ -1067,560 +592,301 @@ class ExtensionsContributions } private registerQuickAccessProvider(): void { - if ( - this.extensionManagementServerService - .localExtensionManagementServer || - this.extensionManagementServerService - .remoteExtensionManagementServer || - this.extensionManagementServerService.webExtensionManagementServer + if (this.extensionManagementServerService.localExtensionManagementServer + || this.extensionManagementServerService.remoteExtensionManagementServer + || this.extensionManagementServerService.webExtensionManagementServer ) { - Registry.as( - Extensions.Quickaccess, - ).registerQuickAccessProvider({ + Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ ctor: InstallExtensionQuickAccessProvider, prefix: InstallExtensionQuickAccessProvider.PREFIX, - placeholder: localize( - "installExtensionQuickAccessPlaceholder", - "Type the name of an extension to install or search.", - ), - helpEntries: [ - { - description: localize( - "installExtensionQuickAccessHelp", - "Install or Search Extensions", - ), - }, - ], + placeholder: localize('installExtensionQuickAccessPlaceholder', "Type the name of an extension to install or search."), + helpEntries: [{ description: localize('installExtensionQuickAccessHelp', "Install or Search Extensions") }] }); } } // Global actions private registerGlobalActions(): void { - this._register( - MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { - command: { - id: VIEWLET_ID, - title: localize( - { - key: "miPreferencesExtensions", - comment: ["&& denotes a mnemonic"], - }, - "&&Extensions", - ), - }, - group: "2_configuration", - order: 3, - }), - ); - this._register( - MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - command: { - id: VIEWLET_ID, - title: localize("showExtensions", "Extensions"), - }, - group: "2_configuration", - order: 3, - }), - ); + this._register(MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + command: { + id: VIEWLET_ID, + title: localize({ key: 'miPreferencesExtensions', comment: ['&& denotes a mnemonic'] }, "&&Extensions") + }, + group: '2_configuration', + order: 3 + })); + this._register(MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { + command: { + id: VIEWLET_ID, + title: localize('showExtensions', "Extensions") + }, + group: '2_configuration', + order: 3 + })); this.registerExtensionAction({ - id: "workbench.extensions.action.focusExtensionsView", - title: localize2("focusExtensions", "Focus on Extensions View"), + id: 'workbench.extensions.action.focusExtensionsView', + title: localize2('focusExtensions', 'Focus on Extensions View'), category: ExtensionsLocalizedLabel, f1: true, run: async (accessor: ServicesAccessor) => { - await accessor.get(IExtensionsWorkbenchService).openSearch(""); - }, + await accessor.get(IExtensionsWorkbenchService).openSearch(''); + } }); this.registerExtensionAction({ - id: "workbench.extensions.action.installExtensions", - title: localize2("installExtensions", "Install Extensions"), + id: 'workbench.extensions.action.installExtensions', + title: localize2('installExtensions', 'Install Extensions'), category: ExtensionsLocalizedLabel, menu: { id: MenuId.CommandPalette, - when: ContextKeyExpr.and( - CONTEXT_HAS_GALLERY, - ContextKeyExpr.or( - CONTEXT_HAS_LOCAL_SERVER, - CONTEXT_HAS_REMOTE_SERVER, - CONTEXT_HAS_WEB_SERVER, - ), - ), + when: ContextKeyExpr.and(CONTEXT_HAS_GALLERY, ContextKeyExpr.or(CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER)) }, run: async (accessor: ServicesAccessor) => { accessor.get(IViewsService).openViewContainer(VIEWLET_ID, true); - }, + } }); this.registerExtensionAction({ - id: "workbench.extensions.action.showRecommendedKeymapExtensions", - title: localize2("showRecommendedKeymapExtensionsShort", "Keymaps"), + id: 'workbench.extensions.action.showRecommendedKeymapExtensions', + title: localize2('showRecommendedKeymapExtensionsShort', 'Keymaps'), category: PreferencesLocalizedLabel, - menu: [ - { - id: MenuId.CommandPalette, - when: CONTEXT_HAS_GALLERY, - }, - { - id: MenuId.EditorTitle, - when: ContextKeyExpr.and( - CONTEXT_KEYBINDINGS_EDITOR, - CONTEXT_HAS_GALLERY, - ), - group: "2_keyboard_discover_actions", - }, - ], + menu: [{ + id: MenuId.CommandPalette, + when: CONTEXT_HAS_GALLERY + }, { + id: MenuId.EditorTitle, + when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_HAS_GALLERY), + group: '2_keyboard_discover_actions' + }], menuTitles: { - [MenuId.EditorTitle.id]: localize( - "importKeyboardShortcutsFroms", - "Migrate Keyboard Shortcuts from...", - ), + [MenuId.EditorTitle.id]: localize('importKeyboardShortcutsFroms', "Migrate Keyboard Shortcuts from...") }, - run: () => - this.extensionsWorkbenchService.openSearch( - "@recommended:keymaps ", - ), + run: () => this.extensionsWorkbenchService.openSearch('@recommended:keymaps ') }); this.registerExtensionAction({ - id: "workbench.extensions.action.showLanguageExtensions", - title: localize2( - "showLanguageExtensionsShort", - "Language Extensions", - ), + id: 'workbench.extensions.action.showLanguageExtensions', + title: localize2('showLanguageExtensionsShort', 'Language Extensions'), category: PreferencesLocalizedLabel, menu: { id: MenuId.CommandPalette, - when: CONTEXT_HAS_GALLERY, + when: CONTEXT_HAS_GALLERY }, - run: () => - this.extensionsWorkbenchService.openSearch( - "@recommended:languages ", - ), + run: () => this.extensionsWorkbenchService.openSearch('@recommended:languages ') }); this.registerExtensionAction({ - id: "workbench.extensions.action.checkForUpdates", - title: localize2("checkForUpdates", "Check for Extension Updates"), + id: 'workbench.extensions.action.checkForUpdates', + title: localize2('checkForUpdates', 'Check for Extension Updates'), category: ExtensionsLocalizedLabel, - menu: [ - { - id: MenuId.CommandPalette, - when: ContextKeyExpr.and( - CONTEXT_HAS_GALLERY, - ContextKeyExpr.or( - CONTEXT_HAS_LOCAL_SERVER, - CONTEXT_HAS_REMOTE_SERVER, - CONTEXT_HAS_WEB_SERVER, - ), - ), - }, - { - id: MenuId.ViewContainerTitle, - when: ContextKeyExpr.and( - ContextKeyExpr.equals("viewContainer", VIEWLET_ID), - CONTEXT_HAS_GALLERY, - ), - group: "1_updates", - order: 1, - }, - ], + menu: [{ + id: MenuId.CommandPalette, + when: ContextKeyExpr.and(CONTEXT_HAS_GALLERY, ContextKeyExpr.or(CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER)) + }, { + id: MenuId.ViewContainerTitle, + when: ContextKeyExpr.and(ContextKeyExpr.equals('viewContainer', VIEWLET_ID), CONTEXT_HAS_GALLERY), + group: '1_updates', + order: 1 + }], run: async () => { await this.extensionsWorkbenchService.checkForUpdates(); const outdated = this.extensionsWorkbenchService.outdated; if (outdated.length) { - return this.extensionsWorkbenchService.openSearch( - "@outdated ", - ); + return this.extensionsWorkbenchService.openSearch('@outdated '); } else { - return this.dialogService.info( - localize( - "noUpdatesAvailable", - "All extensions are up to date.", - ), - ); + return this.dialogService.info(localize('noUpdatesAvailable', "All extensions are up to date.")); } - }, + } }); - const enableAutoUpdateWhenCondition = ContextKeyExpr.equals( - `config.${AutoUpdateConfigurationKey}`, - false, - ); + const enableAutoUpdateWhenCondition = ContextKeyExpr.equals(`config.${AutoUpdateConfigurationKey}`, false); this.registerExtensionAction({ - id: "workbench.extensions.action.enableAutoUpdate", - title: localize2( - "enableAutoUpdate", - "Enable Auto Update for All Extensions", - ), + id: 'workbench.extensions.action.enableAutoUpdate', + title: localize2('enableAutoUpdate', 'Enable Auto Update for All Extensions'), category: ExtensionsLocalizedLabel, precondition: enableAutoUpdateWhenCondition, - menu: [ - { - id: MenuId.ViewContainerTitle, - order: 5, - group: "1_updates", - when: ContextKeyExpr.and( - ContextKeyExpr.equals("viewContainer", VIEWLET_ID), - enableAutoUpdateWhenCondition, - ), - }, - { - id: MenuId.CommandPalette, - }, - ], - run: (accessor: ServicesAccessor) => - accessor - .get(IExtensionsWorkbenchService) - .updateAutoUpdateForAllExtensions(true), + menu: [{ + id: MenuId.ViewContainerTitle, + order: 5, + group: '1_updates', + when: ContextKeyExpr.and(ContextKeyExpr.equals('viewContainer', VIEWLET_ID), enableAutoUpdateWhenCondition) + }, { + id: MenuId.CommandPalette, + }], + run: (accessor: ServicesAccessor) => accessor.get(IExtensionsWorkbenchService).updateAutoUpdateForAllExtensions(true) }); - const disableAutoUpdateWhenCondition = ContextKeyExpr.notEquals( - `config.${AutoUpdateConfigurationKey}`, - false, - ); + const disableAutoUpdateWhenCondition = ContextKeyExpr.notEquals(`config.${AutoUpdateConfigurationKey}`, false); this.registerExtensionAction({ - id: "workbench.extensions.action.disableAutoUpdate", - title: localize2( - "disableAutoUpdate", - "Disable Auto Update for All Extensions", - ), + id: 'workbench.extensions.action.disableAutoUpdate', + title: localize2('disableAutoUpdate', 'Disable Auto Update for All Extensions'), precondition: disableAutoUpdateWhenCondition, category: ExtensionsLocalizedLabel, - menu: [ - { - id: MenuId.ViewContainerTitle, - order: 5, - group: "1_updates", - when: ContextKeyExpr.and( - ContextKeyExpr.equals("viewContainer", VIEWLET_ID), - disableAutoUpdateWhenCondition, - ), - }, - { - id: MenuId.CommandPalette, - }, - ], - run: (accessor: ServicesAccessor) => - accessor - .get(IExtensionsWorkbenchService) - .updateAutoUpdateForAllExtensions(false), + menu: [{ + id: MenuId.ViewContainerTitle, + order: 5, + group: '1_updates', + when: ContextKeyExpr.and(ContextKeyExpr.equals('viewContainer', VIEWLET_ID), disableAutoUpdateWhenCondition) + }, { + id: MenuId.CommandPalette, + }], + run: (accessor: ServicesAccessor) => accessor.get(IExtensionsWorkbenchService).updateAutoUpdateForAllExtensions(false) }); this.registerExtensionAction({ - id: "workbench.extensions.action.updateAllExtensions", - title: localize2("updateAll", "Update All Extensions"), + id: 'workbench.extensions.action.updateAllExtensions', + title: localize2('updateAll', 'Update All Extensions'), category: ExtensionsLocalizedLabel, precondition: HasOutdatedExtensionsContext, menu: [ { id: MenuId.CommandPalette, - when: ContextKeyExpr.and( - CONTEXT_HAS_GALLERY, - ContextKeyExpr.or( - CONTEXT_HAS_LOCAL_SERVER, - CONTEXT_HAS_REMOTE_SERVER, - CONTEXT_HAS_WEB_SERVER, - ), - ), - }, - { + when: ContextKeyExpr.and(CONTEXT_HAS_GALLERY, ContextKeyExpr.or(CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER)) + }, { id: MenuId.ViewContainerTitle, - when: ContextKeyExpr.and( - ContextKeyExpr.equals("viewContainer", VIEWLET_ID), - ContextKeyExpr.or( - ContextKeyExpr.has( - `config.${AutoUpdateConfigurationKey}`, - ).negate(), - ContextKeyExpr.equals( - `config.${AutoUpdateConfigurationKey}`, - "onlyEnabledExtensions", - ), - ), - ), - group: "1_updates", - order: 2, - }, - { + when: ContextKeyExpr.and(ContextKeyExpr.equals('viewContainer', VIEWLET_ID), ContextKeyExpr.or(ContextKeyExpr.has(`config.${AutoUpdateConfigurationKey}`).negate(), ContextKeyExpr.equals(`config.${AutoUpdateConfigurationKey}`, 'onlyEnabledExtensions'))), + group: '1_updates', + order: 2 + }, { id: MenuId.ViewTitle, - when: ContextKeyExpr.equals( - "view", - OUTDATED_EXTENSIONS_VIEW_ID, - ), - group: "navigation", - order: 1, - }, + when: ContextKeyExpr.equals('view', OUTDATED_EXTENSIONS_VIEW_ID), + group: 'navigation', + order: 1 + } ], icon: installWorkspaceRecommendedIcon, run: async () => { await this.extensionsWorkbenchService.updateAll(); - }, + } }); this.registerExtensionAction({ - id: "workbench.extensions.action.enableAll", - title: localize2("enableAll", "Enable All Extensions"), + id: 'workbench.extensions.action.enableAll', + title: localize2('enableAll', 'Enable All Extensions'), category: ExtensionsLocalizedLabel, - menu: [ - { - id: MenuId.CommandPalette, - when: ContextKeyExpr.or( - CONTEXT_HAS_LOCAL_SERVER, - CONTEXT_HAS_REMOTE_SERVER, - CONTEXT_HAS_WEB_SERVER, - ), - }, - { - id: MenuId.ViewContainerTitle, - when: ContextKeyExpr.equals("viewContainer", VIEWLET_ID), - group: "2_enablement", - order: 1, - }, - ], + menu: [{ + id: MenuId.CommandPalette, + when: ContextKeyExpr.or(CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER) + }, { + id: MenuId.ViewContainerTitle, + when: ContextKeyExpr.equals('viewContainer', VIEWLET_ID), + group: '2_enablement', + order: 1 + }], run: async () => { - const extensionsToEnable = - this.extensionsWorkbenchService.local.filter( - (e) => - !!e.local && - this.extensionEnablementService.canChangeEnablement( - e.local, - ) && - !this.extensionEnablementService.isEnabled(e.local), - ); + const extensionsToEnable = this.extensionsWorkbenchService.local.filter(e => !!e.local && this.extensionEnablementService.canChangeEnablement(e.local) && !this.extensionEnablementService.isEnabled(e.local)); if (extensionsToEnable.length) { - await this.extensionsWorkbenchService.setEnablement( - extensionsToEnable, - EnablementState.EnabledGlobally, - ); + await this.extensionsWorkbenchService.setEnablement(extensionsToEnable, EnablementState.EnabledGlobally); } - }, + } }); this.registerExtensionAction({ - id: "workbench.extensions.action.enableAllWorkspace", - title: localize2( - "enableAllWorkspace", - "Enable All Extensions for this Workspace", - ), + id: 'workbench.extensions.action.enableAllWorkspace', + title: localize2('enableAllWorkspace', 'Enable All Extensions for this Workspace'), category: ExtensionsLocalizedLabel, menu: { id: MenuId.CommandPalette, - when: ContextKeyExpr.and( - WorkbenchStateContext.notEqualsTo("empty"), - ContextKeyExpr.or( - CONTEXT_HAS_LOCAL_SERVER, - CONTEXT_HAS_REMOTE_SERVER, - CONTEXT_HAS_WEB_SERVER, - ), - ), + when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('empty'), ContextKeyExpr.or(CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER)) }, run: async () => { - const extensionsToEnable = - this.extensionsWorkbenchService.local.filter( - (e) => - !!e.local && - this.extensionEnablementService.canChangeEnablement( - e.local, - ) && - !this.extensionEnablementService.isEnabled(e.local), - ); + const extensionsToEnable = this.extensionsWorkbenchService.local.filter(e => !!e.local && this.extensionEnablementService.canChangeEnablement(e.local) && !this.extensionEnablementService.isEnabled(e.local)); if (extensionsToEnable.length) { - await this.extensionsWorkbenchService.setEnablement( - extensionsToEnable, - EnablementState.EnabledWorkspace, - ); + await this.extensionsWorkbenchService.setEnablement(extensionsToEnable, EnablementState.EnabledWorkspace); } - }, + } }); this.registerExtensionAction({ - id: "workbench.extensions.action.disableAll", - title: localize2("disableAll", "Disable All Installed Extensions"), + id: 'workbench.extensions.action.disableAll', + title: localize2('disableAll', 'Disable All Installed Extensions'), category: ExtensionsLocalizedLabel, - menu: [ - { - id: MenuId.CommandPalette, - when: ContextKeyExpr.or( - CONTEXT_HAS_LOCAL_SERVER, - CONTEXT_HAS_REMOTE_SERVER, - CONTEXT_HAS_WEB_SERVER, - ), - }, - { - id: MenuId.ViewContainerTitle, - when: ContextKeyExpr.equals("viewContainer", VIEWLET_ID), - group: "2_enablement", - order: 2, - }, - ], + menu: [{ + id: MenuId.CommandPalette, + when: ContextKeyExpr.or(CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER) + }, { + id: MenuId.ViewContainerTitle, + when: ContextKeyExpr.equals('viewContainer', VIEWLET_ID), + group: '2_enablement', + order: 2 + }], run: async () => { - const extensionsToDisable = - this.extensionsWorkbenchService.local.filter( - (e) => - !e.isBuiltin && - !!e.local && - this.extensionEnablementService.isEnabled( - e.local, - ) && - this.extensionEnablementService.canChangeEnablement( - e.local, - ), - ); + const extensionsToDisable = this.extensionsWorkbenchService.local.filter(e => !e.isBuiltin && !!e.local && this.extensionEnablementService.isEnabled(e.local) && this.extensionEnablementService.canChangeEnablement(e.local)); if (extensionsToDisable.length) { - await this.extensionsWorkbenchService.setEnablement( - extensionsToDisable, - EnablementState.DisabledGlobally, - ); + await this.extensionsWorkbenchService.setEnablement(extensionsToDisable, EnablementState.DisabledGlobally); } - }, + } }); this.registerExtensionAction({ - id: "workbench.extensions.action.disableAllWorkspace", - title: localize2( - "disableAllWorkspace", - "Disable All Installed Extensions for this Workspace", - ), + id: 'workbench.extensions.action.disableAllWorkspace', + title: localize2('disableAllWorkspace', 'Disable All Installed Extensions for this Workspace'), category: ExtensionsLocalizedLabel, menu: { id: MenuId.CommandPalette, - when: ContextKeyExpr.and( - WorkbenchStateContext.notEqualsTo("empty"), - ContextKeyExpr.or( - CONTEXT_HAS_LOCAL_SERVER, - CONTEXT_HAS_REMOTE_SERVER, - CONTEXT_HAS_WEB_SERVER, - ), - ), + when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('empty'), ContextKeyExpr.or(CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER)) }, run: async () => { - const extensionsToDisable = - this.extensionsWorkbenchService.local.filter( - (e) => - !e.isBuiltin && - !!e.local && - this.extensionEnablementService.isEnabled( - e.local, - ) && - this.extensionEnablementService.canChangeEnablement( - e.local, - ), - ); + const extensionsToDisable = this.extensionsWorkbenchService.local.filter(e => !e.isBuiltin && !!e.local && this.extensionEnablementService.isEnabled(e.local) && this.extensionEnablementService.canChangeEnablement(e.local)); if (extensionsToDisable.length) { - await this.extensionsWorkbenchService.setEnablement( - extensionsToDisable, - EnablementState.DisabledWorkspace, - ); + await this.extensionsWorkbenchService.setEnablement(extensionsToDisable, EnablementState.DisabledWorkspace); } - }, + } }); this.registerExtensionAction({ id: SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, - title: localize2("InstallFromVSIX", "Install from VSIX..."), + title: localize2('InstallFromVSIX', 'Install from VSIX...'), category: ExtensionsLocalizedLabel, - menu: [ - { - id: MenuId.CommandPalette, - when: ContextKeyExpr.or( - CONTEXT_HAS_LOCAL_SERVER, - CONTEXT_HAS_REMOTE_SERVER, - ), - }, - { - id: MenuId.ViewContainerTitle, - when: ContextKeyExpr.and( - ContextKeyExpr.equals("viewContainer", VIEWLET_ID), - ContextKeyExpr.or( - CONTEXT_HAS_LOCAL_SERVER, - CONTEXT_HAS_REMOTE_SERVER, - ), - ), - group: "3_install", - order: 1, - }, - ], + menu: [{ + id: MenuId.CommandPalette, + when: ContextKeyExpr.or(CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER) + }, { + id: MenuId.ViewContainerTitle, + when: ContextKeyExpr.and(ContextKeyExpr.equals('viewContainer', VIEWLET_ID), ContextKeyExpr.or(CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER)), + group: '3_install', + order: 1 + }], run: async (accessor: ServicesAccessor) => { const fileDialogService = accessor.get(IFileDialogService); const commandService = accessor.get(ICommandService); const vsixPaths = await fileDialogService.showOpenDialog({ - title: localize("installFromVSIX", "Install from VSIX"), - filters: [ - { name: "VSIX Extensions", extensions: ["vsix"] }, - ], + title: localize('installFromVSIX', "Install from VSIX"), + filters: [{ name: 'VSIX Extensions', extensions: ['vsix'] }], canSelectFiles: true, canSelectMany: true, - openLabel: mnemonicButtonLabel( - localize( - { - key: "installButton", - comment: ["&& denotes a mnemonic"], - }, - "&&Install", - ), - ), + openLabel: mnemonicButtonLabel(localize({ key: 'installButton', comment: ['&& denotes a mnemonic'] }, "&&Install")) }); if (vsixPaths) { - await commandService.executeCommand( - INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, - vsixPaths, - ); + await commandService.executeCommand(INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, vsixPaths); } - }, + } }); this.registerExtensionAction({ id: INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, - title: localize("installVSIX", "Install Extension VSIX"), - menu: [ - { - id: MenuId.ExplorerContext, - group: "extensions", - when: ContextKeyExpr.and( - ResourceContextKey.Extension.isEqualTo(".vsix"), - ContextKeyExpr.or( - CONTEXT_HAS_LOCAL_SERVER, - CONTEXT_HAS_REMOTE_SERVER, - ), - ), - }, - ], + title: localize('installVSIX', "Install Extension VSIX"), + menu: [{ + id: MenuId.ExplorerContext, + group: 'extensions', + when: ContextKeyExpr.and(ResourceContextKey.Extension.isEqualTo('.vsix'), ContextKeyExpr.or(CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER)), + }], run: async (accessor: ServicesAccessor, resources: URI[] | URI) => { - const extensionsWorkbenchService = accessor.get( - IExtensionsWorkbenchService, - ); + const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); const hostService = accessor.get(IHostService); const notificationService = accessor.get(INotificationService); - const vsixs = Array.isArray(resources) - ? resources - : [resources]; - const result = await Promise.allSettled( - vsixs.map( - async (vsix) => - await extensionsWorkbenchService.install(vsix, { - installGivenVersion: true, - }), - ), - ); - let error: Error | undefined, - requireReload = false, - requireRestart = false; + const vsixs = Array.isArray(resources) ? resources : [resources]; + const result = await Promise.allSettled(vsixs.map(async (vsix) => await extensionsWorkbenchService.install(vsix, { installGivenVersion: true }))); + let error: Error | undefined, requireReload = false, requireRestart = false; for (const r of result) { - if (r.status === "rejected") { + if (r.status === 'rejected') { error = new Error(r.reason); break; } - requireReload = - requireReload || - r.value.runtimeState?.action === - ExtensionRuntimeActionType.ReloadWindow; - requireRestart = - requireRestart || - r.value.runtimeState?.action === - ExtensionRuntimeActionType.RestartExtensions; + requireReload = requireReload || r.value.runtimeState?.action === ExtensionRuntimeActionType.ReloadWindow; + requireRestart = requireRestart || r.value.runtimeState?.action === ExtensionRuntimeActionType.RestartExtensions; } if (error) { throw error; @@ -1628,272 +894,175 @@ class ExtensionsContributions if (requireReload) { notificationService.prompt( Severity.Info, - localize( - "InstallVSIXAction.successReload", - "Completed installing extension from VSIX. Please reload Visual Studio Code to enable it.", - ), - [ - { - label: localize( - "InstallVSIXAction.reloadNow", - "Reload Now", - ), - run: () => hostService.reload(), - }, - ], + localize('InstallVSIXAction.successReload', "Completed installing extension from VSIX. Please reload Visual Studio Code to enable it."), + [{ + label: localize('InstallVSIXAction.reloadNow', "Reload Now"), + run: () => hostService.reload() + }] ); - } else if (requireRestart) { + } + else if (requireRestart) { notificationService.prompt( Severity.Info, - localize( - "InstallVSIXAction.successRestart", - "Completed installing extension from VSIX. Please restart extensions to enable it.", - ), - [ - { - label: localize( - "InstallVSIXAction.restartExtensions", - "Restart Extensions", - ), - run: () => - extensionsWorkbenchService.updateRunningExtensions(), - }, - ], + localize('InstallVSIXAction.successRestart', "Completed installing extension from VSIX. Please restart extensions to enable it."), + [{ + label: localize('InstallVSIXAction.restartExtensions', "Restart Extensions"), + run: () => extensionsWorkbenchService.updateRunningExtensions() + }] ); - } else { + } + else { notificationService.prompt( Severity.Info, - localize( - "InstallVSIXAction.successNoReload", - "Completed installing extension.", - ), - [], + localize('InstallVSIXAction.successNoReload', "Completed installing extension."), + [] ); } - }, + } }); this.registerExtensionAction({ - id: "workbench.extensions.action.installExtensionFromLocation", - title: localize2( - "installExtensionFromLocation", - "Install Extension from Location...", - ), + id: 'workbench.extensions.action.installExtensionFromLocation', + title: localize2('installExtensionFromLocation', 'Install Extension from Location...'), category: Categories.Developer, - menu: [ - { - id: MenuId.CommandPalette, - when: ContextKeyExpr.or( - CONTEXT_HAS_WEB_SERVER, - CONTEXT_HAS_LOCAL_SERVER, - ), - }, - ], + menu: [{ + id: MenuId.CommandPalette, + when: ContextKeyExpr.or(CONTEXT_HAS_WEB_SERVER, CONTEXT_HAS_LOCAL_SERVER) + }], run: async (accessor: ServicesAccessor) => { - const extensionManagementService = accessor.get( - IWorkbenchExtensionManagementService, - ); + const extensionManagementService = accessor.get(IWorkbenchExtensionManagementService); if (isWeb) { return new Promise((c, e) => { - const quickInputService = - accessor.get(IQuickInputService); + const quickInputService = accessor.get(IQuickInputService); const disposables = new DisposableStore(); - const quickPick = disposables.add( - quickInputService.createQuickPick(), - ); - quickPick.title = localize( - "installFromLocation", - "Install Extension from Location", - ); + const quickPick = disposables.add(quickInputService.createQuickPick()); + quickPick.title = localize('installFromLocation', "Install Extension from Location"); quickPick.customButton = true; - quickPick.customLabel = localize( - "install button", - "Install", - ); - quickPick.placeholder = localize( - "installFromLocationPlaceHolder", - "Location of the web extension", - ); + quickPick.customLabel = localize('install button', "Install"); + quickPick.placeholder = localize('installFromLocationPlaceHolder', "Location of the web extension"); quickPick.ignoreFocusOut = true; - disposables.add( - Event.any( - quickPick.onDidAccept, - quickPick.onDidCustom, - )(async () => { - quickPick.hide(); - if (quickPick.value) { - try { - await extensionManagementService.installFromLocation( - URI.parse(quickPick.value), - ); - } catch (error) { - e(error); - return; - } + disposables.add(Event.any(quickPick.onDidAccept, quickPick.onDidCustom)(async () => { + quickPick.hide(); + if (quickPick.value) { + try { + await extensionManagementService.installFromLocation(URI.parse(quickPick.value)); + } catch (error) { + e(error); + return; } - c(); - }), - ); - disposables.add( - quickPick.onDidHide(() => disposables.dispose()), - ); + } + c(); + })); + disposables.add(quickPick.onDidHide(() => disposables.dispose())); quickPick.show(); }); } else { const fileDialogService = accessor.get(IFileDialogService); - const extensionLocation = - await fileDialogService.showOpenDialog({ - canSelectFolders: true, - canSelectFiles: false, - canSelectMany: false, - title: localize( - "installFromLocation", - "Install Extension from Location", - ), - }); + const extensionLocation = await fileDialogService.showOpenDialog({ + canSelectFolders: true, + canSelectFiles: false, + canSelectMany: false, + title: localize('installFromLocation', "Install Extension from Location"), + }); if (extensionLocation?.[0]) { - await extensionManagementService.installFromLocation( - extensionLocation[0], - ); + await extensionManagementService.installFromLocation(extensionLocation[0]); } } - }, + } }); - const extensionsFilterSubMenu = new MenuId("extensionsFilterSubMenu"); + const extensionsFilterSubMenu = new MenuId('extensionsFilterSubMenu'); MenuRegistry.appendMenuItem(extensionsSearchActionsMenu, { submenu: extensionsFilterSubMenu, - title: localize("filterExtensions", "Filter Extensions..."), - group: "navigation", + title: localize('filterExtensions', "Filter Extensions..."), + group: 'navigation', order: 2, icon: filterIcon, }); - const showFeaturedExtensionsId = "extensions.filter.featured"; + const showFeaturedExtensionsId = 'extensions.filter.featured'; this.registerExtensionAction({ id: showFeaturedExtensionsId, - title: localize2( - "showFeaturedExtensions", - "Show Featured Extensions", - ), + title: localize2('showFeaturedExtensions', 'Show Featured Extensions'), category: ExtensionsLocalizedLabel, - menu: [ - { - id: MenuId.CommandPalette, - when: CONTEXT_HAS_GALLERY, - }, - { - id: extensionsFilterSubMenu, - when: CONTEXT_HAS_GALLERY, - group: "1_predefined", - order: 1, - }, - ], + menu: [{ + id: MenuId.CommandPalette, + when: CONTEXT_HAS_GALLERY + }, { + id: extensionsFilterSubMenu, + when: CONTEXT_HAS_GALLERY, + group: '1_predefined', + order: 1, + }], menuTitles: { - [extensionsFilterSubMenu.id]: localize( - "featured filter", - "Featured", - ), + [extensionsFilterSubMenu.id]: localize('featured filter', "Featured") }, - run: () => this.extensionsWorkbenchService.openSearch("@featured "), + run: () => this.extensionsWorkbenchService.openSearch('@featured ') }); this.registerExtensionAction({ - id: "workbench.extensions.action.showPopularExtensions", - title: localize2( - "showPopularExtensions", - "Show Popular Extensions", - ), + id: 'workbench.extensions.action.showPopularExtensions', + title: localize2('showPopularExtensions', 'Show Popular Extensions'), category: ExtensionsLocalizedLabel, - menu: [ - { - id: MenuId.CommandPalette, - when: CONTEXT_HAS_GALLERY, - }, - { - id: extensionsFilterSubMenu, - when: CONTEXT_HAS_GALLERY, - group: "1_predefined", - order: 2, - }, - ], + menu: [{ + id: MenuId.CommandPalette, + when: CONTEXT_HAS_GALLERY + }, { + id: extensionsFilterSubMenu, + when: CONTEXT_HAS_GALLERY, + group: '1_predefined', + order: 2, + }], menuTitles: { - [extensionsFilterSubMenu.id]: localize( - "most popular filter", - "Most Popular", - ), + [extensionsFilterSubMenu.id]: localize('most popular filter', "Most Popular") }, - run: () => this.extensionsWorkbenchService.openSearch("@popular "), + run: () => this.extensionsWorkbenchService.openSearch('@popular ') }); this.registerExtensionAction({ - id: "workbench.extensions.action.showRecommendedExtensions", - title: localize2( - "showRecommendedExtensions", - "Show Recommended Extensions", - ), + id: 'workbench.extensions.action.showRecommendedExtensions', + title: localize2('showRecommendedExtensions', 'Show Recommended Extensions'), category: ExtensionsLocalizedLabel, - menu: [ - { - id: MenuId.CommandPalette, - when: CONTEXT_HAS_GALLERY, - }, - { - id: extensionsFilterSubMenu, - when: CONTEXT_HAS_GALLERY, - group: "1_predefined", - order: 2, - }, - ], + menu: [{ + id: MenuId.CommandPalette, + when: CONTEXT_HAS_GALLERY + }, { + id: extensionsFilterSubMenu, + when: CONTEXT_HAS_GALLERY, + group: '1_predefined', + order: 2, + }], menuTitles: { - [extensionsFilterSubMenu.id]: localize( - "most popular recommended", - "Recommended", - ), + [extensionsFilterSubMenu.id]: localize('most popular recommended', "Recommended") }, - run: () => - this.extensionsWorkbenchService.openSearch("@recommended "), + run: () => this.extensionsWorkbenchService.openSearch('@recommended ') }); this.registerExtensionAction({ - id: "workbench.extensions.action.recentlyPublishedExtensions", - title: localize2( - "recentlyPublishedExtensions", - "Show Recently Published Extensions", - ), + id: 'workbench.extensions.action.recentlyPublishedExtensions', + title: localize2('recentlyPublishedExtensions', 'Show Recently Published Extensions'), category: ExtensionsLocalizedLabel, - menu: [ - { - id: MenuId.CommandPalette, - when: CONTEXT_HAS_GALLERY, - }, - { - id: extensionsFilterSubMenu, - when: CONTEXT_HAS_GALLERY, - group: "1_predefined", - order: 2, - }, - ], + menu: [{ + id: MenuId.CommandPalette, + when: CONTEXT_HAS_GALLERY + }, { + id: extensionsFilterSubMenu, + when: CONTEXT_HAS_GALLERY, + group: '1_predefined', + order: 2, + }], menuTitles: { - [extensionsFilterSubMenu.id]: localize( - "recently published filter", - "Recently Published", - ), + [extensionsFilterSubMenu.id]: localize('recently published filter', "Recently Published") }, - run: () => - this.extensionsWorkbenchService.openSearch( - "@recentlyPublished ", - ), + run: () => this.extensionsWorkbenchService.openSearch('@recentlyPublished ') }); - const extensionsCategoryFilterSubMenu = new MenuId( - "extensionsCategoryFilterSubMenu", - ); + const extensionsCategoryFilterSubMenu = new MenuId('extensionsCategoryFilterSubMenu'); MenuRegistry.appendMenuItem(extensionsFilterSubMenu, { submenu: extensionsCategoryFilterSubMenu, - title: localize("filter by category", "Category"), + title: localize('filter by category', "Category"), when: CONTEXT_HAS_GALLERY, - group: "2_categories", + group: '2_categories', order: 1, }); @@ -1901,415 +1070,241 @@ class ExtensionsContributions this.registerExtensionAction({ id: `extensions.actions.searchByCategory.${category}`, title: category, - menu: [ - { - id: extensionsCategoryFilterSubMenu, - when: CONTEXT_HAS_GALLERY, - order: index, - }, - ], - run: () => - this.extensionsWorkbenchService.openSearch( - `@category:"${category.toLowerCase()}"`, - ), + menu: [{ + id: extensionsCategoryFilterSubMenu, + when: CONTEXT_HAS_GALLERY, + order: index, + }], + run: () => this.extensionsWorkbenchService.openSearch(`@category:"${category.toLowerCase()}"`) }); }); this.registerExtensionAction({ - id: "workbench.extensions.action.listBuiltInExtensions", - title: localize2( - "showBuiltInExtensions", - "Show Built-in Extensions", - ), + id: 'workbench.extensions.action.listBuiltInExtensions', + title: localize2('showBuiltInExtensions', 'Show Built-in Extensions'), category: ExtensionsLocalizedLabel, - menu: [ - { - id: MenuId.CommandPalette, - when: ContextKeyExpr.or( - CONTEXT_HAS_LOCAL_SERVER, - CONTEXT_HAS_REMOTE_SERVER, - CONTEXT_HAS_WEB_SERVER, - ), - }, - { - id: extensionsFilterSubMenu, - group: "3_installed", - order: 2, - }, - ], + menu: [{ + id: MenuId.CommandPalette, + when: ContextKeyExpr.or(CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER) + }, { + id: extensionsFilterSubMenu, + group: '3_installed', + order: 2, + }], menuTitles: { - [extensionsFilterSubMenu.id]: localize( - "builtin filter", - "Built-in", - ), + [extensionsFilterSubMenu.id]: localize('builtin filter', "Built-in") }, - run: () => this.extensionsWorkbenchService.openSearch("@builtin "), + run: () => this.extensionsWorkbenchService.openSearch('@builtin ') }); this.registerExtensionAction({ - id: "workbench.extensions.action.extensionUpdates", - title: localize2("extensionUpdates", "Show Extension Updates"), + id: 'workbench.extensions.action.extensionUpdates', + title: localize2('extensionUpdates', 'Show Extension Updates'), category: ExtensionsLocalizedLabel, precondition: CONTEXT_HAS_GALLERY, f1: true, - menu: [ - { - id: extensionsFilterSubMenu, - group: "3_installed", - when: CONTEXT_HAS_GALLERY, - order: 1, - }, - ], + menu: [{ + id: extensionsFilterSubMenu, + group: '3_installed', + when: CONTEXT_HAS_GALLERY, + order: 1, + }], menuTitles: { - [extensionsFilterSubMenu.id]: localize( - "extension updates filter", - "Updates", - ), + [extensionsFilterSubMenu.id]: localize('extension updates filter', "Updates") }, - run: () => this.extensionsWorkbenchService.openSearch("@updates"), + run: () => this.extensionsWorkbenchService.openSearch('@updates') }); this.registerExtensionAction({ id: LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, - title: localize2( - "showWorkspaceUnsupportedExtensions", - "Show Extensions Unsupported By Workspace", - ), + title: localize2('showWorkspaceUnsupportedExtensions', 'Show Extensions Unsupported By Workspace'), category: ExtensionsLocalizedLabel, - menu: [ - { - id: MenuId.CommandPalette, - when: ContextKeyExpr.or( - CONTEXT_HAS_LOCAL_SERVER, - CONTEXT_HAS_REMOTE_SERVER, - ), - }, - { - id: extensionsFilterSubMenu, - group: "3_installed", - order: 5, - when: ContextKeyExpr.or( - CONTEXT_HAS_LOCAL_SERVER, - CONTEXT_HAS_REMOTE_SERVER, - ), - }, - ], + menu: [{ + id: MenuId.CommandPalette, + when: ContextKeyExpr.or(CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER), + }, { + id: extensionsFilterSubMenu, + group: '3_installed', + order: 5, + when: ContextKeyExpr.or(CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER), + }], menuTitles: { - [extensionsFilterSubMenu.id]: localize( - "workspace unsupported filter", - "Workspace Unsupported", - ), + [extensionsFilterSubMenu.id]: localize('workspace unsupported filter', "Workspace Unsupported") }, - run: () => - this.extensionsWorkbenchService.openSearch( - "@workspaceUnsupported", - ), + run: () => this.extensionsWorkbenchService.openSearch('@workspaceUnsupported') }); this.registerExtensionAction({ - id: "workbench.extensions.action.showEnabledExtensions", - title: localize2( - "showEnabledExtensions", - "Show Enabled Extensions", - ), + id: 'workbench.extensions.action.showEnabledExtensions', + title: localize2('showEnabledExtensions', 'Show Enabled Extensions'), category: ExtensionsLocalizedLabel, - menu: [ - { - id: MenuId.CommandPalette, - when: ContextKeyExpr.or( - CONTEXT_HAS_LOCAL_SERVER, - CONTEXT_HAS_REMOTE_SERVER, - CONTEXT_HAS_WEB_SERVER, - ), - }, - { - id: extensionsFilterSubMenu, - group: "3_installed", - order: 3, - }, - ], + menu: [{ + id: MenuId.CommandPalette, + when: ContextKeyExpr.or(CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER) + }, { + id: extensionsFilterSubMenu, + group: '3_installed', + order: 3, + }], menuTitles: { - [extensionsFilterSubMenu.id]: localize( - "enabled filter", - "Enabled", - ), + [extensionsFilterSubMenu.id]: localize('enabled filter', "Enabled") }, - run: () => this.extensionsWorkbenchService.openSearch("@enabled "), + run: () => this.extensionsWorkbenchService.openSearch('@enabled ') }); this.registerExtensionAction({ - id: "workbench.extensions.action.showDisabledExtensions", - title: localize2( - "showDisabledExtensions", - "Show Disabled Extensions", - ), + id: 'workbench.extensions.action.showDisabledExtensions', + title: localize2('showDisabledExtensions', 'Show Disabled Extensions'), category: ExtensionsLocalizedLabel, - menu: [ - { - id: MenuId.CommandPalette, - when: ContextKeyExpr.or( - CONTEXT_HAS_LOCAL_SERVER, - CONTEXT_HAS_REMOTE_SERVER, - CONTEXT_HAS_WEB_SERVER, - ), - }, - { - id: extensionsFilterSubMenu, - group: "3_installed", - order: 4, - }, - ], + menu: [{ + id: MenuId.CommandPalette, + when: ContextKeyExpr.or(CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER) + }, { + id: extensionsFilterSubMenu, + group: '3_installed', + order: 4, + }], menuTitles: { - [extensionsFilterSubMenu.id]: localize( - "disabled filter", - "Disabled", - ), + [extensionsFilterSubMenu.id]: localize('disabled filter', "Disabled") }, - run: () => this.extensionsWorkbenchService.openSearch("@disabled "), + run: () => this.extensionsWorkbenchService.openSearch('@disabled ') }); - const extensionsSortSubMenu = new MenuId("extensionsSortSubMenu"); + const extensionsSortSubMenu = new MenuId('extensionsSortSubMenu'); MenuRegistry.appendMenuItem(extensionsFilterSubMenu, { submenu: extensionsSortSubMenu, - title: localize("sorty by", "Sort By"), - when: ContextKeyExpr.and( - ContextKeyExpr.or(CONTEXT_HAS_GALLERY, DefaultViewsContext), - ), - group: "4_sort", + title: localize('sorty by', "Sort By"), + when: ContextKeyExpr.and(ContextKeyExpr.or(CONTEXT_HAS_GALLERY, DefaultViewsContext)), + group: '4_sort', order: 1, }); [ - { - id: "installs", - title: localize("sort by installs", "Install Count"), - precondition: BuiltInExtensionsContext.negate(), - }, - { - id: "rating", - title: localize("sort by rating", "Rating"), - precondition: BuiltInExtensionsContext.negate(), - }, - { - id: "name", - title: localize("sort by name", "Name"), - precondition: BuiltInExtensionsContext.negate(), - }, - { - id: "publishedDate", - title: localize("sort by published date", "Published Date"), - precondition: BuiltInExtensionsContext.negate(), - }, - { - id: "updateDate", - title: localize("sort by update date", "Updated Date"), - precondition: ContextKeyExpr.and( - SearchMarketplaceExtensionsContext.negate(), - RecommendedExtensionsContext.negate(), - BuiltInExtensionsContext.negate(), - ), - }, + { id: 'installs', title: localize('sort by installs', "Install Count"), precondition: BuiltInExtensionsContext.negate() }, + { id: 'rating', title: localize('sort by rating', "Rating"), precondition: BuiltInExtensionsContext.negate() }, + { id: 'name', title: localize('sort by name', "Name"), precondition: BuiltInExtensionsContext.negate() }, + { id: 'publishedDate', title: localize('sort by published date', "Published Date"), precondition: BuiltInExtensionsContext.negate() }, + { id: 'updateDate', title: localize('sort by update date', "Updated Date"), precondition: ContextKeyExpr.and(SearchMarketplaceExtensionsContext.negate(), RecommendedExtensionsContext.negate(), BuiltInExtensionsContext.negate()) }, ].map(({ id, title, precondition }, index) => { this.registerExtensionAction({ id: `extensions.sort.${id}`, title, precondition: precondition, - menu: [ - { - id: extensionsSortSubMenu, - when: ContextKeyExpr.or( - CONTEXT_HAS_GALLERY, - DefaultViewsContext, - ), - order: index, - }, - ], + menu: [{ + id: extensionsSortSubMenu, + when: ContextKeyExpr.or(CONTEXT_HAS_GALLERY, DefaultViewsContext), + order: index, + }], toggled: ExtensionsSortByContext.isEqualTo(id), run: async () => { - const extensionsViewPaneContainer = ( - await this.viewsService.openViewContainer( - VIEWLET_ID, - true, - ) - )?.getViewPaneContainer() as - | IExtensionsViewPaneContainer - | undefined; - const currentQuery = Query.parse( - extensionsViewPaneContainer?.searchValue ?? "", - ); - extensionsViewPaneContainer?.search( - new Query(currentQuery.value, id).toString(), - ); + const extensionsViewPaneContainer = ((await this.viewsService.openViewContainer(VIEWLET_ID, true))?.getViewPaneContainer()) as IExtensionsViewPaneContainer | undefined; + const currentQuery = Query.parse(extensionsViewPaneContainer?.searchValue ?? ''); + extensionsViewPaneContainer?.search(new Query(currentQuery.value, id).toString()); extensionsViewPaneContainer?.focus(); - }, + } }); }); this.registerExtensionAction({ - id: "workbench.extensions.action.clearExtensionsSearchResults", - title: localize2( - "clearExtensionsSearchResults", - "Clear Extensions Search Results", - ), + id: 'workbench.extensions.action.clearExtensionsSearchResults', + title: localize2('clearExtensionsSearchResults', 'Clear Extensions Search Results'), category: ExtensionsLocalizedLabel, icon: clearSearchResultsIcon, f1: true, precondition: SearchHasTextContext, menu: { id: extensionsSearchActionsMenu, - group: "navigation", + group: 'navigation', order: 1, }, run: async (accessor: ServicesAccessor) => { - const viewPaneContainer = accessor - .get(IViewsService) - .getActiveViewPaneContainerWithId(VIEWLET_ID); + const viewPaneContainer = accessor.get(IViewsService).getActiveViewPaneContainerWithId(VIEWLET_ID); if (viewPaneContainer) { - const extensionsViewPaneContainer = - viewPaneContainer as IExtensionsViewPaneContainer; - extensionsViewPaneContainer.search(""); + const extensionsViewPaneContainer = viewPaneContainer as IExtensionsViewPaneContainer; + extensionsViewPaneContainer.search(''); extensionsViewPaneContainer.focus(); } - }, + } }); this.registerExtensionAction({ - id: "workbench.extensions.action.refreshExtension", - title: localize2("refreshExtension", "Refresh"), + id: 'workbench.extensions.action.refreshExtension', + title: localize2('refreshExtension', 'Refresh'), category: ExtensionsLocalizedLabel, icon: refreshIcon, f1: true, menu: { id: MenuId.ViewContainerTitle, - when: ContextKeyExpr.equals("viewContainer", VIEWLET_ID), - group: "navigation", - order: 2, + when: ContextKeyExpr.equals('viewContainer', VIEWLET_ID), + group: 'navigation', + order: 2 }, run: async (accessor: ServicesAccessor) => { - const viewPaneContainer = accessor - .get(IViewsService) - .getActiveViewPaneContainerWithId(VIEWLET_ID); + const viewPaneContainer = accessor.get(IViewsService).getActiveViewPaneContainerWithId(VIEWLET_ID); if (viewPaneContainer) { - await ( - viewPaneContainer as IExtensionsViewPaneContainer - ).refresh(); + await (viewPaneContainer as IExtensionsViewPaneContainer).refresh(); } - }, + } }); this.registerExtensionAction({ - id: "workbench.extensions.action.installWorkspaceRecommendedExtensions", - title: localize( - "installWorkspaceRecommendedExtensions", - "Install Workspace Recommended Extensions", - ), + id: 'workbench.extensions.action.installWorkspaceRecommendedExtensions', + title: localize('installWorkspaceRecommendedExtensions', "Install Workspace Recommended Extensions"), icon: installWorkspaceRecommendedIcon, menu: { id: MenuId.ViewTitle, - when: ContextKeyExpr.equals( - "view", - WORKSPACE_RECOMMENDATIONS_VIEW_ID, - ), - group: "navigation", - order: 1, + when: ContextKeyExpr.equals('view', WORKSPACE_RECOMMENDATIONS_VIEW_ID), + group: 'navigation', + order: 1 }, run: async (accessor: ServicesAccessor) => { - const view = accessor - .get(IViewsService) - .getActiveViewWithId( - WORKSPACE_RECOMMENDATIONS_VIEW_ID, - ) as IWorkspaceRecommendedExtensionsView; + const view = accessor.get(IViewsService).getActiveViewWithId(WORKSPACE_RECOMMENDATIONS_VIEW_ID) as IWorkspaceRecommendedExtensionsView; return view.installWorkspaceRecommendations(); - }, + } }); this.registerExtensionAction({ id: ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, title: ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL, icon: configureRecommendedIcon, - menu: [ - { - id: MenuId.CommandPalette, - when: WorkbenchStateContext.notEqualsTo("empty"), - }, - { - id: MenuId.ViewTitle, - when: ContextKeyExpr.equals( - "view", - WORKSPACE_RECOMMENDATIONS_VIEW_ID, - ), - group: "navigation", - order: 2, - }, - ], - run: () => - runAction( - this.instantiationService.createInstance( - ConfigureWorkspaceFolderRecommendedExtensionsAction, - ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, - ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL, - ), - ), + menu: [{ + id: MenuId.CommandPalette, + when: WorkbenchStateContext.notEqualsTo('empty'), + }, { + id: MenuId.ViewTitle, + when: ContextKeyExpr.equals('view', WORKSPACE_RECOMMENDATIONS_VIEW_ID), + group: 'navigation', + order: 2 + }], + run: () => runAction(this.instantiationService.createInstance(ConfigureWorkspaceFolderRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL)) }); this.registerExtensionAction({ id: InstallSpecificVersionOfExtensionAction.ID, - title: { - value: InstallSpecificVersionOfExtensionAction.LABEL, - original: "Install Specific Version of Extension...", - }, + title: { value: InstallSpecificVersionOfExtensionAction.LABEL, original: 'Install Specific Version of Extension...' }, category: ExtensionsLocalizedLabel, menu: { id: MenuId.CommandPalette, - when: ContextKeyExpr.and( - CONTEXT_HAS_GALLERY, - ContextKeyExpr.or( - CONTEXT_HAS_LOCAL_SERVER, - CONTEXT_HAS_REMOTE_SERVER, - CONTEXT_HAS_WEB_SERVER, - ), - ), - }, - run: () => - runAction( - this.instantiationService.createInstance( - InstallSpecificVersionOfExtensionAction, - InstallSpecificVersionOfExtensionAction.ID, - InstallSpecificVersionOfExtensionAction.LABEL, - ), - ), + when: ContextKeyExpr.and(CONTEXT_HAS_GALLERY, ContextKeyExpr.or(CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER)) + }, + run: () => runAction(this.instantiationService.createInstance(InstallSpecificVersionOfExtensionAction, InstallSpecificVersionOfExtensionAction.ID, InstallSpecificVersionOfExtensionAction.LABEL)) }); this.registerExtensionAction({ id: ReinstallAction.ID, - title: { - value: ReinstallAction.LABEL, - original: "Reinstall Extension...", - }, + title: { value: ReinstallAction.LABEL, original: 'Reinstall Extension...' }, category: Categories.Developer, menu: { id: MenuId.CommandPalette, - when: ContextKeyExpr.and( - CONTEXT_HAS_GALLERY, - ContextKeyExpr.or( - CONTEXT_HAS_LOCAL_SERVER, - CONTEXT_HAS_REMOTE_SERVER, - ), - ), - }, - run: () => - runAction( - this.instantiationService.createInstance( - ReinstallAction, - ReinstallAction.ID, - ReinstallAction.LABEL, - ), - ), + when: ContextKeyExpr.and(CONTEXT_HAS_GALLERY, ContextKeyExpr.or(CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER)) + }, + run: () => runAction(this.instantiationService.createInstance(ReinstallAction, ReinstallAction.ID, ReinstallAction.LABEL)) }); } // Extension Context Menu private registerContextMenuActions(): void { + this.registerExtensionAction({ id: SetColorThemeAction.ID, title: SetColorThemeAction.TITLE, @@ -2317,31 +1312,18 @@ class ExtensionsContributions id: MenuId.ExtensionContext, group: THEME_ACTIONS_GROUP, order: 0, - when: ContextKeyExpr.and( - ContextKeyExpr.not("inExtensionEditor"), - ContextKeyExpr.equals("extensionStatus", "installed"), - ContextKeyExpr.has("extensionHasColorThemes"), - ), + when: ContextKeyExpr.and(ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.has('extensionHasColorThemes')) }, run: async (accessor: ServicesAccessor, extensionId: string) => { - const extensionWorkbenchService = accessor.get( - IExtensionsWorkbenchService, - ); - const instantiationService = accessor.get( - IInstantiationService, - ); - const extension = extensionWorkbenchService.local.find((e) => - areSameExtensions(e.identifier, { id: extensionId }), - ); + const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); + const instantiationService = accessor.get(IInstantiationService); + const extension = extensionWorkbenchService.local.find(e => areSameExtensions(e.identifier, { id: extensionId })); if (extension) { - const action = - instantiationService.createInstance( - SetColorThemeAction, - ); + const action = instantiationService.createInstance(SetColorThemeAction); action.extension = extension; return action.run(); } - }, + } }); this.registerExtensionAction({ @@ -2351,30 +1333,18 @@ class ExtensionsContributions id: MenuId.ExtensionContext, group: THEME_ACTIONS_GROUP, order: 0, - when: ContextKeyExpr.and( - ContextKeyExpr.not("inExtensionEditor"), - ContextKeyExpr.equals("extensionStatus", "installed"), - ContextKeyExpr.has("extensionHasFileIconThemes"), - ), + when: ContextKeyExpr.and(ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.has('extensionHasFileIconThemes')) }, run: async (accessor: ServicesAccessor, extensionId: string) => { - const extensionWorkbenchService = accessor.get( - IExtensionsWorkbenchService, - ); - const instantiationService = accessor.get( - IInstantiationService, - ); - const extension = extensionWorkbenchService.local.find((e) => - areSameExtensions(e.identifier, { id: extensionId }), - ); + const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); + const instantiationService = accessor.get(IInstantiationService); + const extension = extensionWorkbenchService.local.find(e => areSameExtensions(e.identifier, { id: extensionId })); if (extension) { - const action = instantiationService.createInstance( - SetFileIconThemeAction, - ); + const action = instantiationService.createInstance(SetFileIconThemeAction); action.extension = extension; return action.run(); } - }, + } }); this.registerExtensionAction({ @@ -2384,261 +1354,144 @@ class ExtensionsContributions id: MenuId.ExtensionContext, group: THEME_ACTIONS_GROUP, order: 0, - when: ContextKeyExpr.and( - ContextKeyExpr.not("inExtensionEditor"), - ContextKeyExpr.equals("extensionStatus", "installed"), - ContextKeyExpr.has("extensionHasProductIconThemes"), - ), + when: ContextKeyExpr.and(ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.has('extensionHasProductIconThemes')) }, run: async (accessor: ServicesAccessor, extensionId: string) => { - const extensionWorkbenchService = accessor.get( - IExtensionsWorkbenchService, - ); - const instantiationService = accessor.get( - IInstantiationService, - ); - const extension = extensionWorkbenchService.local.find((e) => - areSameExtensions(e.identifier, { id: extensionId }), - ); + const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); + const instantiationService = accessor.get(IInstantiationService); + const extension = extensionWorkbenchService.local.find(e => areSameExtensions(e.identifier, { id: extensionId })); if (extension) { - const action = instantiationService.createInstance( - SetProductIconThemeAction, - ); + const action = instantiationService.createInstance(SetProductIconThemeAction); action.extension = extension; return action.run(); } - }, + } }); this.registerExtensionAction({ - id: "workbench.extensions.action.showPreReleaseVersion", - title: localize2( - "show pre-release version", - "Show Pre-Release Version", - ), + id: 'workbench.extensions.action.showPreReleaseVersion', + title: localize2('show pre-release version', 'Show Pre-Release Version'), menu: { id: MenuId.ExtensionContext, group: INSTALL_ACTIONS_GROUP, order: 0, - when: ContextKeyExpr.and( - ContextKeyExpr.has("inExtensionEditor"), - ContextKeyExpr.has("galleryExtensionHasPreReleaseVersion"), - ContextKeyExpr.has("isPreReleaseExtensionAllowed"), - ContextKeyExpr.not("showPreReleaseVersion"), - ContextKeyExpr.not("isBuiltinExtension"), - ), + when: ContextKeyExpr.and(ContextKeyExpr.has('inExtensionEditor'), ContextKeyExpr.has('galleryExtensionHasPreReleaseVersion'), ContextKeyExpr.has('isPreReleaseExtensionAllowed'), ContextKeyExpr.not('showPreReleaseVersion'), ContextKeyExpr.not('isBuiltinExtension')) }, run: async (accessor: ServicesAccessor, extensionId: string) => { - const extensionWorkbenchService = accessor.get( - IExtensionsWorkbenchService, - ); - const extension = ( - await extensionWorkbenchService.getExtensions( - [{ id: extensionId }], - CancellationToken.None, - ) - )[0]; - extensionWorkbenchService.open(extension, { - showPreReleaseVersion: true, - }); - }, + const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); + const extension = (await extensionWorkbenchService.getExtensions([{ id: extensionId }], CancellationToken.None))[0]; + extensionWorkbenchService.open(extension, { showPreReleaseVersion: true }); + } }); this.registerExtensionAction({ - id: "workbench.extensions.action.showReleasedVersion", - title: localize2("show released version", "Show Release Version"), + id: 'workbench.extensions.action.showReleasedVersion', + title: localize2('show released version', 'Show Release Version'), menu: { id: MenuId.ExtensionContext, group: INSTALL_ACTIONS_GROUP, order: 1, - when: ContextKeyExpr.and( - ContextKeyExpr.has("inExtensionEditor"), - ContextKeyExpr.has("galleryExtensionHasPreReleaseVersion"), - ContextKeyExpr.has("extensionHasReleaseVersion"), - ContextKeyExpr.has("showPreReleaseVersion"), - ContextKeyExpr.not("isBuiltinExtension"), - ), + when: ContextKeyExpr.and(ContextKeyExpr.has('inExtensionEditor'), ContextKeyExpr.has('galleryExtensionHasPreReleaseVersion'), ContextKeyExpr.has('extensionHasReleaseVersion'), ContextKeyExpr.has('showPreReleaseVersion'), ContextKeyExpr.not('isBuiltinExtension')) }, run: async (accessor: ServicesAccessor, extensionId: string) => { - const extensionWorkbenchService = accessor.get( - IExtensionsWorkbenchService, - ); - const extension = ( - await extensionWorkbenchService.getExtensions( - [{ id: extensionId }], - CancellationToken.None, - ) - )[0]; - extensionWorkbenchService.open(extension, { - showPreReleaseVersion: false, - }); - }, + const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); + const extension = (await extensionWorkbenchService.getExtensions([{ id: extensionId }], CancellationToken.None))[0]; + extensionWorkbenchService.open(extension, { showPreReleaseVersion: false }); + } }); this.registerExtensionAction({ id: ToggleAutoUpdateForExtensionAction.ID, title: ToggleAutoUpdateForExtensionAction.LABEL, category: ExtensionsLocalizedLabel, - precondition: ContextKeyExpr.and( - ContextKeyExpr.or( - ContextKeyExpr.notEquals( - `config.${AutoUpdateConfigurationKey}`, - "onlyEnabledExtensions", - ), - ContextKeyExpr.equals("isExtensionEnabled", true), - ), - ContextKeyExpr.not("extensionDisallowInstall"), - ContextKeyExpr.has("isExtensionAllowed"), - ), + precondition: ContextKeyExpr.and(ContextKeyExpr.or(ContextKeyExpr.notEquals(`config.${AutoUpdateConfigurationKey}`, 'onlyEnabledExtensions'), ContextKeyExpr.equals('isExtensionEnabled', true)), ContextKeyExpr.not('extensionDisallowInstall'), ContextKeyExpr.has('isExtensionAllowed')), menu: { id: MenuId.ExtensionContext, group: UPDATE_ACTIONS_GROUP, order: 1, when: ContextKeyExpr.and( - ContextKeyExpr.not("inExtensionEditor"), - ContextKeyExpr.equals("extensionStatus", "installed"), - ContextKeyExpr.not("isBuiltinExtension"), - ), + ContextKeyExpr.not('inExtensionEditor'), + ContextKeyExpr.equals('extensionStatus', 'installed'), + ContextKeyExpr.not('isBuiltinExtension'), + ) }, run: async (accessor: ServicesAccessor, id: string) => { - const instantiationService = accessor.get( - IInstantiationService, - ); - const extensionWorkbenchService = accessor.get( - IExtensionsWorkbenchService, - ); - const extension = extensionWorkbenchService.local.find((e) => - areSameExtensions(e.identifier, { id }), - ); + const instantiationService = accessor.get(IInstantiationService); + const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); + const extension = extensionWorkbenchService.local.find(e => areSameExtensions(e.identifier, { id })); if (extension) { - const action = instantiationService.createInstance( - ToggleAutoUpdateForExtensionAction, - ); + const action = instantiationService.createInstance(ToggleAutoUpdateForExtensionAction); action.extension = extension; return action.run(); } - }, + } }); this.registerExtensionAction({ id: ToggleAutoUpdatesForPublisherAction.ID, - title: { - value: ToggleAutoUpdatesForPublisherAction.LABEL, - original: "Auto Update (Publisher)", - }, + title: { value: ToggleAutoUpdatesForPublisherAction.LABEL, original: 'Auto Update (Publisher)' }, category: ExtensionsLocalizedLabel, - precondition: ContextKeyExpr.equals( - `config.${AutoUpdateConfigurationKey}`, - false, - ), + precondition: ContextKeyExpr.equals(`config.${AutoUpdateConfigurationKey}`, false), menu: { id: MenuId.ExtensionContext, group: UPDATE_ACTIONS_GROUP, order: 2, - when: ContextKeyExpr.and( - ContextKeyExpr.equals("extensionStatus", "installed"), - ContextKeyExpr.not("isBuiltinExtension"), - ), + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.not('isBuiltinExtension')) }, run: async (accessor: ServicesAccessor, id: string) => { - const instantiationService = accessor.get( - IInstantiationService, - ); - const extensionWorkbenchService = accessor.get( - IExtensionsWorkbenchService, - ); - const extension = extensionWorkbenchService.local.find((e) => - areSameExtensions(e.identifier, { id }), - ); + const instantiationService = accessor.get(IInstantiationService); + const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); + const extension = extensionWorkbenchService.local.find(e => areSameExtensions(e.identifier, { id })); if (extension) { - const action = instantiationService.createInstance( - ToggleAutoUpdatesForPublisherAction, - ); + const action = instantiationService.createInstance(ToggleAutoUpdatesForPublisherAction); action.extension = extension; return action.run(); } - }, + } }); this.registerExtensionAction({ - id: "workbench.extensions.action.switchToPreRlease", - title: localize( - "enablePreRleaseLabel", - "Switch to Pre-Release Version", - ), + id: 'workbench.extensions.action.switchToPreRlease', + title: localize('enablePreRleaseLabel', "Switch to Pre-Release Version"), category: ExtensionsLocalizedLabel, menu: { id: MenuId.ExtensionContext, group: INSTALL_ACTIONS_GROUP, order: 2, - when: ContextKeyExpr.and( - CONTEXT_HAS_GALLERY, - ContextKeyExpr.has("galleryExtensionHasPreReleaseVersion"), - ContextKeyExpr.has("isPreReleaseExtensionAllowed"), - ContextKeyExpr.not("installedExtensionIsOptedToPreRelease"), - ContextKeyExpr.not("inExtensionEditor"), - ContextKeyExpr.equals("extensionStatus", "installed"), - ContextKeyExpr.not("isBuiltinExtension"), - ), + when: ContextKeyExpr.and(CONTEXT_HAS_GALLERY, ContextKeyExpr.has('galleryExtensionHasPreReleaseVersion'), ContextKeyExpr.has('isPreReleaseExtensionAllowed'), ContextKeyExpr.not('installedExtensionIsOptedToPreRelease'), ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.not('isBuiltinExtension')) }, run: async (accessor: ServicesAccessor, id: string) => { - const instantiationService = accessor.get( - IInstantiationService, - ); - const extensionWorkbenchService = accessor.get( - IExtensionsWorkbenchService, - ); - const extension = extensionWorkbenchService.local.find((e) => - areSameExtensions(e.identifier, { id }), - ); + const instantiationService = accessor.get(IInstantiationService); + const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); + const extension = extensionWorkbenchService.local.find(e => areSameExtensions(e.identifier, { id })); if (extension) { - const action = instantiationService.createInstance( - TogglePreReleaseExtensionAction, - ); + const action = instantiationService.createInstance(TogglePreReleaseExtensionAction); action.extension = extension; return action.run(); } - }, + } }); this.registerExtensionAction({ - id: "workbench.extensions.action.switchToRelease", - title: localize( - "disablePreRleaseLabel", - "Switch to Release Version", - ), + id: 'workbench.extensions.action.switchToRelease', + title: localize('disablePreRleaseLabel', "Switch to Release Version"), category: ExtensionsLocalizedLabel, menu: { id: MenuId.ExtensionContext, group: INSTALL_ACTIONS_GROUP, order: 2, - when: ContextKeyExpr.and( - CONTEXT_HAS_GALLERY, - ContextKeyExpr.has("galleryExtensionHasPreReleaseVersion"), - ContextKeyExpr.has("installedExtensionIsOptedToPreRelease"), - ContextKeyExpr.not("inExtensionEditor"), - ContextKeyExpr.equals("extensionStatus", "installed"), - ContextKeyExpr.not("isBuiltinExtension"), - ), + when: ContextKeyExpr.and(CONTEXT_HAS_GALLERY, ContextKeyExpr.has('galleryExtensionHasPreReleaseVersion'), ContextKeyExpr.has('installedExtensionIsOptedToPreRelease'), ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.not('isBuiltinExtension')) }, run: async (accessor: ServicesAccessor, id: string) => { - const instantiationService = accessor.get( - IInstantiationService, - ); - const extensionWorkbenchService = accessor.get( - IExtensionsWorkbenchService, - ); - const extension = extensionWorkbenchService.local.find((e) => - areSameExtensions(e.identifier, { id }), - ); + const instantiationService = accessor.get(IInstantiationService); + const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); + const extension = extensionWorkbenchService.local.find(e => areSameExtensions(e.identifier, { id })); if (extension) { - const action = instantiationService.createInstance( - TogglePreReleaseExtensionAction, - ); + const action = instantiationService.createInstance(TogglePreReleaseExtensionAction); action.extension = extension; return action.run(); } - }, + } }); this.registerExtensionAction({ @@ -2648,168 +1501,85 @@ class ExtensionsContributions id: MenuId.ExtensionContext, group: INSTALL_ACTIONS_GROUP, order: 0, - when: ContextKeyExpr.and( - ContextKeyExpr.not("inExtensionEditor"), - ContextKeyExpr.has("canSetLanguage"), - ContextKeyExpr.has("isActiveLanguagePackExtension"), - ), + when: ContextKeyExpr.and(ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.has('canSetLanguage'), ContextKeyExpr.has('isActiveLanguagePackExtension')) }, run: async (accessor: ServicesAccessor, extensionId: string) => { - const instantiationService = accessor.get( - IInstantiationService, - ); - const extensionsWorkbenchService = accessor.get( - IExtensionsWorkbenchService, - ); - const extension = ( - await extensionsWorkbenchService.getExtensions( - [{ id: extensionId }], - CancellationToken.None, - ) - )[0]; - const action = - instantiationService.createInstance(ClearLanguageAction); + const instantiationService = accessor.get(IInstantiationService); + const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); + const extension = (await extensionsWorkbenchService.getExtensions([{ id: extensionId }], CancellationToken.None))[0]; + const action = instantiationService.createInstance(ClearLanguageAction); action.extension = extension; return action.run(); - }, + } }); this.registerExtensionAction({ - id: "workbench.extensions.action.installUnsigned", - title: localize("install", "Install"), + id: 'workbench.extensions.action.installUnsigned', + title: localize('install', "Install"), menu: { id: MenuId.ExtensionContext, - group: "0_install", - when: ContextKeyExpr.and( - ContextKeyExpr.equals("extensionStatus", "uninstalled"), - ContextKeyExpr.has("isGalleryExtension"), - ContextKeyExpr.not("extensionDisallowInstall"), - ContextKeyExpr.has("extensionIsUnsigned"), - ), - order: 1, + group: '0_install', + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'uninstalled'), ContextKeyExpr.has('isGalleryExtension'), ContextKeyExpr.not('extensionDisallowInstall'), ContextKeyExpr.has('extensionIsUnsigned')), + order: 1 }, run: async (accessor: ServicesAccessor, extensionId: string) => { - const instantiationService = accessor.get( - IInstantiationService, - ); - const extension = - this.extensionsWorkbenchService.local.filter((e) => - areSameExtensions(e.identifier, { id: extensionId }), - )[0] || - ( - await this.extensionsWorkbenchService.getExtensions( - [{ id: extensionId }], - CancellationToken.None, - ) - )[0]; + const instantiationService = accessor.get(IInstantiationService); + const extension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, { id: extensionId }))[0] + || (await this.extensionsWorkbenchService.getExtensions([{ id: extensionId }], CancellationToken.None))[0]; if (extension) { - const action = instantiationService.createInstance( - InstallAction, - { - installPreReleaseVersion: - this.extensionsWorkbenchService - .preferPreReleases, - }, - ); + const action = instantiationService.createInstance(InstallAction, { installPreReleaseVersion: this.extensionsWorkbenchService.preferPreReleases }); action.extension = extension; return action.run(); } - }, + } }); this.registerExtensionAction({ - id: "workbench.extensions.action.installAndDonotSync", - title: localize( - "install installAndDonotSync", - "Install (Do not Sync)", - ), + id: 'workbench.extensions.action.installAndDonotSync', + title: localize('install installAndDonotSync', "Install (Do not Sync)"), menu: { id: MenuId.ExtensionContext, - group: "0_install", - when: ContextKeyExpr.and( - ContextKeyExpr.equals("extensionStatus", "uninstalled"), - ContextKeyExpr.has("isGalleryExtension"), - ContextKeyExpr.has("isExtensionAllowed"), - ContextKeyExpr.not("extensionDisallowInstall"), - CONTEXT_SYNC_ENABLEMENT, - ), - order: 1, + group: '0_install', + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'uninstalled'), ContextKeyExpr.has('isGalleryExtension'), ContextKeyExpr.has('isExtensionAllowed'), ContextKeyExpr.not('extensionDisallowInstall'), CONTEXT_SYNC_ENABLEMENT), + order: 1 }, run: async (accessor: ServicesAccessor, extensionId: string) => { - const instantiationService = accessor.get( - IInstantiationService, - ); - const extension = - this.extensionsWorkbenchService.local.filter((e) => - areSameExtensions(e.identifier, { id: extensionId }), - )[0] || - ( - await this.extensionsWorkbenchService.getExtensions( - [{ id: extensionId }], - CancellationToken.None, - ) - )[0]; + const instantiationService = accessor.get(IInstantiationService); + const extension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, { id: extensionId }))[0] + || (await this.extensionsWorkbenchService.getExtensions([{ id: extensionId }], CancellationToken.None))[0]; if (extension) { - const action = instantiationService.createInstance( - InstallAction, - { - installPreReleaseVersion: - this.extensionsWorkbenchService - .preferPreReleases, - isMachineScoped: true, - }, - ); + const action = instantiationService.createInstance(InstallAction, { + installPreReleaseVersion: this.extensionsWorkbenchService.preferPreReleases, + isMachineScoped: true, + }); action.extension = extension; return action.run(); } - }, + } }); this.registerExtensionAction({ - id: "workbench.extensions.action.installPrereleaseAndDonotSync", - title: localize( - "installPrereleaseAndDonotSync", - "Install Pre-Release (Do not Sync)", - ), + id: 'workbench.extensions.action.installPrereleaseAndDonotSync', + title: localize('installPrereleaseAndDonotSync', "Install Pre-Release (Do not Sync)"), menu: { id: MenuId.ExtensionContext, - group: "0_install", - when: ContextKeyExpr.and( - ContextKeyExpr.equals("extensionStatus", "uninstalled"), - ContextKeyExpr.has("isGalleryExtension"), - ContextKeyExpr.has("extensionHasPreReleaseVersion"), - ContextKeyExpr.has("isPreReleaseExtensionAllowed"), - ContextKeyExpr.not("extensionDisallowInstall"), - CONTEXT_SYNC_ENABLEMENT, - ), - order: 2, + group: '0_install', + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'uninstalled'), ContextKeyExpr.has('isGalleryExtension'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.has('isPreReleaseExtensionAllowed'), ContextKeyExpr.not('extensionDisallowInstall'), CONTEXT_SYNC_ENABLEMENT), + order: 2 }, run: async (accessor: ServicesAccessor, extensionId: string) => { - const instantiationService = accessor.get( - IInstantiationService, - ); - const extension = - this.extensionsWorkbenchService.local.filter((e) => - areSameExtensions(e.identifier, { id: extensionId }), - )[0] || - ( - await this.extensionsWorkbenchService.getExtensions( - [{ id: extensionId }], - CancellationToken.None, - ) - )[0]; + const instantiationService = accessor.get(IInstantiationService); + const extension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, { id: extensionId }))[0] + || (await this.extensionsWorkbenchService.getExtensions([{ id: extensionId }], CancellationToken.None))[0]; if (extension) { - const action = instantiationService.createInstance( - InstallAction, - { - isMachineScoped: true, - preRelease: true, - }, - ); + const action = instantiationService.createInstance(InstallAction, { + isMachineScoped: true, + preRelease: true + }); action.extension = extension; return action.run(); } - }, + } }); this.registerExtensionAction({ @@ -2817,538 +1587,291 @@ class ExtensionsContributions title: InstallAnotherVersionAction.LABEL, menu: { id: MenuId.ExtensionContext, - group: "0_install", - when: ContextKeyExpr.and( - ContextKeyExpr.equals("extensionStatus", "uninstalled"), - ContextKeyExpr.has("isGalleryExtension"), - ContextKeyExpr.has("isExtensionAllowed"), - ContextKeyExpr.not("extensionDisallowInstall"), - ), - order: 3, + group: '0_install', + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'uninstalled'), ContextKeyExpr.has('isGalleryExtension'), ContextKeyExpr.has('isExtensionAllowed'), ContextKeyExpr.not('extensionDisallowInstall')), + order: 3 }, run: async (accessor: ServicesAccessor, extensionId: string) => { - const instantiationService = accessor.get( - IInstantiationService, - ); - const extension = - this.extensionsWorkbenchService.local.filter((e) => - areSameExtensions(e.identifier, { id: extensionId }), - )[0] || - ( - await this.extensionsWorkbenchService.getExtensions( - [{ id: extensionId }], - CancellationToken.None, - ) - )[0]; + const instantiationService = accessor.get(IInstantiationService); + const extension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, { id: extensionId }))[0] + || (await this.extensionsWorkbenchService.getExtensions([{ id: extensionId }], CancellationToken.None))[0]; if (extension) { - return instantiationService - .createInstance( - InstallAnotherVersionAction, - extension, - false, - ) - .run(); + return instantiationService.createInstance(InstallAnotherVersionAction, extension, false).run(); } - }, + } }); this.registerExtensionAction({ - id: "workbench.extensions.action.copyExtension", - title: localize2( - "workbench.extensions.action.copyExtension", - "Copy", - ), + id: 'workbench.extensions.action.copyExtension', + title: localize2('workbench.extensions.action.copyExtension', 'Copy'), menu: { id: MenuId.ExtensionContext, - group: "1_copy", + group: '1_copy' }, run: async (accessor: ServicesAccessor, extensionId: string) => { const clipboardService = accessor.get(IClipboardService); - const extension = - this.extensionsWorkbenchService.local.filter((e) => - areSameExtensions(e.identifier, { id: extensionId }), - )[0] || - ( - await this.extensionsWorkbenchService.getExtensions( - [{ id: extensionId }], - CancellationToken.None, - ) - )[0]; + const extension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, { id: extensionId }))[0] + || (await this.extensionsWorkbenchService.getExtensions([{ id: extensionId }], CancellationToken.None))[0]; if (extension) { - const name = localize( - "extensionInfoName", - "Name: {0}", - extension.displayName, - ); - const id = localize( - "extensionInfoId", - "Id: {0}", - extensionId, - ); - const description = localize( - "extensionInfoDescription", - "Description: {0}", - extension.description, - ); - const verision = localize( - "extensionInfoVersion", - "Version: {0}", - extension.version, - ); - const publisher = localize( - "extensionInfoPublisher", - "Publisher: {0}", - extension.publisherDisplayName, - ); - const link = extension.url - ? localize( - "extensionInfoVSMarketplaceLink", - "VS Marketplace Link: {0}", - `${extension.url}`, - ) - : null; - const clipboardStr = `${name}\n${id}\n${description}\n${verision}\n${publisher}${link ? "\n" + link : ""}`; + const name = localize('extensionInfoName', 'Name: {0}', extension.displayName); + const id = localize('extensionInfoId', 'Id: {0}', extensionId); + const description = localize('extensionInfoDescription', 'Description: {0}', extension.description); + const verision = localize('extensionInfoVersion', 'Version: {0}', extension.version); + const publisher = localize('extensionInfoPublisher', 'Publisher: {0}', extension.publisherDisplayName); + const link = extension.url ? localize('extensionInfoVSMarketplaceLink', 'VS Marketplace Link: {0}', `${extension.url}`) : null; + const clipboardStr = `${name}\n${id}\n${description}\n${verision}\n${publisher}${link ? '\n' + link : ''}`; await clipboardService.writeText(clipboardStr); } - }, + } }); this.registerExtensionAction({ - id: "workbench.extensions.action.copyExtensionId", - title: localize2( - "workbench.extensions.action.copyExtensionId", - "Copy Extension ID", - ), + id: 'workbench.extensions.action.copyExtensionId', + title: localize2('workbench.extensions.action.copyExtensionId', 'Copy Extension ID'), menu: { id: MenuId.ExtensionContext, - group: "1_copy", + group: '1_copy' }, - run: async (accessor: ServicesAccessor, id: string) => - accessor.get(IClipboardService).writeText(id), + run: async (accessor: ServicesAccessor, id: string) => accessor.get(IClipboardService).writeText(id) }); this.registerExtensionAction({ - id: "workbench.extensions.action.configure", - title: localize2( - "workbench.extensions.action.configure", - "Settings", - ), + id: 'workbench.extensions.action.configure', + title: localize2('workbench.extensions.action.configure', 'Settings'), menu: { id: MenuId.ExtensionContext, - group: "2_configure", - when: ContextKeyExpr.and( - ContextKeyExpr.equals("extensionStatus", "installed"), - ContextKeyExpr.has("extensionHasConfiguration"), - ), - order: 1, + group: '2_configure', + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.has('extensionHasConfiguration')), + order: 1 }, - run: async (accessor: ServicesAccessor, id: string) => - accessor - .get(IPreferencesService) - .openSettings({ jsonEditor: false, query: `@ext:${id}` }), + run: async (accessor: ServicesAccessor, id: string) => accessor.get(IPreferencesService).openSettings({ jsonEditor: false, query: `@ext:${id}` }) }); this.registerExtensionAction({ - id: "workbench.extensions.action.download", - title: localize("download VSIX", "Download VSIX"), + id: 'workbench.extensions.action.download', + title: localize('download VSIX', "Download VSIX"), menu: { id: MenuId.ExtensionContext, - when: ContextKeyExpr.and( - ContextKeyExpr.equals("extensionStatus", "uninstalled"), - ContextKeyExpr.has("isGalleryExtension"), - ), - order: this.productService.quality === "stable" ? 0 : 1, + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'uninstalled'), ContextKeyExpr.has('isGalleryExtension')), + order: this.productService.quality === 'stable' ? 0 : 1 }, run: async (accessor: ServicesAccessor, extensionId: string) => { - accessor - .get(IExtensionsWorkbenchService) - .downloadVSIX(extensionId, false); - }, + accessor.get(IExtensionsWorkbenchService).downloadVSIX(extensionId, false); + } }); this.registerExtensionAction({ - id: "workbench.extensions.action.downloadPreRelease", - title: localize( - "download pre-release", - "Download Pre-Release VSIX", - ), + id: 'workbench.extensions.action.downloadPreRelease', + title: localize('download pre-release', "Download Pre-Release VSIX"), menu: { id: MenuId.ExtensionContext, - when: ContextKeyExpr.and( - ContextKeyExpr.equals("extensionStatus", "uninstalled"), - ContextKeyExpr.has("isGalleryExtension"), - ContextKeyExpr.has("extensionHasPreReleaseVersion"), - ), - order: this.productService.quality === "stable" ? 1 : 0, + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'uninstalled'), ContextKeyExpr.has('isGalleryExtension'), ContextKeyExpr.has('extensionHasPreReleaseVersion')), + order: this.productService.quality === 'stable' ? 1 : 0 }, run: async (accessor: ServicesAccessor, extensionId: string) => { - accessor - .get(IExtensionsWorkbenchService) - .downloadVSIX(extensionId, true); - }, + accessor.get(IExtensionsWorkbenchService).downloadVSIX(extensionId, true); + } }); this.registerExtensionAction({ - id: "workbench.extensions.action.manageAccountPreferences", - title: localize2( - "workbench.extensions.action.changeAccountPreference", - "Account Preferences", - ), + id: 'workbench.extensions.action.manageAccountPreferences', + title: localize2('workbench.extensions.action.changeAccountPreference', "Account Preferences"), menu: { id: MenuId.ExtensionContext, - group: "2_configure", - when: ContextKeyExpr.and( - ContextKeyExpr.equals("extensionStatus", "installed"), - ContextKeyExpr.has("extensionHasAccountPreferences"), - ), + group: '2_configure', + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.has('extensionHasAccountPreferences')), order: 2, }, - run: (accessor: ServicesAccessor, id: string) => - accessor - .get(ICommandService) - .executeCommand( - "_manageAccountPreferencesForExtension", - id, - ), + run: (accessor: ServicesAccessor, id: string) => accessor.get(ICommandService).executeCommand('_manageAccountPreferencesForExtension', id) }); this.registerExtensionAction({ - id: "workbench.extensions.action.configureKeybindings", - title: localize2( - "workbench.extensions.action.configureKeybindings", - "Keyboard Shortcuts", - ), + id: 'workbench.extensions.action.configureKeybindings', + title: localize2('workbench.extensions.action.configureKeybindings', 'Keyboard Shortcuts'), menu: { id: MenuId.ExtensionContext, - group: "2_configure", - when: ContextKeyExpr.and( - ContextKeyExpr.equals("extensionStatus", "installed"), - ContextKeyExpr.has("extensionHasKeybindings"), - ), - order: 2, + group: '2_configure', + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.has('extensionHasKeybindings')), + order: 2 }, - run: async (accessor: ServicesAccessor, id: string) => - accessor - .get(IPreferencesService) - .openGlobalKeybindingSettings(false, { - query: `@ext:${id}`, - }), + run: async (accessor: ServicesAccessor, id: string) => accessor.get(IPreferencesService).openGlobalKeybindingSettings(false, { query: `@ext:${id}` }) }); this.registerExtensionAction({ - id: "workbench.extensions.action.toggleApplyToAllProfiles", - title: localize2( - "workbench.extensions.action.toggleApplyToAllProfiles", - "Apply Extension to all Profiles", - ), - toggled: ContextKeyExpr.has("isApplicationScopedExtension"), + id: 'workbench.extensions.action.toggleApplyToAllProfiles', + title: localize2('workbench.extensions.action.toggleApplyToAllProfiles', "Apply Extension to all Profiles"), + toggled: ContextKeyExpr.has('isApplicationScopedExtension'), menu: { id: MenuId.ExtensionContext, - group: "2_configure", - when: ContextKeyExpr.and( - ContextKeyExpr.equals("extensionStatus", "installed"), - ContextKeyExpr.has( - "isDefaultApplicationScopedExtension", - ).negate(), - ContextKeyExpr.has("isBuiltinExtension").negate(), - ContextKeyExpr.equals("isWorkspaceScopedExtension", false), - ), - order: 3, + group: '2_configure', + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.has('isDefaultApplicationScopedExtension').negate(), ContextKeyExpr.has('isBuiltinExtension').negate(), ContextKeyExpr.equals('isWorkspaceScopedExtension', false)), + order: 3 }, - run: async ( - accessor: ServicesAccessor, - _: string, - extensionArg: IExtensionArg, - ) => { + run: async (accessor: ServicesAccessor, _: string, extensionArg: IExtensionArg) => { const uriIdentityService = accessor.get(IUriIdentityService); - const extension = extensionArg.location - ? this.extensionsWorkbenchService.installed.find((e) => - uriIdentityService.extUri.isEqual( - e.local?.location, - extensionArg.location, - ), - ) - : undefined; + const extension = extensionArg.location ? this.extensionsWorkbenchService.installed.find(e => uriIdentityService.extUri.isEqual(e.local?.location, extensionArg.location)) : undefined; if (extension) { - return this.extensionsWorkbenchService.toggleApplyExtensionToAllProfiles( - extension, - ); + return this.extensionsWorkbenchService.toggleApplyExtensionToAllProfiles(extension); } - }, + } }); this.registerExtensionAction({ id: TOGGLE_IGNORE_EXTENSION_ACTION_ID, - title: localize2( - "workbench.extensions.action.toggleIgnoreExtension", - "Sync This Extension", - ), + title: localize2('workbench.extensions.action.toggleIgnoreExtension', "Sync This Extension"), menu: { id: MenuId.ExtensionContext, - group: "2_configure", - when: ContextKeyExpr.and( - ContextKeyExpr.equals("extensionStatus", "installed"), - CONTEXT_SYNC_ENABLEMENT, - ContextKeyExpr.equals("isWorkspaceScopedExtension", false), - ), - order: 4, + group: '2_configure', + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'installed'), CONTEXT_SYNC_ENABLEMENT, ContextKeyExpr.equals('isWorkspaceScopedExtension', false)), + order: 4 }, run: async (accessor: ServicesAccessor, id: string) => { - const extension = this.extensionsWorkbenchService.local.find( - (e) => areSameExtensions({ id }, e.identifier), - ); + const extension = this.extensionsWorkbenchService.local.find(e => areSameExtensions({ id }, e.identifier)); if (extension) { - return this.extensionsWorkbenchService.toggleExtensionIgnoredToSync( - extension, - ); + return this.extensionsWorkbenchService.toggleExtensionIgnoredToSync(extension); } - }, + } }); this.registerExtensionAction({ - id: "workbench.extensions.action.ignoreRecommendation", - title: localize2( - "workbench.extensions.action.ignoreRecommendation", - "Ignore Recommendation", - ), + id: 'workbench.extensions.action.ignoreRecommendation', + title: localize2('workbench.extensions.action.ignoreRecommendation', "Ignore Recommendation"), menu: { id: MenuId.ExtensionContext, - group: "3_recommendations", - when: ContextKeyExpr.has("isExtensionRecommended"), - order: 1, + group: '3_recommendations', + when: ContextKeyExpr.has('isExtensionRecommended'), + order: 1 }, - run: async (accessor: ServicesAccessor, id: string) => - accessor - .get(IExtensionIgnoredRecommendationsService) - .toggleGlobalIgnoredRecommendation(id, true), + run: async (accessor: ServicesAccessor, id: string) => accessor.get(IExtensionIgnoredRecommendationsService).toggleGlobalIgnoredRecommendation(id, true) }); this.registerExtensionAction({ - id: "workbench.extensions.action.undoIgnoredRecommendation", - title: localize2( - "workbench.extensions.action.undoIgnoredRecommendation", - "Undo Ignored Recommendation", - ), + id: 'workbench.extensions.action.undoIgnoredRecommendation', + title: localize2('workbench.extensions.action.undoIgnoredRecommendation', "Undo Ignored Recommendation"), menu: { id: MenuId.ExtensionContext, - group: "3_recommendations", - when: ContextKeyExpr.has("isUserIgnoredRecommendation"), - order: 1, + group: '3_recommendations', + when: ContextKeyExpr.has('isUserIgnoredRecommendation'), + order: 1 }, - run: async (accessor: ServicesAccessor, id: string) => - accessor - .get(IExtensionIgnoredRecommendationsService) - .toggleGlobalIgnoredRecommendation(id, false), + run: async (accessor: ServicesAccessor, id: string) => accessor.get(IExtensionIgnoredRecommendationsService).toggleGlobalIgnoredRecommendation(id, false) }); this.registerExtensionAction({ - id: "workbench.extensions.action.addExtensionToWorkspaceRecommendations", - title: localize2( - "workbench.extensions.action.addExtensionToWorkspaceRecommendations", - "Add to Workspace Recommendations", - ), + id: 'workbench.extensions.action.addExtensionToWorkspaceRecommendations', + title: localize2('workbench.extensions.action.addExtensionToWorkspaceRecommendations', "Add to Workspace Recommendations"), menu: { id: MenuId.ExtensionContext, - group: "3_recommendations", - when: ContextKeyExpr.and( - WorkbenchStateContext.notEqualsTo("empty"), - ContextKeyExpr.has("isBuiltinExtension").negate(), - ContextKeyExpr.has( - "isExtensionWorkspaceRecommended", - ).negate(), - ContextKeyExpr.has("isUserIgnoredRecommendation").negate(), - ContextKeyExpr.notEquals("extensionSource", "resource"), - ), - order: 2, + group: '3_recommendations', + when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('empty'), ContextKeyExpr.has('isBuiltinExtension').negate(), ContextKeyExpr.has('isExtensionWorkspaceRecommended').negate(), ContextKeyExpr.has('isUserIgnoredRecommendation').negate(), ContextKeyExpr.notEquals('extensionSource', 'resource')), + order: 2 }, - run: (accessor: ServicesAccessor, id: string) => - accessor - .get(IWorkspaceExtensionsConfigService) - .toggleRecommendation(id), + run: (accessor: ServicesAccessor, id: string) => accessor.get(IWorkspaceExtensionsConfigService).toggleRecommendation(id) }); this.registerExtensionAction({ - id: "workbench.extensions.action.removeExtensionFromWorkspaceRecommendations", - title: localize2( - "workbench.extensions.action.removeExtensionFromWorkspaceRecommendations", - "Remove from Workspace Recommendations", - ), + id: 'workbench.extensions.action.removeExtensionFromWorkspaceRecommendations', + title: localize2('workbench.extensions.action.removeExtensionFromWorkspaceRecommendations', "Remove from Workspace Recommendations"), menu: { id: MenuId.ExtensionContext, - group: "3_recommendations", - when: ContextKeyExpr.and( - WorkbenchStateContext.notEqualsTo("empty"), - ContextKeyExpr.has("isBuiltinExtension").negate(), - ContextKeyExpr.has("isExtensionWorkspaceRecommended"), - ), - order: 2, + group: '3_recommendations', + when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('empty'), ContextKeyExpr.has('isBuiltinExtension').negate(), ContextKeyExpr.has('isExtensionWorkspaceRecommended')), + order: 2 }, - run: (accessor: ServicesAccessor, id: string) => - accessor - .get(IWorkspaceExtensionsConfigService) - .toggleRecommendation(id), + run: (accessor: ServicesAccessor, id: string) => accessor.get(IWorkspaceExtensionsConfigService).toggleRecommendation(id) }); this.registerExtensionAction({ - id: "workbench.extensions.action.addToWorkspaceRecommendations", - title: localize2( - "workbench.extensions.action.addToWorkspaceRecommendations", - "Add Extension to Workspace Recommendations", - ), - category: localize("extensions", "Extensions"), + id: 'workbench.extensions.action.addToWorkspaceRecommendations', + title: localize2('workbench.extensions.action.addToWorkspaceRecommendations', "Add Extension to Workspace Recommendations"), + category: localize('extensions', "Extensions"), menu: { id: MenuId.CommandPalette, - when: ContextKeyExpr.and( - WorkbenchStateContext.isEqualTo("workspace"), - ContextKeyExpr.equals("resourceScheme", Schemas.extension), - ), + when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace'), ContextKeyExpr.equals('resourceScheme', Schemas.extension)), }, async run(accessor: ServicesAccessor): Promise { const editorService = accessor.get(IEditorService); - const workspaceExtensionsConfigService = accessor.get( - IWorkspaceExtensionsConfigService, - ); + const workspaceExtensionsConfigService = accessor.get(IWorkspaceExtensionsConfigService); if (!(editorService.activeEditor instanceof ExtensionsInput)) { return; } - const extensionId = - editorService.activeEditor.extension.identifier.id.toLowerCase(); - const recommendations = - await workspaceExtensionsConfigService.getRecommendations(); + const extensionId = editorService.activeEditor.extension.identifier.id.toLowerCase(); + const recommendations = await workspaceExtensionsConfigService.getRecommendations(); if (recommendations.includes(extensionId)) { return; } - await workspaceExtensionsConfigService.toggleRecommendation( - extensionId, - ); - }, + await workspaceExtensionsConfigService.toggleRecommendation(extensionId); + } }); this.registerExtensionAction({ - id: "workbench.extensions.action.addToWorkspaceFolderRecommendations", - title: localize2( - "workbench.extensions.action.addToWorkspaceFolderRecommendations", - "Add Extension to Workspace Folder Recommendations", - ), - category: localize("extensions", "Extensions"), + id: 'workbench.extensions.action.addToWorkspaceFolderRecommendations', + title: localize2('workbench.extensions.action.addToWorkspaceFolderRecommendations', "Add Extension to Workspace Folder Recommendations"), + category: localize('extensions', "Extensions"), menu: { id: MenuId.CommandPalette, - when: ContextKeyExpr.and( - WorkbenchStateContext.isEqualTo("folder"), - ContextKeyExpr.equals("resourceScheme", Schemas.extension), - ), + when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('folder'), ContextKeyExpr.equals('resourceScheme', Schemas.extension)), }, - run: () => - this.commandService.executeCommand( - "workbench.extensions.action.addToWorkspaceRecommendations", - ), + run: () => this.commandService.executeCommand('workbench.extensions.action.addToWorkspaceRecommendations') }); this.registerExtensionAction({ - id: "workbench.extensions.action.addToWorkspaceIgnoredRecommendations", - title: localize2( - "workbench.extensions.action.addToWorkspaceIgnoredRecommendations", - "Add Extension to Workspace Ignored Recommendations", - ), - category: localize("extensions", "Extensions"), + id: 'workbench.extensions.action.addToWorkspaceIgnoredRecommendations', + title: localize2('workbench.extensions.action.addToWorkspaceIgnoredRecommendations', "Add Extension to Workspace Ignored Recommendations"), + category: localize('extensions', "Extensions"), menu: { id: MenuId.CommandPalette, - when: ContextKeyExpr.and( - WorkbenchStateContext.isEqualTo("workspace"), - ContextKeyExpr.equals("resourceScheme", Schemas.extension), - ), + when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace'), ContextKeyExpr.equals('resourceScheme', Schemas.extension)), }, async run(accessor: ServicesAccessor): Promise { const editorService = accessor.get(IEditorService); - const workspaceExtensionsConfigService = accessor.get( - IWorkspaceExtensionsConfigService, - ); + const workspaceExtensionsConfigService = accessor.get(IWorkspaceExtensionsConfigService); if (!(editorService.activeEditor instanceof ExtensionsInput)) { return; } - const extensionId = - editorService.activeEditor.extension.identifier.id.toLowerCase(); - const unwantedRecommendations = - await workspaceExtensionsConfigService.getUnwantedRecommendations(); + const extensionId = editorService.activeEditor.extension.identifier.id.toLowerCase(); + const unwantedRecommendations = await workspaceExtensionsConfigService.getUnwantedRecommendations(); if (unwantedRecommendations.includes(extensionId)) { return; } - await workspaceExtensionsConfigService.toggleUnwantedRecommendation( - extensionId, - ); - }, + await workspaceExtensionsConfigService.toggleUnwantedRecommendation(extensionId); + } }); this.registerExtensionAction({ - id: "workbench.extensions.action.addToWorkspaceFolderIgnoredRecommendations", - title: localize2( - "workbench.extensions.action.addToWorkspaceFolderIgnoredRecommendations", - "Add Extension to Workspace Folder Ignored Recommendations", - ), - category: localize("extensions", "Extensions"), + id: 'workbench.extensions.action.addToWorkspaceFolderIgnoredRecommendations', + title: localize2('workbench.extensions.action.addToWorkspaceFolderIgnoredRecommendations', "Add Extension to Workspace Folder Ignored Recommendations"), + category: localize('extensions', "Extensions"), menu: { id: MenuId.CommandPalette, - when: ContextKeyExpr.and( - WorkbenchStateContext.isEqualTo("folder"), - ContextKeyExpr.equals("resourceScheme", Schemas.extension), - ), + when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('folder'), ContextKeyExpr.equals('resourceScheme', Schemas.extension)), }, - run: () => - this.commandService.executeCommand( - "workbench.extensions.action.addToWorkspaceIgnoredRecommendations", - ), + run: () => this.commandService.executeCommand('workbench.extensions.action.addToWorkspaceIgnoredRecommendations') }); this.registerExtensionAction({ id: ConfigureWorkspaceRecommendedExtensionsAction.ID, - title: { - value: ConfigureWorkspaceRecommendedExtensionsAction.LABEL, - original: "Configure Recommended Extensions (Workspace)", - }, - category: localize("extensions", "Extensions"), + title: { value: ConfigureWorkspaceRecommendedExtensionsAction.LABEL, original: 'Configure Recommended Extensions (Workspace)' }, + category: localize('extensions', "Extensions"), menu: { id: MenuId.CommandPalette, - when: WorkbenchStateContext.isEqualTo("workspace"), + when: WorkbenchStateContext.isEqualTo('workspace'), }, - run: () => - runAction( - this.instantiationService.createInstance( - ConfigureWorkspaceRecommendedExtensionsAction, - ConfigureWorkspaceRecommendedExtensionsAction.ID, - ConfigureWorkspaceRecommendedExtensionsAction.LABEL, - ), - ), + run: () => runAction(this.instantiationService.createInstance(ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceRecommendedExtensionsAction.ID, ConfigureWorkspaceRecommendedExtensionsAction.LABEL)) }); + } - private registerExtensionAction( - extensionActionOptions: IExtensionActionOptions, - ): IDisposable { - const menus = extensionActionOptions.menu - ? Array.isArray(extensionActionOptions.menu) - ? extensionActionOptions.menu - : [extensionActionOptions.menu] - : []; - let menusWithOutTitles: ({ id: MenuId } & Omit< - IMenuItem, - "command" - >)[] = []; + private registerExtensionAction(extensionActionOptions: IExtensionActionOptions): IDisposable { + const menus = extensionActionOptions.menu ? Array.isArray(extensionActionOptions.menu) ? extensionActionOptions.menu : [extensionActionOptions.menu] : []; + let menusWithOutTitles: ({ id: MenuId } & Omit)[] = []; const menusWithTitles: { id: MenuId; item: IMenuItem }[] = []; if (extensionActionOptions.menuTitles) { for (let index = 0; index < menus.length; index++) { const menu = menus[index]; const menuTitle = extensionActionOptions.menuTitles[menu.id.id]; if (menuTitle) { - menusWithTitles.push({ - id: menu.id, - item: { - ...menu, - command: { - id: extensionActionOptions.id, - title: menuTitle, - }, - }, - }); + menusWithTitles.push({ id: menu.id, item: { ...menu, command: { id: extensionActionOptions.id, title: menuTitle } } }); } else { menusWithOutTitles.push(menu); } @@ -3357,107 +1880,61 @@ class ExtensionsContributions menusWithOutTitles = menus; } const disposables = new DisposableStore(); - disposables.add( - registerAction2( - class extends Action2 { - constructor() { - super({ - ...extensionActionOptions, - menu: menusWithOutTitles, - }); - } - run( - accessor: ServicesAccessor, - ...args: any[] - ): Promise { - return extensionActionOptions.run(accessor, ...args); - } - }, - ), - ); + disposables.add(registerAction2(class extends Action2 { + constructor() { + super({ + ...extensionActionOptions, + menu: menusWithOutTitles + }); + } + run(accessor: ServicesAccessor, ...args: any[]): Promise { + return extensionActionOptions.run(accessor, ...args); + } + })); if (menusWithTitles.length) { disposables.add(MenuRegistry.appendMenuItems(menusWithTitles)); } return disposables; } + } class ExtensionStorageCleaner implements IWorkbenchContribution { + constructor( - @IExtensionManagementService - extensionManagementService: IExtensionManagementService, + @IExtensionManagementService extensionManagementService: IExtensionManagementService, @IStorageService storageService: IStorageService, ) { - ExtensionStorageService.removeOutdatedExtensionVersions( - extensionManagementService, - storageService, - ); + ExtensionStorageService.removeOutdatedExtensionVersions(extensionManagementService, storageService); } } -const workbenchRegistry = Registry.as( - WorkbenchExtensions.Workbench, -); -workbenchRegistry.registerWorkbenchContribution( - ExtensionsContributions, - LifecyclePhase.Restored, -); -workbenchRegistry.registerWorkbenchContribution( - StatusUpdater, - LifecyclePhase.Eventually, -); -workbenchRegistry.registerWorkbenchContribution( - MaliciousExtensionChecker, - LifecyclePhase.Eventually, -); -workbenchRegistry.registerWorkbenchContribution( - KeymapExtensions, - LifecyclePhase.Restored, -); -workbenchRegistry.registerWorkbenchContribution( - ExtensionsViewletViewsContribution, - LifecyclePhase.Restored, -); -workbenchRegistry.registerWorkbenchContribution( - ExtensionActivationProgress, - LifecyclePhase.Eventually, -); -workbenchRegistry.registerWorkbenchContribution( - ExtensionDependencyChecker, - LifecyclePhase.Eventually, -); -workbenchRegistry.registerWorkbenchContribution( - ExtensionEnablementWorkspaceTrustTransitionParticipant, - LifecyclePhase.Restored, -); -workbenchRegistry.registerWorkbenchContribution( - ExtensionsCompletionItemsProvider, - LifecyclePhase.Restored, -); -workbenchRegistry.registerWorkbenchContribution( - UnsupportedExtensionsMigrationContrib, - LifecyclePhase.Eventually, -); +const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchRegistry.registerWorkbenchContribution(ExtensionsContributions, LifecyclePhase.Restored); +workbenchRegistry.registerWorkbenchContribution(StatusUpdater, LifecyclePhase.Eventually); +workbenchRegistry.registerWorkbenchContribution(MaliciousExtensionChecker, LifecyclePhase.Eventually); +workbenchRegistry.registerWorkbenchContribution(KeymapExtensions, LifecyclePhase.Restored); +workbenchRegistry.registerWorkbenchContribution(ExtensionsViewletViewsContribution, LifecyclePhase.Restored); +workbenchRegistry.registerWorkbenchContribution(ExtensionActivationProgress, LifecyclePhase.Eventually); +workbenchRegistry.registerWorkbenchContribution(ExtensionDependencyChecker, LifecyclePhase.Eventually); +workbenchRegistry.registerWorkbenchContribution(ExtensionEnablementWorkspaceTrustTransitionParticipant, LifecyclePhase.Restored); +workbenchRegistry.registerWorkbenchContribution(ExtensionsCompletionItemsProvider, LifecyclePhase.Restored); +workbenchRegistry.registerWorkbenchContribution(UnsupportedExtensionsMigrationContrib, LifecyclePhase.Eventually); if (isWeb) { - workbenchRegistry.registerWorkbenchContribution( - ExtensionStorageCleaner, - LifecyclePhase.Eventually, - ); + workbenchRegistry.registerWorkbenchContribution(ExtensionStorageCleaner, LifecyclePhase.Eventually); } + // Running Extensions registerAction2(ShowRuntimeExtensionsAction); -Registry.as( - ConfigurationMigrationExtensions.ConfigurationMigration, -).registerConfigurationMigrations([ - { +Registry.as(ConfigurationMigrationExtensions.ConfigurationMigration) + .registerConfigurationMigrations([{ key: AutoUpdateConfigurationKey, migrateFn: (value, accessor) => { - if (value === "onlySelectedExtensions") { + if (value === 'onlySelectedExtensions') { return { value: false }; } return []; - }, - }, -]); + } + }]); diff --git a/Source/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/Source/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 8b5b86a903321..196334c29243d 100644 --- a/Source/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/Source/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -3,214 +3,78 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import "./media/extensionActions.css"; - -import { IContextMenuProvider } from "../../../../base/browser/contextmenu.js"; -import * as DOM from "../../../../base/browser/dom.js"; -import { - ActionViewItem, - IActionViewItemOptions, -} from "../../../../base/browser/ui/actionbar/actionViewItems.js"; -import { alert } from "../../../../base/browser/ui/aria/aria.js"; -import { - ActionWithDropdownActionViewItem, - IActionWithDropdownActionViewItemOptions, -} from "../../../../base/browser/ui/dropdown/dropdownActionViewItem.js"; -import { - Action, - IAction, - IActionChangeEvent, - Separator, - SubmenuAction, -} from "../../../../base/common/actions.js"; -import { Delayer, Promises, Throttler } from "../../../../base/common/async.js"; -import { CancellationToken } from "../../../../base/common/cancellation.js"; -import { fromNow } from "../../../../base/common/date.js"; -import { - getErrorMessage, - isCancellationError, -} from "../../../../base/common/errors.js"; -import { Emitter, Event } from "../../../../base/common/event.js"; -import { - escapeMarkdownSyntaxTokens, - IMarkdownString, - MarkdownString, -} from "../../../../base/common/htmlContent.js"; -import * as json from "../../../../base/common/json.js"; -import { disposeIfDisposable } from "../../../../base/common/lifecycle.js"; -import { isIOS, isWeb, language } from "../../../../base/common/platform.js"; -import { ThemeIcon } from "../../../../base/common/themables.js"; -import { isString } from "../../../../base/common/types.js"; -import { URI } from "../../../../base/common/uri.js"; -import { ITextModelService } from "../../../../editor/common/services/resolverService.js"; -import { localize, localize2 } from "../../../../nls.js"; -import { - IMenuService, - MenuId, - MenuItemAction, - SubmenuItemAction, -} from "../../../../platform/actions/common/actions.js"; -import { - CommandsRegistry, - ICommandService, -} from "../../../../platform/commands/common/commands.js"; -import { IConfigurationService } from "../../../../platform/configuration/common/configuration.js"; -import { IContextKeyService } from "../../../../platform/contextkey/common/contextkey.js"; -import { IContextMenuService } from "../../../../platform/contextview/browser/contextView.js"; -import { - IDialogService, - IPromptButton, -} from "../../../../platform/dialogs/common/dialogs.js"; -import { ITextEditorSelection } from "../../../../platform/editor/common/editor.js"; -import { - ExtensionManagementErrorCode, - IAllowedExtensionsService, - IExtensionGalleryService, - IGalleryExtension, - ILocalExtension, - InstallOperation, - InstallOptions, -} from "../../../../platform/extensionManagement/common/extensionManagement.js"; -import { - areSameExtensions, - getExtensionId, -} from "../../../../platform/extensionManagement/common/extensionManagementUtil.js"; -import { - ExtensionIdentifier, - ExtensionType, - getWorkspaceSupportTypeMessage, - IExtensionDescription, - IExtensionManifest, - isApplicationScopedExtension, - isLanguagePackExtension, - TargetPlatform, -} from "../../../../platform/extensions/common/extensions.js"; -import { - IFileContent, - IFileService, -} from "../../../../platform/files/common/files.js"; -import { - IInstantiationService, - ServicesAccessor, -} from "../../../../platform/instantiation/common/instantiation.js"; -import { ILabelService } from "../../../../platform/label/common/label.js"; -import { getLocale } from "../../../../platform/languagePacks/common/languagePacks.js"; -import { ILogService } from "../../../../platform/log/common/log.js"; -import { - INotificationService, - IPromptChoice, - Severity, -} from "../../../../platform/notification/common/notification.js"; -import { IOpenerService } from "../../../../platform/opener/common/opener.js"; -import { IProductService } from "../../../../platform/product/common/productService.js"; -import { - IProgressService, - ProgressLocation, -} from "../../../../platform/progress/common/progress.js"; -import { - IQuickInputService, - IQuickPickItem, - QuickPickItem, -} from "../../../../platform/quickinput/common/quickInput.js"; -import { Registry } from "../../../../platform/registry/common/platform.js"; -import { ITelemetryService } from "../../../../platform/telemetry/common/telemetry.js"; -import { - buttonBackground, - buttonForeground, - buttonHoverBackground, - buttonSeparator, - editorErrorForeground, - editorInfoForeground, - editorWarningForeground, - registerColor, -} from "../../../../platform/theme/common/colorRegistry.js"; -import { - IColorTheme, - ICssStyleCollector, - registerThemingParticipant, -} from "../../../../platform/theme/common/themeService.js"; -import { IUpdateService } from "../../../../platform/update/common/update.js"; -import { IUserDataSyncEnablementService } from "../../../../platform/userDataSync/common/userDataSync.js"; -import { isVirtualWorkspace } from "../../../../platform/workspace/common/virtualWorkspace.js"; -import { - IWorkspaceContextService, - IWorkspaceFolder, - WorkbenchState, -} from "../../../../platform/workspace/common/workspace.js"; -import { - IWorkspaceTrustEnablementService, - IWorkspaceTrustManagementService, -} from "../../../../platform/workspace/common/workspaceTrust.js"; -import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from "../../../browser/actions/workspaceCommands.js"; -import { IAuthenticationUsageService } from "../../../services/authentication/browser/authenticationUsageService.js"; -import { IJSONEditingService } from "../../../services/configuration/common/jsonEditing.js"; -import { IEditorService } from "../../../services/editor/common/editorService.js"; -import { - Extensions, - IExtensionFeaturesManagementService, - IExtensionFeaturesRegistry, -} from "../../../services/extensionManagement/common/extensionFeatures.js"; -import { - EnablementState, - IExtensionManagementServer, - IExtensionManagementServerService, - IWorkbenchExtensionEnablementService, - IWorkbenchExtensionManagementService, -} from "../../../services/extensionManagement/common/extensionManagement.js"; -import { - ExtensionRecommendationReason, - IExtensionIgnoredRecommendationsService, - IExtensionRecommendationsService, -} from "../../../services/extensionRecommendations/common/extensionRecommendations.js"; -import { - EXTENSIONS_CONFIG, - IExtensionsConfigContent, -} from "../../../services/extensionRecommendations/common/workspaceExtensionsConfig.js"; -import { IExtensionManifestPropertiesService } from "../../../services/extensions/common/extensionManifestPropertiesService.js"; -import { - IExtensionService, - toExtension, - toExtensionDescription, -} from "../../../services/extensions/common/extensions.js"; -import { IHostService } from "../../../services/host/browser/host.js"; -import { ILocaleService } from "../../../services/localization/common/locale.js"; -import { showWindowLogActionId } from "../../../services/log/common/logConstants.js"; -import { IPreferencesService } from "../../../services/preferences/common/preferences.js"; -import { ITextFileService } from "../../../services/textfile/common/textfiles.js"; -import { - IWorkbenchColorTheme, - IWorkbenchFileIconTheme, - IWorkbenchProductIconTheme, - IWorkbenchTheme, - IWorkbenchThemeService, -} from "../../../services/themes/common/workbenchThemeService.js"; -import { - AutoUpdateConfigurationKey, - ExtensionEditorTab, - ExtensionRuntimeActionType, - ExtensionState, - IExtension, - IExtensionArg, - IExtensionContainer, - IExtensionsWorkbenchService, - INSTALL_ACTIONS_GROUP, - SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, - THEME_ACTIONS_GROUP, - TOGGLE_IGNORE_EXTENSION_ACTION_ID, - UPDATE_ACTIONS_GROUP, -} from "../common/extensions.js"; -import { ExtensionsConfigurationInitialContent } from "../common/extensionsFileTemplate.js"; -import { - errorIcon, - infoIcon, - manageExtensionIcon, - syncEnabledIcon, - syncIgnoredIcon, - trustIcon, - warningIcon, -} from "./extensionsIcons.js"; +import './media/extensionActions.css'; +import { localize, localize2 } from '../../../../nls.js'; +import { IAction, Action, Separator, SubmenuAction, IActionChangeEvent } from '../../../../base/common/actions.js'; +import { Delayer, Promises, Throttler } from '../../../../base/common/async.js'; +import * as DOM from '../../../../base/browser/dom.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; +import * as json from '../../../../base/common/json.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; +import { disposeIfDisposable } from '../../../../base/common/lifecycle.js'; +import { IExtension, ExtensionState, IExtensionsWorkbenchService, IExtensionContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP, UPDATE_ACTIONS_GROUP, ExtensionEditorTab, ExtensionRuntimeActionType, IExtensionArg, AutoUpdateConfigurationKey } from '../common/extensions.js'; +import { ExtensionsConfigurationInitialContent } from '../common/extensionsFileTemplate.js'; +import { IGalleryExtension, IExtensionGalleryService, ILocalExtension, InstallOptions, InstallOperation, ExtensionManagementErrorCode, IAllowedExtensionsService } from '../../../../platform/extensionManagement/common/extensionManagement.js'; +import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService } from '../../../services/extensionManagement/common/extensionManagement.js'; +import { ExtensionRecommendationReason, IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from '../../../services/extensionRecommendations/common/extensionRecommendations.js'; +import { areSameExtensions, getExtensionId } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js'; +import { ExtensionType, ExtensionIdentifier, IExtensionDescription, IExtensionManifest, isLanguagePackExtension, getWorkspaceSupportTypeMessage, TargetPlatform, isApplicationScopedExtension } from '../../../../platform/extensions/common/extensions.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { IFileService, IFileContent } from '../../../../platform/files/common/files.js'; +import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from '../../../../platform/workspace/common/workspace.js'; +import { IHostService } from '../../../services/host/browser/host.js'; +import { IExtensionService, toExtension, toExtensionDescription } from '../../../services/extensions/common/extensions.js'; +import { URI } from '../../../../base/common/uri.js'; +import { CommandsRegistry, ICommandService } from '../../../../platform/commands/common/commands.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from '../../../../platform/theme/common/themeService.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { buttonBackground, buttonForeground, buttonHoverBackground, registerColor, editorWarningForeground, editorInfoForeground, editorErrorForeground, buttonSeparator } from '../../../../platform/theme/common/colorRegistry.js'; +import { IJSONEditingService } from '../../../services/configuration/common/jsonEditing.js'; +import { ITextEditorSelection } from '../../../../platform/editor/common/editor.js'; +import { ITextModelService } from '../../../../editor/common/services/resolverService.js'; +import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { MenuId, IMenuService, MenuItemAction, SubmenuItemAction } from '../../../../platform/actions/common/actions.js'; +import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from '../../../browser/actions/workspaceCommands.js'; +import { INotificationService, IPromptChoice, Severity } from '../../../../platform/notification/common/notification.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; +import { IEditorService } from '../../../services/editor/common/editorService.js'; +import { IQuickPickItem, IQuickInputService, QuickPickItem } from '../../../../platform/quickinput/common/quickInput.js'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { alert } from '../../../../base/browser/ui/aria/aria.js'; +import { IWorkbenchThemeService, IWorkbenchTheme, IWorkbenchColorTheme, IWorkbenchFileIconTheme, IWorkbenchProductIconTheme } from '../../../services/themes/common/workbenchThemeService.js'; +import { ILabelService } from '../../../../platform/label/common/label.js'; +import { ITextFileService } from '../../../services/textfile/common/textfiles.js'; +import { IProductService } from '../../../../platform/product/common/productService.js'; +import { IDialogService, IPromptButton } from '../../../../platform/dialogs/common/dialogs.js'; +import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js'; +import { IActionViewItemOptions, ActionViewItem } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; +import { EXTENSIONS_CONFIG, IExtensionsConfigContent } from '../../../services/extensionRecommendations/common/workspaceExtensionsConfig.js'; +import { getErrorMessage, isCancellationError } from '../../../../base/common/errors.js'; +import { IUserDataSyncEnablementService } from '../../../../platform/userDataSync/common/userDataSync.js'; +import { IContextMenuProvider } from '../../../../base/browser/contextmenu.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; +import { errorIcon, infoIcon, manageExtensionIcon, syncEnabledIcon, syncIgnoredIcon, trustIcon, warningIcon } from './extensionsIcons.js'; +import { isIOS, isWeb, language } from '../../../../base/common/platform.js'; +import { IExtensionManifestPropertiesService } from '../../../services/extensions/common/extensionManifestPropertiesService.js'; +import { IWorkspaceTrustEnablementService, IWorkspaceTrustManagementService } from '../../../../platform/workspace/common/workspaceTrust.js'; +import { isVirtualWorkspace } from '../../../../platform/workspace/common/virtualWorkspace.js'; +import { escapeMarkdownSyntaxTokens, IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js'; +import { fromNow } from '../../../../base/common/date.js'; +import { IPreferencesService } from '../../../services/preferences/common/preferences.js'; +import { getLocale } from '../../../../platform/languagePacks/common/languagePacks.js'; +import { ILocaleService } from '../../../services/localization/common/locale.js'; +import { isString } from '../../../../base/common/types.js'; +import { showWindowLogActionId } from '../../../services/log/common/logConstants.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { Extensions, IExtensionFeaturesManagementService, IExtensionFeaturesRegistry } from '../../../services/extensionManagement/common/extensionFeatures.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { IUpdateService } from '../../../../platform/update/common/update.js'; +import { ActionWithDropdownActionViewItem, IActionWithDropdownActionViewItemOptions } from '../../../../base/browser/ui/dropdown/dropdownActionViewItem.js'; +import { IAuthenticationUsageService } from '../../../services/authentication/browser/authenticationUsageService.js'; export class PromptExtensionInstallFailureAction extends Action { + constructor( private readonly extension: IExtension, private readonly options: InstallOptions | undefined, @@ -219,21 +83,16 @@ export class PromptExtensionInstallFailureAction extends Action { private readonly error: Error, @IProductService private readonly productService: IProductService, @IOpenerService private readonly openerService: IOpenerService, - @INotificationService - private readonly notificationService: INotificationService, + @INotificationService private readonly notificationService: INotificationService, @IDialogService private readonly dialogService: IDialogService, @ICommandService private readonly commandService: ICommandService, @ILogService private readonly logService: ILogService, - @IExtensionManagementServerService - private readonly extensionManagementServerService: IExtensionManagementServerService, - @IInstantiationService - private readonly instantiationService: IInstantiationService, - @IExtensionGalleryService - private readonly galleryService: IExtensionGalleryService, - @IExtensionManifestPropertiesService - private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, + @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { - super("extension.promptExtensionInstallFailure"); + super('extension.promptExtensionInstallFailure'); } override async run(): Promise { @@ -244,219 +103,105 @@ export class PromptExtensionInstallFailureAction extends Action { this.logService.error(this.error); if (this.error.name === ExtensionManagementErrorCode.Unsupported) { - const productName = isWeb - ? localize( - "VS Code for Web", - "{0} for the Web", - this.productService.nameLong, - ) - : this.productService.nameLong; - const message = localize( - "cannot be installed", - "The '{0}' extension is not available in {1}. Click 'More Information' to learn more.", - this.extension.displayName || this.extension.identifier.id, - productName, - ); + const productName = isWeb ? localize('VS Code for Web', "{0} for the Web", this.productService.nameLong) : this.productService.nameLong; + const message = localize('cannot be installed', "The '{0}' extension is not available in {1}. Click 'More Information' to learn more.", this.extension.displayName || this.extension.identifier.id, productName); const { confirmed } = await this.dialogService.confirm({ type: Severity.Info, message, - primaryButton: localize( - { - key: "more information", - comment: ["&& denotes a mnemonic"], - }, - "&&More Information", - ), - cancelButton: localize("close", "Close"), + primaryButton: localize({ key: 'more information', comment: ['&& denotes a mnemonic'] }, "&&More Information"), + cancelButton: localize('close', "Close") }); if (confirmed) { - this.openerService.open( - isWeb - ? URI.parse( - "https://aka.ms/vscode-web-extensions-guide", - ) - : URI.parse("https://aka.ms/vscode-remote"), - ); + this.openerService.open(isWeb ? URI.parse('https://aka.ms/vscode-web-extensions-guide') : URI.parse('https://aka.ms/vscode-remote')); } return; } - if ( - ExtensionManagementErrorCode.ReleaseVersionNotFound === - this.error.name - ) { + if (ExtensionManagementErrorCode.ReleaseVersionNotFound === (this.error.name)) { await this.dialogService.prompt({ - type: "error", + type: 'error', message: getErrorMessage(this.error), - buttons: [ - { - label: localize( - "install prerelease", - "Install Pre-Release", - ), - run: () => { - const installAction = - this.instantiationService.createInstance( - InstallAction, - { installPreReleaseVersion: true }, - ); - installAction.extension = this.extension; - return installAction.run(); - }, - }, - ], - cancelButton: localize("cancel", "Cancel"), + buttons: [{ + label: localize('install prerelease', "Install Pre-Release"), + run: () => { + const installAction = this.instantiationService.createInstance(InstallAction, { installPreReleaseVersion: true }); + installAction.extension = this.extension; + return installAction.run(); + } + }], + cancelButton: localize('cancel', "Cancel") }); return; } - if ( - [ - ExtensionManagementErrorCode.Incompatible, - ExtensionManagementErrorCode.IncompatibleApi, - ExtensionManagementErrorCode.IncompatibleTargetPlatform, - ExtensionManagementErrorCode.Malicious, - ExtensionManagementErrorCode.Deprecated, - ].includes(this.error.name) - ) { + if ([ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatibleApi, ExtensionManagementErrorCode.IncompatibleTargetPlatform, ExtensionManagementErrorCode.Malicious, ExtensionManagementErrorCode.Deprecated].includes(this.error.name)) { await this.dialogService.info(getErrorMessage(this.error)); return; } - if ( - ExtensionManagementErrorCode.PackageNotSigned === - this.error.name - ) { + if (ExtensionManagementErrorCode.PackageNotSigned === (this.error.name)) { await this.dialogService.prompt({ - type: "error", - message: localize( - "not signed", - "'{0}' is an extension from an unknown source. Are you sure you want to install?", - this.extension.displayName, - ), + type: 'error', + message: localize('not signed', "'{0}' is an extension from an unknown source. Are you sure you want to install?", this.extension.displayName), detail: getErrorMessage(this.error), - buttons: [ - { - label: localize("install anyway", "Install Anyway"), - run: () => { - const installAction = - this.instantiationService.createInstance( - InstallAction, - { - ...this.options, - donotVerifySignature: true, - }, - ); - installAction.extension = this.extension; - return installAction.run(); - }, - }, - ], - cancelButton: true, + buttons: [{ + label: localize('install anyway', "Install Anyway"), + run: () => { + const installAction = this.instantiationService.createInstance(InstallAction, { ...this.options, donotVerifySignature: true, }); + installAction.extension = this.extension; + return installAction.run(); + } + }], + cancelButton: true }); return; } - if ( - ExtensionManagementErrorCode.SignatureVerificationFailed === - this.error.name || - ExtensionManagementErrorCode.SignatureVerificationInternal === - this.error.name - ) { + if (ExtensionManagementErrorCode.SignatureVerificationFailed === (this.error.name) || ExtensionManagementErrorCode.SignatureVerificationInternal === (this.error.name)) { await this.dialogService.prompt({ - type: "error", - message: localize( - "verification failed", - "Cannot install '{0}' extension because {1} cannot verify the extension signature", - this.extension.displayName, - this.productService.nameLong, - ), + type: 'error', + message: localize('verification failed', "Cannot install '{0}' extension because {1} cannot verify the extension signature", this.extension.displayName, this.productService.nameLong), detail: getErrorMessage(this.error), - buttons: [ - { - label: localize("learn more", "Learn More"), - run: () => - this.openerService.open( - "https://code.visualstudio.com/docs/editor/extension-marketplace#_the-extension-signature-cannot-be-verified-by-vs-code", - ), - }, - { - label: localize( - "install donot verify", - "Install Anyway (Don't Verify Signature)", - ), - run: () => { - const installAction = - this.instantiationService.createInstance( - InstallAction, - { - ...this.options, - donotVerifySignature: true, - }, - ); - installAction.extension = this.extension; - return installAction.run(); - }, - }, - ], - cancelButton: true, + buttons: [{ + label: localize('learn more', "Learn More"), + run: () => this.openerService.open('https://code.visualstudio.com/docs/editor/extension-marketplace#_the-extension-signature-cannot-be-verified-by-vs-code') + }, { + label: localize('install donot verify', "Install Anyway (Don't Verify Signature)"), + run: () => { + const installAction = this.instantiationService.createInstance(InstallAction, { ...this.options, donotVerifySignature: true, }); + installAction.extension = this.extension; + return installAction.run(); + } + }], + cancelButton: true }); return; } - const operationMessage = - this.installOperation === InstallOperation.Update - ? localize( - "update operation", - "Error while updating '{0}' extension.", - this.extension.displayName || - this.extension.identifier.id, - ) - : localize( - "install operation", - "Error while installing '{0}' extension.", - this.extension.displayName || - this.extension.identifier.id, - ); + const operationMessage = this.installOperation === InstallOperation.Update ? localize('update operation', "Error while updating '{0}' extension.", this.extension.displayName || this.extension.identifier.id) + : localize('install operation', "Error while installing '{0}' extension.", this.extension.displayName || this.extension.identifier.id); let additionalMessage; const promptChoices: IPromptChoice[] = []; const downloadUrl = await this.getDownloadUrl(); if (downloadUrl) { - additionalMessage = localize( - "check logs", - "Please check the [log]({0}) for more details.", - `command:${showWindowLogActionId}`, - ); + additionalMessage = localize('check logs', "Please check the [log]({0}) for more details.", `command:${showWindowLogActionId}`); promptChoices.push({ - label: localize("download", "Try Downloading Manually..."), - run: () => - this.openerService.open(downloadUrl).then(() => { - this.notificationService.prompt( - Severity.Info, - localize( - "install vsix", - "Once downloaded, please manually install the downloaded VSIX of '{0}'.", - this.extension.identifier.id, - ), - [ - { - label: localize( - "installVSIX", - "Install from VSIX...", - ), - run: () => - this.commandService.executeCommand( - SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, - ), - }, - ], - ); - }), + label: localize('download', "Try Downloading Manually..."), + run: () => this.openerService.open(downloadUrl).then(() => { + this.notificationService.prompt( + Severity.Info, + localize('install vsix', 'Once downloaded, please manually install the downloaded VSIX of \'{0}\'.', this.extension.identifier.id), + [{ + label: localize('installVSIX', "Install from VSIX..."), + run: () => this.commandService.executeCommand(SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID) + }] + ); + }) }); } - const message = `${operationMessage}${additionalMessage ? ` ${additionalMessage}` : ""}`; + const message = `${operationMessage}${additionalMessage ? ` ${additionalMessage}` : ''}`; this.notificationService.prompt(Severity.Error, message, promptChoices); } @@ -470,34 +215,15 @@ export class PromptExtensionInstallFailureAction extends Action { if (!this.productService.extensionsGallery) { return undefined; } - if ( - !this.extensionManagementServerService - .localExtensionManagementServer && - !this.extensionManagementServerService - .remoteExtensionManagementServer - ) { + if (!this.extensionManagementServerService.localExtensionManagementServer && !this.extensionManagementServerService.remoteExtensionManagementServer) { return undefined; } let targetPlatform = this.extension.gallery.properties.targetPlatform; - if ( - targetPlatform !== TargetPlatform.UNIVERSAL && - targetPlatform !== TargetPlatform.UNDEFINED && - this.extensionManagementServerService - .remoteExtensionManagementServer - ) { + if (targetPlatform !== TargetPlatform.UNIVERSAL && targetPlatform !== TargetPlatform.UNDEFINED && this.extensionManagementServerService.remoteExtensionManagementServer) { try { - const manifest = await this.galleryService.getManifest( - this.extension.gallery, - CancellationToken.None, - ); - if ( - manifest && - this.extensionManifestPropertiesService.prefersExecuteOnWorkspace( - manifest, - ) - ) { - targetPlatform = - await this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.getTargetPlatform(); + const manifest = await this.galleryService.getManifest(this.extension.gallery, CancellationToken.None); + if (manifest && this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(manifest)) { + targetPlatform = await this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.getTargetPlatform(); } } catch (error) { this.logService.error(error); @@ -507,10 +233,9 @@ export class PromptExtensionInstallFailureAction extends Action { if (targetPlatform === TargetPlatform.UNKNOWN) { return undefined; } - return URI.parse( - `${this.productService.extensionsGallery.serviceUrl}/publishers/${this.extension.publisher}/vsextensions/${this.extension.name}/${this.version}/vspackage${targetPlatform !== TargetPlatform.UNDEFINED ? `?targetPlatform=${targetPlatform}` : ""}`, - ); + return URI.parse(`${this.productService.extensionsGallery.serviceUrl}/publishers/${this.extension.publisher}/vsextensions/${this.extension.name}/${this.version}/vspackage${targetPlatform !== TargetPlatform.UNDEFINED ? `?targetPlatform=${targetPlatform}` : ''}`); } + } export interface IExtensionActionChangeEvent extends IActionChangeEvent { @@ -518,34 +243,23 @@ export interface IExtensionActionChangeEvent extends IActionChangeEvent { readonly menuActions?: IAction[]; } -export abstract class ExtensionAction - extends Action - implements IExtensionContainer -{ - protected override _onDidChange = this._register( - new Emitter(), - ); +export abstract class ExtensionAction extends Action implements IExtensionContainer { + + protected override _onDidChange = this._register(new Emitter()); override readonly onDidChange = this._onDidChange.event; - static readonly EXTENSION_ACTION_CLASS = "extension-action"; + static readonly EXTENSION_ACTION_CLASS = 'extension-action'; static readonly TEXT_ACTION_CLASS = `${ExtensionAction.EXTENSION_ACTION_CLASS} text`; static readonly LABEL_ACTION_CLASS = `${ExtensionAction.EXTENSION_ACTION_CLASS} label`; static readonly PROMINENT_LABEL_ACTION_CLASS = `${ExtensionAction.LABEL_ACTION_CLASS} prominent`; static readonly ICON_ACTION_CLASS = `${ExtensionAction.EXTENSION_ACTION_CLASS} icon`; private _extension: IExtension | null = null; - get extension(): IExtension | null { - return this._extension; - } - set extension(extension: IExtension | null) { - this._extension = extension; - this.update(); - } + get extension(): IExtension | null { return this._extension; } + set extension(extension: IExtension | null) { this._extension = extension; this.update(); } private _hidden: boolean = false; - get hidden(): boolean { - return this._hidden; - } + get hidden(): boolean { return this._hidden; } set hidden(hidden: boolean) { if (this._hidden !== hidden) { this._hidden = hidden; @@ -566,20 +280,19 @@ export abstract class ExtensionAction } export class ButtonWithDropDownExtensionAction extends ExtensionAction { + private primaryAction: IAction | undefined; readonly menuActionClassNames: string[] = []; private _menuActions: IAction[] = []; - get menuActions(): IAction[] { - return [...this._menuActions]; - } + get menuActions(): IAction[] { return [...this._menuActions]; } override get extension(): IExtension | null { return super.extension; } override set extension(extension: IExtension | null) { - this.extensionActions.forEach((a) => (a.extension = extension)); + this.extensionActions.forEach(a => a.extension = extension); super.extension = extension; } @@ -592,26 +305,20 @@ export class ButtonWithDropDownExtensionAction extends ExtensionAction { ) { clazz = `${clazz} action-dropdown`; super(id, undefined, clazz); - this.menuActionClassNames = clazz.split(" "); + this.menuActionClassNames = clazz.split(' '); this.hideOnDisabled = false; this.extensionActions = actionsGroups.flat(); this.update(); - this._register( - Event.any(...this.extensionActions.map((a) => a.onDidChange))(() => - this.update(true), - ), - ); - this.extensionActions.forEach((a) => this._register(a)); + this._register(Event.any(...this.extensionActions.map(a => a.onDidChange))(() => this.update(true))); + this.extensionActions.forEach(a => this._register(a)); } update(donotUpdateActions?: boolean): void { if (!donotUpdateActions) { - this.extensionActions.forEach((a) => a.update()); + this.extensionActions.forEach(a => a.update()); } - const actionsGroups = this.actionsGroups.map((actionsGroup) => - actionsGroup.filter((a) => !a.hidden), - ); + const actionsGroups = this.actionsGroups.map(actionsGroup => actionsGroup.filter(a => !a.hidden)); let actions: IAction[] = []; for (const visibleActions of actionsGroups) { @@ -619,9 +326,7 @@ export class ButtonWithDropDownExtensionAction extends ExtensionAction { actions = [...actions, ...visibleActions, new Separator()]; } } - actions = actions.length - ? actions.slice(0, actions.length - 1) - : actions; + actions = actions.length ? actions.slice(0, actions.length - 1) : actions; this.primaryAction = actions[0]; this._menuActions = actions.length > 1 ? actions : []; @@ -650,20 +355,18 @@ export class ButtonWithDropDownExtensionAction extends ExtensionAction { } export class ButtonWithDropdownExtensionActionViewItem extends ActionWithDropdownActionViewItem { + constructor( action: ButtonWithDropDownExtensionAction, - options: IActionViewItemOptions & - IActionWithDropdownActionViewItemOptions, - contextMenuProvider: IContextMenuProvider, + options: IActionViewItemOptions & IActionWithDropdownActionViewItemOptions, + contextMenuProvider: IContextMenuProvider ) { super(null, action, options, contextMenuProvider); - this._register( - action.onDidChange((e) => { - if (e.hidden !== undefined || e.menuActions !== undefined) { - this.updateClass(); - } - }), - ); + this._register(action.onDidChange(e => { + if (e.hidden !== undefined || e.menuActions !== undefined) { + this.updateClass(); + } + })); } override render(container: HTMLElement): void { @@ -674,23 +377,17 @@ export class ButtonWithDropdownExtensionActionViewItem extends ActionWithDropdow protected override updateClass(): void { super.updateClass(); if (this.element && this.dropdownMenuActionViewItem?.element) { - this.element.classList.toggle( - "hide", - (this._action).hidden, - ); - const isMenuEmpty = - (this._action).menuActions - .length === 0; - this.element.classList.toggle("empty", isMenuEmpty); - this.dropdownMenuActionViewItem.element.classList.toggle( - "hide", - isMenuEmpty, - ); + this.element.classList.toggle('hide', (this._action).hidden); + const isMenuEmpty = (this._action).menuActions.length === 0; + this.element.classList.toggle('empty', isMenuEmpty); + this.dropdownMenuActionViewItem.element.classList.toggle('hide', isMenuEmpty); } } + } export class InstallAction extends ExtensionAction { + static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent install`; private static readonly HIDE = `${this.CLASS} hide`; @@ -705,44 +402,23 @@ export class InstallAction extends ExtensionAction { constructor( options: InstallOptions, - @IExtensionsWorkbenchService - private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IInstantiationService - private readonly instantiationService: IInstantiationService, - @IExtensionService - private readonly runtimeExtensionService: IExtensionService, - @IWorkbenchThemeService - private readonly workbenchThemeService: IWorkbenchThemeService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IExtensionService private readonly runtimeExtensionService: IExtensionService, + @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, @ILabelService private readonly labelService: ILabelService, @IDialogService private readonly dialogService: IDialogService, - @IPreferencesService - private readonly preferencesService: IPreferencesService, + @IPreferencesService private readonly preferencesService: IPreferencesService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IWorkspaceContextService - private readonly contextService: IWorkspaceContextService, - @IAllowedExtensionsService - private readonly allowedExtensionsService: IAllowedExtensionsService, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService, ) { - super( - "extensions.install", - localize("install", "Install"), - InstallAction.CLASS, - false, - ); + super('extensions.install', localize('install', "Install"), InstallAction.CLASS, false); this.hideOnDisabled = false; this.options = { isMachineScoped: false, ...options }; this.update(); - this._register( - allowedExtensionsService.onDidChangeAllowedExtensions(() => - this.update(), - ), - ); - this._register( - this.labelService.onDidChangeFormatters( - () => this.updateLabel(), - this, - ), - ); + this._register(allowedExtensionsService.onDidChangeAllowedExtensions(() => this.update())); + this._register(this.labelService.onDidChangeFormatters(() => this.updateLabel(), this)); } update(): void { @@ -765,29 +441,15 @@ export class InstallAction extends ExtensionAction { if (this.extension.state !== ExtensionState.Uninstalled) { return; } - if ( - this.options.installPreReleaseVersion && - (!this.extension.hasPreReleaseVersion || - this.allowedExtensionsService.isAllowed({ - id: this.extension.identifier.id, - prerelease: true, - }) !== true) - ) { + if (this.options.installPreReleaseVersion && (!this.extension.hasPreReleaseVersion || this.allowedExtensionsService.isAllowed({ id: this.extension.identifier.id, publisherDisplayName: this.extension.publisherDisplayName, prerelease: true }) !== true)) { return; } - if ( - !this.options.installPreReleaseVersion && - !this.extension.hasReleaseVersion - ) { + if (!this.options.installPreReleaseVersion && !this.extension.hasReleaseVersion) { return; } this.hidden = false; this.class = InstallAction.CLASS; - if ( - (await this.extensionsWorkbenchService.canInstall( - this.extension, - )) === true - ) { + if (await this.extensionsWorkbenchService.canInstall(this.extension) === true) { this.enabled = true; this.updateLabel(); } @@ -801,27 +463,20 @@ export class InstallAction extends ExtensionAction { if (this.extension.gallery && !this.extension.gallery.isSigned) { const { result } = await this.dialogService.prompt({ type: Severity.Warning, - message: localize( - "not signed", - "'{0}' is an extension from an unknown source. Are you sure you want to install?", - this.extension.displayName, - ), - detail: localize( - "not signed detail", - "Extension is not signed.", - ), + message: localize('not signed', "'{0}' is an extension from an unknown source. Are you sure you want to install?", this.extension.displayName), + detail: localize('not signed detail', "Extension is not signed."), buttons: [ { - label: localize("install anyway", "Install Anyway"), + label: localize('install anyway', "Install Anyway"), run: () => { this.options.donotVerifySignature = true; return true; - }, - }, + } + } ], cancelButton: { - run: () => false, - }, + run: () => false + } }); if (!result) { return; @@ -829,127 +484,71 @@ export class InstallAction extends ExtensionAction { } if (this.extension.deprecationInfo) { - let detail: string | MarkdownString = localize( - "deprecated message", - "This extension is deprecated as it is no longer being maintained.", - ); + let detail: string | MarkdownString = localize('deprecated message', "This extension is deprecated as it is no longer being maintained."); enum DeprecationChoice { InstallAnyway = 0, ShowAlternateExtension = 1, ConfigureSettings = 2, - Cancel = 3, + Cancel = 3 } const buttons: IPromptButton[] = [ { - label: localize("install anyway", "Install Anyway"), - run: () => DeprecationChoice.InstallAnyway, - }, + label: localize('install anyway', "Install Anyway"), + run: () => DeprecationChoice.InstallAnyway + } ]; if (this.extension.deprecationInfo.extension) { - detail = localize( - "deprecated with alternate extension message", - "This extension is deprecated. Use the {0} extension instead.", - this.extension.deprecationInfo.extension.displayName, - ); - - const alternateExtension = - this.extension.deprecationInfo.extension; + detail = localize('deprecated with alternate extension message', "This extension is deprecated. Use the {0} extension instead.", this.extension.deprecationInfo.extension.displayName); + + const alternateExtension = this.extension.deprecationInfo.extension; buttons.push({ - label: localize( - { - key: "Show alternate extension", - comment: ["&& denotes a mnemonic"], - }, - "&&Open {0}", - this.extension.deprecationInfo.extension.displayName, - ), + label: localize({ key: 'Show alternate extension', comment: ['&& denotes a mnemonic'] }, "&&Open {0}", this.extension.deprecationInfo.extension.displayName), run: async () => { - const [extension] = - await this.extensionsWorkbenchService.getExtensions( - [ - { - id: alternateExtension.id, - preRelease: - alternateExtension.preRelease, - }, - ], - CancellationToken.None, - ); + const [extension] = await this.extensionsWorkbenchService.getExtensions([{ id: alternateExtension.id, preRelease: alternateExtension.preRelease }], CancellationToken.None); await this.extensionsWorkbenchService.open(extension); return DeprecationChoice.ShowAlternateExtension; - }, + } }); } else if (this.extension.deprecationInfo.settings) { - detail = localize( - "deprecated with alternate settings message", - "This extension is deprecated as this functionality is now built-in to VS Code.", - ); + detail = localize('deprecated with alternate settings message', "This extension is deprecated as this functionality is now built-in to VS Code."); const settings = this.extension.deprecationInfo.settings; buttons.push({ - label: localize( - { - key: "configure in settings", - comment: ["&& denotes a mnemonic"], - }, - "&&Configure Settings", - ), + label: localize({ key: 'configure in settings', comment: ['&& denotes a mnemonic'] }, "&&Configure Settings"), run: async () => { - await this.preferencesService.openSettings({ - query: settings - .map((setting) => `@id:${setting}`) - .join(" "), - }); + await this.preferencesService.openSettings({ query: settings.map(setting => `@id:${setting}`).join(' ') }); return DeprecationChoice.ConfigureSettings; - }, + } }); } else if (this.extension.deprecationInfo.additionalInfo) { - detail = new MarkdownString( - `${detail} ${this.extension.deprecationInfo.additionalInfo}`, - ); + detail = new MarkdownString(`${detail} ${this.extension.deprecationInfo.additionalInfo}`); } const { result } = await this.dialogService.prompt({ type: Severity.Warning, - message: localize( - "install confirmation", - "Are you sure you want to install '{0}'?", - this.extension.displayName, - ), + message: localize('install confirmation', "Are you sure you want to install '{0}'?", this.extension.displayName), detail: isString(detail) ? detail : undefined, - custom: isString(detail) - ? undefined - : { - markdownDetails: [ - { - markdown: detail, - }, - ], - }, + custom: isString(detail) ? undefined : { + markdownDetails: [{ + markdown: detail + }] + }, buttons, cancelButton: { - run: () => DeprecationChoice.Cancel, - }, + run: () => DeprecationChoice.Cancel + } }); if (result !== DeprecationChoice.InstallAnyway) { return; } } - this.extensionsWorkbenchService.open(this.extension, { - showPreReleaseVersion: this.options.installPreReleaseVersion, - }); + this.extensionsWorkbenchService.open(this.extension, { showPreReleaseVersion: this.options.installPreReleaseVersion }); - alert( - localize( - "installExtensionStart", - "Installing extension {0} started. An editor is now open with more details on this extension", - this.extension.displayName, - ), - ); + alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName)); /* __GDPR__ "extensions:action:install" : { @@ -960,137 +559,67 @@ export class InstallAction extends ExtensionAction { ] } */ - this.telemetryService.publicLog("extensions:action:install", { - ...this.extension.telemetryData, - actionId: this.id, - }); + this.telemetryService.publicLog('extensions:action:install', { ...this.extension.telemetryData, actionId: this.id }); const extension = await this.install(this.extension); if (extension?.local) { - alert( - localize( - "installExtensionComplete", - "Installing extension {0} is completed.", - this.extension.displayName, - ), - ); - const runningExtension = await this.getRunningExtension( - extension.local, - ); - if ( - runningExtension && - !( - runningExtension.activationEvents && - runningExtension.activationEvents.some((activationEent) => - activationEent.startsWith("onLanguage"), - ) - ) - ) { + alert(localize('installExtensionComplete', "Installing extension {0} is completed.", this.extension.displayName)); + const runningExtension = await this.getRunningExtension(extension.local); + if (runningExtension && !(runningExtension.activationEvents && runningExtension.activationEvents.some(activationEent => activationEent.startsWith('onLanguage')))) { const action = await this.getThemeAction(extension); if (action) { action.extension = extension; try { - return action.run({ - showCurrentTheme: true, - ignoreFocusLost: true, - }); + return action.run({ showCurrentTheme: true, ignoreFocusLost: true }); } finally { action.dispose(); } } } } + } - private async getThemeAction( - extension: IExtension, - ): Promise { + private async getThemeAction(extension: IExtension): Promise { const colorThemes = await this.workbenchThemeService.getColorThemes(); - if ( - colorThemes.some((theme) => isThemeFromExtension(theme, extension)) - ) { - return this.instantiationService.createInstance( - SetColorThemeAction, - ); + if (colorThemes.some(theme => isThemeFromExtension(theme, extension))) { + return this.instantiationService.createInstance(SetColorThemeAction); } - const fileIconThemes = - await this.workbenchThemeService.getFileIconThemes(); - if ( - fileIconThemes.some((theme) => - isThemeFromExtension(theme, extension), - ) - ) { - return this.instantiationService.createInstance( - SetFileIconThemeAction, - ); + const fileIconThemes = await this.workbenchThemeService.getFileIconThemes(); + if (fileIconThemes.some(theme => isThemeFromExtension(theme, extension))) { + return this.instantiationService.createInstance(SetFileIconThemeAction); } - const productIconThemes = - await this.workbenchThemeService.getProductIconThemes(); - if ( - productIconThemes.some((theme) => - isThemeFromExtension(theme, extension), - ) - ) { - return this.instantiationService.createInstance( - SetProductIconThemeAction, - ); + const productIconThemes = await this.workbenchThemeService.getProductIconThemes(); + if (productIconThemes.some(theme => isThemeFromExtension(theme, extension))) { + return this.instantiationService.createInstance(SetProductIconThemeAction); } return undefined; } - private async install( - extension: IExtension, - ): Promise { + private async install(extension: IExtension): Promise { try { - return await this.extensionsWorkbenchService.install( - extension, - this.options, - ); + return await this.extensionsWorkbenchService.install(extension, this.options); } catch (error) { - await this.instantiationService - .createInstance( - PromptExtensionInstallFailureAction, - extension, - this.options, - extension.latestVersion, - InstallOperation.Install, - error, - ) - .run(); + await this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, this.options, extension.latestVersion, InstallOperation.Install, error).run(); return undefined; } } - private async getRunningExtension( - extension: ILocalExtension, - ): Promise { - const runningExtension = - await this.runtimeExtensionService.getExtension( - extension.identifier.id, - ); + private async getRunningExtension(extension: ILocalExtension): Promise { + const runningExtension = await this.runtimeExtensionService.getExtension(extension.identifier.id); if (runningExtension) { return runningExtension; } - if ( - this.runtimeExtensionService.canAddExtension( - toExtensionDescription(extension), - ) - ) { + if (this.runtimeExtensionService.canAddExtension(toExtensionDescription(extension))) { return new Promise((c, e) => { - const disposable = - this.runtimeExtensionService.onDidChangeExtensions( - async () => { - const runningExtension = - await this.runtimeExtensionService.getExtension( - extension.identifier.id, - ); - if (runningExtension) { - disposable.dispose(); - c(runningExtension); - } - }, - ); + const disposable = this.runtimeExtensionService.onDidChangeExtensions(async () => { + const runningExtension = await this.runtimeExtensionService.getExtension(extension.identifier.id); + if (runningExtension) { + disposable.dispose(); + c(runningExtension); + } + }); }); } return null; @@ -1101,99 +630,65 @@ export class InstallAction extends ExtensionAction { } getLabel(primary?: boolean): string { - if ( - this.extension?.isWorkspaceScoped && - this.extension.resourceExtension && - this.contextService.isInsideWorkspace( - this.extension.resourceExtension.location, - ) - ) { - return localize( - "install workspace version", - "Install Workspace Extension", - ); + if (this.extension?.isWorkspaceScoped && this.extension.resourceExtension && this.contextService.isInsideWorkspace(this.extension.resourceExtension.location)) { + return localize('install workspace version', "Install Workspace Extension"); } /* install pre-release version */ - if ( - this.options.installPreReleaseVersion && - this.extension?.hasPreReleaseVersion - ) { - return primary - ? localize("install pre-release", "Install Pre-Release") - : localize( - "install pre-release version", - "Install Pre-Release Version", - ); + if (this.options.installPreReleaseVersion && this.extension?.hasPreReleaseVersion) { + return primary ? localize('install pre-release', "Install Pre-Release") : localize('install pre-release version', "Install Pre-Release Version"); } /* install released version that has a pre release version */ if (this.extension?.hasPreReleaseVersion) { - return primary - ? localize("install", "Install") - : localize( - "install release version", - "Install Release Version", - ); + return primary ? localize('install', "Install") : localize('install release version', "Install Release Version"); } - return localize("install", "Install"); + return localize('install', "Install"); } + } export class InstallDropdownAction extends ButtonWithDropDownExtensionAction { + set manifest(manifest: IExtensionManifest | null) { - this.extensionActions.forEach( - (a) => ((a).manifest = manifest), - ); + this.extensionActions.forEach(a => (a).manifest = manifest); this.update(); } constructor( @IInstantiationService instantiationService: IInstantiationService, - @IExtensionsWorkbenchService - extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, ) { super(`extensions.installActions`, InstallAction.CLASS, [ [ - instantiationService.createInstance(InstallAction, { - installPreReleaseVersion: - extensionsWorkbenchService.preferPreReleases, - }), - instantiationService.createInstance(InstallAction, { - installPreReleaseVersion: - !extensionsWorkbenchService.preferPreReleases, - }), - ], + instantiationService.createInstance(InstallAction, { installPreReleaseVersion: extensionsWorkbenchService.preferPreReleases }), + instantiationService.createInstance(InstallAction, { installPreReleaseVersion: !extensionsWorkbenchService.preferPreReleases }), + ] ]); } protected override getLabel(action: InstallAction): string { return action.getLabel(true); } + } export class InstallingLabelAction extends ExtensionAction { - private static readonly LABEL = localize("installing", "Installing"); + + private static readonly LABEL = localize('installing', "Installing"); private static readonly CLASS = `${ExtensionAction.LABEL_ACTION_CLASS} install installing`; constructor() { - super( - "extension.installing", - InstallingLabelAction.LABEL, - InstallingLabelAction.CLASS, - false, - ); + super('extension.installing', InstallingLabelAction.LABEL, InstallingLabelAction.CLASS, false); } update(): void { - this.class = `${InstallingLabelAction.CLASS}${this.extension && this.extension.state === ExtensionState.Installing ? "" : " hide"}`; + this.class = `${InstallingLabelAction.CLASS}${this.extension && this.extension.state === ExtensionState.Installing ? '' : ' hide'}`; } } export abstract class InstallInOtherServerAction extends ExtensionAction { - protected static readonly INSTALL_LABEL = localize("install", "Install"); - protected static readonly INSTALLING_LABEL = localize( - "installing", - "Installing", - ); + + protected static readonly INSTALL_LABEL = localize('install', "Install"); + protected static readonly INSTALLING_LABEL = localize('installing', "Installing"); private static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} prominent install-other-server`; private static readonly InstallingClass = `${ExtensionAction.LABEL_ACTION_CLASS} install-other-server installing`; @@ -1204,19 +699,11 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { id: string, private readonly server: IExtensionManagementServer | null, private readonly canInstallAnyWhere: boolean, - @IExtensionsWorkbenchService - private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionManagementServerService - protected readonly extensionManagementServerService: IExtensionManagementServerService, - @IExtensionManifestPropertiesService - private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService, + @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { - super( - id, - InstallInOtherServerAction.INSTALL_LABEL, - InstallInOtherServerAction.Class, - false, - ); + super(id, InstallInOtherServerAction.INSTALL_LABEL, InstallInOtherServerAction.Class, false); this.update(); } @@ -1225,21 +712,10 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { this.class = InstallInOtherServerAction.Class; if (this.canInstall()) { - const extensionInOtherServer = - this.extensionsWorkbenchService.installed.filter( - (e) => - areSameExtensions( - e.identifier, - this.extension!.identifier, - ) && e.server === this.server, - )[0]; + const extensionInOtherServer = this.extensionsWorkbenchService.installed.filter(e => areSameExtensions(e.identifier, this.extension!.identifier) && e.server === this.server)[0]; if (extensionInOtherServer) { // Getting installed in other server - if ( - extensionInOtherServer.state === - ExtensionState.Installing && - !extensionInOtherServer.local - ) { + if (extensionInOtherServer.state === ExtensionState.Installing && !extensionInOtherServer.local) { this.enabled = true; this.label = InstallInOtherServerAction.INSTALLING_LABEL; this.class = InstallInOtherServerAction.InstallingClass; @@ -1255,17 +731,12 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { protected canInstall(): boolean { // Disable if extension is not installed or not an user extension if ( - !this.extension || - !this.server || - !this.extension.local || - this.extension.state !== ExtensionState.Installed || - this.extension.type !== ExtensionType.User || - this.extension.enablementState === - EnablementState.DisabledByEnvironment || - this.extension.enablementState === - EnablementState.DisabledByTrustRequirement || - this.extension.enablementState === - EnablementState.DisabledByVirtualWorkspace + !this.extension + || !this.server + || !this.extension.local + || this.extension.state !== ExtensionState.Installed + || this.extension.type !== ExtensionType.User + || this.extension.enablementState === EnablementState.DisabledByEnvironment || this.extension.enablementState === EnablementState.DisabledByTrustRequirement || this.extension.enablementState === EnablementState.DisabledByVirtualWorkspace ) { return false; } @@ -1275,63 +746,28 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { } // Prefers to run on UI - if ( - this.server === - this.extensionManagementServerService - .localExtensionManagementServer && - this.extensionManifestPropertiesService.prefersExecuteOnUI( - this.extension.local.manifest, - ) - ) { + if (this.server === this.extensionManagementServerService.localExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnUI(this.extension.local.manifest)) { return true; } // Prefers to run on Workspace - if ( - this.server === - this.extensionManagementServerService - .remoteExtensionManagementServer && - this.extensionManifestPropertiesService.prefersExecuteOnWorkspace( - this.extension.local.manifest, - ) - ) { + if (this.server === this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(this.extension.local.manifest)) { return true; } // Prefers to run on Web - if ( - this.server === - this.extensionManagementServerService - .webExtensionManagementServer && - this.extensionManifestPropertiesService.prefersExecuteOnWeb( - this.extension.local.manifest, - ) - ) { + if (this.server === this.extensionManagementServerService.webExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnWeb(this.extension.local.manifest)) { return true; } if (this.canInstallAnyWhere) { // Can run on UI - if ( - this.server === - this.extensionManagementServerService - .localExtensionManagementServer && - this.extensionManifestPropertiesService.canExecuteOnUI( - this.extension.local.manifest, - ) - ) { + if (this.server === this.extensionManagementServerService.localExtensionManagementServer && this.extensionManifestPropertiesService.canExecuteOnUI(this.extension.local.manifest)) { return true; } // Can run on Workspace - if ( - this.server === - this.extensionManagementServerService - .remoteExtensionManagementServer && - this.extensionManifestPropertiesService.canExecuteOnWorkspace( - this.extension.local.manifest, - ) - ) { + if (this.server === this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManifestPropertiesService.canExecuteOnWorkspace(this.extension.local.manifest)) { return true; } } @@ -1350,129 +786,77 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { return; } this.extensionsWorkbenchService.open(this.extension); - alert( - localize( - "installExtensionStart", - "Installing extension {0} started. An editor is now open with more details on this extension", - this.extension.displayName, - ), - ); - return this.extensionsWorkbenchService.installInServer( - this.extension, - this.server, - ); + alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName)); + return this.extensionsWorkbenchService.installInServer(this.extension, this.server); } protected abstract getInstallLabel(): string; } export class RemoteInstallAction extends InstallInOtherServerAction { + constructor( canInstallAnyWhere: boolean, - @IExtensionsWorkbenchService - extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionManagementServerService - extensionManagementServerService: IExtensionManagementServerService, - @IExtensionManifestPropertiesService - extensionManifestPropertiesService: IExtensionManifestPropertiesService, + @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService, + @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { - super( - `extensions.remoteinstall`, - extensionManagementServerService.remoteExtensionManagementServer, - canInstallAnyWhere, - extensionsWorkbenchService, - extensionManagementServerService, - extensionManifestPropertiesService, - ); + super(`extensions.remoteinstall`, extensionManagementServerService.remoteExtensionManagementServer, canInstallAnyWhere, extensionsWorkbenchService, extensionManagementServerService, extensionManifestPropertiesService); } protected getInstallLabel(): string { - return this.extensionManagementServerService - .remoteExtensionManagementServer - ? localize( - { - key: "install in remote", - comment: [ - "This is the name of the action to install an extension in remote server. Placeholder is for the name of remote server.", - ], - }, - "Install in {0}", - this.extensionManagementServerService - .remoteExtensionManagementServer.label, - ) + return this.extensionManagementServerService.remoteExtensionManagementServer + ? localize({ key: 'install in remote', comment: ['This is the name of the action to install an extension in remote server. Placeholder is for the name of remote server.'] }, "Install in {0}", this.extensionManagementServerService.remoteExtensionManagementServer.label) : InstallInOtherServerAction.INSTALL_LABEL; } + } export class LocalInstallAction extends InstallInOtherServerAction { + constructor( - @IExtensionsWorkbenchService - extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionManagementServerService - extensionManagementServerService: IExtensionManagementServerService, - @IExtensionManifestPropertiesService - extensionManifestPropertiesService: IExtensionManifestPropertiesService, + @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService, + @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { - super( - `extensions.localinstall`, - extensionManagementServerService.localExtensionManagementServer, - false, - extensionsWorkbenchService, - extensionManagementServerService, - extensionManifestPropertiesService, - ); + super(`extensions.localinstall`, extensionManagementServerService.localExtensionManagementServer, false, extensionsWorkbenchService, extensionManagementServerService, extensionManifestPropertiesService); } protected getInstallLabel(): string { - return localize("install locally", "Install Locally"); + return localize('install locally', "Install Locally"); } + } export class WebInstallAction extends InstallInOtherServerAction { + constructor( - @IExtensionsWorkbenchService - extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionManagementServerService - extensionManagementServerService: IExtensionManagementServerService, - @IExtensionManifestPropertiesService - extensionManifestPropertiesService: IExtensionManifestPropertiesService, + @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService, + @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { - super( - `extensions.webInstall`, - extensionManagementServerService.webExtensionManagementServer, - false, - extensionsWorkbenchService, - extensionManagementServerService, - extensionManifestPropertiesService, - ); + super(`extensions.webInstall`, extensionManagementServerService.webExtensionManagementServer, false, extensionsWorkbenchService, extensionManagementServerService, extensionManifestPropertiesService); } protected getInstallLabel(): string { - return localize("install browser", "Install in Browser"); + return localize('install browser', "Install in Browser"); } + } export class UninstallAction extends ExtensionAction { - static readonly UninstallLabel = localize("uninstallAction", "Uninstall"); - private static readonly UninstallingLabel = localize( - "Uninstalling", - "Uninstalling", - ); + + static readonly UninstallLabel = localize('uninstallAction', "Uninstall"); + private static readonly UninstallingLabel = localize('Uninstalling', "Uninstalling"); static readonly UninstallClass = `${ExtensionAction.LABEL_ACTION_CLASS} uninstall`; private static readonly UnInstallingClass = `${ExtensionAction.LABEL_ACTION_CLASS} uninstall uninstalling`; constructor( - @IExtensionsWorkbenchService - private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IDialogService private readonly dialogService: IDialogService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IDialogService private readonly dialogService: IDialogService ) { - super( - "extensions.uninstall", - UninstallAction.UninstallLabel, - UninstallAction.UninstallClass, - false, - ); + super('extensions.uninstall', UninstallAction.UninstallLabel, UninstallAction.UninstallClass, false); this.update(); } @@ -1512,23 +896,11 @@ export class UninstallAction extends ExtensionAction { if (!this.extension) { return; } - alert( - localize( - "uninstallExtensionStart", - "Uninstalling extension {0} started.", - this.extension.displayName, - ), - ); + alert(localize('uninstallExtensionStart', "Uninstalling extension {0} started.", this.extension.displayName)); try { await this.extensionsWorkbenchService.uninstall(this.extension); - alert( - localize( - "uninstallExtensionComplete", - "Please reload Visual Studio Code to complete the uninstallation of the extension {0}.", - this.extension.displayName, - ), - ); + alert(localize('uninstallExtensionComplete', "Please reload Visual Studio Code to complete the uninstallation of the extension {0}.", this.extension.displayName)); } catch (error) { if (!isCancellationError(error)) { this.dialogService.error(getErrorMessage(error)); @@ -1538,6 +910,7 @@ export class UninstallAction extends ExtensionAction { } export class UpdateAction extends ExtensionAction { + private static readonly EnabledClass = `${this.LABEL_ACTION_CLASS} prominent update`; private static readonly DisabledClass = `${this.EnabledClass} disabled`; @@ -1545,32 +918,19 @@ export class UpdateAction extends ExtensionAction { constructor( private readonly verbose: boolean, - @IExtensionsWorkbenchService - private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IDialogService private readonly dialogService: IDialogService, @IOpenerService private readonly openerService: IOpenerService, - @IInstantiationService - private readonly instantiationService: IInstantiationService, + @IInstantiationService private readonly instantiationService: IInstantiationService, ) { - super( - `extensions.update`, - localize("update", "Update"), - UpdateAction.DisabledClass, - false, - ); + super(`extensions.update`, localize('update', "Update"), UpdateAction.DisabledClass, false); this.update(); } update(): void { this.updateThrottler.queue(() => this.computeAndUpdateEnablement()); if (this.extension) { - this.label = this.verbose - ? localize( - "update to", - "Update to v{0}", - this.extension.latestVersion, - ) - : localize("update", "Update"); + this.label = this.verbose ? localize('update to', "Update to v{0}", this.extension.latestVersion) : localize('update', "Update"); } } @@ -1586,16 +946,11 @@ export class UpdateAction extends ExtensionAction { return; } - const canInstall = await this.extensionsWorkbenchService.canInstall( - this.extension, - ); + const canInstall = await this.extensionsWorkbenchService.canInstall(this.extension); const isInstalled = this.extension.state === ExtensionState.Installed; - this.enabled = - canInstall === true && isInstalled && this.extension.outdated; - this.class = this.enabled - ? UpdateAction.EnabledClass - : UpdateAction.DisabledClass; + this.enabled = canInstall === true && isInstalled && this.extension.outdated; + this.class = this.enabled ? UpdateAction.EnabledClass : UpdateAction.DisabledClass; } override async run(): Promise { @@ -1603,49 +958,29 @@ export class UpdateAction extends ExtensionAction { return; } - const consent = - await this.extensionsWorkbenchService.shouldRequireConsentToUpdate( - this.extension, - ); + const consent = await this.extensionsWorkbenchService.shouldRequireConsentToUpdate(this.extension); if (consent) { - const { result } = await this.dialogService.prompt< - "update" | "review" | "cancel" - >({ - type: "warning", - title: localize( - "updateExtensionConsentTitle", - "Update {0} Extension", - this.extension.displayName, - ), - message: localize( - "updateExtensionConsent", - "{0}\n\nWould you like to update the extension?", - consent, - ), - buttons: [ - { - label: localize("update", "Update"), - run: () => "update", - }, - { - label: localize("review", "Review"), - run: () => "review", - }, - { - label: localize("cancel", "Cancel"), - run: () => "cancel", - }, - ], + const { result } = await this.dialogService.prompt<'update' | 'review' | 'cancel'>({ + type: 'warning', + title: localize('updateExtensionConsentTitle', "Update {0} Extension", this.extension.displayName), + message: localize('updateExtensionConsent', "{0}\n\nWould you like to update the extension?", consent), + buttons: [{ + label: localize('update', "Update"), + run: () => 'update' + }, { + label: localize('review', "Review"), + run: () => 'review' + }, { + label: localize('cancel', "Cancel"), + run: () => 'cancel' + }] }); - if (result === "cancel") { + if (result === 'cancel') { return; } - if (result === "review") { + if (result === 'review') { if (this.extension.hasChangelog()) { - return this.extensionsWorkbenchService.open( - this.extension, - { tab: ExtensionEditorTab.Changelog }, - ); + return this.extensionsWorkbenchService.open(this.extension, { tab: ExtensionEditorTab.Changelog }); } if (this.extension.repository) { return this.openerService.open(this.extension.repository); @@ -1654,80 +989,42 @@ export class UpdateAction extends ExtensionAction { } } - alert( - localize( - "updateExtensionStart", - "Updating extension {0} to version {1} started.", - this.extension.displayName, - this.extension.latestVersion, - ), - ); + alert(localize('updateExtensionStart', "Updating extension {0} to version {1} started.", this.extension.displayName, this.extension.latestVersion)); return this.install(this.extension); } private async install(extension: IExtension): Promise { - const options = extension.local?.preRelease - ? { installPreReleaseVersion: true } - : undefined; + const options = extension.local?.preRelease ? { installPreReleaseVersion: true } : undefined; try { await this.extensionsWorkbenchService.install(extension, options); - alert( - localize( - "updateExtensionComplete", - "Updating extension {0} to version {1} completed.", - extension.displayName, - extension.latestVersion, - ), - ); + alert(localize('updateExtensionComplete', "Updating extension {0} to version {1} completed.", extension.displayName, extension.latestVersion)); } catch (err) { - this.instantiationService - .createInstance( - PromptExtensionInstallFailureAction, - extension, - options, - extension.latestVersion, - InstallOperation.Update, - err, - ) - .run(); + this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, options, extension.latestVersion, InstallOperation.Update, err).run(); } } } export class ToggleAutoUpdateForExtensionAction extends ExtensionAction { - static readonly ID = - "workbench.extensions.action.toggleAutoUpdateForExtension"; - static readonly LABEL = localize2("enableAutoUpdateLabel", "Auto Update"); + + static readonly ID = 'workbench.extensions.action.toggleAutoUpdateForExtension'; + static readonly LABEL = localize2('enableAutoUpdateLabel', "Auto Update"); private static readonly EnabledClass = `${ExtensionAction.EXTENSION_ACTION_CLASS} auto-update`; private static readonly DisabledClass = `${this.EnabledClass} hide`; constructor( - @IExtensionsWorkbenchService - private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IWorkbenchExtensionEnablementService - private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, - @IAllowedExtensionsService - private readonly allowedExtensionsService: IAllowedExtensionsService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, + @IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService, @IConfigurationService configurationService: IConfigurationService, ) { - super( - ToggleAutoUpdateForExtensionAction.ID, - ToggleAutoUpdateForExtensionAction.LABEL.value, - ToggleAutoUpdateForExtensionAction.DisabledClass, - ); - this._register( - configurationService.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration(AutoUpdateConfigurationKey)) { - this.update(); - } - }), - ); - this._register( - allowedExtensionsService.onDidChangeAllowedExtensions((e) => - this.update(), - ), - ); + super(ToggleAutoUpdateForExtensionAction.ID, ToggleAutoUpdateForExtensionAction.LABEL.value, ToggleAutoUpdateForExtensionAction.DisabledClass); + this._register(configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(AutoUpdateConfigurationKey)) { + this.update(); + } + })); + this._register(allowedExtensionsService.onDidChangeAllowedExtensions(e => this.update())); this.update(); } @@ -1743,27 +1040,17 @@ export class ToggleAutoUpdateForExtensionAction extends ExtensionAction { if (this.extension.deprecationInfo?.disallowInstall) { return; } + const extension = this.extension.local ?? this.extension.gallery; - if ( - extension && - this.allowedExtensionsService.isAllowed(extension) !== true - ) { + if (extension && this.allowedExtensionsService.isAllowed(extension) !== true) { return; } - if ( - this.extensionsWorkbenchService.getAutoUpdateValue() === - "onlyEnabledExtensions" && - !this.extensionEnablementService.isEnabledEnablementState( - this.extension.enablementState, - ) - ) { + if (this.extensionsWorkbenchService.getAutoUpdateValue() === 'onlyEnabledExtensions' && !this.extensionEnablementService.isEnabledEnablementState(this.extension.enablementState)) { return; } this.enabled = true; this.class = ToggleAutoUpdateForExtensionAction.EnabledClass; - this.checked = this.extensionsWorkbenchService.isAutoUpdateEnabledFor( - this.extension, - ); + this.checked = this.extensionsWorkbenchService.isAutoUpdateEnabledFor(this.extension); } override async run(): Promise { @@ -1771,109 +1058,55 @@ export class ToggleAutoUpdateForExtensionAction extends ExtensionAction { return; } - const enableAutoUpdate = - !this.extensionsWorkbenchService.isAutoUpdateEnabledFor( - this.extension, - ); - await this.extensionsWorkbenchService.updateAutoUpdateEnablementFor( - this.extension, - enableAutoUpdate, - ); + const enableAutoUpdate = !this.extensionsWorkbenchService.isAutoUpdateEnabledFor(this.extension); + await this.extensionsWorkbenchService.updateAutoUpdateEnablementFor(this.extension, enableAutoUpdate); if (enableAutoUpdate) { - alert( - localize( - "enableAutoUpdate", - "Enabled auto updates for", - this.extension.displayName, - ), - ); + alert(localize('enableAutoUpdate', "Enabled auto updates for", this.extension.displayName)); } else { - alert( - localize( - "disableAutoUpdate", - "Disabled auto updates for", - this.extension.displayName, - ), - ); + alert(localize('disableAutoUpdate', "Disabled auto updates for", this.extension.displayName)); } } } export class ToggleAutoUpdatesForPublisherAction extends ExtensionAction { - static readonly ID = - "workbench.extensions.action.toggleAutoUpdatesForPublisher"; - static readonly LABEL = localize( - "toggleAutoUpdatesForPublisherLabel", - "Auto Update All (From Publisher)", - ); + + static readonly ID = 'workbench.extensions.action.toggleAutoUpdatesForPublisher'; + static readonly LABEL = localize('toggleAutoUpdatesForPublisherLabel', "Auto Update All (From Publisher)"); constructor( - @IExtensionsWorkbenchService - private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService ) { - super( - ToggleAutoUpdatesForPublisherAction.ID, - ToggleAutoUpdatesForPublisherAction.LABEL, - ); + super(ToggleAutoUpdatesForPublisherAction.ID, ToggleAutoUpdatesForPublisherAction.LABEL); } - override update() {} + override update() { } override async run(): Promise { if (!this.extension) { return; } - alert( - localize( - "ignoreExtensionUpdatePublisher", - "Ignoring updates published by {0}.", - this.extension.publisherDisplayName, - ), - ); - const enableAutoUpdate = - !this.extensionsWorkbenchService.isAutoUpdateEnabledFor( - this.extension.publisher, - ); - await this.extensionsWorkbenchService.updateAutoUpdateEnablementFor( - this.extension.publisher, - enableAutoUpdate, - ); + alert(localize('ignoreExtensionUpdatePublisher', "Ignoring updates published by {0}.", this.extension.publisherDisplayName)); + const enableAutoUpdate = !this.extensionsWorkbenchService.isAutoUpdateEnabledFor(this.extension.publisher); + await this.extensionsWorkbenchService.updateAutoUpdateEnablementFor(this.extension.publisher, enableAutoUpdate); if (enableAutoUpdate) { - alert( - localize( - "enableAutoUpdate", - "Enabled auto updates for", - this.extension.displayName, - ), - ); + alert(localize('enableAutoUpdate', "Enabled auto updates for", this.extension.displayName)); } else { - alert( - localize( - "disableAutoUpdate", - "Disabled auto updates for", - this.extension.displayName, - ), - ); + alert(localize('disableAutoUpdate', "Disabled auto updates for", this.extension.displayName)); } } } export class MigrateDeprecatedExtensionAction extends ExtensionAction { + private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} migrate`; private static readonly DisabledClass = `${this.EnabledClass} disabled`; constructor( private readonly small: boolean, - @IExtensionsWorkbenchService - private extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService ) { - super( - "extensionsAction.migrateDeprecatedExtension", - localize("migrateExtension", "Migrate"), - MigrateDeprecatedExtensionAction.DisabledClass, - false, - ); + super('extensionsAction.migrateDeprecatedExtension', localize('migrateExtension', "Migrate"), MigrateDeprecatedExtensionAction.DisabledClass, false); this.update(); } @@ -1890,21 +1123,13 @@ export class MigrateDeprecatedExtensionAction extends ExtensionAction { return; } const id = this.extension.deprecationInfo.extension.id; - if ( - this.extensionsWorkbenchService.local.some((e) => - areSameExtensions(e.identifier, { id }), - ) - ) { + if (this.extensionsWorkbenchService.local.some(e => areSameExtensions(e.identifier, { id }))) { return; } this.enabled = true; this.class = MigrateDeprecatedExtensionAction.EnabledClass; - this.tooltip = localize( - "migrate to", - "Migrate to {0}", - this.extension.deprecationInfo.extension.displayName, - ); - this.label = this.small ? localize("migrate", "Migrate") : this.tooltip; + this.tooltip = localize('migrate to', "Migrate to {0}", this.extension.deprecationInfo.extension.displayName); + this.label = this.small ? localize('migrate', "Migrate") : this.tooltip; } override async run(): Promise { @@ -1913,88 +1138,55 @@ export class MigrateDeprecatedExtensionAction extends ExtensionAction { } const local = this.extension.local; await this.extensionsWorkbenchService.uninstall(this.extension); - const [extension] = await this.extensionsWorkbenchService.getExtensions( - [ - { - id: this.extension.deprecationInfo.extension.id, - preRelease: - this.extension.deprecationInfo?.extension?.preRelease, - }, - ], - CancellationToken.None, - ); - await this.extensionsWorkbenchService.install(extension, { - isMachineScoped: local?.isMachineScoped, - }); + const [extension] = await this.extensionsWorkbenchService.getExtensions([{ id: this.extension.deprecationInfo.extension.id, preRelease: this.extension.deprecationInfo?.extension?.preRelease }], CancellationToken.None); + await this.extensionsWorkbenchService.install(extension, { isMachineScoped: local?.isMachineScoped }); } } export abstract class DropDownExtensionAction extends ExtensionAction { + constructor( id: string, label: string, cssClass: string, enabled: boolean, - @IInstantiationService - protected instantiationService: IInstantiationService, + @IInstantiationService protected instantiationService: IInstantiationService ) { super(id, label, cssClass, enabled); } private _actionViewItem: DropDownExtensionActionViewItem | null = null; - createActionViewItem( - options: IActionViewItemOptions, - ): DropDownExtensionActionViewItem { - this._actionViewItem = this.instantiationService.createInstance( - DropDownExtensionActionViewItem, - this, - options, - ); + createActionViewItem(options: IActionViewItemOptions): DropDownExtensionActionViewItem { + this._actionViewItem = this.instantiationService.createInstance(DropDownExtensionActionViewItem, this, options); return this._actionViewItem; } - public override run({ - actionGroups, - disposeActionsOnHide, - }: { - actionGroups: IAction[][]; - disposeActionsOnHide: boolean; - }): Promise { + public override run({ actionGroups, disposeActionsOnHide }: { actionGroups: IAction[][]; disposeActionsOnHide: boolean }): Promise { this._actionViewItem?.showMenu(actionGroups, disposeActionsOnHide); return Promise.resolve(); } } export class DropDownExtensionActionViewItem extends ActionViewItem { + constructor( action: DropDownExtensionAction, options: IActionViewItemOptions, - @IContextMenuService - private readonly contextMenuService: IContextMenuService, + @IContextMenuService private readonly contextMenuService: IContextMenuService ) { super(null, action, { ...options, icon: true, label: true }); } - public showMenu( - menuActionGroups: IAction[][], - disposeActionsOnHide: boolean, - ): void { + public showMenu(menuActionGroups: IAction[][], disposeActionsOnHide: boolean): void { if (this.element) { const actions = this.getActions(menuActionGroups); const elementPosition = DOM.getDomNodePagePosition(this.element); - const anchor = { - x: elementPosition.left, - y: elementPosition.top + elementPosition.height + 10, - }; + const anchor = { x: elementPosition.left, y: elementPosition.top + elementPosition.height + 10 }; this.contextMenuService.showContextMenu({ getAnchor: () => anchor, getActions: () => actions, actionRunner: this.actionRunner, - onHide: () => { - if (disposeActionsOnHide) { - disposeIfDisposable(actions); - } - }, + onHide: () => { if (disposeActionsOnHide) { disposeIfDisposable(actions); } } }); } } @@ -2008,307 +1200,128 @@ export class DropDownExtensionActionViewItem extends ActionViewItem { } } -async function getContextMenuActionsGroups( - extension: IExtension | undefined | null, - contextKeyService: IContextKeyService, - instantiationService: IInstantiationService, -): Promise<[string, Array][]> { - return instantiationService.invokeFunction(async (accessor) => { - const extensionsWorkbenchService = accessor.get( - IExtensionsWorkbenchService, - ); - const extensionEnablementService = accessor.get( - IWorkbenchExtensionEnablementService, - ); +async function getContextMenuActionsGroups(extension: IExtension | undefined | null, contextKeyService: IContextKeyService, instantiationService: IInstantiationService): Promise<[string, Array][]> { + return instantiationService.invokeFunction(async accessor => { + const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); + const extensionEnablementService = accessor.get(IWorkbenchExtensionEnablementService); const menuService = accessor.get(IMenuService); - const extensionRecommendationsService = accessor.get( - IExtensionRecommendationsService, - ); - const extensionIgnoredRecommendationsService = accessor.get( - IExtensionIgnoredRecommendationsService, - ); + const extensionRecommendationsService = accessor.get(IExtensionRecommendationsService); + const extensionIgnoredRecommendationsService = accessor.get(IExtensionIgnoredRecommendationsService); const workbenchThemeService = accessor.get(IWorkbenchThemeService); - const authenticationUsageService = accessor.get( - IAuthenticationUsageService, - ); - const allowedExtensionsService = accessor.get( - IAllowedExtensionsService, - ); + const authenticationUsageService = accessor.get(IAuthenticationUsageService); + const allowedExtensionsService = accessor.get(IAllowedExtensionsService); const cksOverlay: [string, any][] = []; if (extension) { - cksOverlay.push(["extension", extension.identifier.id]); - cksOverlay.push(["isBuiltinExtension", extension.isBuiltin]); - cksOverlay.push([ - "isDefaultApplicationScopedExtension", - extension.local && - isApplicationScopedExtension(extension.local.manifest), - ]); - cksOverlay.push([ - "isApplicationScopedExtension", - extension.local && extension.local.isApplicationScoped, - ]); - cksOverlay.push([ - "isWorkspaceScopedExtension", - extension.isWorkspaceScoped, - ]); - cksOverlay.push([ - "isGalleryExtension", - !!extension.identifier.uuid, - ]); + cksOverlay.push(['extension', extension.identifier.id]); + cksOverlay.push(['isBuiltinExtension', extension.isBuiltin]); + cksOverlay.push(['isDefaultApplicationScopedExtension', extension.local && isApplicationScopedExtension(extension.local.manifest)]); + cksOverlay.push(['isApplicationScopedExtension', extension.local && extension.local.isApplicationScoped]); + cksOverlay.push(['isWorkspaceScopedExtension', extension.isWorkspaceScoped]); + cksOverlay.push(['isGalleryExtension', !!extension.identifier.uuid]); if (extension.local) { - cksOverlay.push(["extensionSource", extension.local.source]); + cksOverlay.push(['extensionSource', extension.local.source]); } - cksOverlay.push([ - "extensionHasConfiguration", - extension.local && - !!extension.local.manifest.contributes && - !!extension.local.manifest.contributes.configuration, - ]); - cksOverlay.push([ - "extensionHasKeybindings", - extension.local && - !!extension.local.manifest.contributes && - !!extension.local.manifest.contributes.keybindings, - ]); - cksOverlay.push([ - "extensionHasCommands", - extension.local && - !!extension.local.manifest.contributes && - !!extension.local.manifest.contributes?.commands, - ]); - cksOverlay.push([ - "isExtensionRecommended", - !!extensionRecommendationsService.getAllRecommendationsWithReason()[ - extension.identifier.id.toLowerCase() - ], - ]); - cksOverlay.push([ - "isExtensionWorkspaceRecommended", - extensionRecommendationsService.getAllRecommendationsWithReason()[ - extension.identifier.id.toLowerCase() - ]?.reasonId === ExtensionRecommendationReason.Workspace, - ]); - cksOverlay.push([ - "isUserIgnoredRecommendation", - extensionIgnoredRecommendationsService.globalIgnoredRecommendations.some( - (e) => e === extension.identifier.id.toLowerCase(), - ), - ]); - cksOverlay.push(["isExtensionPinned", extension.pinned]); - cksOverlay.push([ - "isExtensionEnabled", - extensionEnablementService.isEnabledEnablementState( - extension.enablementState, - ), - ]); + cksOverlay.push(['extensionHasConfiguration', extension.local && !!extension.local.manifest.contributes && !!extension.local.manifest.contributes.configuration]); + cksOverlay.push(['extensionHasKeybindings', extension.local && !!extension.local.manifest.contributes && !!extension.local.manifest.contributes.keybindings]); + cksOverlay.push(['extensionHasCommands', extension.local && !!extension.local.manifest.contributes && !!extension.local.manifest.contributes?.commands]); + cksOverlay.push(['isExtensionRecommended', !!extensionRecommendationsService.getAllRecommendationsWithReason()[extension.identifier.id.toLowerCase()]]); + cksOverlay.push(['isExtensionWorkspaceRecommended', extensionRecommendationsService.getAllRecommendationsWithReason()[extension.identifier.id.toLowerCase()]?.reasonId === ExtensionRecommendationReason.Workspace]); + cksOverlay.push(['isUserIgnoredRecommendation', extensionIgnoredRecommendationsService.globalIgnoredRecommendations.some(e => e === extension.identifier.id.toLowerCase())]); + cksOverlay.push(['isExtensionPinned', extension.pinned]); + cksOverlay.push(['isExtensionEnabled', extensionEnablementService.isEnabledEnablementState(extension.enablementState)]); switch (extension.state) { case ExtensionState.Installing: - cksOverlay.push(["extensionStatus", "installing"]); + cksOverlay.push(['extensionStatus', 'installing']); break; case ExtensionState.Installed: - cksOverlay.push(["extensionStatus", "installed"]); + cksOverlay.push(['extensionStatus', 'installed']); break; case ExtensionState.Uninstalling: - cksOverlay.push(["extensionStatus", "uninstalling"]); + cksOverlay.push(['extensionStatus', 'uninstalling']); break; case ExtensionState.Uninstalled: - cksOverlay.push(["extensionStatus", "uninstalled"]); + cksOverlay.push(['extensionStatus', 'uninstalled']); break; } - cksOverlay.push([ - "installedExtensionIsPreReleaseVersion", - !!extension.local?.isPreReleaseVersion, - ]); - cksOverlay.push([ - "installedExtensionIsOptedToPreRelease", - !!extension.local?.preRelease, - ]); - cksOverlay.push([ - "galleryExtensionIsPreReleaseVersion", - !!extension.gallery?.properties.isPreReleaseVersion, - ]); - cksOverlay.push([ - "galleryExtensionHasPreReleaseVersion", - extension.gallery?.hasPreReleaseVersion, - ]); - cksOverlay.push([ - "extensionHasPreReleaseVersion", - extension.hasPreReleaseVersion, - ]); - cksOverlay.push([ - "extensionHasReleaseVersion", - extension.hasReleaseVersion, - ]); - cksOverlay.push([ - "extensionDisallowInstall", - !!extension.deprecationInfo?.disallowInstall, - ]); - cksOverlay.push([ - "isExtensionAllowed", - allowedExtensionsService.isAllowed({ - id: extension.identifier.id, - }) === true, - ]); - cksOverlay.push([ - "isPreReleaseExtensionAllowed", - allowedExtensionsService.isAllowed({ - id: extension.identifier.id, - prerelease: true, - }) === true, - ]); - cksOverlay.push([ - "extensionIsUnsigned", - extension.gallery && !extension.gallery.isSigned, - ]); - - const [ - colorThemes, - fileIconThemes, - productIconThemes, - extensionUsesAuth, - ] = await Promise.all([ - workbenchThemeService.getColorThemes(), - workbenchThemeService.getFileIconThemes(), - workbenchThemeService.getProductIconThemes(), - authenticationUsageService.extensionUsesAuth( - extension.identifier.id.toLowerCase(), - ), - ]); - cksOverlay.push([ - "extensionHasColorThemes", - colorThemes.some((theme) => - isThemeFromExtension(theme, extension), - ), - ]); - cksOverlay.push([ - "extensionHasFileIconThemes", - fileIconThemes.some((theme) => - isThemeFromExtension(theme, extension), - ), - ]); - cksOverlay.push([ - "extensionHasProductIconThemes", - productIconThemes.some((theme) => - isThemeFromExtension(theme, extension), - ), - ]); - cksOverlay.push([ - "extensionHasAccountPreferences", - extensionUsesAuth, - ]); - - cksOverlay.push([ - "canSetLanguage", - extensionsWorkbenchService.canSetLanguage(extension), - ]); - cksOverlay.push([ - "isActiveLanguagePackExtension", - extension.gallery && language === getLocale(extension.gallery), - ]); - } - - const actionsGroups = menuService.getMenuActions( - MenuId.ExtensionContext, - contextKeyService.createOverlay(cksOverlay), - { shouldForwardArgs: true }, - ); + cksOverlay.push(['installedExtensionIsPreReleaseVersion', !!extension.local?.isPreReleaseVersion]); + cksOverlay.push(['installedExtensionIsOptedToPreRelease', !!extension.local?.preRelease]); + cksOverlay.push(['galleryExtensionIsPreReleaseVersion', !!extension.gallery?.properties.isPreReleaseVersion]); + cksOverlay.push(['galleryExtensionHasPreReleaseVersion', extension.gallery?.hasPreReleaseVersion]); + cksOverlay.push(['extensionHasPreReleaseVersion', extension.hasPreReleaseVersion]); + cksOverlay.push(['extensionHasReleaseVersion', extension.hasReleaseVersion]); + cksOverlay.push(['extensionDisallowInstall', !!extension.deprecationInfo?.disallowInstall]); + cksOverlay.push(['isExtensionAllowed', allowedExtensionsService.isAllowed({ id: extension.identifier.id, publisherDisplayName: extension.publisherDisplayName }) === true]); + cksOverlay.push(['isPreReleaseExtensionAllowed', allowedExtensionsService.isAllowed({ id: extension.identifier.id, publisherDisplayName: extension.publisherDisplayName, prerelease: true }) === true]); + cksOverlay.push(['extensionIsUnsigned', extension.gallery && !extension.gallery.isSigned]); + + const [colorThemes, fileIconThemes, productIconThemes, extensionUsesAuth] = await Promise.all([workbenchThemeService.getColorThemes(), workbenchThemeService.getFileIconThemes(), workbenchThemeService.getProductIconThemes(), authenticationUsageService.extensionUsesAuth(extension.identifier.id.toLowerCase())]); + cksOverlay.push(['extensionHasColorThemes', colorThemes.some(theme => isThemeFromExtension(theme, extension))]); + cksOverlay.push(['extensionHasFileIconThemes', fileIconThemes.some(theme => isThemeFromExtension(theme, extension))]); + cksOverlay.push(['extensionHasProductIconThemes', productIconThemes.some(theme => isThemeFromExtension(theme, extension))]); + cksOverlay.push(['extensionHasAccountPreferences', extensionUsesAuth]); + + cksOverlay.push(['canSetLanguage', extensionsWorkbenchService.canSetLanguage(extension)]); + cksOverlay.push(['isActiveLanguagePackExtension', extension.gallery && language === getLocale(extension.gallery)]); + } + + const actionsGroups = menuService.getMenuActions(MenuId.ExtensionContext, contextKeyService.createOverlay(cksOverlay), { shouldForwardArgs: true }); return actionsGroups; }); } -function toActions( - actionsGroups: [string, Array][], - instantiationService: IInstantiationService, -): IAction[][] { +function toActions(actionsGroups: [string, Array][], instantiationService: IInstantiationService): IAction[][] { const result: IAction[][] = []; for (const [, actions] of actionsGroups) { - result.push( - actions.map((action) => { - if (action instanceof SubmenuAction) { - return action; - } - return instantiationService.createInstance( - MenuItemExtensionAction, - action, - ); - }), - ); + result.push(actions.map(action => { + if (action instanceof SubmenuAction) { + return action; + } + return instantiationService.createInstance(MenuItemExtensionAction, action); + })); } return result; } -export async function getContextMenuActions( - extension: IExtension | undefined | null, - contextKeyService: IContextKeyService, - instantiationService: IInstantiationService, -): Promise { - const actionsGroups = await getContextMenuActionsGroups( - extension, - contextKeyService, - instantiationService, - ); + +export async function getContextMenuActions(extension: IExtension | undefined | null, contextKeyService: IContextKeyService, instantiationService: IInstantiationService): Promise { + const actionsGroups = await getContextMenuActionsGroups(extension, contextKeyService, instantiationService); return toActions(actionsGroups, instantiationService); } export class ManageExtensionAction extends DropDownExtensionAction { - static readonly ID = "extensions.manage"; - private static readonly Class = - `${ExtensionAction.ICON_ACTION_CLASS} manage ` + - ThemeIcon.asClassName(manageExtensionIcon); + static readonly ID = 'extensions.manage'; + + private static readonly Class = `${ExtensionAction.ICON_ACTION_CLASS} manage ` + ThemeIcon.asClassName(manageExtensionIcon); private static readonly HideManageExtensionClass = `${this.Class} hide`; constructor( @IInstantiationService instantiationService: IInstantiationService, @IExtensionService private readonly extensionService: IExtensionService, - @IContextKeyService - private readonly contextKeyService: IContextKeyService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { - super(ManageExtensionAction.ID, "", "", true, instantiationService); - this.tooltip = localize("manage", "Manage"); + super(ManageExtensionAction.ID, '', '', true, instantiationService); + + this.tooltip = localize('manage', "Manage"); this.update(); } async getActionGroups(): Promise { const groups: IAction[][] = []; - const contextMenuActionsGroups = await getContextMenuActionsGroups( - this.extension, - this.contextKeyService, - this.instantiationService, - ); - const themeActions: IAction[] = [], - installActions: IAction[] = [], - updateActions: IAction[] = [], - otherActionGroups: IAction[][] = []; + const contextMenuActionsGroups = await getContextMenuActionsGroups(this.extension, this.contextKeyService, this.instantiationService); + const themeActions: IAction[] = [], installActions: IAction[] = [], updateActions: IAction[] = [], otherActionGroups: IAction[][] = []; for (const [group, actions] of contextMenuActionsGroups) { if (group === INSTALL_ACTIONS_GROUP) { - installActions.push( - ...toActions( - [[group, actions]], - this.instantiationService, - )[0], - ); + installActions.push(...toActions([[group, actions]], this.instantiationService)[0]); } else if (group === UPDATE_ACTIONS_GROUP) { - updateActions.push( - ...toActions( - [[group, actions]], - this.instantiationService, - )[0], - ); + updateActions.push(...toActions([[group, actions]], this.instantiationService)[0]); } else if (group === THEME_ACTIONS_GROUP) { - themeActions.push( - ...toActions( - [[group, actions]], - this.instantiationService, - )[0], - ); + themeActions.push(...toActions([[group, actions]], this.instantiationService)[0]); } else { - otherActionGroups.push( - ...toActions([[group, actions]], this.instantiationService), - ); + otherActionGroups.push(...toActions([[group, actions]], this.instantiationService)); } } @@ -2318,44 +1331,35 @@ export class ManageExtensionAction extends DropDownExtensionAction { groups.push([ this.instantiationService.createInstance(EnableGloballyAction), - this.instantiationService.createInstance(EnableForWorkspaceAction), + this.instantiationService.createInstance(EnableForWorkspaceAction) ]); groups.push([ this.instantiationService.createInstance(DisableGloballyAction), - this.instantiationService.createInstance(DisableForWorkspaceAction), + this.instantiationService.createInstance(DisableForWorkspaceAction) ]); if (updateActions.length) { groups.push(updateActions); } groups.push([ ...(installActions.length ? installActions : []), - this.instantiationService.createInstance( - InstallAnotherVersionAction, - this.extension, - false, - ), + this.instantiationService.createInstance(InstallAnotherVersionAction, this.extension, false), this.instantiationService.createInstance(UninstallAction), ]); - otherActionGroups.forEach((actions) => groups.push(actions)); + otherActionGroups.forEach(actions => groups.push(actions)); - groups.forEach((group) => - group.forEach((extensionAction) => { - if (extensionAction instanceof ExtensionAction) { - extensionAction.extension = this.extension; - } - }), - ); + groups.forEach(group => group.forEach(extensionAction => { + if (extensionAction instanceof ExtensionAction) { + extensionAction.extension = this.extension; + } + })); return groups; } override async run(): Promise { await this.extensionService.whenInstalledExtensionsRegistered(); - return super.run({ - actionGroups: await this.getActionGroups(), - disposeActionsOnHide: true, - }); + return super.run({ actionGroups: await this.getActionGroups(), disposeActionsOnHide: true }); } update(): void { @@ -2364,56 +1368,41 @@ export class ManageExtensionAction extends DropDownExtensionAction { if (this.extension) { const state = this.extension.state; this.enabled = state === ExtensionState.Installed; - this.class = - this.enabled || state === ExtensionState.Uninstalling - ? ManageExtensionAction.Class - : ManageExtensionAction.HideManageExtensionClass; + this.class = this.enabled || state === ExtensionState.Uninstalling ? ManageExtensionAction.Class : ManageExtensionAction.HideManageExtensionClass; } } } export class ExtensionEditorManageExtensionAction extends DropDownExtensionAction { + constructor( private readonly contextKeyService: IContextKeyService, - instantiationService: IInstantiationService, + instantiationService: IInstantiationService ) { - super( - "extensionEditor.manageExtension", - "", - `${ExtensionAction.ICON_ACTION_CLASS} manage ${ThemeIcon.asClassName(manageExtensionIcon)}`, - true, - instantiationService, - ); - this.tooltip = localize("manage", "Manage"); + super('extensionEditor.manageExtension', '', `${ExtensionAction.ICON_ACTION_CLASS} manage ${ThemeIcon.asClassName(manageExtensionIcon)}`, true, instantiationService); + this.tooltip = localize('manage', "Manage"); } - update(): void {} + update(): void { } override async run(): Promise { const actionGroups: IAction[][] = []; - ( - await getContextMenuActions( - this.extension, - this.contextKeyService, - this.instantiationService, - ) - ).forEach((actions) => actionGroups.push(actions)); - actionGroups.forEach((group) => - group.forEach((extensionAction) => { - if (extensionAction instanceof ExtensionAction) { - extensionAction.extension = this.extension; - } - }), - ); + (await getContextMenuActions(this.extension, this.contextKeyService, this.instantiationService)).forEach(actions => actionGroups.push(actions)); + actionGroups.forEach(group => group.forEach(extensionAction => { + if (extensionAction instanceof ExtensionAction) { + extensionAction.extension = this.extension; + } + })); return super.run({ actionGroups, disposeActionsOnHide: true }); } + } export class MenuItemExtensionAction extends ExtensionAction { + constructor( private readonly action: IAction, - @IExtensionsWorkbenchService - private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, ) { super(action.id, action.label); } @@ -2431,20 +1420,11 @@ export class MenuItemExtensionAction extends ExtensionAction { return; } if (this.action.id === TOGGLE_IGNORE_EXTENSION_ACTION_ID) { - this.checked = - !this.extensionsWorkbenchService.isExtensionIgnoredToSync( - this.extension, - ); + this.checked = !this.extensionsWorkbenchService.isExtensionIgnoredToSync(this.extension); } else if (this.action.id === ToggleAutoUpdateForExtensionAction.ID) { - this.checked = - this.extensionsWorkbenchService.isAutoUpdateEnabledFor( - this.extension, - ); + this.checked = this.extensionsWorkbenchService.isAutoUpdateEnabledFor(this.extension); } else if (this.action.id === ToggleAutoUpdatesForPublisherAction.ID) { - this.checked = - this.extensionsWorkbenchService.isAutoUpdateEnabledFor( - this.extension.publisher, - ); + this.checked = this.extensionsWorkbenchService.isAutoUpdateEnabledFor(this.extension.publisher); } else { this.checked = this.action.checked; } @@ -2452,21 +1432,13 @@ export class MenuItemExtensionAction extends ExtensionAction { override async run(): Promise { if (this.extension) { - const id = this.extension.local - ? getExtensionId( - this.extension.local.manifest.publisher, - this.extension.local.manifest.name, - ) - : this.extension.gallery - ? getExtensionId( - this.extension.gallery.publisher, - this.extension.gallery.name, - ) + const id = this.extension.local ? getExtensionId(this.extension.local.manifest.publisher, this.extension.local.manifest.name) + : this.extension.gallery ? getExtensionId(this.extension.gallery.publisher, this.extension.gallery.name) : this.extension.identifier.id; const extensionArg: IExtensionArg = { id: this.extension.identifier.id, version: this.extension.version, - location: this.extension.local?.location, + location: this.extension.local?.location }; await this.action.run(id, extensionArg); } @@ -2474,21 +1446,17 @@ export class MenuItemExtensionAction extends ExtensionAction { } export class TogglePreReleaseExtensionAction extends ExtensionAction { - static readonly ID = "workbench.extensions.action.togglePreRlease"; - static readonly LABEL = localize("togglePreRleaseLabel", "Pre-Release"); + + static readonly ID = 'workbench.extensions.action.togglePreRlease'; + static readonly LABEL = localize('togglePreRleaseLabel', "Pre-Release"); private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} pre-release`; private static readonly DisabledClass = `${this.EnabledClass} hide`; constructor( - @IExtensionsWorkbenchService - private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, ) { - super( - TogglePreReleaseExtensionAction.ID, - TogglePreReleaseExtensionAction.LABEL, - TogglePreReleaseExtensionAction.DisabledClass, - ); + super(TogglePreReleaseExtensionAction.ID, TogglePreReleaseExtensionAction.LABEL, TogglePreReleaseExtensionAction.DisabledClass); this.update(); } @@ -2513,33 +1481,18 @@ export class TogglePreReleaseExtensionAction extends ExtensionAction { if (this.extension.preRelease && !this.extension.isPreReleaseVersion) { return; } - if ( - !this.extension.preRelease && - !this.extension.gallery.hasPreReleaseVersion - ) { + if (!this.extension.preRelease && !this.extension.gallery.hasPreReleaseVersion) { return; } this.enabled = true; this.class = TogglePreReleaseExtensionAction.EnabledClass; if (this.extension.preRelease) { - this.label = localize( - "togglePreRleaseDisableLabel", - "Switch to Release Version", - ); - this.tooltip = localize( - "togglePreRleaseDisableTooltip", - "This will switch and enable updates to release versions", - ); + this.label = localize('togglePreRleaseDisableLabel', "Switch to Release Version"); + this.tooltip = localize('togglePreRleaseDisableTooltip', "This will switch and enable updates to release versions"); } else { - this.label = localize( - "switchToPreReleaseLabel", - "Switch to Pre-Release Version", - ); - this.tooltip = localize( - "switchToPreReleaseTooltip", - "This will switch to pre-release version and enable updates to latest version always", - ); + this.label = localize('switchToPreReleaseLabel', "Switch to Pre-Release Version"); + this.tooltip = localize('switchToPreReleaseTooltip', "This will switch to pre-release version and enable updates to latest version always"); } } @@ -2547,55 +1500,35 @@ export class TogglePreReleaseExtensionAction extends ExtensionAction { if (!this.extension) { return; } - this.extensionsWorkbenchService.open(this.extension, { - showPreReleaseVersion: !this.extension.preRelease, - }); + this.extensionsWorkbenchService.open(this.extension, { showPreReleaseVersion: !this.extension.preRelease }); await this.extensionsWorkbenchService.togglePreRelease(this.extension); } } export class InstallAnotherVersionAction extends ExtensionAction { - static readonly ID = "workbench.extensions.action.install.anotherVersion"; - static readonly LABEL = localize( - "install another version", - "Install Specific Version...", - ); + + static readonly ID = 'workbench.extensions.action.install.anotherVersion'; + static readonly LABEL = localize('install another version', "Install Specific Version..."); constructor( extension: IExtension | null, private readonly whenInstalled: boolean, - @IExtensionsWorkbenchService - private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IWorkbenchExtensionManagementService - private readonly extensionManagementService: IWorkbenchExtensionManagementService, - @IExtensionGalleryService - private readonly extensionGalleryService: IExtensionGalleryService, - @IQuickInputService - private readonly quickInputService: IQuickInputService, - @IInstantiationService - private readonly instantiationService: IInstantiationService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IWorkbenchExtensionManagementService private readonly extensionManagementService: IWorkbenchExtensionManagementService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IInstantiationService private readonly instantiationService: IInstantiationService, @IDialogService private readonly dialogService: IDialogService, ) { - super( - InstallAnotherVersionAction.ID, - InstallAnotherVersionAction.LABEL, - ExtensionAction.LABEL_ACTION_CLASS, - ); + super(InstallAnotherVersionAction.ID, InstallAnotherVersionAction.LABEL, ExtensionAction.LABEL_ACTION_CLASS); this.extension = extension; this.update(); } update(): void { - this.enabled = - !!this.extension && - !this.extension.isBuiltin && - !!this.extension.identifier.uuid && - !this.extension.deprecationInfo; + this.enabled = !!this.extension && !this.extension.isBuiltin && !!this.extension.identifier.uuid && !this.extension.deprecationInfo; if (this.enabled && this.whenInstalled) { - this.enabled = - !!this.extension?.local && - !!this.extension.server && - this.extension.state === ExtensionState.Installed; + this.enabled = !!this.extension?.local && !!this.extension.server && this.extension.state === ExtensionState.Installed; } } @@ -2606,24 +1539,10 @@ export class InstallAnotherVersionAction extends ExtensionAction { if (!this.extension) { return; } - const targetPlatform = this.extension.server - ? await this.extension.server.extensionManagementService.getTargetPlatform() - : await this.extensionManagementService.getTargetPlatform(); - const allVersions = - await this.extensionGalleryService.getAllCompatibleVersions( - this.extension.identifier, - this.extension.local?.preRelease ?? - this.extension.gallery?.properties.isPreReleaseVersion ?? - false, - targetPlatform, - ); + const targetPlatform = this.extension.server ? await this.extension.server.extensionManagementService.getTargetPlatform() : await this.extensionManagementService.getTargetPlatform(); + const allVersions = await this.extensionGalleryService.getAllCompatibleVersions(this.extension.identifier, this.extension.local?.preRelease ?? this.extension.gallery?.properties.isPreReleaseVersion ?? false, targetPlatform); if (!allVersions.length) { - await this.dialogService.info( - localize( - "no versions", - "This extension has no other versions.", - ), - ); + await this.dialogService.info(localize('no versions', "This extension has no other versions.")); return; } @@ -2631,85 +1550,52 @@ export class InstallAnotherVersionAction extends ExtensionAction { return { id: v.version, label: v.version, - description: `${fromNow(new Date(Date.parse(v.date)), true)}${v.isPreReleaseVersion ? ` (${localize("pre-release", "pre-release")})` : ""}${v.version === this.extension?.local?.manifest.version ? ` (${localize("current", "current")})` : ""}`, - ariaLabel: `${v.isPreReleaseVersion ? "Pre-Release version" : "Release version"} ${v.version}`, - isPreReleaseVersion: v.isPreReleaseVersion, + description: `${fromNow(new Date(Date.parse(v.date)), true)}${v.isPreReleaseVersion ? ` (${localize('pre-release', "pre-release")})` : ''}${v.version === this.extension?.local?.manifest.version ? ` (${localize('current', "current")})` : ''}`, + ariaLabel: `${v.isPreReleaseVersion ? 'Pre-Release version' : 'Release version'} ${v.version}`, + isPreReleaseVersion: v.isPreReleaseVersion }; }); - const pick = await this.quickInputService.pick(picks, { - placeHolder: localize("selectVersion", "Select Version to Install"), - matchOnDetail: true, - }); + const pick = await this.quickInputService.pick(picks, + { + placeHolder: localize('selectVersion', "Select Version to Install"), + matchOnDetail: true + }); if (pick) { if (this.extension.local?.manifest.version === pick.id) { return; } - const options = { - installPreReleaseVersion: pick.isPreReleaseVersion, - version: pick.id, - }; + const options = { installPreReleaseVersion: pick.isPreReleaseVersion, version: pick.id }; try { - await this.extensionsWorkbenchService.install( - this.extension, - options, - ); + await this.extensionsWorkbenchService.install(this.extension, options); } catch (error) { - this.instantiationService - .createInstance( - PromptExtensionInstallFailureAction, - this.extension, - options, - pick.id, - InstallOperation.Install, - error, - ) - .run(); + this.instantiationService.createInstance(PromptExtensionInstallFailureAction, this.extension, options, pick.id, InstallOperation.Install, error).run(); } } return null; } + } export class EnableForWorkspaceAction extends ExtensionAction { - static readonly ID = "extensions.enableForWorkspace"; - static readonly LABEL = localize( - "enableForWorkspaceAction", - "Enable (Workspace)", - ); + + static readonly ID = 'extensions.enableForWorkspace'; + static readonly LABEL = localize('enableForWorkspaceAction', "Enable (Workspace)"); constructor( - @IExtensionsWorkbenchService - private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IWorkbenchExtensionEnablementService - private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService ) { - super( - EnableForWorkspaceAction.ID, - EnableForWorkspaceAction.LABEL, - ExtensionAction.LABEL_ACTION_CLASS, - ); - this.tooltip = localize( - "enableForWorkspaceActionToolTip", - "Enable this extension only in this workspace", - ); + super(EnableForWorkspaceAction.ID, EnableForWorkspaceAction.LABEL, ExtensionAction.LABEL_ACTION_CLASS); + this.tooltip = localize('enableForWorkspaceActionToolTip', "Enable this extension only in this workspace"); this.update(); } update(): void { this.enabled = false; - if ( - this.extension && - this.extension.local && - !this.extension.isWorkspaceScoped - ) { - this.enabled = - this.extension.state === ExtensionState.Installed && - !this.extensionEnablementService.isEnabled( - this.extension.local, - ) && - this.extensionEnablementService.canChangeWorkspaceEnablement( - this.extension.local, - ); + if (this.extension && this.extension.local && !this.extension.isWorkspaceScoped) { + this.enabled = this.extension.state === ExtensionState.Installed + && !this.extensionEnablementService.isEnabled(this.extension.local) + && this.extensionEnablementService.canChangeWorkspaceEnablement(this.extension.local); } } @@ -2717,50 +1603,30 @@ export class EnableForWorkspaceAction extends ExtensionAction { if (!this.extension) { return; } - return this.extensionsWorkbenchService.setEnablement( - this.extension, - EnablementState.EnabledWorkspace, - ); + return this.extensionsWorkbenchService.setEnablement(this.extension, EnablementState.EnabledWorkspace); } } export class EnableGloballyAction extends ExtensionAction { - static readonly ID = "extensions.enableGlobally"; - static readonly LABEL = localize("enableGloballyAction", "Enable"); + + static readonly ID = 'extensions.enableGlobally'; + static readonly LABEL = localize('enableGloballyAction', "Enable"); constructor( - @IExtensionsWorkbenchService - private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IWorkbenchExtensionEnablementService - private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService ) { - super( - EnableGloballyAction.ID, - EnableGloballyAction.LABEL, - ExtensionAction.LABEL_ACTION_CLASS, - ); - this.tooltip = localize( - "enableGloballyActionToolTip", - "Enable this extension", - ); + super(EnableGloballyAction.ID, EnableGloballyAction.LABEL, ExtensionAction.LABEL_ACTION_CLASS); + this.tooltip = localize('enableGloballyActionToolTip', "Enable this extension"); this.update(); } update(): void { this.enabled = false; - if ( - this.extension && - this.extension.local && - !this.extension.isWorkspaceScoped - ) { - this.enabled = - this.extension.state === ExtensionState.Installed && - this.extensionEnablementService.isDisabledGlobally( - this.extension.local, - ) && - this.extensionEnablementService.canChangeEnablement( - this.extension.local, - ); + if (this.extension && this.extension.local && !this.extension.isWorkspaceScoped) { + this.enabled = this.extension.state === ExtensionState.Installed + && this.extensionEnablementService.isDisabledGlobally(this.extension.local) + && this.extensionEnablementService.canChangeEnablement(this.extension.local); } } @@ -2768,69 +1634,33 @@ export class EnableGloballyAction extends ExtensionAction { if (!this.extension) { return; } - return this.extensionsWorkbenchService.setEnablement( - this.extension, - EnablementState.EnabledGlobally, - ); + return this.extensionsWorkbenchService.setEnablement(this.extension, EnablementState.EnabledGlobally); } } export class DisableForWorkspaceAction extends ExtensionAction { - static readonly ID = "extensions.disableForWorkspace"; - static readonly LABEL = localize( - "disableForWorkspaceAction", - "Disable (Workspace)", - ); + + static readonly ID = 'extensions.disableForWorkspace'; + static readonly LABEL = localize('disableForWorkspaceAction', "Disable (Workspace)"); constructor( - @IWorkspaceContextService - private readonly workspaceContextService: IWorkspaceContextService, - @IExtensionsWorkbenchService - private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IWorkbenchExtensionEnablementService - private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, @IExtensionService private readonly extensionService: IExtensionService, ) { - super( - DisableForWorkspaceAction.ID, - DisableForWorkspaceAction.LABEL, - ExtensionAction.LABEL_ACTION_CLASS, - ); - this.tooltip = localize( - "disableForWorkspaceActionToolTip", - "Disable this extension only in this workspace", - ); + super(DisableForWorkspaceAction.ID, DisableForWorkspaceAction.LABEL, ExtensionAction.LABEL_ACTION_CLASS); + this.tooltip = localize('disableForWorkspaceActionToolTip', "Disable this extension only in this workspace"); this.update(); - this._register( - this.extensionService.onDidChangeExtensions(() => this.update()), - ); + this._register(this.extensionService.onDidChangeExtensions(() => this.update())); } update(): void { this.enabled = false; - if ( - this.extension && - this.extension.local && - !this.extension.isWorkspaceScoped && - this.extensionService.extensions.some( - (e) => - areSameExtensions( - { id: e.identifier.value, uuid: e.uuid }, - this.extension!.identifier, - ) && - this.workspaceContextService.getWorkbenchState() !== - WorkbenchState.EMPTY, - ) - ) { - this.enabled = - this.extension.state === ExtensionState.Installed && - (this.extension.enablementState === - EnablementState.EnabledGlobally || - this.extension.enablementState === - EnablementState.EnabledWorkspace) && - this.extensionEnablementService.canChangeWorkspaceEnablement( - this.extension.local, - ); + if (this.extension && this.extension.local && !this.extension.isWorkspaceScoped && this.extensionService.extensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier) && this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY)) { + this.enabled = this.extension.state === ExtensionState.Installed + && (this.extension.enablementState === EnablementState.EnabledGlobally || this.extension.enablementState === EnablementState.EnabledWorkspace) + && this.extensionEnablementService.canChangeWorkspaceEnablement(this.extension.local); } } @@ -2838,61 +1668,32 @@ export class DisableForWorkspaceAction extends ExtensionAction { if (!this.extension) { return; } - return this.extensionsWorkbenchService.setEnablement( - this.extension, - EnablementState.DisabledWorkspace, - ); + return this.extensionsWorkbenchService.setEnablement(this.extension, EnablementState.DisabledWorkspace); } } export class DisableGloballyAction extends ExtensionAction { - static readonly ID = "extensions.disableGlobally"; - static readonly LABEL = localize("disableGloballyAction", "Disable"); + + static readonly ID = 'extensions.disableGlobally'; + static readonly LABEL = localize('disableGloballyAction', "Disable"); constructor( - @IExtensionsWorkbenchService - private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IWorkbenchExtensionEnablementService - private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, @IExtensionService private readonly extensionService: IExtensionService, ) { - super( - DisableGloballyAction.ID, - DisableGloballyAction.LABEL, - ExtensionAction.LABEL_ACTION_CLASS, - ); - this.tooltip = localize( - "disableGloballyActionToolTip", - "Disable this extension", - ); + super(DisableGloballyAction.ID, DisableGloballyAction.LABEL, ExtensionAction.LABEL_ACTION_CLASS); + this.tooltip = localize('disableGloballyActionToolTip', "Disable this extension"); this.update(); - this._register( - this.extensionService.onDidChangeExtensions(() => this.update()), - ); + this._register(this.extensionService.onDidChangeExtensions(() => this.update())); } update(): void { this.enabled = false; - if ( - this.extension && - this.extension.local && - !this.extension.isWorkspaceScoped && - this.extensionService.extensions.some((e) => - areSameExtensions( - { id: e.identifier.value, uuid: e.uuid }, - this.extension!.identifier, - ), - ) - ) { - this.enabled = - this.extension.state === ExtensionState.Installed && - (this.extension.enablementState === - EnablementState.EnabledGlobally || - this.extension.enablementState === - EnablementState.EnabledWorkspace) && - this.extensionEnablementService.canChangeEnablement( - this.extension.local, - ); + if (this.extension && this.extension.local && !this.extension.isWorkspaceScoped && this.extensionService.extensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier))) { + this.enabled = this.extension.state === ExtensionState.Installed + && (this.extension.enablementState === EnablementState.EnabledGlobally || this.extension.enablementState === EnablementState.EnabledWorkspace) + && this.extensionEnablementService.canChangeEnablement(this.extension.local); } } @@ -2900,40 +1701,39 @@ export class DisableGloballyAction extends ExtensionAction { if (!this.extension) { return; } - return this.extensionsWorkbenchService.setEnablement( - this.extension, - EnablementState.DisabledGlobally, - ); + return this.extensionsWorkbenchService.setEnablement(this.extension, EnablementState.DisabledGlobally); } } export class EnableDropDownAction extends ButtonWithDropDownExtensionAction { + constructor( - @IInstantiationService instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService ) { - super("extensions.enable", ExtensionAction.LABEL_ACTION_CLASS, [ + super('extensions.enable', ExtensionAction.LABEL_ACTION_CLASS, [ [ instantiationService.createInstance(EnableGloballyAction), - instantiationService.createInstance(EnableForWorkspaceAction), - ], + instantiationService.createInstance(EnableForWorkspaceAction) + ] ]); } } export class DisableDropDownAction extends ButtonWithDropDownExtensionAction { + constructor( - @IInstantiationService instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService ) { - super("extensions.disable", ExtensionAction.LABEL_ACTION_CLASS, [ - [ - instantiationService.createInstance(DisableGloballyAction), - instantiationService.createInstance(DisableForWorkspaceAction), - ], - ]); + super('extensions.disable', ExtensionAction.LABEL_ACTION_CLASS, [[ + instantiationService.createInstance(DisableGloballyAction), + instantiationService.createInstance(DisableForWorkspaceAction) + ]]); } + } export class ExtensionRuntimeStateAction extends ExtensionAction { + private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} reload`; private static readonly DisabledClass = `${this.EnabledClass} disabled`; @@ -2941,28 +1741,20 @@ export class ExtensionRuntimeStateAction extends ExtensionAction { constructor( @IHostService private readonly hostService: IHostService, - @IExtensionsWorkbenchService - private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IUpdateService private readonly updateService: IUpdateService, @IExtensionService private readonly extensionService: IExtensionService, @IProductService private readonly productService: IProductService, @ITelemetryService private readonly telemetryService: ITelemetryService, ) { - super( - "extensions.runtimeState", - "", - ExtensionRuntimeStateAction.DisabledClass, - false, - ); - this._register( - this.extensionService.onDidChangeExtensions(() => this.update()), - ); + super('extensions.runtimeState', '', ExtensionRuntimeStateAction.DisabledClass, false); + this._register(this.extensionService.onDidChangeExtensions(() => this.update())); this.update(); } update(): void { this.enabled = false; - this.tooltip = ""; + this.tooltip = ''; this.class = ExtensionRuntimeStateAction.DisabledClass; if (!this.extension) { @@ -2970,20 +1762,11 @@ export class ExtensionRuntimeStateAction extends ExtensionAction { } const state = this.extension.state; - if ( - state === ExtensionState.Installing || - state === ExtensionState.Uninstalling - ) { + if (state === ExtensionState.Installing || state === ExtensionState.Uninstalling) { return; } - if ( - this.extension.local && - this.extension.local.manifest && - this.extension.local.manifest.contributes && - this.extension.local.manifest.contributes.localizations && - this.extension.local.manifest.contributes.localizations.length > 0 - ) { + if (this.extension.local && this.extension.local.manifest && this.extension.local.manifest.contributes && this.extension.local.manifest.contributes.localizations && this.extension.local.manifest.contributes.localizations.length > 0) { return; } @@ -2995,25 +1778,10 @@ export class ExtensionRuntimeStateAction extends ExtensionAction { this.enabled = true; this.class = ExtensionRuntimeStateAction.EnabledClass; this.tooltip = runtimeState.reason; - this.label = - runtimeState.action === ExtensionRuntimeActionType.ReloadWindow - ? localize("reload window", "Reload Window") - : runtimeState.action === - ExtensionRuntimeActionType.RestartExtensions - ? localize("restart extensions", "Restart Extensions") - : runtimeState.action === - ExtensionRuntimeActionType.QuitAndInstall - ? localize("restart product", "Restart to Update") - : runtimeState.action === - ExtensionRuntimeActionType.ApplyUpdate || - runtimeState.action === - ExtensionRuntimeActionType.DownloadUpdate - ? localize( - "update product", - "Update {0}", - this.productService.nameShort, - ) - : ""; + this.label = runtimeState.action === ExtensionRuntimeActionType.ReloadWindow ? localize('reload window', 'Reload Window') + : runtimeState.action === ExtensionRuntimeActionType.RestartExtensions ? localize('restart extensions', 'Restart Extensions') + : runtimeState.action === ExtensionRuntimeActionType.QuitAndInstall ? localize('restart product', 'Restart to Update') + : runtimeState.action === ExtensionRuntimeActionType.ApplyUpdate || runtimeState.action === ExtensionRuntimeActionType.DownloadUpdate ? localize('update product', 'Update {0}', this.productService.nameShort) : ''; } override async run(): Promise { @@ -3023,149 +1791,89 @@ export class ExtensionRuntimeStateAction extends ExtensionAction { } type ExtensionRuntimeStateActionClassification = { - owner: "sandy081"; - comment: "Extension runtime state action event"; - action: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "Executed action"; - }; + owner: 'sandy081'; + comment: 'Extension runtime state action event'; + action: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Executed action' }; }; type ExtensionRuntimeStateActionEvent = { action: string; }; - this.telemetryService.publicLog2< - ExtensionRuntimeStateActionEvent, - ExtensionRuntimeStateActionClassification - >("extensions:runtimestate:action", { - action: runtimeState.action, + this.telemetryService.publicLog2('extensions:runtimestate:action', { + action: runtimeState.action }); if (runtimeState?.action === ExtensionRuntimeActionType.ReloadWindow) { return this.hostService.reload(); - } else if ( - runtimeState?.action === - ExtensionRuntimeActionType.RestartExtensions - ) { + } + + else if (runtimeState?.action === ExtensionRuntimeActionType.RestartExtensions) { return this.extensionsWorkbenchService.updateRunningExtensions(); - } else if ( - runtimeState?.action === ExtensionRuntimeActionType.DownloadUpdate - ) { + } + + else if (runtimeState?.action === ExtensionRuntimeActionType.DownloadUpdate) { return this.updateService.downloadUpdate(); - } else if ( - runtimeState?.action === ExtensionRuntimeActionType.ApplyUpdate - ) { + } + + else if (runtimeState?.action === ExtensionRuntimeActionType.ApplyUpdate) { return this.updateService.applyUpdate(); - } else if ( - runtimeState?.action === ExtensionRuntimeActionType.QuitAndInstall - ) { + } + + else if (runtimeState?.action === ExtensionRuntimeActionType.QuitAndInstall) { return this.updateService.quitAndInstall(); } + } } -function isThemeFromExtension( - theme: IWorkbenchTheme, - extension: IExtension | undefined | null, -): boolean { - return !!( - extension && - theme.extensionData && - ExtensionIdentifier.equals( - theme.extensionData.extensionId, - extension.identifier.id, - ) - ); +function isThemeFromExtension(theme: IWorkbenchTheme, extension: IExtension | undefined | null): boolean { + return !!(extension && theme.extensionData && ExtensionIdentifier.equals(theme.extensionData.extensionId, extension.identifier.id)); } -function getQuickPickEntries( - themes: IWorkbenchTheme[], - currentTheme: IWorkbenchTheme, - extension: IExtension | null | undefined, - showCurrentTheme: boolean, -): QuickPickItem[] { +function getQuickPickEntries(themes: IWorkbenchTheme[], currentTheme: IWorkbenchTheme, extension: IExtension | null | undefined, showCurrentTheme: boolean): QuickPickItem[] { const picks: QuickPickItem[] = []; for (const theme of themes) { - if ( - isThemeFromExtension(theme, extension) && - !(showCurrentTheme && theme === currentTheme) - ) { + if (isThemeFromExtension(theme, extension) && !(showCurrentTheme && theme === currentTheme)) { picks.push({ label: theme.label, id: theme.id }); } } if (showCurrentTheme) { - picks.push({ - type: "separator", - label: localize("current", "current"), - }); + picks.push({ type: 'separator', label: localize('current', "current") }); picks.push({ label: currentTheme.label, id: currentTheme.id }); } return picks; } export class SetColorThemeAction extends ExtensionAction { - static readonly ID = "workbench.extensions.action.setColorTheme"; - static readonly TITLE = localize2( - "workbench.extensions.action.setColorTheme", - "Set Color Theme", - ); + + static readonly ID = 'workbench.extensions.action.setColorTheme'; + static readonly TITLE = localize2('workbench.extensions.action.setColorTheme', 'Set Color Theme'); private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`; private static readonly DisabledClass = `${this.EnabledClass} disabled`; constructor( @IExtensionService extensionService: IExtensionService, - @IWorkbenchThemeService - private readonly workbenchThemeService: IWorkbenchThemeService, - @IQuickInputService - private readonly quickInputService: IQuickInputService, - @IWorkbenchExtensionEnablementService - private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, + @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, ) { - super( - SetColorThemeAction.ID, - SetColorThemeAction.TITLE.value, - SetColorThemeAction.DisabledClass, - false, - ); - this._register( - Event.any( - extensionService.onDidChangeExtensions, - workbenchThemeService.onDidColorThemeChange, - )(() => this.update(), this), - ); + super(SetColorThemeAction.ID, SetColorThemeAction.TITLE.value, SetColorThemeAction.DisabledClass, false); + this._register(Event.any(extensionService.onDidChangeExtensions, workbenchThemeService.onDidColorThemeChange)(() => this.update(), this)); this.update(); } update(): void { - this.workbenchThemeService.getColorThemes().then((colorThemes) => { + this.workbenchThemeService.getColorThemes().then(colorThemes => { this.enabled = this.computeEnablement(colorThemes); - this.class = this.enabled - ? SetColorThemeAction.EnabledClass - : SetColorThemeAction.DisabledClass; + this.class = this.enabled ? SetColorThemeAction.EnabledClass : SetColorThemeAction.DisabledClass; }); } private computeEnablement(colorThemes: IWorkbenchColorTheme[]): boolean { - return ( - !!this.extension && - this.extension.state === ExtensionState.Installed && - this.extensionEnablementService.isEnabledEnablementState( - this.extension.enablementState, - ) && - colorThemes.some((th) => isThemeFromExtension(th, this.extension)) - ); - } - - override async run( - { - showCurrentTheme, - ignoreFocusLost, - }: { showCurrentTheme: boolean; ignoreFocusLost: boolean } = { - showCurrentTheme: false, - ignoreFocusLost: false, - }, - ): Promise { + return !!this.extension && this.extension.state === ExtensionState.Installed && this.extensionEnablementService.isEnabledEnablementState(this.extension.enablementState) && colorThemes.some(th => isThemeFromExtension(th, this.extension)); + } + + override async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean; ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise { const colorThemes = await this.workbenchThemeService.getColorThemes(); if (!this.computeEnablement(colorThemes)) { @@ -3174,205 +1882,100 @@ export class SetColorThemeAction extends ExtensionAction { const currentTheme = this.workbenchThemeService.getColorTheme(); const delayer = new Delayer(100); - const picks = getQuickPickEntries( - colorThemes, - currentTheme, - this.extension, - showCurrentTheme, - ); - const pickedTheme = await this.quickInputService.pick(picks, { - placeHolder: localize("select color theme", "Select Color Theme"), - onDidFocus: (item) => - delayer.trigger(() => - this.workbenchThemeService.setColorTheme( - item.id, - undefined, - ), - ), - ignoreFocusLost, - }); - return this.workbenchThemeService.setColorTheme( - pickedTheme ? pickedTheme.id : currentTheme.id, - "auto", - ); + const picks = getQuickPickEntries(colorThemes, currentTheme, this.extension, showCurrentTheme); + const pickedTheme = await this.quickInputService.pick( + picks, + { + placeHolder: localize('select color theme', "Select Color Theme"), + onDidFocus: item => delayer.trigger(() => this.workbenchThemeService.setColorTheme(item.id, undefined)), + ignoreFocusLost + }); + return this.workbenchThemeService.setColorTheme(pickedTheme ? pickedTheme.id : currentTheme.id, 'auto'); } } export class SetFileIconThemeAction extends ExtensionAction { - static readonly ID = "workbench.extensions.action.setFileIconTheme"; - static readonly TITLE = localize2( - "workbench.extensions.action.setFileIconTheme", - "Set File Icon Theme", - ); + + static readonly ID = 'workbench.extensions.action.setFileIconTheme'; + static readonly TITLE = localize2('workbench.extensions.action.setFileIconTheme', 'Set File Icon Theme'); private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`; private static readonly DisabledClass = `${this.EnabledClass} disabled`; constructor( @IExtensionService extensionService: IExtensionService, - @IWorkbenchThemeService - private readonly workbenchThemeService: IWorkbenchThemeService, - @IQuickInputService - private readonly quickInputService: IQuickInputService, - @IWorkbenchExtensionEnablementService - private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, + @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, ) { - super( - SetFileIconThemeAction.ID, - SetFileIconThemeAction.TITLE.value, - SetFileIconThemeAction.DisabledClass, - false, - ); - this._register( - Event.any( - extensionService.onDidChangeExtensions, - workbenchThemeService.onDidFileIconThemeChange, - )(() => this.update(), this), - ); + super(SetFileIconThemeAction.ID, SetFileIconThemeAction.TITLE.value, SetFileIconThemeAction.DisabledClass, false); + this._register(Event.any(extensionService.onDidChangeExtensions, workbenchThemeService.onDidFileIconThemeChange)(() => this.update(), this)); this.update(); } update(): void { - this.workbenchThemeService - .getFileIconThemes() - .then((fileIconThemes) => { - this.enabled = this.computeEnablement(fileIconThemes); - this.class = this.enabled - ? SetFileIconThemeAction.EnabledClass - : SetFileIconThemeAction.DisabledClass; - }); + this.workbenchThemeService.getFileIconThemes().then(fileIconThemes => { + this.enabled = this.computeEnablement(fileIconThemes); + this.class = this.enabled ? SetFileIconThemeAction.EnabledClass : SetFileIconThemeAction.DisabledClass; + }); + } + + private computeEnablement(colorThemfileIconThemess: IWorkbenchFileIconTheme[]): boolean { + return !!this.extension && this.extension.state === ExtensionState.Installed && this.extensionEnablementService.isEnabledEnablementState(this.extension.enablementState) && colorThemfileIconThemess.some(th => isThemeFromExtension(th, this.extension)); } - private computeEnablement( - colorThemfileIconThemess: IWorkbenchFileIconTheme[], - ): boolean { - return ( - !!this.extension && - this.extension.state === ExtensionState.Installed && - this.extensionEnablementService.isEnabledEnablementState( - this.extension.enablementState, - ) && - colorThemfileIconThemess.some((th) => - isThemeFromExtension(th, this.extension), - ) - ); - } - - override async run( - { - showCurrentTheme, - ignoreFocusLost, - }: { showCurrentTheme: boolean; ignoreFocusLost: boolean } = { - showCurrentTheme: false, - ignoreFocusLost: false, - }, - ): Promise { - const fileIconThemes = - await this.workbenchThemeService.getFileIconThemes(); + override async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean; ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise { + const fileIconThemes = await this.workbenchThemeService.getFileIconThemes(); if (!this.computeEnablement(fileIconThemes)) { return; } const currentTheme = this.workbenchThemeService.getFileIconTheme(); const delayer = new Delayer(100); - const picks = getQuickPickEntries( - fileIconThemes, - currentTheme, - this.extension, - showCurrentTheme, - ); - const pickedTheme = await this.quickInputService.pick(picks, { - placeHolder: localize( - "select file icon theme", - "Select File Icon Theme", - ), - onDidFocus: (item) => - delayer.trigger(() => - this.workbenchThemeService.setFileIconTheme( - item.id, - undefined, - ), - ), - ignoreFocusLost, - }); - return this.workbenchThemeService.setFileIconTheme( - pickedTheme ? pickedTheme.id : currentTheme.id, - "auto", - ); + const picks = getQuickPickEntries(fileIconThemes, currentTheme, this.extension, showCurrentTheme); + const pickedTheme = await this.quickInputService.pick( + picks, + { + placeHolder: localize('select file icon theme', "Select File Icon Theme"), + onDidFocus: item => delayer.trigger(() => this.workbenchThemeService.setFileIconTheme(item.id, undefined)), + ignoreFocusLost + }); + return this.workbenchThemeService.setFileIconTheme(pickedTheme ? pickedTheme.id : currentTheme.id, 'auto'); } } export class SetProductIconThemeAction extends ExtensionAction { - static readonly ID = "workbench.extensions.action.setProductIconTheme"; - static readonly TITLE = localize2( - "workbench.extensions.action.setProductIconTheme", - "Set Product Icon Theme", - ); + + static readonly ID = 'workbench.extensions.action.setProductIconTheme'; + static readonly TITLE = localize2('workbench.extensions.action.setProductIconTheme', 'Set Product Icon Theme'); private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`; private static readonly DisabledClass = `${this.EnabledClass} disabled`; constructor( @IExtensionService extensionService: IExtensionService, - @IWorkbenchThemeService - private readonly workbenchThemeService: IWorkbenchThemeService, - @IQuickInputService - private readonly quickInputService: IQuickInputService, - @IWorkbenchExtensionEnablementService - private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, + @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, ) { - super( - SetProductIconThemeAction.ID, - SetProductIconThemeAction.TITLE.value, - SetProductIconThemeAction.DisabledClass, - false, - ); - this._register( - Event.any( - extensionService.onDidChangeExtensions, - workbenchThemeService.onDidProductIconThemeChange, - )(() => this.update(), this), - ); + super(SetProductIconThemeAction.ID, SetProductIconThemeAction.TITLE.value, SetProductIconThemeAction.DisabledClass, false); + this._register(Event.any(extensionService.onDidChangeExtensions, workbenchThemeService.onDidProductIconThemeChange)(() => this.update(), this)); this.update(); } update(): void { - this.workbenchThemeService - .getProductIconThemes() - .then((productIconThemes) => { - this.enabled = this.computeEnablement(productIconThemes); - this.class = this.enabled - ? SetProductIconThemeAction.EnabledClass - : SetProductIconThemeAction.DisabledClass; - }); + this.workbenchThemeService.getProductIconThemes().then(productIconThemes => { + this.enabled = this.computeEnablement(productIconThemes); + this.class = this.enabled ? SetProductIconThemeAction.EnabledClass : SetProductIconThemeAction.DisabledClass; + }); + } + + private computeEnablement(productIconThemes: IWorkbenchProductIconTheme[]): boolean { + return !!this.extension && this.extension.state === ExtensionState.Installed && this.extensionEnablementService.isEnabledEnablementState(this.extension.enablementState) && productIconThemes.some(th => isThemeFromExtension(th, this.extension)); } - private computeEnablement( - productIconThemes: IWorkbenchProductIconTheme[], - ): boolean { - return ( - !!this.extension && - this.extension.state === ExtensionState.Installed && - this.extensionEnablementService.isEnabledEnablementState( - this.extension.enablementState, - ) && - productIconThemes.some((th) => - isThemeFromExtension(th, this.extension), - ) - ); - } - - override async run( - { - showCurrentTheme, - ignoreFocusLost, - }: { showCurrentTheme: boolean; ignoreFocusLost: boolean } = { - showCurrentTheme: false, - ignoreFocusLost: false, - }, - ): Promise { - const productIconThemes = - await this.workbenchThemeService.getProductIconThemes(); + override async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean; ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise { + const productIconThemes = await this.workbenchThemeService.getProductIconThemes(); if (!this.computeEnablement(productIconThemes)) { return; } @@ -3380,53 +1983,30 @@ export class SetProductIconThemeAction extends ExtensionAction { const currentTheme = this.workbenchThemeService.getProductIconTheme(); const delayer = new Delayer(100); - const picks = getQuickPickEntries( - productIconThemes, - currentTheme, - this.extension, - showCurrentTheme, - ); - const pickedTheme = await this.quickInputService.pick(picks, { - placeHolder: localize( - "select product icon theme", - "Select Product Icon Theme", - ), - onDidFocus: (item) => - delayer.trigger(() => - this.workbenchThemeService.setProductIconTheme( - item.id, - undefined, - ), - ), - ignoreFocusLost, - }); - return this.workbenchThemeService.setProductIconTheme( - pickedTheme ? pickedTheme.id : currentTheme.id, - "auto", - ); + const picks = getQuickPickEntries(productIconThemes, currentTheme, this.extension, showCurrentTheme); + const pickedTheme = await this.quickInputService.pick( + picks, + { + placeHolder: localize('select product icon theme', "Select Product Icon Theme"), + onDidFocus: item => delayer.trigger(() => this.workbenchThemeService.setProductIconTheme(item.id, undefined)), + ignoreFocusLost + }); + return this.workbenchThemeService.setProductIconTheme(pickedTheme ? pickedTheme.id : currentTheme.id, 'auto'); } } export class SetLanguageAction extends ExtensionAction { - static readonly ID = "workbench.extensions.action.setDisplayLanguage"; - static readonly TITLE = localize2( - "workbench.extensions.action.setDisplayLanguage", - "Set Display Language", - ); + + static readonly ID = 'workbench.extensions.action.setDisplayLanguage'; + static readonly TITLE = localize2('workbench.extensions.action.setDisplayLanguage', 'Set Display Language'); private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} language`; private static readonly DisabledClass = `${this.EnabledClass} disabled`; constructor( - @IExtensionsWorkbenchService - private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, ) { - super( - SetLanguageAction.ID, - SetLanguageAction.TITLE.value, - SetLanguageAction.DisabledClass, - false, - ); + super(SetLanguageAction.ID, SetLanguageAction.TITLE.value, SetLanguageAction.DisabledClass, false); this.update(); } @@ -3439,10 +2019,7 @@ export class SetLanguageAction extends ExtensionAction { if (!this.extensionsWorkbenchService.canSetLanguage(this.extension)) { return; } - if ( - this.extension.gallery && - language === getLocale(this.extension.gallery) - ) { + if (this.extension.gallery && language === getLocale(this.extension.gallery)) { return; } this.enabled = true; @@ -3450,34 +2027,23 @@ export class SetLanguageAction extends ExtensionAction { } override async run(): Promise { - return ( - this.extension && - this.extensionsWorkbenchService.setLanguage(this.extension) - ); + return this.extension && this.extensionsWorkbenchService.setLanguage(this.extension); } } export class ClearLanguageAction extends ExtensionAction { - static readonly ID = "workbench.extensions.action.clearLanguage"; - static readonly TITLE = localize2( - "workbench.extensions.action.clearLanguage", - "Clear Display Language", - ); + + static readonly ID = 'workbench.extensions.action.clearLanguage'; + static readonly TITLE = localize2('workbench.extensions.action.clearLanguage', 'Clear Display Language'); private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} language`; private static readonly DisabledClass = `${this.EnabledClass} disabled`; constructor( - @IExtensionsWorkbenchService - private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @ILocaleService private readonly localeService: ILocaleService, ) { - super( - ClearLanguageAction.ID, - ClearLanguageAction.TITLE.value, - ClearLanguageAction.DisabledClass, - false, - ); + super(ClearLanguageAction.ID, ClearLanguageAction.TITLE.value, ClearLanguageAction.DisabledClass, false); this.update(); } @@ -3490,10 +2056,7 @@ export class ClearLanguageAction extends ExtensionAction { if (!this.extensionsWorkbenchService.canSetLanguage(this.extension)) { return; } - if ( - this.extension.gallery && - language !== getLocale(this.extension.gallery) - ) { + if (this.extension.gallery && language !== getLocale(this.extension.gallery)) { return; } this.enabled = true; @@ -3506,37 +2069,23 @@ export class ClearLanguageAction extends ExtensionAction { } export class ShowRecommendedExtensionAction extends Action { - static readonly ID = "workbench.extensions.action.showRecommendedExtension"; - static readonly LABEL = localize( - "showRecommendedExtension", - "Show Recommended Extension", - ); + + static readonly ID = 'workbench.extensions.action.showRecommendedExtension'; + static readonly LABEL = localize('showRecommendedExtension', "Show Recommended Extension"); private extensionId: string; constructor( extensionId: string, - @IExtensionsWorkbenchService - private readonly extensionWorkbenchService: IExtensionsWorkbenchService, + @IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService, ) { - super( - ShowRecommendedExtensionAction.ID, - ShowRecommendedExtensionAction.LABEL, - undefined, - false, - ); + super(ShowRecommendedExtensionAction.ID, ShowRecommendedExtensionAction.LABEL, undefined, false); this.extensionId = extensionId; } override async run(): Promise { - await this.extensionWorkbenchService.openSearch( - `@id:${this.extensionId}`, - ); - const [extension] = await this.extensionWorkbenchService.getExtensions( - [{ id: this.extensionId }], - { source: "install-recommendation" }, - CancellationToken.None, - ); + await this.extensionWorkbenchService.openSearch(`@id:${this.extensionId}`); + const [extension] = await this.extensionWorkbenchService.getExtensions([{ id: this.extensionId }], { source: 'install-recommendation' }, CancellationToken.None); if (extension) { return this.extensionWorkbenchService.open(extension); } @@ -3545,293 +2094,171 @@ export class ShowRecommendedExtensionAction extends Action { } export class InstallRecommendedExtensionAction extends Action { - static readonly ID = - "workbench.extensions.action.installRecommendedExtension"; - static readonly LABEL = localize( - "installRecommendedExtension", - "Install Recommended Extension", - ); + + static readonly ID = 'workbench.extensions.action.installRecommendedExtension'; + static readonly LABEL = localize('installRecommendedExtension', "Install Recommended Extension"); private extensionId: string; constructor( extensionId: string, - @IInstantiationService - private readonly instantiationService: IInstantiationService, - @IExtensionsWorkbenchService - private readonly extensionWorkbenchService: IExtensionsWorkbenchService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService, ) { - super( - InstallRecommendedExtensionAction.ID, - InstallRecommendedExtensionAction.LABEL, - undefined, - false, - ); + super(InstallRecommendedExtensionAction.ID, InstallRecommendedExtensionAction.LABEL, undefined, false); this.extensionId = extensionId; } override async run(): Promise { - await this.extensionWorkbenchService.openSearch( - `@id:${this.extensionId}`, - ); - const [extension] = await this.extensionWorkbenchService.getExtensions( - [{ id: this.extensionId }], - { source: "install-recommendation" }, - CancellationToken.None, - ); + await this.extensionWorkbenchService.openSearch(`@id:${this.extensionId}`); + const [extension] = await this.extensionWorkbenchService.getExtensions([{ id: this.extensionId }], { source: 'install-recommendation' }, CancellationToken.None); if (extension) { await this.extensionWorkbenchService.open(extension); try { await this.extensionWorkbenchService.install(extension); } catch (err) { - this.instantiationService - .createInstance( - PromptExtensionInstallFailureAction, - extension, - undefined, - extension.latestVersion, - InstallOperation.Install, - err, - ) - .run(); + this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, undefined, extension.latestVersion, InstallOperation.Install, err).run(); } } } } export class IgnoreExtensionRecommendationAction extends Action { - static readonly ID = "extensions.ignore"; + + static readonly ID = 'extensions.ignore'; private static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} ignore`; constructor( private readonly extension: IExtension, - @IExtensionIgnoredRecommendationsService - private readonly extensionRecommendationsManagementService: IExtensionIgnoredRecommendationsService, + @IExtensionIgnoredRecommendationsService private readonly extensionRecommendationsManagementService: IExtensionIgnoredRecommendationsService, ) { - super(IgnoreExtensionRecommendationAction.ID, "Ignore Recommendation"); + super(IgnoreExtensionRecommendationAction.ID, 'Ignore Recommendation'); this.class = IgnoreExtensionRecommendationAction.Class; - this.tooltip = localize( - "ignoreExtensionRecommendation", - "Do not recommend this extension again", - ); + this.tooltip = localize('ignoreExtensionRecommendation', "Do not recommend this extension again"); this.enabled = true; } public override run(): Promise { - this.extensionRecommendationsManagementService.toggleGlobalIgnoredRecommendation( - this.extension.identifier.id, - true, - ); + this.extensionRecommendationsManagementService.toggleGlobalIgnoredRecommendation(this.extension.identifier.id, true); return Promise.resolve(); } } export class UndoIgnoreExtensionRecommendationAction extends Action { - static readonly ID = "extensions.ignore"; + + static readonly ID = 'extensions.ignore'; private static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} undo-ignore`; constructor( private readonly extension: IExtension, - @IExtensionIgnoredRecommendationsService - private readonly extensionRecommendationsManagementService: IExtensionIgnoredRecommendationsService, + @IExtensionIgnoredRecommendationsService private readonly extensionRecommendationsManagementService: IExtensionIgnoredRecommendationsService, ) { - super(UndoIgnoreExtensionRecommendationAction.ID, "Undo"); + super(UndoIgnoreExtensionRecommendationAction.ID, 'Undo'); this.class = UndoIgnoreExtensionRecommendationAction.Class; - this.tooltip = localize("undo", "Undo"); + this.tooltip = localize('undo', "Undo"); this.enabled = true; } public override run(): Promise { - this.extensionRecommendationsManagementService.toggleGlobalIgnoredRecommendation( - this.extension.identifier.id, - false, - ); + this.extensionRecommendationsManagementService.toggleGlobalIgnoredRecommendation(this.extension.identifier.id, false); return Promise.resolve(); } } export abstract class AbstractConfigureRecommendedExtensionsAction extends Action { + constructor( id: string, label: string, - @IWorkspaceContextService - protected contextService: IWorkspaceContextService, + @IWorkspaceContextService protected contextService: IWorkspaceContextService, @IFileService private readonly fileService: IFileService, @ITextFileService private readonly textFileService: ITextFileService, @IEditorService protected editorService: IEditorService, - @IJSONEditingService - private readonly jsonEditingService: IJSONEditingService, - @ITextModelService - private readonly textModelResolverService: ITextModelService, + @IJSONEditingService private readonly jsonEditingService: IJSONEditingService, + @ITextModelService private readonly textModelResolverService: ITextModelService ) { super(id, label); } protected openExtensionsFile(extensionsFileResource: URI): Promise { - return this.getOrCreateExtensionsFile(extensionsFileResource).then( - ({ created, content }) => - this.getSelectionPosition(content, extensionsFileResource, [ - "recommendations", - ]).then((selection) => - this.editorService.openEditor({ + return this.getOrCreateExtensionsFile(extensionsFileResource) + .then(({ created, content }) => + this.getSelectionPosition(content, extensionsFileResource, ['recommendations']) + .then(selection => this.editorService.openEditor({ resource: extensionsFileResource, options: { pinned: created, - selection, - }, - }), - ), - (error) => - Promise.reject( - new Error( - localize( - "OpenExtensionsFile.failed", - "Unable to create 'extensions.json' file inside the '.vscode' folder ({0}).", - error, - ), - ), - ), - ); - } - - protected openWorkspaceConfigurationFile( - workspaceConfigurationFile: URI, - ): Promise { - return this.getOrUpdateWorkspaceConfigurationFile( - workspaceConfigurationFile, - ) - .then((content) => - this.getSelectionPosition( - content.value.toString(), - content.resource, - ["extensions", "recommendations"], - ), - ) - .then((selection) => - this.editorService.openEditor({ - resource: workspaceConfigurationFile, - options: { - selection, - forceReload: true, // because content has changed - }, - }), - ); + selection + } + })), + error => Promise.reject(new Error(localize('OpenExtensionsFile.failed', "Unable to create 'extensions.json' file inside the '.vscode' folder ({0}).", error)))); + } + + protected openWorkspaceConfigurationFile(workspaceConfigurationFile: URI): Promise { + return this.getOrUpdateWorkspaceConfigurationFile(workspaceConfigurationFile) + .then(content => this.getSelectionPosition(content.value.toString(), content.resource, ['extensions', 'recommendations'])) + .then(selection => this.editorService.openEditor({ + resource: workspaceConfigurationFile, + options: { + selection, + forceReload: true // because content has changed + } + })); } - private getOrUpdateWorkspaceConfigurationFile( - workspaceConfigurationFile: URI, - ): Promise { - return Promise.resolve( - this.fileService.readFile(workspaceConfigurationFile), - ).then((content) => { - const workspaceRecommendations = ( - json.parse(content.value.toString())["extensions"] - ); - if ( - !workspaceRecommendations || - !workspaceRecommendations.recommendations - ) { - return this.jsonEditingService - .write( - workspaceConfigurationFile, - [ - { - path: ["extensions"], - value: { recommendations: [] }, - }, - ], - true, - ) - .then(() => - this.fileService.readFile(workspaceConfigurationFile), - ); - } - return content; - }); + private getOrUpdateWorkspaceConfigurationFile(workspaceConfigurationFile: URI): Promise { + return Promise.resolve(this.fileService.readFile(workspaceConfigurationFile)) + .then(content => { + const workspaceRecommendations = json.parse(content.value.toString())['extensions']; + if (!workspaceRecommendations || !workspaceRecommendations.recommendations) { + return this.jsonEditingService.write(workspaceConfigurationFile, [{ path: ['extensions'], value: { recommendations: [] } }], true) + .then(() => this.fileService.readFile(workspaceConfigurationFile)); + } + return content; + }); } - private getSelectionPosition( - content: string, - resource: URI, - path: json.JSONPath, - ): Promise { + private getSelectionPosition(content: string, resource: URI, path: json.JSONPath): Promise { const tree = json.parseTree(content); const node = json.findNodeAtLocation(tree, path); if (node && node.parent && node.parent.children) { const recommendationsValueNode = node.parent.children[1]; - const lastExtensionNode = - recommendationsValueNode.children && - recommendationsValueNode.children.length - ? recommendationsValueNode.children[ - recommendationsValueNode.children.length - 1 - ] - : null; - const offset = lastExtensionNode - ? lastExtensionNode.offset + lastExtensionNode.length - : recommendationsValueNode.offset + 1; - return Promise.resolve( - this.textModelResolverService.createModelReference(resource), - ).then((reference) => { - const position = - reference.object.textEditorModel.getPositionAt(offset); - reference.dispose(); - return { - startLineNumber: position.lineNumber, - startColumn: position.column, - endLineNumber: position.lineNumber, - endColumn: position.column, - }; - }); + const lastExtensionNode = recommendationsValueNode.children && recommendationsValueNode.children.length ? recommendationsValueNode.children[recommendationsValueNode.children.length - 1] : null; + const offset = lastExtensionNode ? lastExtensionNode.offset + lastExtensionNode.length : recommendationsValueNode.offset + 1; + return Promise.resolve(this.textModelResolverService.createModelReference(resource)) + .then(reference => { + const position = reference.object.textEditorModel.getPositionAt(offset); + reference.dispose(); + return { + startLineNumber: position.lineNumber, + startColumn: position.column, + endLineNumber: position.lineNumber, + endColumn: position.column, + }; + }); } return Promise.resolve(undefined); } - private getOrCreateExtensionsFile( - extensionsFileResource: URI, - ): Promise<{ - created: boolean; - extensionsFileResource: URI; - content: string; - }> { - return Promise.resolve( - this.fileService.readFile(extensionsFileResource), - ).then( - (content) => { - return { - created: false, - extensionsFileResource, - content: content.value.toString(), - }; - }, - (err) => { - return this.textFileService - .write( - extensionsFileResource, - ExtensionsConfigurationInitialContent, - ) - .then(() => { - return { - created: true, - extensionsFileResource, - content: ExtensionsConfigurationInitialContent, - }; - }); - }, - ); + private getOrCreateExtensionsFile(extensionsFileResource: URI): Promise<{ created: boolean; extensionsFileResource: URI; content: string }> { + return Promise.resolve(this.fileService.readFile(extensionsFileResource)).then(content => { + return { created: false, extensionsFileResource, content: content.value.toString() }; + }, err => { + return this.textFileService.write(extensionsFileResource, ExtensionsConfigurationInitialContent).then(() => { + return { created: true, extensionsFileResource, content: ExtensionsConfigurationInitialContent }; + }); + }); } } export class ConfigureWorkspaceRecommendedExtensionsAction extends AbstractConfigureRecommendedExtensionsAction { - static readonly ID = - "workbench.extensions.action.configureWorkspaceRecommendedExtensions"; - static readonly LABEL = localize( - "configureWorkspaceRecommendedExtensions", - "Configure Recommended Extensions (Workspace)", - ); + + static readonly ID = 'workbench.extensions.action.configureWorkspaceRecommendedExtensions'; + static readonly LABEL = localize('configureWorkspaceRecommendedExtensions', "Configure Recommended Extensions (Workspace)"); constructor( id: string, @@ -3841,56 +2268,32 @@ export class ConfigureWorkspaceRecommendedExtensionsAction extends AbstractConfi @IWorkspaceContextService contextService: IWorkspaceContextService, @IEditorService editorService: IEditorService, @IJSONEditingService jsonEditingService: IJSONEditingService, - @ITextModelService textModelResolverService: ITextModelService, + @ITextModelService textModelResolverService: ITextModelService ) { - super( - id, - label, - contextService, - fileService, - textFileService, - editorService, - jsonEditingService, - textModelResolverService, - ); - this._register( - this.contextService.onDidChangeWorkbenchState( - () => this.update(), - this, - ), - ); + super(id, label, contextService, fileService, textFileService, editorService, jsonEditingService, textModelResolverService); + this._register(this.contextService.onDidChangeWorkbenchState(() => this.update(), this)); this.update(); } private update(): void { - this.enabled = - this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY; + this.enabled = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY; } public override run(): Promise { switch (this.contextService.getWorkbenchState()) { case WorkbenchState.FOLDER: - return this.openExtensionsFile( - this.contextService - .getWorkspace() - .folders[0].toResource(EXTENSIONS_CONFIG), - ); + return this.openExtensionsFile(this.contextService.getWorkspace().folders[0].toResource(EXTENSIONS_CONFIG)); case WorkbenchState.WORKSPACE: - return this.openWorkspaceConfigurationFile( - this.contextService.getWorkspace().configuration!, - ); + return this.openWorkspaceConfigurationFile(this.contextService.getWorkspace().configuration!); } return Promise.resolve(); } } export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends AbstractConfigureRecommendedExtensionsAction { - static readonly ID = - "workbench.extensions.action.configureWorkspaceFolderRecommendedExtensions"; - static readonly LABEL = localize( - "configureWorkspaceFolderRecommendedExtensions", - "Configure Recommended Extensions (Workspace Folder)", - ); + + static readonly ID = 'workbench.extensions.action.configureWorkspaceFolderRecommendedExtensions'; + static readonly LABEL = localize('configureWorkspaceFolderRecommendedExtensions', "Configure Recommended Extensions (Workspace Folder)"); constructor( id: string, @@ -3901,43 +2304,26 @@ export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends Abstrac @IEditorService editorService: IEditorService, @IJSONEditingService jsonEditingService: IJSONEditingService, @ITextModelService textModelResolverService: ITextModelService, - @ICommandService private readonly commandService: ICommandService, + @ICommandService private readonly commandService: ICommandService ) { - super( - id, - label, - contextService, - fileService, - textFileService, - editorService, - jsonEditingService, - textModelResolverService, - ); + super(id, label, contextService, fileService, textFileService, editorService, jsonEditingService, textModelResolverService); } public override run(): Promise { const folderCount = this.contextService.getWorkspace().folders.length; - const pickFolderPromise = - folderCount === 1 - ? Promise.resolve(this.contextService.getWorkspace().folders[0]) - : this.commandService.executeCommand( - PICK_WORKSPACE_FOLDER_COMMAND_ID, - ); - return Promise.resolve(pickFolderPromise).then((workspaceFolder) => { - if (workspaceFolder) { - return this.openExtensionsFile( - workspaceFolder.toResource(EXTENSIONS_CONFIG), - ); - } - return null; - }); + const pickFolderPromise = folderCount === 1 ? Promise.resolve(this.contextService.getWorkspace().folders[0]) : this.commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID); + return Promise.resolve(pickFolderPromise) + .then(workspaceFolder => { + if (workspaceFolder) { + return this.openExtensionsFile(workspaceFolder.toResource(EXTENSIONS_CONFIG)); + } + return null; + }); } } -export class ExtensionStatusLabelAction - extends Action - implements IExtensionContainer -{ +export class ExtensionStatusLabelAction extends Action implements IExtensionContainer { + private static readonly ENABLED_CLASS = `${ExtensionAction.TEXT_ACTION_CLASS} extension-status-label`; private static readonly DISABLED_CLASS = `${this.ENABLED_CLASS} hide`; @@ -3947,20 +2333,9 @@ export class ExtensionStatusLabelAction private enablementState: EnablementState | null = null; private _extension: IExtension | null = null; - get extension(): IExtension | null { - return this._extension; - } + get extension(): IExtension | null { return this._extension; } set extension(extension: IExtension | null) { - if ( - !( - this._extension && - extension && - areSameExtensions( - this._extension.identifier, - extension.identifier, - ) - ) - ) { + if (!(this._extension && extension && areSameExtensions(this._extension.identifier, extension.identifier))) { // Different extension. Reset this.initialStatus = null; this.status = null; @@ -3972,25 +2347,16 @@ export class ExtensionStatusLabelAction constructor( @IExtensionService private readonly extensionService: IExtensionService, - @IExtensionManagementServerService - private readonly extensionManagementServerService: IExtensionManagementServerService, - @IWorkbenchExtensionEnablementService - private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService ) { - super( - "extensions.action.statusLabel", - "", - ExtensionStatusLabelAction.DISABLED_CLASS, - false, - ); + super('extensions.action.statusLabel', '', ExtensionStatusLabelAction.DISABLED_CLASS, false); } update(): void { const label = this.computeLabel(); - this.label = label || ""; - this.class = label - ? ExtensionStatusLabelAction.ENABLED_CLASS - : ExtensionStatusLabelAction.DISABLED_CLASS; + this.label = label || ''; + this.class = label ? ExtensionStatusLabelAction.ENABLED_CLASS : ExtensionStatusLabelAction.DISABLED_CLASS; } private computeLabel(): string | null { @@ -4009,102 +2375,51 @@ export class ExtensionStatusLabelAction this.enablementState = this.extension.enablementState; const canAddExtension = () => { - const runningExtension = this.extensionService.extensions.filter( - (e) => - areSameExtensions( - { id: e.identifier.value, uuid: e.uuid }, - this.extension!.identifier, - ), - )[0]; + const runningExtension = this.extensionService.extensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier))[0]; if (this.extension!.local) { - if ( - runningExtension && - this.extension!.version === runningExtension.version - ) { + if (runningExtension && this.extension!.version === runningExtension.version) { return true; } - return this.extensionService.canAddExtension( - toExtensionDescription(this.extension!.local), - ); + return this.extensionService.canAddExtension(toExtensionDescription(this.extension!.local)); } return false; }; const canRemoveExtension = () => { if (this.extension!.local) { - if ( - this.extensionService.extensions.every( - (e) => - !( - areSameExtensions( - { id: e.identifier.value, uuid: e.uuid }, - this.extension!.identifier, - ) && - this.extension!.server === - this.extensionManagementServerService.getExtensionManagementServer( - toExtension(e), - ) - ), - ) - ) { + if (this.extensionService.extensions.every(e => !(areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier) && this.extension!.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(e))))) { return true; } - return this.extensionService.canRemoveExtension( - toExtensionDescription(this.extension!.local), - ); + return this.extensionService.canRemoveExtension(toExtensionDescription(this.extension!.local)); } return false; }; if (currentStatus !== null) { - if ( - currentStatus === ExtensionState.Installing && - this.status === ExtensionState.Installed - ) { - if ( - this.initialStatus === ExtensionState.Uninstalled && - canAddExtension() - ) { - return localize("installed", "Installed"); + if (currentStatus === ExtensionState.Installing && this.status === ExtensionState.Installed) { + if (this.initialStatus === ExtensionState.Uninstalled && canAddExtension()) { + return localize('installed', "Installed"); } - if ( - this.initialStatus === ExtensionState.Installed && - this.version !== currentVersion && - canAddExtension() - ) { - return localize("updated", "Updated"); + if (this.initialStatus === ExtensionState.Installed && this.version !== currentVersion && canAddExtension()) { + return localize('updated', "Updated"); } return null; } - if ( - currentStatus === ExtensionState.Uninstalling && - this.status === ExtensionState.Uninstalled - ) { + if (currentStatus === ExtensionState.Uninstalling && this.status === ExtensionState.Uninstalled) { this.initialStatus = this.status; - return canRemoveExtension() - ? localize("uninstalled", "Uninstalled") - : null; + return canRemoveExtension() ? localize('uninstalled', "Uninstalled") : null; } } if (currentEnablementState !== null) { - const currentlyEnabled = - this.extensionEnablementService.isEnabledEnablementState( - currentEnablementState, - ); - const enabled = - this.extensionEnablementService.isEnabledEnablementState( - this.enablementState, - ); + const currentlyEnabled = this.extensionEnablementService.isEnabledEnablementState(currentEnablementState); + const enabled = this.extensionEnablementService.isEnabledEnablementState(this.enablementState); if (!currentlyEnabled && enabled) { - return canAddExtension() - ? localize("enabled", "Enabled") - : null; + return canAddExtension() ? localize('enabled', "Enabled") : null; } if (currentlyEnabled && !enabled) { - return canRemoveExtension() - ? localize("disabled", "Disabled") - : null; + return canRemoveExtension() ? localize('disabled', "Disabled") : null; } + } return null; @@ -4113,58 +2428,32 @@ export class ExtensionStatusLabelAction override run(): Promise { return Promise.resolve(); } + } export class ToggleSyncExtensionAction extends DropDownExtensionAction { + private static readonly IGNORED_SYNC_CLASS = `${ExtensionAction.ICON_ACTION_CLASS} extension-sync ${ThemeIcon.asClassName(syncIgnoredIcon)}`; private static readonly SYNC_CLASS = `${this.ICON_ACTION_CLASS} extension-sync ${ThemeIcon.asClassName(syncEnabledIcon)}`; constructor( - @IConfigurationService - private readonly configurationService: IConfigurationService, - @IExtensionsWorkbenchService - private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IUserDataSyncEnablementService - private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @IInstantiationService instantiationService: IInstantiationService, ) { - super( - "extensions.sync", - "", - ToggleSyncExtensionAction.SYNC_CLASS, - false, - instantiationService, - ); - this._register( - Event.filter( - this.configurationService.onDidChangeConfiguration, - (e) => e.affectsConfiguration("settingsSync.ignoredExtensions"), - )(() => this.update()), - ); - this._register( - userDataSyncEnablementService.onDidChangeEnablement(() => - this.update(), - ), - ); + super('extensions.sync', '', ToggleSyncExtensionAction.SYNC_CLASS, false, instantiationService); + this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('settingsSync.ignoredExtensions'))(() => this.update())); + this._register(userDataSyncEnablementService.onDidChangeEnablement(() => this.update())); this.update(); } update(): void { - this.enabled = - !!this.extension && - this.userDataSyncEnablementService.isEnabled() && - this.extension.state === ExtensionState.Installed; + this.enabled = !!this.extension && this.userDataSyncEnablementService.isEnabled() && this.extension.state === ExtensionState.Installed; if (this.extension) { - const isIgnored = - this.extensionsWorkbenchService.isExtensionIgnoredToSync( - this.extension, - ); - this.class = isIgnored - ? ToggleSyncExtensionAction.IGNORED_SYNC_CLASS - : ToggleSyncExtensionAction.SYNC_CLASS; - this.tooltip = isIgnored - ? localize("ignored", "This extension is ignored during sync") - : localize("synced", "This extension is synced"); + const isIgnored = this.extensionsWorkbenchService.isExtensionIgnoredToSync(this.extension); + this.class = isIgnored ? ToggleSyncExtensionAction.IGNORED_SYNC_CLASS : ToggleSyncExtensionAction.SYNC_CLASS; + this.tooltip = isIgnored ? localize('ignored', "This extension is ignored during sync") : localize('synced', "This extension is synced"); } } @@ -4173,43 +2462,25 @@ export class ToggleSyncExtensionAction extends DropDownExtensionAction { actionGroups: [ [ new Action( - "extensions.syncignore", - this.extensionsWorkbenchService.isExtensionIgnoredToSync( - this.extension!, - ) - ? localize("sync", "Sync this extension") - : localize( - "do not sync", - "Do not sync this extension", - ), - undefined, - true, - () => - this.extensionsWorkbenchService.toggleExtensionIgnoredToSync( - this.extension!, - ), - ), - ], - ], - disposeActionsOnHide: true, + 'extensions.syncignore', + this.extensionsWorkbenchService.isExtensionIgnoredToSync(this.extension!) ? localize('sync', "Sync this extension") : localize('do not sync', "Do not sync this extension") + , undefined, true, () => this.extensionsWorkbenchService.toggleExtensionIgnoredToSync(this.extension!)) + ] + ], disposeActionsOnHide: true }); } } -export type ExtensionStatus = { - readonly message: IMarkdownString; - readonly icon?: ThemeIcon; -}; +export type ExtensionStatus = { readonly message: IMarkdownString; readonly icon?: ThemeIcon }; export class ExtensionStatusAction extends ExtensionAction { + private static readonly CLASS = `${ExtensionAction.ICON_ACTION_CLASS} extension-status`; updateWhenCounterExtensionChanges: boolean = true; private _status: ExtensionStatus[] = []; - get status(): ExtensionStatus[] { - return this._status; - } + get status(): ExtensionStatus[] { return this._status; } private readonly _onDidChangeStatus = this._register(new Emitter()); readonly onDidChangeStatus = this._onDidChangeStatus.event; @@ -4217,51 +2488,25 @@ export class ExtensionStatusAction extends ExtensionAction { private readonly updateThrottler = new Throttler(); constructor( - @IExtensionManagementServerService - private readonly extensionManagementServerService: IExtensionManagementServerService, + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @ILabelService private readonly labelService: ILabelService, @ICommandService private readonly commandService: ICommandService, - @IWorkspaceTrustEnablementService - private readonly workspaceTrustEnablementService: IWorkspaceTrustEnablementService, - @IWorkspaceTrustManagementService - private readonly workspaceTrustService: IWorkspaceTrustManagementService, - @IExtensionsWorkbenchService - private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IWorkspaceTrustEnablementService private readonly workspaceTrustEnablementService: IWorkspaceTrustEnablementService, + @IWorkspaceTrustManagementService private readonly workspaceTrustService: IWorkspaceTrustManagementService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionService private readonly extensionService: IExtensionService, - @IExtensionManifestPropertiesService - private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, - @IWorkspaceContextService - private readonly contextService: IWorkspaceContextService, + @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IProductService private readonly productService: IProductService, - @IAllowedExtensionsService - private readonly allowedExtensionsService: IAllowedExtensionsService, - @IWorkbenchExtensionEnablementService - private readonly workbenchExtensionEnablementService: IWorkbenchExtensionEnablementService, - @IExtensionFeaturesManagementService - private readonly extensionFeaturesManagementService: IExtensionFeaturesManagementService, + @IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService, + @IWorkbenchExtensionEnablementService private readonly workbenchExtensionEnablementService: IWorkbenchExtensionEnablementService, + @IExtensionFeaturesManagementService private readonly extensionFeaturesManagementService: IExtensionFeaturesManagementService, ) { - super( - "extensions.status", - "", - `${ExtensionStatusAction.CLASS} hide`, - false, - ); - this._register( - this.labelService.onDidChangeFormatters(() => this.update(), this), - ); - this._register( - this.extensionService.onDidChangeExtensions(() => this.update()), - ); - this._register( - this.extensionFeaturesManagementService.onDidChangeAccessData(() => - this.update(), - ), - ); - this._register( - allowedExtensionsService.onDidChangeAllowedExtensions(() => - this.update(), - ), - ); + super('extensions.status', '', `${ExtensionStatusAction.CLASS} hide`, false); + this._register(this.labelService.onDidChangeFormatters(() => this.update(), this)); + this._register(this.extensionService.onDidChangeExtensions(() => this.update())); + this._register(this.extensionFeaturesManagementService.onDidChangeAccessData(() => this.update())); + this._register(allowedExtensionsService.onDidChangeAllowedExtensions(() => this.update())); this.update(); } @@ -4278,83 +2523,26 @@ export class ExtensionStatusAction extends ExtensionAction { } if (this.extension.isMalicious) { - this.updateStatus( - { - icon: warningIcon, - message: new MarkdownString( - localize( - "malicious tooltip", - "This extension was reported to be problematic.", - ), - ), - }, - true, - ); + this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('malicious tooltip', "This extension was reported to be problematic.")) }, true); return; } - if ( - this.extension.state === ExtensionState.Uninstalled && - this.extension.gallery && - !this.extension.gallery.isSigned - ) { - this.updateStatus( - { - icon: warningIcon, - message: new MarkdownString( - localize( - "not signed tooltip", - "This extension is not signed by the Extension Marketplace.", - ), - ), - }, - true, - ); + if (this.extension.state === ExtensionState.Uninstalled && this.extension.gallery && !this.extension.gallery.isSigned) { + this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('not signed tooltip', "This extension is not signed by the Extension Marketplace.")) }, true); return; } if (this.extension.deprecationInfo) { if (this.extension.deprecationInfo.extension) { const link = `[${this.extension.deprecationInfo.extension.displayName}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.deprecationInfo.extension.id]))}`)})`; - this.updateStatus( - { - icon: warningIcon, - message: new MarkdownString( - localize( - "deprecated with alternate extension tooltip", - "This extension is deprecated. Use the {0} extension instead.", - link, - ), - ), - }, - true, - ); + this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('deprecated with alternate extension tooltip', "This extension is deprecated. Use the {0} extension instead.", link)) }, true); } else if (this.extension.deprecationInfo.settings) { - const link = `[${localize("settings", "settings")}](${URI.parse(`command:workbench.action.openSettings?${encodeURIComponent(JSON.stringify([this.extension.deprecationInfo.settings.map((setting) => `@id:${setting}`).join(" ")]))}`)})`; - this.updateStatus( - { - icon: warningIcon, - message: new MarkdownString( - localize( - "deprecated with alternate settings tooltip", - "This extension is deprecated as this functionality is now built-in to VS Code. Configure these {0} to use this functionality.", - link, - ), - ), - }, - true, - ); + const link = `[${localize('settings', "settings")}](${URI.parse(`command:workbench.action.openSettings?${encodeURIComponent(JSON.stringify([this.extension.deprecationInfo.settings.map(setting => `@id:${setting}`).join(' ')]))}`)})`; + this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('deprecated with alternate settings tooltip', "This extension is deprecated as this functionality is now built-in to VS Code. Configure these {0} to use this functionality.", link)) }, true); } else { - const message = new MarkdownString( - localize( - "deprecated tooltip", - "This extension is deprecated as it is no longer being maintained.", - ), - ); + const message = new MarkdownString(localize('deprecated tooltip', "This extension is deprecated as it is no longer being maintained.")); if (this.extension.deprecationInfo.additionalInfo) { - message.appendMarkdown( - ` ${this.extension.deprecationInfo.additionalInfo}`, - ); + message.appendMarkdown(` ${this.extension.deprecationInfo.additionalInfo}`); } this.updateStatus({ icon: warningIcon, message }, true); } @@ -4365,56 +2553,32 @@ export class ExtensionStatusAction extends ExtensionAction { return; } - if ( - this.extension.outdated && - this.extensionsWorkbenchService.isAutoUpdateEnabledFor( - this.extension, - ) - ) { - const message = - await this.extensionsWorkbenchService.shouldRequireConsentToUpdate( - this.extension, - ); + if (this.extension.outdated && this.extensionsWorkbenchService.isAutoUpdateEnabledFor(this.extension)) { + const message = await this.extensionsWorkbenchService.shouldRequireConsentToUpdate(this.extension); if (message) { const markdown = new MarkdownString(); markdown.appendMarkdown(`${message} `); markdown.appendMarkdown( - localize( - "auto update message", - "Please [review the extension]({0}) and update it manually.", + localize('auto update message', "Please [review the extension]({0}) and update it manually.", this.extension.hasChangelog() - ? URI.parse( - `command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.identifier.id, ExtensionEditorTab.Changelog]))}`, - ).toString() + ? URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.identifier.id, ExtensionEditorTab.Changelog]))}`).toString() : this.extension.repository ? this.extension.repository - : URI.parse( - `command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.identifier.id]))}`, - ).toString(), - ), - ); - this.updateStatus( - { icon: warningIcon, message: markdown }, - true, - ); + : URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.identifier.id]))}`).toString() + )); + this.updateStatus({ icon: warningIcon, message: markdown }, true); } } - if ( - this.extension.gallery && - this.extension.state === ExtensionState.Uninstalled - ) { - const result = await this.extensionsWorkbenchService.canInstall( - this.extension, - ); + if (this.extension.gallery && this.extension.state === ExtensionState.Uninstalled) { + const result = await this.extensionsWorkbenchService.canInstall(this.extension); if (result !== true) { this.updateStatus({ icon: warningIcon, message: result }, true); return; } } - if ( - !this.extension.local || + if (!this.extension.local || !this.extension.server || this.extension.state !== ExtensionState.Installed ) { @@ -4422,258 +2586,88 @@ export class ExtensionStatusAction extends ExtensionAction { } // Extension is disabled by its dependency - const result = this.allowedExtensionsService.isAllowed( - this.extension.local, - ); + const result = this.allowedExtensionsService.isAllowed(this.extension.local); if (result !== true) { - this.updateStatus( - { - icon: warningIcon, - message: new MarkdownString( - localize( - "disabled - not allowed", - "This extension is disabled because {0}", - result.value, - ), - ), - }, - true, - ); + this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('disabled - not allowed', "This extension is disabled because {0}", result.value)) }, true); return; } // Extension is disabled by environment - if ( - this.extension.enablementState === - EnablementState.DisabledByEnvironment - ) { - this.updateStatus( - { - message: new MarkdownString( - localize( - "disabled by environment", - "This extension is disabled by the environment.", - ), - ), - }, - true, - ); + if (this.extension.enablementState === EnablementState.DisabledByEnvironment) { + this.updateStatus({ message: new MarkdownString(localize('disabled by environment', "This extension is disabled by the environment.")) }, true); return; } // Extension is enabled by environment - if ( - this.extension.enablementState === - EnablementState.EnabledByEnvironment - ) { - this.updateStatus( - { - message: new MarkdownString( - localize( - "enabled by environment", - "This extension is enabled because it is required in the current environment.", - ), - ), - }, - true, - ); + if (this.extension.enablementState === EnablementState.EnabledByEnvironment) { + this.updateStatus({ message: new MarkdownString(localize('enabled by environment', "This extension is enabled because it is required in the current environment.")) }, true); return; } // Extension is disabled by virtual workspace - if ( - this.extension.enablementState === - EnablementState.DisabledByVirtualWorkspace - ) { - const details = getWorkspaceSupportTypeMessage( - this.extension.local.manifest.capabilities?.virtualWorkspaces, - ); - this.updateStatus( - { - icon: infoIcon, - message: new MarkdownString( - details - ? escapeMarkdownSyntaxTokens(details) - : localize( - "disabled because of virtual workspace", - "This extension has been disabled because it does not support virtual workspaces.", - ), - ), - }, - true, - ); + if (this.extension.enablementState === EnablementState.DisabledByVirtualWorkspace) { + const details = getWorkspaceSupportTypeMessage(this.extension.local.manifest.capabilities?.virtualWorkspaces); + this.updateStatus({ icon: infoIcon, message: new MarkdownString(details ? escapeMarkdownSyntaxTokens(details) : localize('disabled because of virtual workspace', "This extension has been disabled because it does not support virtual workspaces.")) }, true); return; } // Limited support in Virtual Workspace if (isVirtualWorkspace(this.contextService.getWorkspace())) { - const virtualSupportType = - this.extensionManifestPropertiesService.getExtensionVirtualWorkspaceSupportType( - this.extension.local.manifest, - ); - const details = getWorkspaceSupportTypeMessage( - this.extension.local.manifest.capabilities?.virtualWorkspaces, - ); - if (virtualSupportType === "limited" || details) { - this.updateStatus( - { - icon: warningIcon, - message: new MarkdownString( - details - ? escapeMarkdownSyntaxTokens(details) - : localize( - "extension limited because of virtual workspace", - "This extension has limited features because the current workspace is virtual.", - ), - ), - }, - true, - ); + const virtualSupportType = this.extensionManifestPropertiesService.getExtensionVirtualWorkspaceSupportType(this.extension.local.manifest); + const details = getWorkspaceSupportTypeMessage(this.extension.local.manifest.capabilities?.virtualWorkspaces); + if (virtualSupportType === 'limited' || details) { + this.updateStatus({ icon: warningIcon, message: new MarkdownString(details ? escapeMarkdownSyntaxTokens(details) : localize('extension limited because of virtual workspace', "This extension has limited features because the current workspace is virtual.")) }, true); return; } } - if ( - !this.workspaceTrustService.isWorkspaceTrusted() && + if (!this.workspaceTrustService.isWorkspaceTrusted() && // Extension is disabled by untrusted workspace - (this.extension.enablementState === - EnablementState.DisabledByTrustRequirement || + (this.extension.enablementState === EnablementState.DisabledByTrustRequirement || // All disabled dependencies of the extension are disabled by untrusted workspace - (this.extension.enablementState === - EnablementState.DisabledByExtensionDependency && - this.workbenchExtensionEnablementService - .getDependenciesEnablementStates(this.extension.local) - .every( - ([, enablementState]) => - this.workbenchExtensionEnablementService.isEnabledEnablementState( - enablementState, - ) || - enablementState === - EnablementState.DisabledByTrustRequirement, - ))) - ) { + (this.extension.enablementState === EnablementState.DisabledByExtensionDependency && this.workbenchExtensionEnablementService.getDependenciesEnablementStates(this.extension.local).every(([, enablementState]) => this.workbenchExtensionEnablementService.isEnabledEnablementState(enablementState) || enablementState === EnablementState.DisabledByTrustRequirement)))) { this.enabled = true; - const untrustedDetails = getWorkspaceSupportTypeMessage( - this.extension.local.manifest.capabilities?.untrustedWorkspaces, - ); - this.updateStatus( - { - icon: trustIcon, - message: new MarkdownString( - untrustedDetails - ? escapeMarkdownSyntaxTokens(untrustedDetails) - : localize( - "extension disabled because of trust requirement", - "This extension has been disabled because the current workspace is not trusted.", - ), - ), - }, - true, - ); + const untrustedDetails = getWorkspaceSupportTypeMessage(this.extension.local.manifest.capabilities?.untrustedWorkspaces); + this.updateStatus({ icon: trustIcon, message: new MarkdownString(untrustedDetails ? escapeMarkdownSyntaxTokens(untrustedDetails) : localize('extension disabled because of trust requirement', "This extension has been disabled because the current workspace is not trusted.")) }, true); return; } // Limited support in Untrusted Workspace - if ( - this.workspaceTrustEnablementService.isWorkspaceTrustEnabled() && - !this.workspaceTrustService.isWorkspaceTrusted() - ) { - const untrustedSupportType = - this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType( - this.extension.local.manifest, - ); - const untrustedDetails = getWorkspaceSupportTypeMessage( - this.extension.local.manifest.capabilities?.untrustedWorkspaces, - ); - if (untrustedSupportType === "limited" || untrustedDetails) { + if (this.workspaceTrustEnablementService.isWorkspaceTrustEnabled() && !this.workspaceTrustService.isWorkspaceTrusted()) { + const untrustedSupportType = this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(this.extension.local.manifest); + const untrustedDetails = getWorkspaceSupportTypeMessage(this.extension.local.manifest.capabilities?.untrustedWorkspaces); + if (untrustedSupportType === 'limited' || untrustedDetails) { this.enabled = true; - this.updateStatus( - { - icon: trustIcon, - message: new MarkdownString( - untrustedDetails - ? escapeMarkdownSyntaxTokens(untrustedDetails) - : localize( - "extension limited because of trust requirement", - "This extension has limited features because the current workspace is not trusted.", - ), - ), - }, - true, - ); + this.updateStatus({ icon: trustIcon, message: new MarkdownString(untrustedDetails ? escapeMarkdownSyntaxTokens(untrustedDetails) : localize('extension limited because of trust requirement', "This extension has limited features because the current workspace is not trusted.")) }, true); return; } } // Extension is disabled by extension kind - if ( - this.extension.enablementState === - EnablementState.DisabledByExtensionKind - ) { - if ( - !this.extensionsWorkbenchService.installed.some( - (e) => - areSameExtensions( - e.identifier, - this.extension!.identifier, - ) && e.server !== this.extension!.server, - ) - ) { + if (this.extension.enablementState === EnablementState.DisabledByExtensionKind) { + if (!this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension!.identifier) && e.server !== this.extension!.server)) { let message; // Extension on Local Server - if ( - this.extensionManagementServerService - .localExtensionManagementServer === - this.extension.server - ) { - if ( - this.extensionManifestPropertiesService.prefersExecuteOnWorkspace( - this.extension.local.manifest, - ) - ) { - if ( - this.extensionManagementServerService - .remoteExtensionManagementServer - ) { - message = new MarkdownString( - `${localize("Install in remote server to enable", "This extension is disabled in this workspace because it is defined to run in the Remote Extension Host. Please install the extension in '{0}' to enable.", this.extensionManagementServerService.remoteExtensionManagementServer.label)} [${localize("learn more", "Learn More")}](https://code.visualstudio.com/api/advanced-topics/remote-extensions#architecture-and-extension-kinds)`, - ); + if (this.extensionManagementServerService.localExtensionManagementServer === this.extension.server) { + if (this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(this.extension.local.manifest)) { + if (this.extensionManagementServerService.remoteExtensionManagementServer) { + message = new MarkdownString(`${localize('Install in remote server to enable', "This extension is disabled in this workspace because it is defined to run in the Remote Extension Host. Please install the extension in '{0}' to enable.", this.extensionManagementServerService.remoteExtensionManagementServer.label)} [${localize('learn more', "Learn More")}](https://code.visualstudio.com/api/advanced-topics/remote-extensions#architecture-and-extension-kinds)`); } } } // Extension on Remote Server - else if ( - this.extensionManagementServerService - .remoteExtensionManagementServer === - this.extension.server - ) { - if ( - this.extensionManifestPropertiesService.prefersExecuteOnUI( - this.extension.local.manifest, - ) - ) { - if ( - this.extensionManagementServerService - .localExtensionManagementServer - ) { - message = new MarkdownString( - `${localize("Install in local server to enable", "This extension is disabled in this workspace because it is defined to run in the Local Extension Host. Please install the extension locally to enable.", this.extensionManagementServerService.remoteExtensionManagementServer.label)} [${localize("learn more", "Learn More")}](https://code.visualstudio.com/api/advanced-topics/remote-extensions#architecture-and-extension-kinds)`, - ); + else if (this.extensionManagementServerService.remoteExtensionManagementServer === this.extension.server) { + if (this.extensionManifestPropertiesService.prefersExecuteOnUI(this.extension.local.manifest)) { + if (this.extensionManagementServerService.localExtensionManagementServer) { + message = new MarkdownString(`${localize('Install in local server to enable', "This extension is disabled in this workspace because it is defined to run in the Local Extension Host. Please install the extension locally to enable.", this.extensionManagementServerService.remoteExtensionManagementServer.label)} [${localize('learn more', "Learn More")}](https://code.visualstudio.com/api/advanced-topics/remote-extensions#architecture-and-extension-kinds)`); } else if (isWeb) { - message = new MarkdownString( - `${localize("Defined to run in desktop", "This extension is disabled because it is defined to run only in {0} for the Desktop.", this.productService.nameLong)} [${localize("learn more", "Learn More")}](https://code.visualstudio.com/api/advanced-topics/remote-extensions#architecture-and-extension-kinds)`, - ); + message = new MarkdownString(`${localize('Defined to run in desktop', "This extension is disabled because it is defined to run only in {0} for the Desktop.", this.productService.nameLong)} [${localize('learn more', "Learn More")}](https://code.visualstudio.com/api/advanced-topics/remote-extensions#architecture-and-extension-kinds)`); } } } // Extension on Web Server - else if ( - this.extensionManagementServerService - .webExtensionManagementServer === this.extension.server - ) { - message = new MarkdownString( - `${localize("Cannot be enabled", "This extension is disabled because it is not supported in {0} for the Web.", this.productService.nameLong)} [${localize("learn more", "Learn More")}](https://code.visualstudio.com/api/advanced-topics/remote-extensions#architecture-and-extension-kinds)`, - ); + else if (this.extensionManagementServerService.webExtensionManagementServer === this.extension.server) { + message = new MarkdownString(`${localize('Cannot be enabled', "This extension is disabled because it is not supported in {0} for the Web.", this.productService.nameLong)} [${localize('learn more', "Learn More")}](https://code.visualstudio.com/api/advanced-topics/remote-extensions#architecture-and-extension-kinds)`); } if (message) { this.updateStatus({ icon: warningIcon, message }, true); @@ -4682,323 +2676,107 @@ export class ExtensionStatusAction extends ExtensionAction { } } - const extensionId = new ExtensionIdentifier( - this.extension.identifier.id, - ); - const features = Registry.as( - Extensions.ExtensionFeaturesRegistry, - ).getExtensionFeatures(); + const extensionId = new ExtensionIdentifier(this.extension.identifier.id); + const features = Registry.as(Extensions.ExtensionFeaturesRegistry).getExtensionFeatures(); for (const feature of features) { - const status = - this.extensionFeaturesManagementService.getAccessData( - extensionId, - feature.id, - )?.current?.status; - const manageAccessLink = `[${localize("manage access", "Manage Access")}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.identifier.id, ExtensionEditorTab.Features, false, feature.id]))}`)})`; + const status = this.extensionFeaturesManagementService.getAccessData(extensionId, feature.id)?.current?.status; + const manageAccessLink = `[${localize('manage access', 'Manage Access')}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.identifier.id, ExtensionEditorTab.Features, false, feature.id]))}`)})`; if (status?.severity === Severity.Error) { - this.updateStatus( - { - icon: errorIcon, - message: new MarkdownString() - .appendText(status.message) - .appendMarkdown(` ${manageAccessLink}`), - }, - true, - ); + this.updateStatus({ icon: errorIcon, message: new MarkdownString().appendText(status.message).appendMarkdown(` ${manageAccessLink}`) }, true); return; } if (status?.severity === Severity.Warning) { - this.updateStatus( - { - icon: warningIcon, - message: new MarkdownString() - .appendText(status.message) - .appendMarkdown(` ${manageAccessLink}`), - }, - true, - ); + this.updateStatus({ icon: warningIcon, message: new MarkdownString().appendText(status.message).appendMarkdown(` ${manageAccessLink}`) }, true); return; } } // Remote Workspace - if ( - this.extensionManagementServerService - .remoteExtensionManagementServer - ) { + if (this.extensionManagementServerService.remoteExtensionManagementServer) { if (isLanguagePackExtension(this.extension.local.manifest)) { - if ( - !this.extensionsWorkbenchService.installed.some( - (e) => - areSameExtensions( - e.identifier, - this.extension!.identifier, - ) && e.server !== this.extension!.server, - ) - ) { - const message = - this.extension.server === - this.extensionManagementServerService - .localExtensionManagementServer - ? new MarkdownString( - localize( - "Install language pack also in remote server", - "Install the language pack extension on '{0}' to enable it there also.", - this.extensionManagementServerService - .remoteExtensionManagementServer - .label, - ), - ) - : new MarkdownString( - localize( - "Install language pack also locally", - "Install the language pack extension locally to enable it there also.", - ), - ); + if (!this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension!.identifier) && e.server !== this.extension!.server)) { + const message = this.extension.server === this.extensionManagementServerService.localExtensionManagementServer + ? new MarkdownString(localize('Install language pack also in remote server', "Install the language pack extension on '{0}' to enable it there also.", this.extensionManagementServerService.remoteExtensionManagementServer.label)) + : new MarkdownString(localize('Install language pack also locally', "Install the language pack extension locally to enable it there also.")); this.updateStatus({ icon: infoIcon, message }, true); } return; } - const runningExtension = this.extensionService.extensions.filter( - (e) => - areSameExtensions( - { id: e.identifier.value, uuid: e.uuid }, - this.extension!.identifier, - ), - )[0]; - const runningExtensionServer = runningExtension - ? this.extensionManagementServerService.getExtensionManagementServer( - toExtension(runningExtension), - ) - : null; - if ( - this.extension.server === - this.extensionManagementServerService - .localExtensionManagementServer && - runningExtensionServer === - this.extensionManagementServerService - .remoteExtensionManagementServer - ) { - if ( - this.extensionManifestPropertiesService.prefersExecuteOnWorkspace( - this.extension.local.manifest, - ) - ) { - this.updateStatus( - { - icon: infoIcon, - message: new MarkdownString( - `${localize("enabled remotely", "This extension is enabled in the Remote Extension Host because it prefers to run there.")} [${localize("learn more", "Learn More")}](https://code.visualstudio.com/api/advanced-topics/remote-extensions#architecture-and-extension-kinds)`, - ), - }, - true, - ); + const runningExtension = this.extensionService.extensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier))[0]; + const runningExtensionServer = runningExtension ? this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension)) : null; + if (this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) { + if (this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(this.extension.local.manifest)) { + this.updateStatus({ icon: infoIcon, message: new MarkdownString(`${localize('enabled remotely', "This extension is enabled in the Remote Extension Host because it prefers to run there.")} [${localize('learn more', "Learn More")}](https://code.visualstudio.com/api/advanced-topics/remote-extensions#architecture-and-extension-kinds)`) }, true); } return; } - if ( - this.extension.server === - this.extensionManagementServerService - .remoteExtensionManagementServer && - runningExtensionServer === - this.extensionManagementServerService - .localExtensionManagementServer - ) { - if ( - this.extensionManifestPropertiesService.prefersExecuteOnUI( - this.extension.local.manifest, - ) - ) { - this.updateStatus( - { - icon: infoIcon, - message: new MarkdownString( - `${localize("enabled locally", "This extension is enabled in the Local Extension Host because it prefers to run there.")} [${localize("learn more", "Learn More")}](https://code.visualstudio.com/api/advanced-topics/remote-extensions#architecture-and-extension-kinds)`, - ), - }, - true, - ); + if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer) { + if (this.extensionManifestPropertiesService.prefersExecuteOnUI(this.extension.local.manifest)) { + this.updateStatus({ icon: infoIcon, message: new MarkdownString(`${localize('enabled locally', "This extension is enabled in the Local Extension Host because it prefers to run there.")} [${localize('learn more', "Learn More")}](https://code.visualstudio.com/api/advanced-topics/remote-extensions#architecture-and-extension-kinds)`) }, true); } return; } - if ( - this.extension.server === - this.extensionManagementServerService - .remoteExtensionManagementServer && - runningExtensionServer === - this.extensionManagementServerService - .webExtensionManagementServer - ) { - if ( - this.extensionManifestPropertiesService.canExecuteOnWeb( - this.extension.local.manifest, - ) - ) { - this.updateStatus( - { - icon: infoIcon, - message: new MarkdownString( - `${localize("enabled in web worker", "This extension is enabled in the Web Worker Extension Host because it prefers to run there.")} [${localize("learn more", "Learn More")}](https://code.visualstudio.com/api/advanced-topics/remote-extensions#architecture-and-extension-kinds)`, - ), - }, - true, - ); + if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.webExtensionManagementServer) { + if (this.extensionManifestPropertiesService.canExecuteOnWeb(this.extension.local.manifest)) { + this.updateStatus({ icon: infoIcon, message: new MarkdownString(`${localize('enabled in web worker', "This extension is enabled in the Web Worker Extension Host because it prefers to run there.")} [${localize('learn more', "Learn More")}](https://code.visualstudio.com/api/advanced-topics/remote-extensions#architecture-and-extension-kinds)`) }, true); } return; } } // Extension is disabled by its dependency - if ( - this.extension.enablementState === - EnablementState.DisabledByExtensionDependency - ) { - this.updateStatus( - { - icon: warningIcon, - message: new MarkdownString( - localize( - "extension disabled because of dependency", - "This extension depends on an extension that is disabled.", - ), - ).appendMarkdown( - ` [${localize("dependencies", "Show Dependencies")}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.identifier.id, ExtensionEditorTab.Dependencies]))}`)})`, - ), - }, - true, - ); + if (this.extension.enablementState === EnablementState.DisabledByExtensionDependency) { + this.updateStatus({ + icon: warningIcon, + message: new MarkdownString(localize('extension disabled because of dependency', "This extension depends on an extension that is disabled.")) + .appendMarkdown(` [${localize('dependencies', "Show Dependencies")}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.identifier.id, ExtensionEditorTab.Dependencies]))}`)})`) + }, true); return; } if (!this.extension.local.isValid) { - const errors = this.extension.local.validations - .filter(([severity]) => severity === Severity.Error) - .map(([, message]) => message); - this.updateStatus( - { - icon: warningIcon, - message: new MarkdownString(errors.join(" ").trim()), - }, - true, - ); + const errors = this.extension.local.validations.filter(([severity]) => severity === Severity.Error).map(([, message]) => message); + this.updateStatus({ icon: warningIcon, message: new MarkdownString(errors.join(' ').trim()) }, true); return; } - const isEnabled = this.workbenchExtensionEnablementService.isEnabled( - this.extension.local, - ); - const isRunning = this.extensionService.extensions.some((e) => - areSameExtensions( - { id: e.identifier.value, uuid: e.uuid }, - this.extension!.identifier, - ), - ); + const isEnabled = this.workbenchExtensionEnablementService.isEnabled(this.extension.local); + const isRunning = this.extensionService.extensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier)); if (!this.extension.isWorkspaceScoped && isEnabled && isRunning) { - if ( - this.extension.enablementState === - EnablementState.EnabledWorkspace - ) { - this.updateStatus( - { - message: new MarkdownString( - localize( - "workspace enabled", - "This extension is enabled for this workspace by the user.", - ), - ), - }, - true, - ); + if (this.extension.enablementState === EnablementState.EnabledWorkspace) { + this.updateStatus({ message: new MarkdownString(localize('workspace enabled', "This extension is enabled for this workspace by the user.")) }, true); return; } - if ( - this.extensionManagementServerService - .localExtensionManagementServer && - this.extensionManagementServerService - .remoteExtensionManagementServer - ) { - if ( - this.extension.server === - this.extensionManagementServerService - .remoteExtensionManagementServer - ) { - this.updateStatus( - { - message: new MarkdownString( - localize( - "extension enabled on remote", - "Extension is enabled on '{0}'", - this.extension.server.label, - ), - ), - }, - true, - ); + if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { + if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer) { + this.updateStatus({ message: new MarkdownString(localize('extension enabled on remote', "Extension is enabled on '{0}'", this.extension.server.label)) }, true); return; } } - if ( - this.extension.enablementState === - EnablementState.EnabledGlobally - ) { + if (this.extension.enablementState === EnablementState.EnabledGlobally) { return; } } if (!isEnabled && !isRunning) { - if ( - this.extension.enablementState === - EnablementState.DisabledGlobally - ) { - this.updateStatus( - { - message: new MarkdownString( - localize( - "globally disabled", - "This extension is disabled globally by the user.", - ), - ), - }, - true, - ); + if (this.extension.enablementState === EnablementState.DisabledGlobally) { + this.updateStatus({ message: new MarkdownString(localize('globally disabled', "This extension is disabled globally by the user.")) }, true); return; } - if ( - this.extension.enablementState === - EnablementState.DisabledWorkspace - ) { - this.updateStatus( - { - message: new MarkdownString( - localize( - "workspace disabled", - "This extension is disabled for this workspace by the user.", - ), - ), - }, - true, - ); + if (this.extension.enablementState === EnablementState.DisabledWorkspace) { + this.updateStatus({ message: new MarkdownString(localize('workspace disabled', "This extension is disabled for this workspace by the user.")) }, true); return; } } } - private updateStatus( - status: ExtensionStatus | undefined, - updateClass: boolean, - ): void { + private updateStatus(status: ExtensionStatus | undefined, updateClass: boolean): void { if (status) { - if ( - this._status.some( - (s) => - s.message.value === status.message.value && - s.icon?.id === status.icon?.id, - ) - ) { + if (this._status.some(s => s.message.value === status.message.value && s.icon?.id === status.icon?.id)) { return; } } else { @@ -5011,36 +2789,32 @@ export class ExtensionStatusAction extends ExtensionAction { if (status) { this._status.push(status); this._status.sort((a, b) => - b.icon === trustIcon - ? -1 - : a.icon === trustIcon - ? 1 - : b.icon === errorIcon - ? -1 - : a.icon === errorIcon - ? 1 - : b.icon === warningIcon - ? -1 - : a.icon === warningIcon - ? 1 - : b.icon === infoIcon - ? -1 - : a.icon === infoIcon - ? 1 - : 0, + b.icon === trustIcon ? -1 : + a.icon === trustIcon ? 1 : + b.icon === errorIcon ? -1 : + a.icon === errorIcon ? 1 : + b.icon === warningIcon ? -1 : + a.icon === warningIcon ? 1 : + b.icon === infoIcon ? -1 : + a.icon === infoIcon ? 1 : + 0 ); } if (updateClass) { if (status?.icon === errorIcon) { this.class = `${ExtensionStatusAction.CLASS} extension-status-error ${ThemeIcon.asClassName(errorIcon)}`; - } else if (status?.icon === warningIcon) { + } + else if (status?.icon === warningIcon) { this.class = `${ExtensionStatusAction.CLASS} extension-status-warning ${ThemeIcon.asClassName(warningIcon)}`; - } else if (status?.icon === infoIcon) { + } + else if (status?.icon === infoIcon) { this.class = `${ExtensionStatusAction.CLASS} extension-status-info ${ThemeIcon.asClassName(infoIcon)}`; - } else if (status?.icon === trustIcon) { + } + else if (status?.icon === trustIcon) { this.class = `${ExtensionStatusAction.CLASS} ${ThemeIcon.asClassName(trustIcon)}`; - } else { + } + else { this.class = `${ExtensionStatusAction.CLASS} hide`; } } @@ -5049,185 +2823,108 @@ export class ExtensionStatusAction extends ExtensionAction { override async run(): Promise { if (this._status[0]?.icon === trustIcon) { - return this.commandService.executeCommand("workbench.trust.manage"); + return this.commandService.executeCommand('workbench.trust.manage'); } } } export class ReinstallAction extends Action { - static readonly ID = "workbench.extensions.action.reinstall"; - static readonly LABEL = localize("reinstall", "Reinstall Extension..."); + + static readonly ID = 'workbench.extensions.action.reinstall'; + static readonly LABEL = localize('reinstall', "Reinstall Extension..."); constructor( - id: string = ReinstallAction.ID, - label: string = ReinstallAction.LABEL, - @IExtensionsWorkbenchService - private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionManagementServerService - private readonly extensionManagementServerService: IExtensionManagementServerService, - @IQuickInputService - private readonly quickInputService: IQuickInputService, - @INotificationService - private readonly notificationService: INotificationService, + id: string = ReinstallAction.ID, label: string = ReinstallAction.LABEL, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @INotificationService private readonly notificationService: INotificationService, @IHostService private readonly hostService: IHostService, - @IExtensionService private readonly extensionService: IExtensionService, + @IExtensionService private readonly extensionService: IExtensionService ) { super(id, label); } override get enabled(): boolean { - return ( - this.extensionsWorkbenchService.local.filter( - (l) => !l.isBuiltin && l.local, - ).length > 0 - ); + return this.extensionsWorkbenchService.local.filter(l => !l.isBuiltin && l.local).length > 0; } override run(): Promise { - return this.quickInputService - .pick(this.getEntries(), { - placeHolder: localize( - "selectExtensionToReinstall", - "Select Extension to Reinstall", - ), - }) - .then((pick) => pick && this.reinstallExtension(pick.extension)); - } - - private getEntries(): Promise< - (IQuickPickItem & { extension: IExtension })[] - > { - return this.extensionsWorkbenchService.queryLocal().then((local) => { - const entries = local - .filter( - (extension) => - !extension.isBuiltin && - extension.server !== - this.extensionManagementServerService - .webExtensionManagementServer, - ) - .map((extension) => { - return { - id: extension.identifier.id, - label: extension.displayName, - description: extension.identifier.id, - extension, - }; - }); - return entries; - }); + return this.quickInputService.pick(this.getEntries(), { placeHolder: localize('selectExtensionToReinstall', "Select Extension to Reinstall") }) + .then(pick => pick && this.reinstallExtension(pick.extension)); + } + + private getEntries(): Promise<(IQuickPickItem & { extension: IExtension })[]> { + return this.extensionsWorkbenchService.queryLocal() + .then(local => { + const entries = local + .filter(extension => !extension.isBuiltin && extension.server !== this.extensionManagementServerService.webExtensionManagementServer) + .map(extension => { + return { + id: extension.identifier.id, + label: extension.displayName, + description: extension.identifier.id, + extension, + }; + }); + return entries; + }); } private reinstallExtension(extension: IExtension): Promise { - return this.extensionsWorkbenchService - .openSearch("@installed ") + return this.extensionsWorkbenchService.openSearch('@installed ') .then(() => { - return this.extensionsWorkbenchService - .reinstall(extension) - .then( - (extension) => { - const requireReload = !( - extension.local && - this.extensionService.canAddExtension( - toExtensionDescription(extension.local), - ) - ); - const message = requireReload - ? localize( - "ReinstallAction.successReload", - "Please reload Visual Studio Code to complete reinstalling the extension {0}.", - extension.identifier.id, - ) - : localize( - "ReinstallAction.success", - "Reinstalling the extension {0} is completed.", - extension.identifier.id, - ); - const actions = requireReload - ? [ - { - label: localize( - "InstallVSIXAction.reloadNow", - "Reload Now", - ), - run: () => - this.hostService.reload(), - }, - ] - : []; - this.notificationService.prompt( - Severity.Info, - message, - actions, - { sticky: true }, - ); - }, - (error) => this.notificationService.error(error), - ); + return this.extensionsWorkbenchService.reinstall(extension) + .then(extension => { + const requireReload = !(extension.local && this.extensionService.canAddExtension(toExtensionDescription(extension.local))); + const message = requireReload ? localize('ReinstallAction.successReload', "Please reload Visual Studio Code to complete reinstalling the extension {0}.", extension.identifier.id) + : localize('ReinstallAction.success', "Reinstalling the extension {0} is completed.", extension.identifier.id); + const actions = requireReload ? [{ + label: localize('InstallVSIXAction.reloadNow', "Reload Now"), + run: () => this.hostService.reload() + }] : []; + this.notificationService.prompt( + Severity.Info, + message, + actions, + { sticky: true } + ); + }, error => this.notificationService.error(error)); }); } } export class InstallSpecificVersionOfExtensionAction extends Action { - static readonly ID = "workbench.extensions.action.install.specificVersion"; - static readonly LABEL = localize( - "install previous version", - "Install Specific Version of Extension...", - ); + + static readonly ID = 'workbench.extensions.action.install.specificVersion'; + static readonly LABEL = localize('install previous version', "Install Specific Version of Extension..."); constructor( - id: string = InstallSpecificVersionOfExtensionAction.ID, - label: string = InstallSpecificVersionOfExtensionAction.LABEL, - @IExtensionsWorkbenchService - private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IQuickInputService - private readonly quickInputService: IQuickInputService, - @IInstantiationService - private readonly instantiationService: IInstantiationService, - @IWorkbenchExtensionEnablementService - private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, + id: string = InstallSpecificVersionOfExtensionAction.ID, label: string = InstallSpecificVersionOfExtensionAction.LABEL, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, ) { super(id, label); } override get enabled(): boolean { - return this.extensionsWorkbenchService.local.some((l) => - this.isEnabled(l), - ); + return this.extensionsWorkbenchService.local.some(l => this.isEnabled(l)); } override async run(): Promise { - const extensionPick = await this.quickInputService.pick( - this.getExtensionEntries(), - { - placeHolder: localize("selectExtension", "Select Extension"), - matchOnDetail: true, - }, - ); + const extensionPick = await this.quickInputService.pick(this.getExtensionEntries(), { placeHolder: localize('selectExtension', "Select Extension"), matchOnDetail: true }); if (extensionPick && extensionPick.extension) { - const action = this.instantiationService.createInstance( - InstallAnotherVersionAction, - extensionPick.extension, - true, - ); + const action = this.instantiationService.createInstance(InstallAnotherVersionAction, extensionPick.extension, true); await action.run(); - await this.extensionsWorkbenchService.openSearch( - extensionPick.extension.identifier.id, - ); + await this.extensionsWorkbenchService.openSearch(extensionPick.extension.identifier.id); } } private isEnabled(extension: IExtension): boolean { - const action = this.instantiationService.createInstance( - InstallAnotherVersionAction, - extension, - true, - ); - return ( - action.enabled && - !!extension.local && - this.extensionEnablementService.isEnabled(extension.local) - ); + const action = this.instantiationService.createInstance(InstallAnotherVersionAction, extension, true); + return action.enabled && !!extension.local && this.extensionEnablementService.isEnabled(extension.local); } private async getExtensionEntries(): Promise { @@ -5243,9 +2940,7 @@ export class InstallSpecificVersionOfExtensionAction extends Action { }); } } - return entries.sort((e1, e2) => - e1.extension.displayName.localeCompare(e2.extension.displayName), - ); + return entries.sort((e1, e2) => e1.extension.displayName.localeCompare(e2.extension.displayName)); } } @@ -5254,30 +2949,24 @@ interface IExtensionPickItem extends IQuickPickItem { } export abstract class AbstractInstallExtensionsInServerAction extends Action { + private extensions: IExtension[] | undefined = undefined; constructor( id: string, - @IExtensionsWorkbenchService - protected readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IQuickInputService - private readonly quickInputService: IQuickInputService, - @INotificationService - private readonly notificationService: INotificationService, + @IExtensionsWorkbenchService protected readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @INotificationService private readonly notificationService: INotificationService, @IProgressService private readonly progressService: IProgressService, ) { super(id); this.update(); - this.extensionsWorkbenchService - .queryLocal() - .then(() => this.updateExtensions()); - this._register( - this.extensionsWorkbenchService.onChange(() => { - if (this.extensions) { - this.updateExtensions(); - } - }), - ); + this.extensionsWorkbenchService.queryLocal().then(() => this.updateExtensions()); + this._register(this.extensionsWorkbenchService.onChange(() => { + if (this.extensions) { + this.updateExtensions(); + } + })); } private updateExtensions(): void { @@ -5286,9 +2975,7 @@ export abstract class AbstractInstallExtensionsInServerAction extends Action { } private update(): void { - this.enabled = - !!this.extensions && - this.getExtensionsToInstall(this.extensions).length > 0; + this.enabled = !!this.extensions && this.getExtensionsToInstall(this.extensions).length > 0; this.tooltip = this.label; } @@ -5302,8 +2989,7 @@ export abstract class AbstractInstallExtensionsInServerAction extends Action { } private async selectAndInstallExtensions(): Promise { - const quickPick = - this.quickInputService.createQuickPick(); + const quickPick = this.quickInputService.createQuickPick(); quickPick.busy = true; const disposable = quickPick.onDidAccept(() => { disposable.dispose(); @@ -5316,187 +3002,97 @@ export abstract class AbstractInstallExtensionsInServerAction extends Action { quickPick.busy = false; if (localExtensionsToInstall.length) { quickPick.title = this.getQuickPickTitle(); - quickPick.placeholder = localize( - "select extensions to install", - "Select extensions to install", - ); + quickPick.placeholder = localize('select extensions to install', "Select extensions to install"); quickPick.canSelectMany = true; - localExtensionsToInstall.sort((e1, e2) => - e1.displayName.localeCompare(e2.displayName), - ); - quickPick.items = localExtensionsToInstall.map( - (extension) => ({ - extension, - label: extension.displayName, - description: extension.version, - }), - ); + localExtensionsToInstall.sort((e1, e2) => e1.displayName.localeCompare(e2.displayName)); + quickPick.items = localExtensionsToInstall.map(extension => ({ extension, label: extension.displayName, description: extension.version })); } else { quickPick.hide(); quickPick.dispose(); this.notificationService.notify({ severity: Severity.Info, - message: localize( - "no local extensions", - "There are no extensions to install.", - ), + message: localize('no local extensions', "There are no extensions to install.") }); } } - private async onDidAccept( - selectedItems: ReadonlyArray, - ): Promise { + private async onDidAccept(selectedItems: ReadonlyArray): Promise { if (selectedItems.length) { - const localExtensionsToInstall = selectedItems - .filter((r) => !!r.extension) - .map((r) => r.extension); + const localExtensionsToInstall = selectedItems.filter(r => !!r.extension).map(r => r.extension); if (localExtensionsToInstall.length) { await this.progressService.withProgress( { location: ProgressLocation.Notification, - title: localize( - "installing extensions", - "Installing Extensions...", - ), + title: localize('installing extensions', "Installing Extensions...") }, - () => this.installExtensions(localExtensionsToInstall), - ); - this.notificationService.info( - localize( - "finished installing", - "Successfully installed extensions.", - ), - ); + () => this.installExtensions(localExtensionsToInstall)); + this.notificationService.info(localize('finished installing', "Successfully installed extensions.")); } } } protected abstract getQuickPickTitle(): string; - protected abstract getExtensionsToInstall( - local: IExtension[], - ): IExtension[]; - protected abstract installExtensions( - extensions: IExtension[], - ): Promise; + protected abstract getExtensionsToInstall(local: IExtension[]): IExtension[]; + protected abstract installExtensions(extensions: IExtension[]): Promise; } export class InstallLocalExtensionsInRemoteAction extends AbstractInstallExtensionsInServerAction { + constructor( - @IExtensionsWorkbenchService - extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, @IQuickInputService quickInputService: IQuickInputService, @IProgressService progressService: IProgressService, @INotificationService notificationService: INotificationService, - @IExtensionManagementServerService - private readonly extensionManagementServerService: IExtensionManagementServerService, - @IExtensionGalleryService - private readonly extensionGalleryService: IExtensionGalleryService, - @IInstantiationService - private readonly instantiationService: IInstantiationService, + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, + @IInstantiationService private readonly instantiationService: IInstantiationService, @IFileService private readonly fileService: IFileService, @ILogService private readonly logService: ILogService, ) { - super( - "workbench.extensions.actions.installLocalExtensionsInRemote", - extensionsWorkbenchService, - quickInputService, - notificationService, - progressService, - ); + super('workbench.extensions.actions.installLocalExtensionsInRemote', extensionsWorkbenchService, quickInputService, notificationService, progressService); } override get label(): string { - if ( - this.extensionManagementServerService && - this.extensionManagementServerService - .remoteExtensionManagementServer - ) { - return localize( - "select and install local extensions", - "Install Local Extensions in '{0}'...", - this.extensionManagementServerService - .remoteExtensionManagementServer.label, - ); + if (this.extensionManagementServerService && this.extensionManagementServerService.remoteExtensionManagementServer) { + return localize('select and install local extensions', "Install Local Extensions in '{0}'...", this.extensionManagementServerService.remoteExtensionManagementServer.label); } - return ""; + return ''; } protected getQuickPickTitle(): string { - return localize( - "install local extensions title", - "Install Local Extensions in '{0}'", - this.extensionManagementServerService - .remoteExtensionManagementServer!.label, - ); + return localize('install local extensions title', "Install Local Extensions in '{0}'", this.extensionManagementServerService.remoteExtensionManagementServer!.label); } protected getExtensionsToInstall(local: IExtension[]): IExtension[] { - return local.filter((extension) => { - const action = this.instantiationService.createInstance( - RemoteInstallAction, - true, - ); + return local.filter(extension => { + const action = this.instantiationService.createInstance(RemoteInstallAction, true); action.extension = extension; return action.enabled; }); } - protected async installExtensions( - localExtensionsToInstall: IExtension[], - ): Promise { + protected async installExtensions(localExtensionsToInstall: IExtension[]): Promise { const galleryExtensions: IGalleryExtension[] = []; const vsixs: URI[] = []; - const targetPlatform = - await this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.getTargetPlatform(); - await Promises.settled( - localExtensionsToInstall.map(async (extension) => { - if (this.extensionGalleryService.isEnabled()) { - const gallery = ( - await this.extensionGalleryService.getExtensions( - [ - { - ...extension.identifier, - preRelease: !!extension.local?.preRelease, - }, - ], - { targetPlatform, compatible: true }, - CancellationToken.None, - ) - )[0]; - if (gallery) { - galleryExtensions.push(gallery); - return; - } + const targetPlatform = await this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.getTargetPlatform(); + await Promises.settled(localExtensionsToInstall.map(async extension => { + if (this.extensionGalleryService.isEnabled()) { + const gallery = (await this.extensionGalleryService.getExtensions([{ ...extension.identifier, preRelease: !!extension.local?.preRelease }], { targetPlatform, compatible: true }, CancellationToken.None))[0]; + if (gallery) { + galleryExtensions.push(gallery); + return; } - const vsix = - await this.extensionManagementServerService.localExtensionManagementServer!.extensionManagementService.zip( - extension.local!, - ); - vsixs.push(vsix); - }), - ); - - await Promises.settled( - galleryExtensions.map((gallery) => - this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.installFromGallery( - gallery, - ), - ), - ); + } + const vsix = await this.extensionManagementServerService.localExtensionManagementServer!.extensionManagementService.zip(extension.local!); + vsixs.push(vsix); + })); + + await Promises.settled(galleryExtensions.map(gallery => this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.installFromGallery(gallery))); try { - await Promises.settled( - vsixs.map((vsix) => - this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.install( - vsix, - ), - ), - ); + await Promises.settled(vsixs.map(vsix => this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.install(vsix))); } finally { try { - await Promise.allSettled( - vsixs.map((vsix) => this.fileService.del(vsix)), - ); + await Promise.allSettled(vsixs.map(vsix => this.fileService.del(vsix))); } catch (error) { this.logService.error(error); } @@ -5505,113 +3101,57 @@ export class InstallLocalExtensionsInRemoteAction extends AbstractInstallExtensi } export class InstallRemoteExtensionsInLocalAction extends AbstractInstallExtensionsInServerAction { + constructor( id: string, - @IExtensionsWorkbenchService - extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, @IQuickInputService quickInputService: IQuickInputService, @IProgressService progressService: IProgressService, @INotificationService notificationService: INotificationService, - @IExtensionManagementServerService - private readonly extensionManagementServerService: IExtensionManagementServerService, - @IExtensionGalleryService - private readonly extensionGalleryService: IExtensionGalleryService, + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IFileService private readonly fileService: IFileService, @ILogService private readonly logService: ILogService, ) { - super( - id, - extensionsWorkbenchService, - quickInputService, - notificationService, - progressService, - ); + super(id, extensionsWorkbenchService, quickInputService, notificationService, progressService); } override get label(): string { - return localize( - "select and install remote extensions", - "Install Remote Extensions Locally...", - ); + return localize('select and install remote extensions', "Install Remote Extensions Locally..."); } protected getQuickPickTitle(): string { - return localize( - "install remote extensions", - "Install Remote Extensions Locally", - ); + return localize('install remote extensions', "Install Remote Extensions Locally"); } protected getExtensionsToInstall(local: IExtension[]): IExtension[] { - return local.filter( - (extension) => - extension.type === ExtensionType.User && - extension.server !== - this.extensionManagementServerService - .localExtensionManagementServer && - !this.extensionsWorkbenchService.installed.some( - (e) => - e.server === - this.extensionManagementServerService - .localExtensionManagementServer && - areSameExtensions(e.identifier, extension.identifier), - ), - ); + return local.filter(extension => + extension.type === ExtensionType.User && extension.server !== this.extensionManagementServerService.localExtensionManagementServer + && !this.extensionsWorkbenchService.installed.some(e => e.server === this.extensionManagementServerService.localExtensionManagementServer && areSameExtensions(e.identifier, extension.identifier))); } protected async installExtensions(extensions: IExtension[]): Promise { const galleryExtensions: IGalleryExtension[] = []; const vsixs: URI[] = []; - const targetPlatform = - await this.extensionManagementServerService.localExtensionManagementServer!.extensionManagementService.getTargetPlatform(); - await Promises.settled( - extensions.map(async (extension) => { - if (this.extensionGalleryService.isEnabled()) { - const gallery = ( - await this.extensionGalleryService.getExtensions( - [ - { - ...extension.identifier, - preRelease: !!extension.local?.preRelease, - }, - ], - { targetPlatform, compatible: true }, - CancellationToken.None, - ) - )[0]; - if (gallery) { - galleryExtensions.push(gallery); - return; - } + const targetPlatform = await this.extensionManagementServerService.localExtensionManagementServer!.extensionManagementService.getTargetPlatform(); + await Promises.settled(extensions.map(async extension => { + if (this.extensionGalleryService.isEnabled()) { + const gallery = (await this.extensionGalleryService.getExtensions([{ ...extension.identifier, preRelease: !!extension.local?.preRelease }], { targetPlatform, compatible: true }, CancellationToken.None))[0]; + if (gallery) { + galleryExtensions.push(gallery); + return; } - const vsix = - await this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.zip( - extension.local!, - ); - vsixs.push(vsix); - }), - ); - - await Promises.settled( - galleryExtensions.map((gallery) => - this.extensionManagementServerService.localExtensionManagementServer!.extensionManagementService.installFromGallery( - gallery, - ), - ), - ); + } + const vsix = await this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.zip(extension.local!); + vsixs.push(vsix); + })); + + await Promises.settled(galleryExtensions.map(gallery => this.extensionManagementServerService.localExtensionManagementServer!.extensionManagementService.installFromGallery(gallery))); try { - await Promises.settled( - vsixs.map((vsix) => - this.extensionManagementServerService.localExtensionManagementServer!.extensionManagementService.install( - vsix, - ), - ), - ); + await Promises.settled(vsixs.map(vsix => this.extensionManagementServerService.localExtensionManagementServer!.extensionManagementService.install(vsix))); } finally { try { - await Promise.allSettled( - vsixs.map((vsix) => this.fileService.del(vsix)), - ); + await Promise.allSettled(vsixs.map(vsix => this.fileService.del(vsix))); } catch (error) { this.logService.error(error); } @@ -5619,164 +3159,81 @@ export class InstallRemoteExtensionsInLocalAction extends AbstractInstallExtensi } } -CommandsRegistry.registerCommand( - "workbench.extensions.action.showExtensionsForLanguage", - function (accessor: ServicesAccessor, fileExtension: string) { - const extensionsWorkbenchService = accessor.get( - IExtensionsWorkbenchService, - ); - return extensionsWorkbenchService.openSearch( - `ext:${fileExtension.replace(/^\./, "")}`, - ); - }, -); - -export const showExtensionsWithIdsCommandId = - "workbench.extensions.action.showExtensionsWithIds"; -CommandsRegistry.registerCommand( - showExtensionsWithIdsCommandId, - function (accessor: ServicesAccessor, extensionIds: string[]) { - const extensionsWorkbenchService = accessor.get( - IExtensionsWorkbenchService, - ); - return extensionsWorkbenchService.openSearch( - extensionIds.map((id) => `@id:${id}`).join(" "), - ); - }, -); - -registerColor( - "extensionButton.background", - { - dark: buttonBackground, - light: buttonBackground, - hcDark: null, - hcLight: null, - }, - localize( - "extensionButtonBackground", - "Button background color for extension actions.", - ), -); - -registerColor( - "extensionButton.foreground", - { - dark: buttonForeground, - light: buttonForeground, - hcDark: null, - hcLight: null, - }, - localize( - "extensionButtonForeground", - "Button foreground color for extension actions.", - ), -); - -registerColor( - "extensionButton.hoverBackground", - { - dark: buttonHoverBackground, - light: buttonHoverBackground, - hcDark: null, - hcLight: null, - }, - localize( - "extensionButtonHoverBackground", - "Button background hover color for extension actions.", - ), -); - -registerColor( - "extensionButton.separator", - buttonSeparator, - localize( - "extensionButtonSeparator", - "Button separator color for extension actions", - ), -); - -export const extensionButtonProminentBackground = registerColor( - "extensionButton.prominentBackground", - { - dark: buttonBackground, - light: buttonBackground, - hcDark: null, - hcLight: null, - }, - localize( - "extensionButtonProminentBackground", - "Button background color for extension actions that stand out (e.g. install button).", - ), -); - -registerColor( - "extensionButton.prominentForeground", - { - dark: buttonForeground, - light: buttonForeground, - hcDark: null, - hcLight: null, - }, - localize( - "extensionButtonProminentForeground", - "Button foreground color for extension actions that stand out (e.g. install button).", - ), -); - -registerColor( - "extensionButton.prominentHoverBackground", - { - dark: buttonHoverBackground, - light: buttonHoverBackground, - hcDark: null, - hcLight: null, - }, - localize( - "extensionButtonProminentHoverBackground", - "Button background hover color for extension actions that stand out (e.g. install button).", - ), -); - -registerThemingParticipant( - (theme: IColorTheme, collector: ICssStyleCollector) => { - const errorColor = theme.getColor(editorErrorForeground); - if (errorColor) { - collector.addRule( - `.extension-editor .header .actions-status-container > .status ${ThemeIcon.asCSSSelector(errorIcon)} { color: ${errorColor}; }`, - ); - collector.addRule( - `.extension-editor .body .subcontent .runtime-status ${ThemeIcon.asCSSSelector(errorIcon)} { color: ${errorColor}; }`, - ); - collector.addRule( - `.monaco-hover.extension-hover .markdown-hover .hover-contents ${ThemeIcon.asCSSSelector(errorIcon)} { color: ${errorColor}; }`, - ); - } - - const warningColor = theme.getColor(editorWarningForeground); - if (warningColor) { - collector.addRule( - `.extension-editor .header .actions-status-container > .status ${ThemeIcon.asCSSSelector(warningIcon)} { color: ${warningColor}; }`, - ); - collector.addRule( - `.extension-editor .body .subcontent .runtime-status ${ThemeIcon.asCSSSelector(warningIcon)} { color: ${warningColor}; }`, - ); - collector.addRule( - `.monaco-hover.extension-hover .markdown-hover .hover-contents ${ThemeIcon.asCSSSelector(warningIcon)} { color: ${warningColor}; }`, - ); - } - - const infoColor = theme.getColor(editorInfoForeground); - if (infoColor) { - collector.addRule( - `.extension-editor .header .actions-status-container > .status ${ThemeIcon.asCSSSelector(infoIcon)} { color: ${infoColor}; }`, - ); - collector.addRule( - `.extension-editor .body .subcontent .runtime-status ${ThemeIcon.asCSSSelector(infoIcon)} { color: ${infoColor}; }`, - ); - collector.addRule( - `.monaco-hover.extension-hover .markdown-hover .hover-contents ${ThemeIcon.asCSSSelector(infoIcon)} { color: ${infoColor}; }`, - ); - } - }, -); +CommandsRegistry.registerCommand('workbench.extensions.action.showExtensionsForLanguage', function (accessor: ServicesAccessor, fileExtension: string) { + const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); + return extensionsWorkbenchService.openSearch(`ext:${fileExtension.replace(/^\./, '')}`); +}); + +export const showExtensionsWithIdsCommandId = 'workbench.extensions.action.showExtensionsWithIds'; +CommandsRegistry.registerCommand(showExtensionsWithIdsCommandId, function (accessor: ServicesAccessor, extensionIds: string[]) { + const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); + return extensionsWorkbenchService.openSearch(extensionIds.map(id => `@id:${id}`).join(' ')); +}); + +registerColor('extensionButton.background', { + dark: buttonBackground, + light: buttonBackground, + hcDark: null, + hcLight: null +}, localize('extensionButtonBackground', "Button background color for extension actions.")); + +registerColor('extensionButton.foreground', { + dark: buttonForeground, + light: buttonForeground, + hcDark: null, + hcLight: null +}, localize('extensionButtonForeground', "Button foreground color for extension actions.")); + +registerColor('extensionButton.hoverBackground', { + dark: buttonHoverBackground, + light: buttonHoverBackground, + hcDark: null, + hcLight: null +}, localize('extensionButtonHoverBackground', "Button background hover color for extension actions.")); + +registerColor('extensionButton.separator', buttonSeparator, localize('extensionButtonSeparator', "Button separator color for extension actions")); + +export const extensionButtonProminentBackground = registerColor('extensionButton.prominentBackground', { + dark: buttonBackground, + light: buttonBackground, + hcDark: null, + hcLight: null +}, localize('extensionButtonProminentBackground', "Button background color for extension actions that stand out (e.g. install button).")); + +registerColor('extensionButton.prominentForeground', { + dark: buttonForeground, + light: buttonForeground, + hcDark: null, + hcLight: null +}, localize('extensionButtonProminentForeground', "Button foreground color for extension actions that stand out (e.g. install button).")); + +registerColor('extensionButton.prominentHoverBackground', { + dark: buttonHoverBackground, + light: buttonHoverBackground, + hcDark: null, + hcLight: null +}, localize('extensionButtonProminentHoverBackground', "Button background hover color for extension actions that stand out (e.g. install button).")); + +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { + + const errorColor = theme.getColor(editorErrorForeground); + if (errorColor) { + collector.addRule(`.extension-editor .header .actions-status-container > .status ${ThemeIcon.asCSSSelector(errorIcon)} { color: ${errorColor}; }`); + collector.addRule(`.extension-editor .body .subcontent .runtime-status ${ThemeIcon.asCSSSelector(errorIcon)} { color: ${errorColor}; }`); + collector.addRule(`.monaco-hover.extension-hover .markdown-hover .hover-contents ${ThemeIcon.asCSSSelector(errorIcon)} { color: ${errorColor}; }`); + } + + const warningColor = theme.getColor(editorWarningForeground); + if (warningColor) { + collector.addRule(`.extension-editor .header .actions-status-container > .status ${ThemeIcon.asCSSSelector(warningIcon)} { color: ${warningColor}; }`); + collector.addRule(`.extension-editor .body .subcontent .runtime-status ${ThemeIcon.asCSSSelector(warningIcon)} { color: ${warningColor}; }`); + collector.addRule(`.monaco-hover.extension-hover .markdown-hover .hover-contents ${ThemeIcon.asCSSSelector(warningIcon)} { color: ${warningColor}; }`); + } + + const infoColor = theme.getColor(editorInfoForeground); + if (infoColor) { + collector.addRule(`.extension-editor .header .actions-status-container > .status ${ThemeIcon.asCSSSelector(infoIcon)} { color: ${infoColor}; }`); + collector.addRule(`.extension-editor .body .subcontent .runtime-status ${ThemeIcon.asCSSSelector(infoIcon)} { color: ${infoColor}; }`); + collector.addRule(`.monaco-hover.extension-hover .markdown-hover .hover-contents ${ThemeIcon.asCSSSelector(infoIcon)} { color: ${infoColor}; }`); + } +}); diff --git a/Source/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/Source/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 338ecda164079..3325a34ee12b7 100644 --- a/Source/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/Source/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -3,193 +3,69 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { mainWindow } from "../../../../base/browser/window.js"; -import { index } from "../../../../base/common/arrays.js"; +import * as nls from '../../../../nls.js'; +import * as semver from '../../../../base/common/semver/semver.js'; +import { Event, Emitter } from '../../../../base/common/event.js'; +import { index } from '../../../../base/common/arrays.js'; +import { CancelablePromise, Promises, ThrottledDelayer, createCancelablePromise } from '../../../../base/common/async.js'; +import { CancellationError, isCancellationError } from '../../../../base/common/errors.js'; +import { Disposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; +import { IPager, singlePagePager } from '../../../../base/common/paging.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { - CancelablePromise, - createCancelablePromise, - Promises, - ThrottledDelayer, -} from "../../../../base/common/async.js"; -import { CancellationToken } from "../../../../base/common/cancellation.js"; -import { - CancellationError, - isCancellationError, -} from "../../../../base/common/errors.js"; -import { Emitter, Event } from "../../../../base/common/event.js"; -import { - IMarkdownString, - MarkdownString, -} from "../../../../base/common/htmlContent.js"; -import { - Disposable, - MutableDisposable, - toDisposable, -} from "../../../../base/common/lifecycle.js"; -import { FileAccess } from "../../../../base/common/network.js"; -import { IPager, singlePagePager } from "../../../../base/common/paging.js"; -import { isWeb, language } from "../../../../base/common/platform.js"; -import * as resources from "../../../../base/common/resources.js"; -import * as semver from "../../../../base/common/semver/semver.js"; -import { - isBoolean, - isDefined, - isString, - isUndefined, -} from "../../../../base/common/types.js"; -import { URI } from "../../../../base/common/uri.js"; -import { ILanguageService } from "../../../../editor/common/languages/language.js"; -import * as nls from "../../../../nls.js"; -import { IConfigurationService } from "../../../../platform/configuration/common/configuration.js"; -import { - Extensions as ConfigurationExtensions, - IConfigurationRegistry, -} from "../../../../platform/configuration/common/configurationRegistry.js"; -import { - IContextKey, - IContextKeyService, -} from "../../../../platform/contextkey/common/contextkey.js"; -import { - IDialogService, - IFileDialogService, - IPromptButton, -} from "../../../../platform/dialogs/common/dialogs.js"; -import { - DidUninstallExtensionEvent, - EXTENSION_IDENTIFIER_REGEX, - IAllowedExtensionsService, - IDeprecationInfo, - IExtensionGalleryService, - IExtensionInfo, - IExtensionQueryOptions, - IExtensionsControlManifest, - IGalleryExtension, - ILocalExtension, - InstallExtensionEvent, - InstallExtensionInfo, - InstallExtensionResult, - InstallOperation, - InstallOptions, - IProductVersion, - IQueryOptions, - isTargetPlatformCompatible, - TargetPlatformToString, + IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, + InstallExtensionEvent, DidUninstallExtensionEvent, InstallOperation, WEB_EXTENSION_TAG, InstallExtensionResult, + IExtensionsControlManifest, IExtensionInfo, IExtensionQueryOptions, IDeprecationInfo, isTargetPlatformCompatible, InstallExtensionInfo, EXTENSION_IDENTIFIER_REGEX, + InstallOptions, IProductVersion, UninstallExtensionInfo, - WEB_EXTENSION_TAG, -} from "../../../../platform/extensionManagement/common/extensionManagement.js"; -import { - areSameExtensions, - getGalleryExtensionId, - getGalleryExtensionTelemetryData, - getLocalExtensionTelemetryData, - groupByExtension, -} from "../../../../platform/extensionManagement/common/extensionManagementUtil.js"; -import { - ExtensionIdentifier, - ExtensionType, - IExtensionDescription, - IExtensionIdentifier, - IExtensionManifest, - IExtension as IPlatformExtension, - isApplicationScopedExtension, - TargetPlatform, -} from "../../../../platform/extensions/common/extensions.js"; -import { - areApiProposalsCompatible, - isEngineValid, -} from "../../../../platform/extensions/common/extensionValidator.js"; -import { IFileService } from "../../../../platform/files/common/files.js"; -import { IInstantiationService } from "../../../../platform/instantiation/common/instantiation.js"; -import { getLocale } from "../../../../platform/languagePacks/common/languagePacks.js"; -import { ILogService } from "../../../../platform/log/common/log.js"; -import { - INotificationService, - NotificationPriority, - Severity, -} from "../../../../platform/notification/common/notification.js"; -import { IProductService } from "../../../../platform/product/common/productService.js"; -import { - IProgressOptions, - IProgressService, - ProgressLocation, -} from "../../../../platform/progress/common/progress.js"; -import { IQuickInputService } from "../../../../platform/quickinput/common/quickInput.js"; -import { Registry } from "../../../../platform/registry/common/platform.js"; -import { - IStorageService, - StorageScope, - StorageTarget, -} from "../../../../platform/storage/common/storage.js"; -import { ITelemetryService } from "../../../../platform/telemetry/common/telemetry.js"; -import { TelemetryTrustedValue } from "../../../../platform/telemetry/common/telemetryUtils.js"; -import { - IUpdateService, - StateType, -} from "../../../../platform/update/common/update.js"; -import { IUriIdentityService } from "../../../../platform/uriIdentity/common/uriIdentity.js"; -import { - IOpenURLOptions, - IURLHandler, - IURLService, -} from "../../../../platform/url/common/url.js"; -import { IIgnoredExtensionsManagementService } from "../../../../platform/userDataSync/common/ignoredExtensions.js"; -import { - IUserDataAutoSyncService, - IUserDataSyncEnablementService, - SyncResource, -} from "../../../../platform/userDataSync/common/userDataSync.js"; -import { IWorkspaceContextService } from "../../../../platform/workspace/common/workspace.js"; -import { - ACTIVE_GROUP, - IEditorService, - SIDE_GROUP, -} from "../../../services/editor/common/editorService.js"; -import { - DefaultIconPath, - EnablementState, - extensionsConfigurationNodeBase, - IExtensionManagementServer, - IExtensionManagementServerService, - IResourceExtension, - IWorkbenchExtensionEnablementService, - IWorkbenchExtensionManagementService, -} from "../../../services/extensionManagement/common/extensionManagement.js"; -import { IExtensionManifestPropertiesService } from "../../../services/extensions/common/extensionManifestPropertiesService.js"; -import { - IExtensionsStatus as IExtensionRuntimeStatus, - IExtensionService, - toExtension, - toExtensionDescription, -} from "../../../services/extensions/common/extensions.js"; -import { IHostService } from "../../../services/host/browser/host.js"; -import { - ILifecycleService, - LifecyclePhase, -} from "../../../services/lifecycle/common/lifecycle.js"; -import { ILocaleService } from "../../../services/localization/common/locale.js"; -import { IUserDataProfileService } from "../../../services/userDataProfile/common/userDataProfile.js"; -import { IViewsService } from "../../../services/views/common/viewsService.js"; -import { ShowCurrentReleaseNotesActionId } from "../../update/common/update.js"; -import { - AutoCheckUpdatesConfigurationKey, - AutoRestartConfigurationKey, - AutoUpdateConfigurationKey, - AutoUpdateConfigurationValue, - ExtensionRuntimeActionType, - ExtensionRuntimeState, - ExtensionState, - HasOutdatedExtensionsContext, - IExtension, - IExtensionsNotification, - IExtensionsViewPaneContainer, - IExtensionsWorkbenchService, - InstallExtensionOptions, - VIEWLET_ID, -} from "../common/extensions.js"; -import { - ExtensionsInput, - IExtensionEditorOptions, -} from "../common/extensionsInput.js"; + TargetPlatformToString, + IAllowedExtensionsService +} from '../../../../platform/extensionManagement/common/extensionManagement.js'; +import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService, DefaultIconPath, IResourceExtension } from '../../../services/extensionManagement/common/extensionManagement.js'; +import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, groupByExtension, getGalleryExtensionId } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { IHostService } from '../../../services/host/browser/host.js'; +import { URI } from '../../../../base/common/uri.js'; +import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, HasOutdatedExtensionsContext, AutoUpdateConfigurationValue, InstallExtensionOptions, ExtensionRuntimeState, ExtensionRuntimeActionType, AutoRestartConfigurationKey, VIEWLET_ID, IExtensionsViewPaneContainer, IExtensionsNotification } from '../common/extensions.js'; +import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from '../../../services/editor/common/editorService.js'; +import { IURLService, IURLHandler, IOpenURLOptions } from '../../../../platform/url/common/url.js'; +import { ExtensionsInput, IExtensionEditorOptions } from '../common/extensionsInput.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; +import { IProgressOptions, IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js'; +import { INotificationService, NotificationPriority, Severity } from '../../../../platform/notification/common/notification.js'; +import * as resources from '../../../../base/common/resources.js'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; +import { IFileService } from '../../../../platform/files/common/files.js'; +import { IExtensionManifest, ExtensionType, IExtension as IPlatformExtension, TargetPlatform, ExtensionIdentifier, IExtensionIdentifier, IExtensionDescription, isApplicationScopedExtension } from '../../../../platform/extensions/common/extensions.js'; +import { ILanguageService } from '../../../../editor/common/languages/language.js'; +import { IProductService } from '../../../../platform/product/common/productService.js'; +import { FileAccess } from '../../../../base/common/network.js'; +import { IIgnoredExtensionsManagementService } from '../../../../platform/userDataSync/common/ignoredExtensions.js'; +import { IUserDataAutoSyncService, IUserDataSyncEnablementService, SyncResource } from '../../../../platform/userDataSync/common/userDataSync.js'; +import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { isBoolean, isDefined, isString, isUndefined } from '../../../../base/common/types.js'; +import { IExtensionManifestPropertiesService } from '../../../services/extensions/common/extensionManifestPropertiesService.js'; +import { IExtensionService, IExtensionsStatus as IExtensionRuntimeStatus, toExtension, toExtensionDescription } from '../../../services/extensions/common/extensions.js'; +import { isWeb, language } from '../../../../base/common/platform.js'; +import { getLocale } from '../../../../platform/languagePacks/common/languagePacks.js'; +import { ILocaleService } from '../../../services/localization/common/locale.js'; +import { TelemetryTrustedValue } from '../../../../platform/telemetry/common/telemetryUtils.js'; +import { ILifecycleService, LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js'; +import { IUserDataProfileService } from '../../../services/userDataProfile/common/userDataProfile.js'; +import { mainWindow } from '../../../../base/browser/window.js'; +import { IDialogService, IFileDialogService, IPromptButton } from '../../../../platform/dialogs/common/dialogs.js'; +import { IUpdateService, StateType } from '../../../../platform/update/common/update.js'; +import { areApiProposalsCompatible, isEngineValid } from '../../../../platform/extensions/common/extensionValidator.js'; +import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; +import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; +import { ShowCurrentReleaseNotesActionId } from '../../update/common/update.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from '../../../../platform/configuration/common/configurationRegistry.js'; +import { IViewsService } from '../../../services/views/common/viewsService.js'; +import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js'; +import { IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js'; interface IExtensionStateProvider { (extension: Extension): T; @@ -200,46 +76,32 @@ interface InstalledExtensionsEvent { readonly count: number; } type ExtensionsLoadClassification = { - owner: "digitarald"; - comment: "Helps to understand which extensions are the most actively used."; - readonly extensionIds: { - classification: "PublicNonPersonalData"; - purpose: "FeatureInsight"; - comment: "The list of extension ids that are installed."; - }; - readonly count: { - classification: "PublicNonPersonalData"; - purpose: "FeatureInsight"; - comment: "The number of extensions that are installed."; - }; + owner: 'digitarald'; + comment: 'Helps to understand which extensions are the most actively used.'; + readonly extensionIds: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The list of extension ids that are installed.' }; + readonly count: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The number of extensions that are installed.' }; }; export class Extension implements IExtension { + public enablementState: EnablementState = EnablementState.EnabledGlobally; private galleryResourcesCache = new Map(); constructor( private stateProvider: IExtensionStateProvider, - private runtimeStateProvider: IExtensionStateProvider< - ExtensionRuntimeState | undefined - >, + private runtimeStateProvider: IExtensionStateProvider, public readonly server: IExtensionManagementServer | undefined, public local: ILocalExtension | undefined, private _gallery: IGalleryExtension | undefined, - private readonly resourceExtensionInfo: - | { - resourceExtension: IResourceExtension; - isWorkspaceScoped: boolean; - } - | undefined, - @IExtensionGalleryService - private readonly galleryService: IExtensionGalleryService, + private readonly resourceExtensionInfo: { resourceExtension: IResourceExtension; isWorkspaceScoped: boolean } | undefined, + @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, @ITelemetryService private readonly telemetryService: ITelemetryService, @ILogService private readonly logService: ILogService, @IFileService private readonly fileService: IFileService, - @IProductService private readonly productService: IProductService, - ) {} + @IProductService private readonly productService: IProductService + ) { + } get resourceExtension(): IResourceExtension | undefined { if (this.resourceExtensionInfo) { @@ -247,7 +109,7 @@ export class Extension implements IExtension { } if (this.local?.isWorkspaceScoped) { return { - type: "resource", + type: 'resource', identifier: this.local.identifier, location: this.local.location, manifest: this.local.manifest, @@ -289,7 +151,7 @@ export class Extension implements IExtension { if (this.gallery) { return this.gallery.name; } - return this.getManifestFromLocalOrResource()?.name ?? ""; + return this.getManifestFromLocalOrResource()?.name ?? ''; } get displayName(): string { @@ -311,16 +173,14 @@ export class Extension implements IExtension { } get uuid(): string | undefined { - return this.gallery - ? this.gallery.identifier.uuid - : this.local?.identifier.uuid; + return this.gallery ? this.gallery.identifier.uuid : this.local?.identifier.uuid; } get publisher(): string { if (this.gallery) { return this.gallery.publisher; } - return this.getManifestFromLocalOrResource()?.publisher ?? ""; + return this.getManifestFromLocalOrResource()?.publisher ?? ''; } get publisherDisplayName(): string { @@ -340,10 +200,7 @@ export class Extension implements IExtension { return undefined; } - return resources.joinPath( - URI.parse(this.productService.extensionsGallery.publisherUrl), - this.publisher, - ); + return resources.joinPath(URI.parse(this.productService.extensionsGallery.publisherUrl), this.publisher); } get publisherDomain(): { link: string; verified: boolean } | undefined { @@ -351,9 +208,7 @@ export class Extension implements IExtension { } get publisherSponsorLink(): URI | undefined { - return this.gallery?.publisherSponsorLink - ? URI.parse(this.gallery.publisherSponsorLink) - : undefined; + return this.gallery?.publisherSponsorLink ? URI.parse(this.gallery.publisherSponsorLink) : undefined; } get version(): string { @@ -365,15 +220,11 @@ export class Extension implements IExtension { } get latestVersion(): string { - return this.gallery - ? this.gallery.version - : (this.getManifestFromLocalOrResource()?.version ?? ""); + return this.gallery ? this.gallery.version : this.getManifestFromLocalOrResource()?.version ?? ''; } get description(): string { - return this.gallery - ? this.gallery.description - : (this.getManifestFromLocalOrResource()?.description ?? ""); + return this.gallery ? this.gallery.description : this.getManifestFromLocalOrResource()?.description ?? ''; } get url(): string | undefined { @@ -385,43 +236,23 @@ export class Extension implements IExtension { } get iconUrl(): string { - return ( - this.galleryIconUrl || - this.resourceExtensionIconUrl || - this.localIconUrl || - this.defaultIconUrl - ); + return this.galleryIconUrl || this.resourceExtensionIconUrl || this.localIconUrl || this.defaultIconUrl; } get iconUrlFallback(): string { - return ( - this.galleryIconUrlFallback || - this.resourceExtensionIconUrl || - this.localIconUrl || - this.defaultIconUrl - ); + return this.galleryIconUrlFallback || this.resourceExtensionIconUrl || this.localIconUrl || this.defaultIconUrl; } private get localIconUrl(): string | null { if (this.local && this.local.manifest.icon) { - return FileAccess.uriToBrowserUri( - resources.joinPath( - this.local.location, - this.local.manifest.icon, - ), - ).toString(true); + return FileAccess.uriToBrowserUri(resources.joinPath(this.local.location, this.local.manifest.icon)).toString(true); } return null; } private get resourceExtensionIconUrl(): string | null { if (this.resourceExtension?.manifest.icon) { - return FileAccess.uriToBrowserUri( - resources.joinPath( - this.resourceExtension.location, - this.resourceExtension.manifest.icon, - ), - ).toString(true); + return FileAccess.uriToBrowserUri(resources.joinPath(this.resourceExtension.location, this.resourceExtension.manifest.icon)).toString(true); } return null; } @@ -431,29 +262,17 @@ export class Extension implements IExtension { } private get galleryIconUrlFallback(): string | null { - return this.gallery?.assets.icon - ? this.gallery.assets.icon.fallbackUri - : null; + return this.gallery?.assets.icon ? this.gallery.assets.icon.fallbackUri : null; } private get defaultIconUrl(): string { if (this.type === ExtensionType.System && this.local) { if (this.local.manifest && this.local.manifest.contributes) { - if ( - Array.isArray(this.local.manifest.contributes.themes) && - this.local.manifest.contributes.themes.length - ) { - return FileAccess.asBrowserUri( - "vs/workbench/contrib/extensions/browser/media/theme-icon.png", - ).toString(true); + if (Array.isArray(this.local.manifest.contributes.themes) && this.local.manifest.contributes.themes.length) { + return FileAccess.asBrowserUri('vs/workbench/contrib/extensions/browser/media/theme-icon.png').toString(true); } - if ( - Array.isArray(this.local.manifest.contributes.grammars) && - this.local.manifest.contributes.grammars.length - ) { - return FileAccess.asBrowserUri( - "vs/workbench/contrib/extensions/browser/media/language-icon.svg", - ).toString(true); + if (Array.isArray(this.local.manifest.contributes.grammars) && this.local.manifest.contributes.grammars.length) { + return FileAccess.asBrowserUri('vs/workbench/contrib/extensions/browser/media/language-icon.svg').toString(true); } } } @@ -461,21 +280,15 @@ export class Extension implements IExtension { } get repository(): string | undefined { - return this.gallery && this.gallery.assets.repository - ? this.gallery.assets.repository.uri - : undefined; + return this.gallery && this.gallery.assets.repository ? this.gallery.assets.repository.uri : undefined; } get licenseUrl(): string | undefined { - return this.gallery && this.gallery.assets.license - ? this.gallery.assets.license.uri - : undefined; + return this.gallery && this.gallery.assets.license ? this.gallery.assets.license.uri : undefined; } get supportUrl(): string | undefined { - return this.gallery && this.gallery.supportLink - ? this.gallery.supportLink - : undefined; + return this.gallery && this.gallery.supportLink ? this.gallery.supportLink : undefined; } get state(): ExtensionState { @@ -503,16 +316,10 @@ export class Extension implements IExtension { return false; } // Do not allow updating system extensions in stable - if ( - this.type === ExtensionType.System && - this.productService.quality === "stable" - ) { + if (this.type === ExtensionType.System && this.productService.quality === 'stable') { return false; } - if ( - !this.local.preRelease && - this.gallery.properties.isPreReleaseVersion - ) { + if (!this.local.preRelease && this.gallery.properties.isPreReleaseVersion) { return false; } if (semver.gt(this.latestVersion, this.version)) { @@ -528,17 +335,11 @@ export class Extension implements IExtension { } get outdatedTargetPlatform(): boolean { - return ( - !!this.local && - !!this.gallery && - ![TargetPlatform.UNDEFINED, TargetPlatform.WEB].includes( - this.local.targetPlatform, - ) && - this.gallery.properties.targetPlatform !== TargetPlatform.WEB && - this.local.targetPlatform !== - this.gallery.properties.targetPlatform && - semver.eq(this.latestVersion, this.version) - ); + return !!this.local && !!this.gallery + && ![TargetPlatform.UNDEFINED, TargetPlatform.WEB].includes(this.local.targetPlatform) + && this.gallery.properties.targetPlatform !== TargetPlatform.WEB + && this.local.targetPlatform !== this.gallery.properties.targetPlatform + && semver.eq(this.latestVersion, this.version); } get runtimeState(): ExtensionRuntimeState | undefined { @@ -574,11 +375,7 @@ export class Extension implements IExtension { private _extensionEnabledWithPreRelease: boolean | undefined; get hasPreReleaseVersion(): boolean { - return ( - !!this.gallery?.hasPreReleaseVersion || - !!this.local?.hasPreReleaseVersion || - !!this._extensionEnabledWithPreRelease - ); + return !!this.gallery?.hasPreReleaseVersion || !!this.local?.hasPreReleaseVersion || !!this._extensionEnabledWithPreRelease; } get hasReleaseVersion(): boolean { @@ -589,9 +386,7 @@ export class Extension implements IExtension { return this.local && !this.outdated ? this.local : undefined; } - async getManifest( - token: CancellationToken, - ): Promise { + async getManifest(token: CancellationToken): Promise { const local = this.getLocal(); if (local) { return local.manifest; @@ -608,30 +403,18 @@ export class Extension implements IExtension { return null; } - async getGalleryManifest( - token: CancellationToken = CancellationToken.None, - ): Promise { + async getGalleryManifest(token: CancellationToken = CancellationToken.None): Promise { if (this.gallery) { - let cache = this.galleryResourcesCache.get("manifest"); + let cache = this.galleryResourcesCache.get('manifest'); if (!cache) { if (this.gallery.assets.manifest) { - this.galleryResourcesCache.set( - "manifest", - (cache = this.galleryService - .getManifest(this.gallery, token) - .catch((e) => { - this.galleryResourcesCache.delete("manifest"); - throw e; - })), - ); + this.galleryResourcesCache.set('manifest', cache = this.galleryService.getManifest(this.gallery, token) + .catch(e => { + this.galleryResourcesCache.delete('manifest'); + throw e; + })); } else { - this.logService.error( - nls.localize( - "Manifest is not found", - "Manifest is not found", - ), - this.identifier.id, - ); + this.logService.error(nls.localize('Manifest is not found', "Manifest is not found"), this.identifier.id); } } return cache; @@ -666,10 +449,7 @@ export class Extension implements IExtension { if (this.gallery.assets.readme) { return this.galleryService.getReadme(this.gallery, token); } - this.telemetryService.publicLog( - "extensions:NotFoundReadMe", - this.telemetryData, - ); + this.telemetryService.publicLog('extensions:NotFoundReadMe', this.telemetryData); } if (this.type === ExtensionType.System) { @@ -681,13 +461,11 @@ ${this.description} } if (this.resourceExtension?.readmeUri) { - const content = await this.fileService.readFile( - this.resourceExtension?.readmeUri, - ); + const content = await this.fileService.readFile(this.resourceExtension?.readmeUri); return content.value.toString(); } - return Promise.reject(new Error("not available")); + return Promise.reject(new Error('not available')); } hasChangelog(): boolean { @@ -714,12 +492,10 @@ ${this.description} } if (this.type === ExtensionType.System) { - return Promise.resolve( - `Please check the [VS Code Release Notes](command:${ShowCurrentReleaseNotesActionId}) for changes to the built-in extensions.`, - ); + return Promise.resolve(`Please check the [VS Code Release Notes](command:${ShowCurrentReleaseNotesActionId}) for changes to the built-in extensions.`); } - return Promise.reject(new Error("not available")); + return Promise.reject(new Error('not available')); } get categories(): readonly string[] { @@ -739,7 +515,7 @@ ${this.description} get tags(): readonly string[] { const { gallery } = this; if (gallery) { - return gallery.tags.filter((tag) => !tag.startsWith("_")); + return gallery.tags.filter(tag => !tag.startsWith('_')); } return []; } @@ -772,21 +548,10 @@ ${this.description} return []; } - setExtensionsControlManifest( - extensionsControlManifest: IExtensionsControlManifest, - ): void { - this.isMalicious = extensionsControlManifest.malicious.some( - (identifier) => areSameExtensions(this.identifier, identifier), - ); - this.deprecationInfo = extensionsControlManifest.deprecated - ? extensionsControlManifest.deprecated[ - this.identifier.id.toLowerCase() - ] - : undefined; - this._extensionEnabledWithPreRelease = - extensionsControlManifest?.extensionsEnabledWithPreRelease?.includes( - this.identifier.id.toLowerCase(), - ); + setExtensionsControlManifest(extensionsControlManifest: IExtensionsControlManifest): void { + this.isMalicious = extensionsControlManifest.malicious.some(identifier => areSameExtensions(this.identifier, identifier)); + this.deprecationInfo = extensionsControlManifest.deprecated ? extensionsControlManifest.deprecated[this.identifier.id.toLowerCase()] : undefined; + this._extensionEnabledWithPreRelease = extensionsControlManifest?.extensionsEnabledWithPreRelease?.includes(this.identifier.id.toLowerCase()); } private getManifestFromLocalOrResource(): IExtensionManifest | null { @@ -800,25 +565,17 @@ ${this.description} } } -const EXTENSIONS_AUTO_UPDATE_KEY = "extensions.autoUpdate"; -const EXTENSIONS_DONOT_AUTO_UPDATE_KEY = "extensions.donotAutoUpdate"; -const EXTENSIONS_DISMISSED_NOTIFICATIONS_KEY = - "extensions.dismissedNotifications"; +const EXTENSIONS_AUTO_UPDATE_KEY = 'extensions.autoUpdate'; +const EXTENSIONS_DONOT_AUTO_UPDATE_KEY = 'extensions.donotAutoUpdate'; +const EXTENSIONS_DISMISSED_NOTIFICATIONS_KEY = 'extensions.dismissedNotifications'; class Extensions extends Disposable { - private readonly _onChange = this._register( - new Emitter< - { extension: Extension; operation?: InstallOperation } | undefined - >(), - ); - get onChange() { - return this._onChange.event; - } + + private readonly _onChange = this._register(new Emitter<{ extension: Extension; operation?: InstallOperation } | undefined>()); + get onChange() { return this._onChange.event; } private readonly _onReset = this._register(new Emitter()); - get onReset() { - return this._onReset.event; - } + get onReset() { return this._onReset.event; } private installing: Extension[] = []; private uninstalling: Extension[] = []; @@ -827,100 +584,45 @@ class Extensions extends Disposable { constructor( readonly server: IExtensionManagementServer, private readonly stateProvider: IExtensionStateProvider, - private readonly runtimeStateProvider: IExtensionStateProvider< - ExtensionRuntimeState | undefined - >, + private readonly runtimeStateProvider: IExtensionStateProvider, private readonly isWorkspaceServer: boolean, - @IExtensionGalleryService - private readonly galleryService: IExtensionGalleryService, - @IWorkbenchExtensionEnablementService - private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, - @IWorkbenchExtensionManagementService - private readonly workbenchExtensionManagementService: IWorkbenchExtensionManagementService, + @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, + @IWorkbenchExtensionManagementService private readonly workbenchExtensionManagementService: IWorkbenchExtensionManagementService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IInstantiationService - private readonly instantiationService: IInstantiationService, + @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); - this._register( - server.extensionManagementService.onInstallExtension((e) => - this.onInstallExtension(e), - ), - ); - this._register( - server.extensionManagementService.onDidInstallExtensions((e) => - this.onDidInstallExtensions(e), - ), - ); - this._register( - server.extensionManagementService.onUninstallExtension((e) => - this.onUninstallExtension(e.identifier), - ), - ); - this._register( - server.extensionManagementService.onDidUninstallExtension((e) => - this.onDidUninstallExtension(e), - ), - ); - this._register( - server.extensionManagementService.onDidUpdateExtensionMetadata( - (e) => this.onDidUpdateExtensionMetadata(e.local), - ), - ); - this._register( - server.extensionManagementService.onDidChangeProfile(() => - this.reset(), - ), - ); - this._register( - extensionEnablementService.onEnablementChanged((e) => - this.onEnablementChanged(e), - ), - ); - this._register( - Event.any( - this.onChange, - this.onReset, - )(() => (this._local = undefined)), - ); + this._register(server.extensionManagementService.onInstallExtension(e => this.onInstallExtension(e))); + this._register(server.extensionManagementService.onDidInstallExtensions(e => this.onDidInstallExtensions(e))); + this._register(server.extensionManagementService.onUninstallExtension(e => this.onUninstallExtension(e.identifier))); + this._register(server.extensionManagementService.onDidUninstallExtension(e => this.onDidUninstallExtension(e))); + this._register(server.extensionManagementService.onDidUpdateExtensionMetadata(e => this.onDidUpdateExtensionMetadata(e.local))); + this._register(server.extensionManagementService.onDidChangeProfile(() => this.reset())); + this._register(extensionEnablementService.onEnablementChanged(e => this.onEnablementChanged(e))); + this._register(Event.any(this.onChange, this.onReset)(() => this._local = undefined)); if (this.isWorkspaceServer) { - this._register( - this.workbenchExtensionManagementService.onInstallExtension( - (e) => { - if (e.workspaceScoped) { - this.onInstallExtension(e); - } - }, - ), - ); - this._register( - this.workbenchExtensionManagementService.onDidInstallExtensions( - (e) => { - const result = e.filter((e) => e.workspaceScoped); - if (result.length) { - this.onDidInstallExtensions(result); - } - }, - ), - ); - this._register( - this.workbenchExtensionManagementService.onUninstallExtension( - (e) => { - if (e.workspaceScoped) { - this.onUninstallExtension(e.identifier); - } - }, - ), - ); - this._register( - this.workbenchExtensionManagementService.onDidUninstallExtension( - (e) => { - if (e.workspaceScoped) { - this.onDidUninstallExtension(e); - } - }, - ), - ); + this._register(this.workbenchExtensionManagementService.onInstallExtension(e => { + if (e.workspaceScoped) { + this.onInstallExtension(e); + } + })); + this._register(this.workbenchExtensionManagementService.onDidInstallExtensions(e => { + const result = e.filter(e => e.workspaceScoped); + if (result.length) { + this.onDidInstallExtensions(result); + } + })); + this._register(this.workbenchExtensionManagementService.onUninstallExtension(e => { + if (e.workspaceScoped) { + this.onUninstallExtension(e.identifier); + } + })); + this._register(this.workbenchExtensionManagementService.onDidUninstallExtension(e => { + if (e.workspaceScoped) { + this.onDidUninstallExtension(e); + } + })); } } @@ -932,14 +634,7 @@ class Extensions extends Disposable { this._local.push(extension); } for (const extension of this.installing) { - if ( - !this.installed.some((installed) => - areSameExtensions( - installed.identifier, - extension.identifier, - ), - ) - ) { + if (!this.installed.some(installed => areSameExtensions(installed.identifier, extension.identifier))) { this._local.push(extension); } } @@ -947,98 +642,50 @@ class Extensions extends Disposable { return this._local; } - async queryInstalled( - productVersion: IProductVersion, - ): Promise { + async queryInstalled(productVersion: IProductVersion): Promise { await this.fetchInstalledExtensions(productVersion); this._onChange.fire(undefined); return this.local; } - async syncInstalledExtensionsWithGallery( - galleryExtensions: IGalleryExtension[], - productVersion: IProductVersion, - ): Promise { - const extensions = - await this.mapInstalledExtensionWithCompatibleGalleryExtension( - galleryExtensions, - productVersion, - ); + async syncInstalledExtensionsWithGallery(galleryExtensions: IGalleryExtension[], productVersion: IProductVersion): Promise { + const extensions = await this.mapInstalledExtensionWithCompatibleGalleryExtension(galleryExtensions, productVersion); for (const [extension, gallery] of extensions) { // update metadata of the extension if it does not exist if (extension.local && !extension.local.identifier.uuid) { - extension.local = await this.updateMetadata( - extension.local, - gallery, - ); - } - if ( - !extension.gallery || - extension.gallery.version !== gallery.version || - extension.gallery.properties.targetPlatform !== - gallery.properties.targetPlatform - ) { + extension.local = await this.updateMetadata(extension.local, gallery); + } + if (!extension.gallery || extension.gallery.version !== gallery.version || extension.gallery.properties.targetPlatform !== gallery.properties.targetPlatform) { extension.gallery = gallery; this._onChange.fire({ extension }); } } } - private async mapInstalledExtensionWithCompatibleGalleryExtension( - galleryExtensions: IGalleryExtension[], - productVersion: IProductVersion, - ): Promise<[Extension, IGalleryExtension][]> { - const mappedExtensions = - this.mapInstalledExtensionWithGalleryExtension(galleryExtensions); - const targetPlatform = - await this.server.extensionManagementService.getTargetPlatform(); + private async mapInstalledExtensionWithCompatibleGalleryExtension(galleryExtensions: IGalleryExtension[], productVersion: IProductVersion): Promise<[Extension, IGalleryExtension][]> { + const mappedExtensions = this.mapInstalledExtensionWithGalleryExtension(galleryExtensions); + const targetPlatform = await this.server.extensionManagementService.getTargetPlatform(); const compatibleGalleryExtensions: IGalleryExtension[] = []; const compatibleGalleryExtensionsToFetch: IExtensionInfo[] = []; - await Promise.allSettled( - mappedExtensions.map(async ([extension, gallery]) => { - if (extension.local) { - if ( - await this.galleryService.isExtensionCompatible( - gallery, - extension.local.preRelease, - targetPlatform, - productVersion, - ) - ) { - compatibleGalleryExtensions.push(gallery); - } else { - compatibleGalleryExtensionsToFetch.push({ - ...extension.local.identifier, - preRelease: extension.local.preRelease, - }); - } + await Promise.allSettled(mappedExtensions.map(async ([extension, gallery]) => { + if (extension.local) { + if (await this.galleryService.isExtensionCompatible(gallery, extension.local.preRelease, targetPlatform, productVersion)) { + compatibleGalleryExtensions.push(gallery); + } else { + compatibleGalleryExtensionsToFetch.push({ ...extension.local.identifier, preRelease: extension.local.preRelease }); } - }), - ); + } + })); if (compatibleGalleryExtensionsToFetch.length) { - const result = await this.galleryService.getExtensions( - compatibleGalleryExtensionsToFetch, - { - targetPlatform, - compatible: true, - queryAllVersions: true, - productVersion, - }, - CancellationToken.None, - ); + const result = await this.galleryService.getExtensions(compatibleGalleryExtensionsToFetch, { targetPlatform, compatible: true, queryAllVersions: true, productVersion }, CancellationToken.None); compatibleGalleryExtensions.push(...result); } - return this.mapInstalledExtensionWithGalleryExtension( - compatibleGalleryExtensions, - ); + return this.mapInstalledExtensionWithGalleryExtension(compatibleGalleryExtensions); } - private mapInstalledExtensionWithGalleryExtension( - galleryExtensions: IGalleryExtension[], - ): [Extension, IGalleryExtension][] { + private mapInstalledExtensionWithGalleryExtension(galleryExtensions: IGalleryExtension[]): [Extension, IGalleryExtension][] { const mappedExtensions: [Extension, IGalleryExtension][] = []; - const byUUID = new Map(), - byID = new Map(); + const byUUID = new Map(), byID = new Map(); for (const gallery of galleryExtensions) { byUUID.set(gallery.identifier.uuid, gallery); byID.set(gallery.identifier.id.toLowerCase(), gallery); @@ -1051,7 +698,7 @@ class Extensions extends Disposable { continue; } } - if (installed.local?.source !== "resource") { + if (installed.local?.source !== 'resource') { const gallery = byID.get(installed.identifier.id.toLowerCase()); if (gallery) { mappedExtensions.push([installed, gallery]); @@ -1061,139 +708,71 @@ class Extensions extends Disposable { return mappedExtensions; } - private async updateMetadata( - localExtension: ILocalExtension, - gallery: IGalleryExtension, - ): Promise { + private async updateMetadata(localExtension: ILocalExtension, gallery: IGalleryExtension): Promise { let isPreReleaseVersion = false; if (localExtension.manifest.version !== gallery.version) { type GalleryServiceMatchInstalledExtensionClassification = { - owner: "sandy081"; - comment: "Report when a request is made to update metadata of an installed extension"; + owner: 'sandy081'; + comment: 'Report when a request is made to update metadata of an installed extension'; }; - this.telemetryService.publicLog2< - {}, - GalleryServiceMatchInstalledExtensionClassification - >("galleryService:updateMetadata"); - const galleryWithLocalVersion: IGalleryExtension | undefined = ( - await this.galleryService.getExtensions( - [ - { - ...localExtension.identifier, - version: localExtension.manifest.version, - }, - ], - CancellationToken.None, - ) - )[0]; - isPreReleaseVersion = - !!galleryWithLocalVersion?.properties?.isPreReleaseVersion; - } - return this.workbenchExtensionManagementService.updateMetadata( - localExtension, - { - id: gallery.identifier.uuid, - publisherDisplayName: gallery.publisherDisplayName, - publisherId: gallery.publisherId, - isPreReleaseVersion, - }, - ); - } - - canInstall( - galleryExtension: IGalleryExtension, - ): Promise { - return this.server.extensionManagementService.canInstall( - galleryExtension, - ); + this.telemetryService.publicLog2<{}, GalleryServiceMatchInstalledExtensionClassification>('galleryService:updateMetadata'); + const galleryWithLocalVersion: IGalleryExtension | undefined = (await this.galleryService.getExtensions([{ ...localExtension.identifier, version: localExtension.manifest.version }], CancellationToken.None))[0]; + isPreReleaseVersion = !!galleryWithLocalVersion?.properties?.isPreReleaseVersion; + } + return this.workbenchExtensionManagementService.updateMetadata(localExtension, { id: gallery.identifier.uuid, publisherDisplayName: gallery.publisherDisplayName, publisherId: gallery.publisherId, isPreReleaseVersion }); + } + + canInstall(galleryExtension: IGalleryExtension): Promise { + return this.server.extensionManagementService.canInstall(galleryExtension); } private onInstallExtension(event: InstallExtensionEvent): void { const { source } = event; if (source && !URI.isUri(source)) { - const extension = - this.installed.find((e) => - areSameExtensions(e.identifier, source.identifier), - ) ?? - this.instantiationService.createInstance( - Extension, - this.stateProvider, - this.runtimeStateProvider, - this.server, - undefined, - source, - undefined, - ); + const extension = this.installed.find(e => areSameExtensions(e.identifier, source.identifier)) + ?? this.instantiationService.createInstance(Extension, this.stateProvider, this.runtimeStateProvider, this.server, undefined, source, undefined); this.installing.push(extension); this._onChange.fire({ extension }); } } - private async fetchInstalledExtensions( - productVersion?: IProductVersion, - ): Promise { - const extensionsControlManifest = - await this.server.extensionManagementService.getExtensionsControlManifest(); - const all = await this.server.extensionManagementService.getInstalled( - undefined, - undefined, - productVersion, - ); + private async fetchInstalledExtensions(productVersion?: IProductVersion): Promise { + const extensionsControlManifest = await this.server.extensionManagementService.getExtensionsControlManifest(); + const all = await this.server.extensionManagementService.getInstalled(undefined, undefined, productVersion); if (this.isWorkspaceServer) { - all.push( - ...(await this.workbenchExtensionManagementService.getInstalledWorkspaceExtensions( - true, - )), - ); + all.push(...await this.workbenchExtensionManagementService.getInstalledWorkspaceExtensions(true)); } // dedup workspace, user and system extensions by giving priority to workspace first and then to user extension. - const installed = groupByExtension(all, (r) => r.identifier).reduce( - (result, extensions) => { - if (extensions.length === 1) { - result.push(extensions[0]); - } else { - let workspaceExtension: ILocalExtension | undefined, - userExtension: ILocalExtension | undefined, - systemExtension: ILocalExtension | undefined; - for (const extension of extensions) { - if (extension.isWorkspaceScoped) { - workspaceExtension = extension; - } else if (extension.type === ExtensionType.User) { - userExtension = extension; - } else { - systemExtension = extension; - } - } - const extension = - workspaceExtension ?? userExtension ?? systemExtension; - if (extension) { - result.push(extension); + const installed = groupByExtension(all, r => r.identifier).reduce((result, extensions) => { + if (extensions.length === 1) { + result.push(extensions[0]); + } else { + let workspaceExtension: ILocalExtension | undefined, + userExtension: ILocalExtension | undefined, + systemExtension: ILocalExtension | undefined; + for (const extension of extensions) { + if (extension.isWorkspaceScoped) { + workspaceExtension = extension; + } else if (extension.type === ExtensionType.User) { + userExtension = extension; + } else { + systemExtension = extension; } } - return result; - }, - [], - ); - - const byId = index(this.installed, (e) => - e.local ? e.local.identifier.id : e.identifier.id, - ); - this.installed = installed.map((local) => { - const extension = - byId[local.identifier.id] || - this.instantiationService.createInstance( - Extension, - this.stateProvider, - this.runtimeStateProvider, - this.server, - local, - undefined, - undefined, - ); + const extension = workspaceExtension ?? userExtension ?? systemExtension; + if (extension) { + result.push(extension); + } + } + return result; + }, []); + + const byId = index(this.installed, e => e.local ? e.local.identifier.id : e.identifier.id); + this.installed = installed.map(local => { + const extension = byId[local.identifier.id] || this.instantiationService.createInstance(Extension, this.stateProvider, this.runtimeStateProvider, this.server, local, undefined, undefined); extension.local = local; - extension.enablementState = - this.extensionEnablementService.getEnablementState(local); + extension.enablementState = this.extensionEnablementService.getEnablementState(local); extension.setExtensionsControlManifest(extensionsControlManifest); return extension; }); @@ -1207,40 +786,21 @@ class Extensions extends Disposable { this._onReset.fire(); } - private async onDidInstallExtensions( - results: readonly InstallExtensionResult[], - ): Promise { + private async onDidInstallExtensions(results: readonly InstallExtensionResult[]): Promise { + const extensions: Extension[] = []; for (const event of results) { const { local, source } = event; const gallery = source && !URI.isUri(source) ? source : undefined; const location = source && URI.isUri(source) ? source : undefined; - const installingExtension = gallery - ? this.installing.filter((e) => - areSameExtensions(e.identifier, gallery.identifier), - )[0] - : null; - this.installing = installingExtension - ? this.installing.filter((e) => e !== installingExtension) - : this.installing; - - let extension: Extension | undefined = installingExtension - ? installingExtension - : location || local - ? this.instantiationService.createInstance( - Extension, - this.stateProvider, - this.runtimeStateProvider, - this.server, - local, - undefined, - undefined, - ) + const installingExtension = gallery ? this.installing.filter(e => areSameExtensions(e.identifier, gallery.identifier))[0] : null; + this.installing = installingExtension ? this.installing.filter(e => e !== installingExtension) : this.installing; + + let extension: Extension | undefined = installingExtension ? installingExtension + : (location || local) ? this.instantiationService.createInstance(Extension, this.stateProvider, this.runtimeStateProvider, this.server, local, undefined, undefined) : undefined; if (extension) { if (local) { - const installed = this.installed.filter((e) => - areSameExtensions(e.identifier, extension!.identifier), - )[0]; + const installed = this.installed.filter(e => areSameExtensions(e.identifier, extension!.identifier))[0]; if (installed) { extension = installed; } else { @@ -1250,41 +810,27 @@ class Extensions extends Disposable { if (!extension.gallery) { extension.gallery = gallery; } - extension.setExtensionsControlManifest( - await this.server.extensionManagementService.getExtensionsControlManifest(), - ); - extension.enablementState = - this.extensionEnablementService.getEnablementState( - local, - ); + extension.enablementState = this.extensionEnablementService.getEnablementState(local); } + extensions.push(extension); } - this._onChange.fire( - !local || !extension - ? undefined - : { extension, operation: event.operation }, - ); - if ( - extension && - extension.local && - !extension.gallery && - extension.local.source !== "resource" - ) { - await this.syncInstalledExtensionWithGallery(extension); + this._onChange.fire(!local || !extension ? undefined : { extension, operation: event.operation }); + } + + if (extensions.length) { + const manifest = await this.server.extensionManagementService.getExtensionsControlManifest(); + for (const extension of extensions) { + extension.setExtensionsControlManifest(manifest); } + this.matchInstalledExtensionsWithGallery(extensions); } } - private async onDidUpdateExtensionMetadata( - local: ILocalExtension, - ): Promise { - const extension = this.installed.find((e) => - areSameExtensions(e.identifier, local.identifier), - ); + private async onDidUpdateExtensionMetadata(local: ILocalExtension): Promise { + const extension = this.installed.find(e => areSameExtensions(e.identifier, local.identifier)); if (extension?.local) { - const hasChanged = - extension.local.pinned !== local.pinned || - extension.local.preRelease !== local.preRelease; + const hasChanged = extension.local.pinned !== local.pinned + || extension.local.preRelease !== local.preRelease; extension.local = local; if (hasChanged) { this._onChange.fire({ extension }); @@ -1292,99 +838,54 @@ class Extensions extends Disposable { } } - private async syncInstalledExtensionWithGallery( - extension: Extension, - ): Promise { + private async matchInstalledExtensionsWithGallery(extensions: Extension[]): Promise { + const toMatch = extensions.filter(e => e.local && !e.gallery && e.local.source !== 'resource'); + if (!toMatch.length) { + return; + } if (!this.galleryService.isEnabled()) { return; } type GalleryServiceMatchInstalledExtensionClassification = { - owner: "sandy081"; - comment: "Report when a request is made to match installed extension with gallery"; + owner: 'sandy081'; + comment: 'Report when a request is made to match installed extension with gallery'; }; - this.telemetryService.publicLog2< - {}, - GalleryServiceMatchInstalledExtensionClassification - >("galleryService:matchInstalledExtension"); - const [compatible] = await this.galleryService.getExtensions( - [ - { - ...extension.identifier, - preRelease: extension.local?.preRelease, - }, - ], - { - compatible: true, - targetPlatform: - await this.server.extensionManagementService.getTargetPlatform(), - }, - CancellationToken.None, - ); - if (compatible) { - extension.gallery = compatible; - this._onChange.fire({ extension }); + this.telemetryService.publicLog2<{}, GalleryServiceMatchInstalledExtensionClassification>('galleryService:matchInstalledExtension'); + const galleryExtensions = await this.galleryService.getExtensions(toMatch.map(e => ({ ...e.identifier, preRelease: e.local?.preRelease })), { compatible: true, targetPlatform: await this.server.extensionManagementService.getTargetPlatform() }, CancellationToken.None); + for (const extension of extensions) { + const compatible = galleryExtensions.find(e => areSameExtensions(e.identifier, extension.identifier)); + if (compatible) { + extension.gallery = compatible; + this._onChange.fire({ extension }); + } } } private onUninstallExtension(identifier: IExtensionIdentifier): void { - const extension = this.installed.filter((e) => - areSameExtensions(e.identifier, identifier), - )[0]; + const extension = this.installed.filter(e => areSameExtensions(e.identifier, identifier))[0]; if (extension) { - const uninstalling = - this.uninstalling.filter((e) => - areSameExtensions(e.identifier, identifier), - )[0] || extension; - this.uninstalling = [ - uninstalling, - ...this.uninstalling.filter( - (e) => !areSameExtensions(e.identifier, identifier), - ), - ]; - this._onChange.fire( - uninstalling ? { extension: uninstalling } : undefined, - ); - } - } - - private onDidUninstallExtension({ - identifier, - error, - }: DidUninstallExtensionEvent): void { - const uninstalled = - this.uninstalling.find((e) => - areSameExtensions(e.identifier, identifier), - ) || - this.installed.find((e) => - areSameExtensions(e.identifier, identifier), - ); - this.uninstalling = this.uninstalling.filter( - (e) => !areSameExtensions(e.identifier, identifier), - ); + const uninstalling = this.uninstalling.filter(e => areSameExtensions(e.identifier, identifier))[0] || extension; + this.uninstalling = [uninstalling, ...this.uninstalling.filter(e => !areSameExtensions(e.identifier, identifier))]; + this._onChange.fire(uninstalling ? { extension: uninstalling } : undefined); + } + } + + private onDidUninstallExtension({ identifier, error }: DidUninstallExtensionEvent): void { + const uninstalled = this.uninstalling.find(e => areSameExtensions(e.identifier, identifier)) || this.installed.find(e => areSameExtensions(e.identifier, identifier)); + this.uninstalling = this.uninstalling.filter(e => !areSameExtensions(e.identifier, identifier)); if (!error) { - this.installed = this.installed.filter( - (e) => !areSameExtensions(e.identifier, identifier), - ); + this.installed = this.installed.filter(e => !areSameExtensions(e.identifier, identifier)); } if (uninstalled) { this._onChange.fire({ extension: uninstalled }); } } - private onEnablementChanged( - platformExtensions: readonly IPlatformExtension[], - ) { - const extensions = this.local.filter((e) => - platformExtensions.some((p) => - areSameExtensions(e.identifier, p.identifier), - ), - ); + private onEnablementChanged(platformExtensions: readonly IPlatformExtension[]) { + const extensions = this.local.filter(e => platformExtensions.some(p => areSameExtensions(e.identifier, p.identifier))); for (const extension of extensions) { if (extension.local) { - const enablementState = - this.extensionEnablementService.getEnablementState( - extension.local, - ); + const enablementState = this.extensionEnablementService.getEnablementState(extension.local); if (enablementState !== extension.enablementState) { (extension as Extension).enablementState = enablementState; this._onChange.fire({ extension: extension as Extension }); @@ -1394,44 +895,19 @@ class Extensions extends Disposable { } getExtensionState(extension: Extension): ExtensionState { - if ( - extension.gallery && - this.installing.some( - (e) => - !!e.gallery && - areSameExtensions( - e.gallery.identifier, - extension.gallery!.identifier, - ), - ) - ) { + if (extension.gallery && this.installing.some(e => !!e.gallery && areSameExtensions(e.gallery.identifier, extension.gallery!.identifier))) { return ExtensionState.Installing; } - if ( - this.uninstalling.some((e) => - areSameExtensions(e.identifier, extension.identifier), - ) - ) { + if (this.uninstalling.some(e => areSameExtensions(e.identifier, extension.identifier))) { return ExtensionState.Uninstalling; } - const local = this.installed.filter( - (e) => - e === extension || - (e.gallery && - extension.gallery && - areSameExtensions( - e.gallery.identifier, - extension.gallery.identifier, - )), - )[0]; + const local = this.installed.filter(e => e === extension || (e.gallery && extension.gallery && areSameExtensions(e.gallery.identifier, extension.gallery.identifier)))[0]; return local ? ExtensionState.Installed : ExtensionState.Uninstalled; } } -export class ExtensionsWorkbenchService - extends Disposable - implements IExtensionsWorkbenchService, IURLHandler -{ +export class ExtensionsWorkbenchService extends Disposable implements IExtensionsWorkbenchService, IURLHandler { + private static readonly UpdatesCheckInterval = 1000 * 60 * 60 * 12; // 12 hours declare readonly _serviceBrand: undefined; @@ -1445,26 +921,17 @@ export class ExtensionsWorkbenchService private updatesCheckDelayer: ThrottledDelayer; private autoUpdateDelayer: ThrottledDelayer; - private readonly _onChange = this._register( - new Emitter(), - ); - get onChange(): Event { - return this._onChange.event; - } + private readonly _onChange = this._register(new Emitter()); + get onChange(): Event { return this._onChange.event; } private extensionsNotification: IExtensionsNotification | undefined; - private readonly _onDidChangeExtensionsNotification = new Emitter< - IExtensionsNotification | undefined - >(); - readonly onDidChangeExtensionsNotification = - this._onDidChangeExtensionsNotification.event; + private readonly _onDidChangeExtensionsNotification = new Emitter(); + readonly onDidChangeExtensionsNotification = this._onDidChangeExtensionsNotification.event; private readonly _onReset = new Emitter(); - get onReset() { - return this._onReset.event; - } + get onReset() { return this._onReset.event; } - readonly preferPreReleases = this.productService.quality !== "stable"; + readonly preferPreReleases = this.productService.quality !== 'stable'; private installing: IExtension[] = []; private tasksInProgress: CancelablePromise[] = []; @@ -1472,139 +939,91 @@ export class ExtensionsWorkbenchService readonly whenInitialized: Promise; constructor( - @IInstantiationService - private readonly instantiationService: IInstantiationService, + @IInstantiationService private readonly instantiationService: IInstantiationService, @IEditorService private readonly editorService: IEditorService, - @IWorkbenchExtensionManagementService - private readonly extensionManagementService: IWorkbenchExtensionManagementService, - @IExtensionGalleryService - private readonly galleryService: IExtensionGalleryService, - @IConfigurationService - private readonly configurationService: IConfigurationService, + @IWorkbenchExtensionManagementService private readonly extensionManagementService: IWorkbenchExtensionManagementService, + @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, + @IConfigurationService private readonly configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @INotificationService - private readonly notificationService: INotificationService, + @INotificationService private readonly notificationService: INotificationService, @IURLService urlService: IURLService, - @IWorkbenchExtensionEnablementService - private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, @IHostService private readonly hostService: IHostService, @IProgressService private readonly progressService: IProgressService, - @IExtensionManagementServerService - private readonly extensionManagementServerService: IExtensionManagementServerService, + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @ILanguageService private readonly languageService: ILanguageService, - @IIgnoredExtensionsManagementService - private readonly extensionsSyncManagementService: IIgnoredExtensionsManagementService, - @IUserDataAutoSyncService - private readonly userDataAutoSyncService: IUserDataAutoSyncService, + @IIgnoredExtensionsManagementService private readonly extensionsSyncManagementService: IIgnoredExtensionsManagementService, + @IUserDataAutoSyncService private readonly userDataAutoSyncService: IUserDataAutoSyncService, @IProductService private readonly productService: IProductService, @IContextKeyService contextKeyService: IContextKeyService, - @IExtensionManifestPropertiesService - private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, + @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, @ILogService private readonly logService: ILogService, @IExtensionService private readonly extensionService: IExtensionService, @ILocaleService private readonly localeService: ILocaleService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @IFileService private readonly fileService: IFileService, - @IUserDataProfileService - private readonly userDataProfileService: IUserDataProfileService, + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, @IStorageService private readonly storageService: IStorageService, @IDialogService private readonly dialogService: IDialogService, - @IUserDataSyncEnablementService - private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, + @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @IUpdateService private readonly updateService: IUpdateService, - @IUriIdentityService - private readonly uriIdentityService: IUriIdentityService, - @IWorkspaceContextService - private readonly workspaceContextService: IWorkspaceContextService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IViewsService private readonly viewsService: IViewsService, - @IFileDialogService - private readonly fileDialogService: IFileDialogService, - @IQuickInputService - private readonly quickInputService: IQuickInputService, - @IAllowedExtensionsService - private readonly allowedExtensionsService: IAllowedExtensionsService, + @IFileDialogService private readonly fileDialogService: IFileDialogService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService, ) { super(); - const preferPreReleasesValue = configurationService.getValue( - "_extensions.preferPreReleases", - ); + const preferPreReleasesValue = configurationService.getValue('_extensions.preferPreReleases'); if (!isUndefined(preferPreReleasesValue)) { this.preferPreReleases = !!preferPreReleasesValue; } - this.hasOutdatedExtensionsContextKey = - HasOutdatedExtensionsContext.bindTo(contextKeyService); + this.hasOutdatedExtensionsContextKey = HasOutdatedExtensionsContext.bindTo(contextKeyService); if (extensionManagementServerService.localExtensionManagementServer) { - this.localExtensions = this._register( - instantiationService.createInstance( - Extensions, - extensionManagementServerService.localExtensionManagementServer, - (ext) => this.getExtensionState(ext), - (ext) => this.getRuntimeState(ext), - !extensionManagementServerService.remoteExtensionManagementServer, - ), - ); - this._register( - this.localExtensions.onChange((e) => - this.onDidChangeExtensions(e?.extension), - ), - ); - this._register(this.localExtensions.onReset((e) => this.reset())); + this.localExtensions = this._register(instantiationService.createInstance(Extensions, + extensionManagementServerService.localExtensionManagementServer, + ext => this.getExtensionState(ext), + ext => this.getRuntimeState(ext), + !extensionManagementServerService.remoteExtensionManagementServer + )); + this._register(this.localExtensions.onChange(e => this.onDidChangeExtensions(e?.extension))); + this._register(this.localExtensions.onReset(e => this.reset())); this.extensionsServers.push(this.localExtensions); } if (extensionManagementServerService.remoteExtensionManagementServer) { - this.remoteExtensions = this._register( - instantiationService.createInstance( - Extensions, - extensionManagementServerService.remoteExtensionManagementServer, - (ext) => this.getExtensionState(ext), - (ext) => this.getRuntimeState(ext), - true, - ), - ); - this._register( - this.remoteExtensions.onChange((e) => - this.onDidChangeExtensions(e?.extension), - ), - ); - this._register(this.remoteExtensions.onReset((e) => this.reset())); + this.remoteExtensions = this._register(instantiationService.createInstance(Extensions, + extensionManagementServerService.remoteExtensionManagementServer, + ext => this.getExtensionState(ext), + ext => this.getRuntimeState(ext), + true + )); + this._register(this.remoteExtensions.onChange(e => this.onDidChangeExtensions(e?.extension))); + this._register(this.remoteExtensions.onReset(e => this.reset())); this.extensionsServers.push(this.remoteExtensions); } if (extensionManagementServerService.webExtensionManagementServer) { - this.webExtensions = this._register( - instantiationService.createInstance( - Extensions, - extensionManagementServerService.webExtensionManagementServer, - (ext) => this.getExtensionState(ext), - (ext) => this.getRuntimeState(ext), - !( - extensionManagementServerService.remoteExtensionManagementServer || - extensionManagementServerService.localExtensionManagementServer - ), - ), - ); - this._register( - this.webExtensions.onChange((e) => - this.onDidChangeExtensions(e?.extension), - ), - ); - this._register(this.webExtensions.onReset((e) => this.reset())); + this.webExtensions = this._register(instantiationService.createInstance(Extensions, + extensionManagementServerService.webExtensionManagementServer, + ext => this.getExtensionState(ext), + ext => this.getRuntimeState(ext), + !(extensionManagementServerService.remoteExtensionManagementServer || extensionManagementServerService.localExtensionManagementServer) + )); + this._register(this.webExtensions.onChange(e => this.onDidChangeExtensions(e?.extension))); + this._register(this.webExtensions.onReset(e => this.reset())); this.extensionsServers.push(this.webExtensions); } - this.updatesCheckDelayer = new ThrottledDelayer( - ExtensionsWorkbenchService.UpdatesCheckInterval, - ); + this.updatesCheckDelayer = new ThrottledDelayer(ExtensionsWorkbenchService.UpdatesCheckInterval); this.autoUpdateDelayer = new ThrottledDelayer(1000); - this._register( - toDisposable(() => { - this.updatesCheckDelayer.cancel(); - this.autoUpdateDelayer.cancel(); - }), - ); + this._register(toDisposable(() => { + this.updatesCheckDelayer.cancel(); + this.autoUpdateDelayer.cancel(); + })); urlService.registerHandler(this); - if (this.productService.quality !== "stable") { + if (this.productService.quality !== 'stable') { this.registerAutoRestartConfig(); } @@ -1612,38 +1031,30 @@ export class ExtensionsWorkbenchService } private registerAutoRestartConfig(): void { - Registry.as( - ConfigurationExtensions.Configuration, - ).registerConfiguration({ - ...extensionsConfigurationNodeBase, - properties: { - [AutoRestartConfigurationKey]: { - type: "boolean", - description: nls.localize( - "autoRestart", - "If activated, extensions will automatically restart following an update if the window is not in focus. There can be a data loss if you have open Notebooks or Custom Editors.", - ), - default: false, - }, - }, - }); + Registry.as(ConfigurationExtensions.Configuration) + .registerConfiguration({ + id: 'extensions', + order: 30, + title: nls.localize('extensionsConfigurationTitle', "Extensions"), + type: 'object', + properties: { + [AutoRestartConfigurationKey]: { + type: 'boolean', + description: nls.localize('autoRestart', "If activated, extensions will automatically restart following an update if the window is not in focus. There can be a data loss if you have open Notebooks or Custom Editors."), + default: false, + } + } + }); } private async initialize(): Promise { // initialize local extensions - await Promise.all([ - this.queryLocal(), - this.extensionService.whenInstalledExtensionsRegistered(), - ]); + await Promise.all([this.queryLocal(), this.extensionService.whenInstalledExtensionsRegistered()]); if (this._store.isDisposed) { return; } this.onDidChangeRunningExtensions(this.extensionService.extensions, []); - this._register( - this.extensionService.onDidChangeExtensions(({ added, removed }) => - this.onDidChangeRunningExtensions(added, removed), - ), - ); + this._register(this.extensionService.onDidChangeExtensions(({ added, removed }) => this.onDidChangeRunningExtensions(added, removed))); await this.lifecycleService.when(LifecyclePhase.Eventually); if (this._store.isDisposed) { @@ -1653,108 +1064,52 @@ export class ExtensionsWorkbenchService this.initializeAutoUpdate(); this.updateExtensionsNotificaiton(); this.reportInstalledExtensionsTelemetry(); - this._register( - this.storageService.onDidChangeValue( - StorageScope.PROFILE, - EXTENSIONS_DISMISSED_NOTIFICATIONS_KEY, - this._store, - )((e) => this.onDidDismissedNotificationsValueChange()), - ); - this._register( - this.storageService.onDidChangeValue( - StorageScope.APPLICATION, - EXTENSIONS_AUTO_UPDATE_KEY, - this._store, - )((e) => this.onDidSelectedExtensionToAutoUpdateValueChange()), - ); - this._register( - this.storageService.onDidChangeValue( - StorageScope.APPLICATION, - EXTENSIONS_DONOT_AUTO_UPDATE_KEY, - this._store, - )((e) => this.onDidSelectedExtensionToAutoUpdateValueChange()), - ); - this._register( - Event.debounce( - this.onChange, - () => undefined, - 100, - )(() => { - this.updateExtensionsNotificaiton(); - this.reportProgressFromOtherSources(); - }), - ); + this._register(this.storageService.onDidChangeValue(StorageScope.PROFILE, EXTENSIONS_DISMISSED_NOTIFICATIONS_KEY, this._store)(e => this.onDidDismissedNotificationsValueChange())); + this._register(this.storageService.onDidChangeValue(StorageScope.APPLICATION, EXTENSIONS_AUTO_UPDATE_KEY, this._store)(e => this.onDidSelectedExtensionToAutoUpdateValueChange())); + this._register(this.storageService.onDidChangeValue(StorageScope.APPLICATION, EXTENSIONS_DONOT_AUTO_UPDATE_KEY, this._store)(e => this.onDidSelectedExtensionToAutoUpdateValueChange())); + this._register(Event.debounce(this.onChange, () => undefined, 100)(() => { + this.updateExtensionsNotificaiton(); + this.reportProgressFromOtherSources(); + })); } private initializeAutoUpdate(): void { // Register listeners for auto updates - this._register( - this.configurationService.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration(AutoUpdateConfigurationKey)) { - if (this.isAutoUpdateEnabled()) { - this.eventuallyAutoUpdateExtensions(); - } + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(AutoUpdateConfigurationKey)) { + if (this.isAutoUpdateEnabled()) { + this.eventuallyAutoUpdateExtensions(); } - if (e.affectsConfiguration(AutoCheckUpdatesConfigurationKey)) { - if (this.isAutoCheckUpdatesEnabled()) { - this.checkForUpdates(); - } - } - }), - ); - this._register( - this.extensionEnablementService.onEnablementChanged( - (platformExtensions) => { - if ( - this.getAutoUpdateValue() === "onlyEnabledExtensions" && - platformExtensions.some((e) => - this.extensionEnablementService.isEnabled(e), - ) - ) { - this.checkForUpdates(); - } - }, - ), - ); - this._register( - Event.debounce( - this.onChange, - () => undefined, - 100, - )(() => - this.hasOutdatedExtensionsContextKey.set( - this.outdated.length > 0, - ), - ), - ); - this._register( - this.updateService.onStateChange((e) => { - if ( - (e.type === StateType.CheckingForUpdates && e.explicit) || - e.type === StateType.AvailableForDownload || - e.type === StateType.Downloaded - ) { - this.telemetryService.publicLog2< - {}, - { - owner: "sandy081"; - comment: "Report when update check is triggered on product update"; - } - >("extensions:updatecheckonproductupdate"); - if (this.isAutoCheckUpdatesEnabled()) { - this.checkForUpdates(); - } + } + if (e.affectsConfiguration(AutoCheckUpdatesConfigurationKey)) { + if (this.isAutoCheckUpdatesEnabled()) { + this.checkForUpdates(); } - }), - ); - - this._register( - this.allowedExtensionsService.onDidChangeAllowedExtensions(() => { + } + })); + this._register(this.extensionEnablementService.onEnablementChanged(platformExtensions => { + if (this.getAutoUpdateValue() === 'onlyEnabledExtensions' && platformExtensions.some(e => this.extensionEnablementService.isEnabled(e))) { + this.checkForUpdates(); + } + })); + this._register(Event.debounce(this.onChange, () => undefined, 100)(() => this.hasOutdatedExtensionsContextKey.set(this.outdated.length > 0))); + this._register(this.updateService.onStateChange(e => { + if ((e.type === StateType.CheckingForUpdates && e.explicit) || e.type === StateType.AvailableForDownload || e.type === StateType.Downloaded) { + this.telemetryService.publicLog2<{}, { + owner: 'sandy081'; + comment: 'Report when update check is triggered on product update'; + }>('extensions:updatecheckonproductupdate'); if (this.isAutoCheckUpdatesEnabled()) { this.checkForUpdates(); } - }), - ); + } + })); + + this._register(this.allowedExtensionsService.onDidChangeAllowedExtensions(() => { + if (this.isAutoCheckUpdatesEnabled()) { + this.checkForUpdates(); + } + })); // Update AutoUpdate Contexts this.hasOutdatedExtensionsContextKey.set(this.outdated.length > 0); @@ -1771,13 +1126,11 @@ export class ExtensionsWorkbenchService } this.registerAutoRestartListener(); - this._register( - this.configurationService.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration(AutoRestartConfigurationKey)) { - this.registerAutoRestartListener(); - } - }), - ); + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(AutoRestartConfigurationKey)) { + this.registerAutoRestartListener(); + } + })); } private isAutoUpdateEnabled(): boolean { @@ -1785,44 +1138,25 @@ export class ExtensionsWorkbenchService } getAutoUpdateValue(): AutoUpdateConfigurationValue { - const autoUpdate = - this.configurationService.getValue( - AutoUpdateConfigurationKey, - ); - if (autoUpdate === "onlySelectedExtensions") { + const autoUpdate = this.configurationService.getValue(AutoUpdateConfigurationKey); + if (autoUpdate === 'onlySelectedExtensions') { return false; } - return isBoolean(autoUpdate) || autoUpdate === "onlyEnabledExtensions" - ? autoUpdate - : true; + return isBoolean(autoUpdate) || autoUpdate === 'onlyEnabledExtensions' ? autoUpdate : true; } - async updateAutoUpdateForAllExtensions( - isAutoUpdateEnabled: boolean, - ): Promise { + async updateAutoUpdateForAllExtensions(isAutoUpdateEnabled: boolean): Promise { const wasAutoUpdateEnabled = this.isAutoUpdateEnabled(); if (wasAutoUpdateEnabled === isAutoUpdateEnabled) { return; } const result = await this.dialogService.confirm({ - title: nls.localize( - "confirmEnableDisableAutoUpdate", - "Auto Update Extensions", - ), + title: nls.localize('confirmEnableDisableAutoUpdate', "Auto Update Extensions"), message: isAutoUpdateEnabled - ? nls.localize( - "confirmEnableAutoUpdate", - "Do you want to enable auto update for all extensions?", - ) - : nls.localize( - "confirmDisableAutoUpdate", - "Do you want to disable auto update for all extensions?", - ), - detail: nls.localize( - "confirmEnableDisableAutoUpdateDetail", - "This will reset any auto update settings you have set for individual extensions.", - ), + ? nls.localize('confirmEnableAutoUpdate', "Do you want to enable auto update for all extensions?") + : nls.localize('confirmDisableAutoUpdate', "Do you want to disable auto update for all extensions?"), + detail: nls.localize('confirmEnableDisableAutoUpdateDetail', "This will reset any auto update settings you have set for individual extensions."), }); if (!result.confirmed) { return; @@ -1831,74 +1165,39 @@ export class ExtensionsWorkbenchService // Reset extensions enabled for auto update first to prevent them from being updated this.setEnabledAutoUpdateExtensions([]); - await this.configurationService.updateValue( - AutoUpdateConfigurationKey, - isAutoUpdateEnabled, - ); + await this.configurationService.updateValue(AutoUpdateConfigurationKey, isAutoUpdateEnabled); this.setDisabledAutoUpdateExtensions([]); await this.updateExtensionsPinnedState(!isAutoUpdateEnabled); this._onChange.fire(undefined); } - private readonly autoRestartListenerDisposable = this._register( - new MutableDisposable(), - ); + private readonly autoRestartListenerDisposable = this._register(new MutableDisposable()); private registerAutoRestartListener(): void { this.autoRestartListenerDisposable.value = undefined; - if ( - this.configurationService.getValue(AutoRestartConfigurationKey) === - true - ) { - this.autoRestartListenerDisposable.value = - this.hostService.onDidChangeFocus((focus) => { - if ( - !focus && - this.configurationService.getValue( - AutoRestartConfigurationKey, - ) === true - ) { - this.updateRunningExtensions(true); - } - }); + if (this.configurationService.getValue(AutoRestartConfigurationKey) === true) { + this.autoRestartListenerDisposable.value = this.hostService.onDidChangeFocus(focus => { + if (!focus && this.configurationService.getValue(AutoRestartConfigurationKey) === true) { + this.updateRunningExtensions(true); + } + }); } } private reportInstalledExtensionsTelemetry() { - const extensionIds = this.installed - .filter( - (extension) => - !extension.isBuiltin && - (extension.enablementState === - EnablementState.EnabledWorkspace || - extension.enablementState === - EnablementState.EnabledGlobally), - ) - .map((extension) => - ExtensionIdentifier.toKey(extension.identifier.id), - ); - this.telemetryService.publicLog2< - InstalledExtensionsEvent, - ExtensionsLoadClassification - >("installedExtensions", { - extensionIds: new TelemetryTrustedValue(extensionIds.join(";")), - count: extensionIds.length, - }); + const extensionIds = this.installed.filter(extension => + !extension.isBuiltin && + (extension.enablementState === EnablementState.EnabledWorkspace || + extension.enablementState === EnablementState.EnabledGlobally)) + .map(extension => ExtensionIdentifier.toKey(extension.identifier.id)); + this.telemetryService.publicLog2('installedExtensions', { extensionIds: new TelemetryTrustedValue(extensionIds.join(';')), count: extensionIds.length }); } - private async onDidChangeRunningExtensions( - added: ReadonlyArray, - removed: ReadonlyArray, - ): Promise { + private async onDidChangeRunningExtensions(added: ReadonlyArray, removed: ReadonlyArray): Promise { const changedExtensions: IExtension[] = []; const extensionsToFetch: IExtensionDescription[] = []; for (const desc of added) { - const extension = this.installed.find((e) => - areSameExtensions( - { id: desc.identifier.value, uuid: desc.uuid }, - e.identifier, - ), - ); + const extension = this.installed.find(e => areSameExtensions({ id: desc.identifier.value, uuid: desc.uuid }, e.identifier)); if (extension) { changedExtensions.push(extension); } else { @@ -1907,31 +1206,18 @@ export class ExtensionsWorkbenchService } const workspaceExtensions: IExtensionDescription[] = []; for (const desc of removed) { - if ( - this.workspaceContextService.isInsideWorkspace( - desc.extensionLocation, - ) - ) { + if (this.workspaceContextService.isInsideWorkspace(desc.extensionLocation)) { workspaceExtensions.push(desc); } else { extensionsToFetch.push(desc); } } if (extensionsToFetch.length) { - const extensions = await this.getExtensions( - extensionsToFetch.map((e) => ({ - id: e.identifier.value, - uuid: e.uuid, - })), - CancellationToken.None, - ); + const extensions = await this.getExtensions(extensionsToFetch.map(e => ({ id: e.identifier.value, uuid: e.uuid })), CancellationToken.None); changedExtensions.push(...extensions); } if (workspaceExtensions.length) { - const extensions = await this.getResourceExtensions( - workspaceExtensions.map((e) => e.extensionLocation), - true, - ); + const extensions = await this.getResourceExtensions(workspaceExtensions.map(e => e.extensionLocation), true); changedExtensions.push(...extensions); } for (const changedExtension of changedExtensions) { @@ -1940,19 +1226,10 @@ export class ExtensionsWorkbenchService } private updateExtensionsPinnedState(pinned: boolean): Promise { - return this.progressService.withProgress( - { - location: ProgressLocation.Extensions, - title: nls.localize( - "updatingExtensions", - "Updating Extensions Auto Update State", - ), - }, - () => - this.extensionManagementService.resetPinnedStateForAllUserExtensions( - pinned, - ), - ); + return this.progressService.withProgress({ + location: ProgressLocation.Extensions, + title: nls.localize('updatingExtensions', "Updating Extensions Auto Update State"), + }, () => this.extensionManagementService.resetPinnedStateForAllUserExtensions(pinned)); } private reset(): void { @@ -1978,10 +1255,7 @@ export class ExtensionsWorkbenchService this._local = this.installed; } else { this._local = []; - const byId = groupByExtension( - this.installed, - (r) => r.identifier, - ); + const byId = groupByExtension(this.installed, r => r.identifier); for (const extensions of byId) { this._local.push(this.getPrimaryExtension(extensions)); } @@ -2004,69 +1278,43 @@ export class ExtensionsWorkbenchService } get outdated(): IExtension[] { - return this.installed.filter( - (e) => - e.outdated && e.local && e.state === ExtensionState.Installed, - ); + return this.installed.filter(e => e.outdated && e.local && e.state === ExtensionState.Installed); } - async queryLocal( - server?: IExtensionManagementServer, - ): Promise { + async queryLocal(server?: IExtensionManagementServer): Promise { if (server) { - if ( - this.localExtensions && - this.extensionManagementServerService - .localExtensionManagementServer === server - ) { - return this.localExtensions.queryInstalled( - this.getProductVersion(), - ); - } - if ( - this.remoteExtensions && - this.extensionManagementServerService - .remoteExtensionManagementServer === server - ) { - return this.remoteExtensions.queryInstalled( - this.getProductVersion(), - ); - } - if ( - this.webExtensions && - this.extensionManagementServerService - .webExtensionManagementServer === server - ) { - return this.webExtensions.queryInstalled( - this.getProductVersion(), - ); + if (this.localExtensions && this.extensionManagementServerService.localExtensionManagementServer === server) { + return this.localExtensions.queryInstalled(this.getProductVersion()); + } + if (this.remoteExtensions && this.extensionManagementServerService.remoteExtensionManagementServer === server) { + return this.remoteExtensions.queryInstalled(this.getProductVersion()); + } + if (this.webExtensions && this.extensionManagementServerService.webExtensionManagementServer === server) { + return this.webExtensions.queryInstalled(this.getProductVersion()); } } if (this.localExtensions) { try { - await this.localExtensions.queryInstalled( - this.getProductVersion(), - ); - } catch (error) { + await this.localExtensions.queryInstalled(this.getProductVersion()); + } + catch (error) { this.logService.error(error); } } if (this.remoteExtensions) { try { - await this.remoteExtensions.queryInstalled( - this.getProductVersion(), - ); - } catch (error) { + await this.remoteExtensions.queryInstalled(this.getProductVersion()); + } + catch (error) { this.logService.error(error); } } if (this.webExtensions) { try { - await this.webExtensions.queryInstalled( - this.getProductVersion(), - ); - } catch (error) { + await this.webExtensions.queryInstalled(this.getProductVersion()); + } + catch (error) { this.logService.error(error); } } @@ -2074,113 +1322,55 @@ export class ExtensionsWorkbenchService } queryGallery(token: CancellationToken): Promise>; - queryGallery( - options: IQueryOptions, - token: CancellationToken, - ): Promise>; + queryGallery(options: IQueryOptions, token: CancellationToken): Promise>; async queryGallery(arg1: any, arg2?: any): Promise> { if (!this.galleryService.isEnabled()) { return singlePagePager([]); } - const options: IQueryOptions = CancellationToken.isCancellationToken( - arg1, - ) - ? {} - : arg1; - const token: CancellationToken = CancellationToken.isCancellationToken( - arg1, - ) - ? arg1 - : arg2; - options.text = options.text - ? this.resolveQueryText(options.text) - : options.text; - options.includePreRelease = isUndefined(options.includePreRelease) - ? this.preferPreReleases - : options.includePreRelease; - - const extensionsControlManifest = - await this.extensionManagementService.getExtensionsControlManifest(); + const options: IQueryOptions = CancellationToken.isCancellationToken(arg1) ? {} : arg1; + const token: CancellationToken = CancellationToken.isCancellationToken(arg1) ? arg1 : arg2; + options.text = options.text ? this.resolveQueryText(options.text) : options.text; + options.includePreRelease = isUndefined(options.includePreRelease) ? this.preferPreReleases : options.includePreRelease; + + const extensionsControlManifest = await this.extensionManagementService.getExtensionsControlManifest(); const pager = await this.galleryService.query(options, token); this.syncInstalledExtensionsWithGallery(pager.firstPage); return { - firstPage: pager.firstPage.map((gallery) => - this.fromGallery(gallery, extensionsControlManifest), - ), + firstPage: pager.firstPage.map(gallery => this.fromGallery(gallery, extensionsControlManifest)), total: pager.total, pageSize: pager.pageSize, getPage: async (pageIndex, token) => { const page = await pager.getPage(pageIndex, token); this.syncInstalledExtensionsWithGallery(page); - return page.map((gallery) => - this.fromGallery(gallery, extensionsControlManifest), - ); - }, + return page.map(gallery => this.fromGallery(gallery, extensionsControlManifest)); + } }; } - getExtensions( - extensionInfos: IExtensionInfo[], - token: CancellationToken, - ): Promise; - getExtensions( - extensionInfos: IExtensionInfo[], - options: IExtensionQueryOptions, - token: CancellationToken, - ): Promise; - async getExtensions( - extensionInfos: IExtensionInfo[], - arg1: any, - arg2?: any, - ): Promise { + getExtensions(extensionInfos: IExtensionInfo[], token: CancellationToken): Promise; + getExtensions(extensionInfos: IExtensionInfo[], options: IExtensionQueryOptions, token: CancellationToken): Promise; + async getExtensions(extensionInfos: IExtensionInfo[], arg1: any, arg2?: any): Promise { if (!this.galleryService.isEnabled()) { return []; } - extensionInfos.forEach( - (e) => (e.preRelease = e.preRelease ?? this.preferPreReleases), - ); - const extensionsControlManifest = - await this.extensionManagementService.getExtensionsControlManifest(); - const galleryExtensions = await this.galleryService.getExtensions( - extensionInfos, - arg1, - arg2, - ); + extensionInfos.forEach(e => e.preRelease = e.preRelease ?? this.preferPreReleases); + const extensionsControlManifest = await this.extensionManagementService.getExtensionsControlManifest(); + const galleryExtensions = await this.galleryService.getExtensions(extensionInfos, arg1, arg2); this.syncInstalledExtensionsWithGallery(galleryExtensions); - return galleryExtensions.map((gallery) => - this.fromGallery(gallery, extensionsControlManifest), - ); - } - - async getResourceExtensions( - locations: URI[], - isWorkspaceScoped: boolean, - ): Promise { - const resourceExtensions = - await this.extensionManagementService.getExtensions(locations); - return resourceExtensions.map( - (resourceExtension) => - this.getInstalledExtensionMatchingLocation( - resourceExtension.location, - ) ?? - this.instantiationService.createInstance( - Extension, - (ext) => this.getExtensionState(ext), - (ext) => this.getRuntimeState(ext), - undefined, - undefined, - undefined, - { resourceExtension, isWorkspaceScoped }, - ), - ); + return galleryExtensions.map(gallery => this.fromGallery(gallery, extensionsControlManifest)); + } + + async getResourceExtensions(locations: URI[], isWorkspaceScoped: boolean): Promise { + const resourceExtensions = await this.extensionManagementService.getExtensions(locations); + return resourceExtensions.map(resourceExtension => this.getInstalledExtensionMatchingLocation(resourceExtension.location) + ?? this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext), undefined, undefined, undefined, { resourceExtension, isWorkspaceScoped })); } private onDidDismissedNotificationsValueChange(): void { if ( - this.dismissedNotificationsValue !== - this.getDismissedNotificationsValue() /* This checks if current window changed the value or not */ + this.dismissedNotificationsValue !== this.getDismissedNotificationsValue() /* This checks if current window changed the value or not */ ) { this._dismissedNotificationsValue = undefined; this.updateExtensionsNotificaiton(); @@ -2195,26 +1385,17 @@ export class ExtensionsWorkbenchService if (computedNotificiations.length) { // populate dismissed notifications with the ones that are still valid for (const dismissedNotification of this.getDismissedNotifications()) { - if ( - computedNotificiations.some( - (e) => e.key === dismissedNotification, - ) - ) { + if (computedNotificiations.some(e => e.key === dismissedNotification)) { dismissedNotifications.push(dismissedNotification); } } - if ( - !dismissedNotifications.includes(computedNotificiations[0].key) - ) { + if (!dismissedNotifications.includes(computedNotificiations[0].key)) { extensionsNotification = { message: computedNotificiations[0].message, severity: computedNotificiations[0].severity, extensions: computedNotificiations[0].extensions, dismiss: () => { - this.setDismissedNotifications([ - ...this.getDismissedNotifications(), - computedNotificiations[0].key, - ]); + this.setDismissedNotifications([...this.getDismissedNotifications(), computedNotificiations[0].key]); this.updateExtensionsNotificaiton(); }, }; @@ -2222,131 +1403,52 @@ export class ExtensionsWorkbenchService } this.setDismissedNotifications(dismissedNotifications); - if ( - this.extensionsNotification?.message !== - extensionsNotification?.message - ) { + if (this.extensionsNotification?.message !== extensionsNotification?.message) { this.extensionsNotification = extensionsNotification; - this._onDidChangeExtensionsNotification.fire( - this.extensionsNotification, - ); - } - } - - private computeExtensionsNotifications(): Array< - Omit & { key: string } - > { - const computedNotificiations: Array< - Omit & { key: string } - > = []; - const invalidExtensions = this.local.filter( - (e) => - e.enablementState === - EnablementState.DisabledByInvalidExtension && - !e.isWorkspaceScoped, - ); + this._onDidChangeExtensionsNotification.fire(this.extensionsNotification); + } + } + + private computeExtensionsNotifications(): Array & { key: string }> { + const computedNotificiations: Array & { key: string }> = []; + const invalidExtensions = this.local.filter(e => e.enablementState === EnablementState.DisabledByInvalidExtension && !e.isWorkspaceScoped); if (invalidExtensions.length) { - if ( - invalidExtensions.some( - (e) => - e.local && - e.local.manifest.engines?.vscode && - (!isEngineValid( - e.local.manifest.engines.vscode, - this.productService.version, - this.productService.date, - ) || - areApiProposalsCompatible([ - ...(e.local.manifest.enabledApiProposals ?? []), - ])), - ) - ) { + if (invalidExtensions.some(e => e.local && e.local.manifest.engines?.vscode && + (!isEngineValid(e.local.manifest.engines.vscode, this.productService.version, this.productService.date) || areApiProposalsCompatible([...e.local.manifest.enabledApiProposals ?? []])) + )) { computedNotificiations.push({ - message: nls.localize( - "incompatibleExtensions", - "Some extensions are disabled due to version incompatibility. Review and update them.", - ), + message: nls.localize('incompatibleExtensions', "Some extensions are disabled due to version incompatibility. Review and update them."), severity: Severity.Warning, extensions: invalidExtensions, - key: - "incompatibleExtensions:" + - invalidExtensions - .sort((a, b) => - a.identifier.id.localeCompare(b.identifier.id), - ) - .map( - (e) => - `${e.identifier.id.toLowerCase()}@${e.local?.manifest.version}`, - ) - .join("-"), + key: 'incompatibleExtensions:' + invalidExtensions.sort((a, b) => a.identifier.id.localeCompare(b.identifier.id)).map(e => `${e.identifier.id.toLowerCase()}@${e.local?.manifest.version}`).join('-'), }); } else { computedNotificiations.push({ - message: nls.localize( - "invalidExtensions", - "Invalid extensions detected. Review them.", - ), + message: nls.localize('invalidExtensions', "Invalid extensions detected. Review them."), severity: Severity.Warning, extensions: invalidExtensions, - key: - "invalidExtensions:" + - invalidExtensions - .sort((a, b) => - a.identifier.id.localeCompare(b.identifier.id), - ) - .map( - (e) => - `${e.identifier.id.toLowerCase()}@${e.local?.manifest.version}`, - ) - .join("-"), + key: 'invalidExtensions:' + invalidExtensions.sort((a, b) => a.identifier.id.localeCompare(b.identifier.id)).map(e => `${e.identifier.id.toLowerCase()}@${e.local?.manifest.version}`).join('-'), }); } } - const deprecatedExtensions = this.local.filter( - (e) => - !!e.deprecationInfo && - e.local && - this.extensionEnablementService.isEnabled(e.local), - ); + const deprecatedExtensions = this.local.filter(e => !!e.deprecationInfo && e.local && this.extensionEnablementService.isEnabled(e.local)); if (deprecatedExtensions.length) { computedNotificiations.push({ - message: nls.localize( - "deprecated extensions", - "Deprecated extensions detected. Review them and migrate to alternatives.", - ), + message: nls.localize('deprecated extensions', "Deprecated extensions detected. Review them and migrate to alternatives."), severity: Severity.Warning, extensions: deprecatedExtensions, - key: - "deprecatedExtensions:" + - deprecatedExtensions - .sort((a, b) => - a.identifier.id.localeCompare(b.identifier.id), - ) - .map((e) => e.identifier.id.toLowerCase()) - .join("-"), + key: 'deprecatedExtensions:' + deprecatedExtensions.sort((a, b) => a.identifier.id.localeCompare(b.identifier.id)).map(e => e.identifier.id.toLowerCase()).join('-'), }); } - const disallowedExtensions = this.local.filter( - (e) => e.enablementState === EnablementState.DisabledByAllowlist, - ); + const disallowedExtensions = this.local.filter(e => e.enablementState === EnablementState.DisabledByAllowlist); if (disallowedExtensions.length) { computedNotificiations.push({ - message: nls.localize( - "disallowed extensions", - "Some extensions are disabled because they are configured not to be in the allowed list.", - ), + message: nls.localize('disallowed extensions', "Some extensions are disabled because they are configured not to be in the allowed list."), severity: Severity.Warning, extensions: disallowedExtensions, - key: - "disallowedExtensions:" + - disallowedExtensions - .sort((a, b) => - a.identifier.id.localeCompare(b.identifier.id), - ) - .map((e) => e.identifier.id.toLowerCase()) - .join("-"), + key: 'disallowedExtensions:' + disallowedExtensions.sort((a, b) => a.identifier.id.localeCompare(b.identifier.id)).map(e => e.identifier.id.toLowerCase()).join('-'), }); } @@ -2363,65 +1465,40 @@ export class ExtensionsWorkbenchService const extensionRegex = /\bext:([^\s]+)\b/g; if (extensionRegex.test(text)) { text = text.replace(extensionRegex, (m, ext) => { + // Get curated keywords const lookup = this.productService.extensionKeywords || {}; const keywords = lookup[ext] || []; // Get mode name - const languageId = - this.languageService.guessLanguageIdByFilepathOrFirstLine( - URI.file(`.${ext}`), - ); - const languageName = - languageId && - this.languageService.getLanguageName(languageId); - const languageTag = languageName - ? ` tag:"${languageName}"` - : ""; + const languageId = this.languageService.guessLanguageIdByFilepathOrFirstLine(URI.file(`.${ext}`)); + const languageName = languageId && this.languageService.getLanguageName(languageId); + const languageTag = languageName ? ` tag:"${languageName}"` : ''; // Construct a rich query - return `tag:"__ext_${ext}" tag:"__ext_.${ext}" ${keywords.map((tag) => `tag:"${tag}"`).join(" ")}${languageTag} tag:"${ext}"`; + return `tag:"__ext_${ext}" tag:"__ext_.${ext}" ${keywords.map(tag => `tag:"${tag}"`).join(' ')}${languageTag} tag:"${ext}"`; }); } return text.substr(0, 350); } - private fromGallery( - gallery: IGalleryExtension, - extensionsControlManifest: IExtensionsControlManifest, - ): IExtension { + private fromGallery(gallery: IGalleryExtension, extensionsControlManifest: IExtensionsControlManifest): IExtension { let extension = this.getInstalledExtensionMatchingGallery(gallery); if (!extension) { - extension = this.instantiationService.createInstance( - Extension, - (ext) => this.getExtensionState(ext), - (ext) => this.getRuntimeState(ext), - undefined, - undefined, - gallery, - undefined, - ); - (extension).setExtensionsControlManifest( - extensionsControlManifest, - ); + extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext), undefined, undefined, gallery, undefined); + (extension).setExtensionsControlManifest(extensionsControlManifest); } return extension; } - private getInstalledExtensionMatchingGallery( - gallery: IGalleryExtension, - ): IExtension | null { + private getInstalledExtensionMatchingGallery(gallery: IGalleryExtension): IExtension | null { for (const installed of this.local) { - if (installed.identifier.uuid) { - // Installed from Gallery + if (installed.identifier.uuid) { // Installed from Gallery if (installed.identifier.uuid === gallery.identifier.uuid) { return installed; } - } else if (installed.local?.source !== "resource") { - if ( - areSameExtensions(installed.identifier, gallery.identifier) - ) { - // Installed from other sources + } else if (installed.local?.source !== 'resource') { + if (areSameExtensions(installed.identifier, gallery.identifier)) { // Installed from other sources return installed; } } @@ -2429,67 +1506,30 @@ export class ExtensionsWorkbenchService return null; } - private getInstalledExtensionMatchingLocation( - location: URI, - ): IExtension | null { - return ( - this.local.find( - (e) => - e.local && - this.uriIdentityService.extUri.isEqualOrParent( - location, - e.local?.location, - ), - ) ?? null - ); - } - - async open( - extension: IExtension | string, - options?: IExtensionEditorOptions, - ): Promise { - if (typeof extension === "string") { + private getInstalledExtensionMatchingLocation(location: URI): IExtension | null { + return this.local.find(e => e.local && this.uriIdentityService.extUri.isEqualOrParent(location, e.local?.location)) ?? null; + } + + async open(extension: IExtension | string, options?: IExtensionEditorOptions): Promise { + if (typeof extension === 'string') { const id = extension; - extension = - this.installed.find((e) => - areSameExtensions(e.identifier, { id }), - ) ?? - ( - await this.getExtensions( - [{ id: extension }], - CancellationToken.None, - ) - )[0]; + extension = this.installed.find(e => areSameExtensions(e.identifier, { id })) ?? (await this.getExtensions([{ id: extension }], CancellationToken.None))[0]; } if (!extension) { throw new Error(`Extension not found. ${extension}`); } - await this.editorService.openEditor( - this.instantiationService.createInstance( - ExtensionsInput, - extension, - ), - options, - options?.sideByside ? SIDE_GROUP : ACTIVE_GROUP, - ); - } - - async openSearch( - searchValue: string, - preserveFoucs?: boolean, - ): Promise { - const viewPaneContainer = ( - await this.viewsService.openViewContainer(VIEWLET_ID, true) - )?.getViewPaneContainer() as IExtensionsViewPaneContainer; + await this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension), options, options?.sideByside ? SIDE_GROUP : ACTIVE_GROUP); + } + + async openSearch(searchValue: string, preserveFoucs?: boolean): Promise { + const viewPaneContainer = (await this.viewsService.openViewContainer(VIEWLET_ID, true))?.getViewPaneContainer() as IExtensionsViewPaneContainer; viewPaneContainer.search(searchValue); if (!preserveFoucs) { viewPaneContainer.focus(); } } - getExtensionRuntimeStatus( - extension: IExtension, - ): IExtensionRuntimeStatus | undefined { + getExtensionRuntimeStatus(extension: IExtension): IExtensionRuntimeStatus | undefined { const extensionsStatus = this.extensionService.getExtensionsStatus(); for (const id of Object.keys(extensionsStatus)) { if (areSameExtensions({ id }, extension.identifier)) { @@ -2506,11 +1546,7 @@ export class ExtensionsWorkbenchService const extensionsToCheck = [...this.local]; for (const extension of extensionsToCheck) { const runtimeState = extension.runtimeState; - if ( - !runtimeState || - runtimeState.action !== - ExtensionRuntimeActionType.RestartExtensions - ) { + if (!runtimeState || runtimeState.action !== ExtensionRuntimeActionType.RestartExtensions) { continue; } if (extension.state === ExtensionState.Uninstalled) { @@ -2520,17 +1556,9 @@ export class ExtensionsWorkbenchService if (!extension.local) { continue; } - const isEnabled = this.extensionEnablementService.isEnabled( - extension.local, - ); + const isEnabled = this.extensionEnablementService.isEnabled(extension.local); if (isEnabled) { - const runningExtension = this.extensionService.extensions.find( - (e) => - areSameExtensions( - { id: e.identifier.value, uuid: e.uuid }, - extension.identifier, - ), - ); + const runningExtension = this.extensionService.extensions.find(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, extension.identifier)); if (runningExtension) { toRemove.push(runningExtension.identifier.value); } @@ -2544,17 +1572,7 @@ export class ExtensionsWorkbenchService if (extension.isUnderDevelopment) { continue; } - if ( - extensionsToCheck.some((e) => - areSameExtensions( - { - id: extension.identifier.value, - uuid: extension.uuid, - }, - e.local?.identifier ?? e.identifier, - ), - ) - ) { + if (extensionsToCheck.some(e => areSameExtensions({ id: extension.identifier.value, uuid: extension.uuid }, e.local?.identifier ?? e.identifier))) { continue; } // Extension is running but doesn't exist locally. Remove it from running extensions. @@ -2562,321 +1580,117 @@ export class ExtensionsWorkbenchService } if (toAdd.length || toRemove.length) { - if ( - await this.extensionService.stopExtensionHosts( - nls.localize("restart", "Enable or Disable extensions"), - auto, - ) - ) { - await this.extensionService.startExtensionHosts({ - toAdd, - toRemove, - }); + if (await this.extensionService.stopExtensionHosts(nls.localize('restart', "Enable or Disable extensions"), auto)) { + await this.extensionService.startExtensionHosts({ toAdd, toRemove }); if (auto) { this.notificationService.notify({ severity: Severity.Info, - message: nls.localize( - "extensionsAutoRestart", - "Extensions were auto restarted to enable updates.", - ), - priority: NotificationPriority.SILENT, + message: nls.localize('extensionsAutoRestart', "Extensions were auto restarted to enable updates."), + priority: NotificationPriority.SILENT }); } type ExtensionsAutoRestartClassification = { - owner: "sandy081"; - comment: "Report when extensions are auto restarted"; - count: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "Number of extensions auto restarted"; - }; - auto: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "Whether the restart was triggered automatically"; - }; + owner: 'sandy081'; + comment: 'Report when extensions are auto restarted'; + count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of extensions auto restarted' }; + auto: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the restart was triggered automatically' }; }; type ExtensionsAutoRestartEvent = { count: number; auto: boolean; }; - this.telemetryService.publicLog2< - ExtensionsAutoRestartEvent, - ExtensionsAutoRestartClassification - >("extensions:autorestart", { - count: toAdd.length + toRemove.length, - auto, - }); + this.telemetryService.publicLog2('extensions:autorestart', { count: toAdd.length + toRemove.length, auto }); } } } - private getRuntimeState( - extension: IExtension, - ): ExtensionRuntimeState | undefined { + private getRuntimeState(extension: IExtension): ExtensionRuntimeState | undefined { const isUninstalled = extension.state === ExtensionState.Uninstalled; - const runningExtension = this.extensionService.extensions.find((e) => - areSameExtensions({ id: e.identifier.value }, extension.identifier), - ); - const reloadAction = this.extensionManagementServerService - .remoteExtensionManagementServer - ? ExtensionRuntimeActionType.ReloadWindow - : ExtensionRuntimeActionType.RestartExtensions; - const reloadActionLabel = - reloadAction === ExtensionRuntimeActionType.ReloadWindow - ? nls.localize("reload", "reload window") - : nls.localize("restart extensions", "restart extensions"); + const runningExtension = this.extensionService.extensions.find(e => areSameExtensions({ id: e.identifier.value }, extension.identifier)); + const reloadAction = this.extensionManagementServerService.remoteExtensionManagementServer ? ExtensionRuntimeActionType.ReloadWindow : ExtensionRuntimeActionType.RestartExtensions; + const reloadActionLabel = reloadAction === ExtensionRuntimeActionType.ReloadWindow ? nls.localize('reload', "reload window") : nls.localize('restart extensions', "restart extensions"); if (isUninstalled) { - const canRemoveRunningExtension = - runningExtension && - this.extensionService.canRemoveExtension(runningExtension); - const isSameExtensionRunning = - runningExtension && - (!extension.server || - extension.server === - this.extensionManagementServerService.getExtensionManagementServer( - toExtension(runningExtension), - )) && - (!extension.resourceExtension || - this.uriIdentityService.extUri.isEqual( - extension.resourceExtension.location, - runningExtension.extensionLocation, - )); - if ( - !canRemoveRunningExtension && - isSameExtensionRunning && - !runningExtension.isUnderDevelopment - ) { - return { - action: reloadAction, - reason: nls.localize( - "postUninstallTooltip", - "Please {0} to complete the uninstallation of this extension.", - reloadActionLabel, - ), - }; + const canRemoveRunningExtension = runningExtension && this.extensionService.canRemoveExtension(runningExtension); + const isSameExtensionRunning = runningExtension + && (!extension.server || extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension))) + && (!extension.resourceExtension || this.uriIdentityService.extUri.isEqual(extension.resourceExtension.location, runningExtension.extensionLocation)); + if (!canRemoveRunningExtension && isSameExtensionRunning && !runningExtension.isUnderDevelopment) { + return { action: reloadAction, reason: nls.localize('postUninstallTooltip', "Please {0} to complete the uninstallation of this extension.", reloadActionLabel) }; } return undefined; } if (extension.local) { - const isSameExtensionRunning = - runningExtension && - extension.server === - this.extensionManagementServerService.getExtensionManagementServer( - toExtension(runningExtension), - ); - const isEnabled = this.extensionEnablementService.isEnabled( - extension.local, - ); + const isSameExtensionRunning = runningExtension && extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension)); + const isEnabled = this.extensionEnablementService.isEnabled(extension.local); // Extension is running if (runningExtension) { if (isEnabled) { // No Reload is required if extension can run without reload - if ( - this.extensionService.canAddExtension( - toExtensionDescription(extension.local), - ) - ) { + if (this.extensionService.canAddExtension(toExtensionDescription(extension.local))) { return undefined; } - const runningExtensionServer = - this.extensionManagementServerService.getExtensionManagementServer( - toExtension(runningExtension), - ); + const runningExtensionServer = this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension)); if (isSameExtensionRunning) { // Different version or target platform of same extension is running. Requires reload to run the current version - if ( - !runningExtension.isUnderDevelopment && - (extension.version !== runningExtension.version || - extension.local.targetPlatform !== - runningExtension.targetPlatform) - ) { - const productCurrentVersion = - this.getProductCurrentVersion(); - const productUpdateVersion = - this.getProductUpdateVersion(); - if ( - productUpdateVersion && - !isEngineValid( - extension.local.manifest.engines.vscode, - productCurrentVersion.version, - productCurrentVersion.date, - ) && - isEngineValid( - extension.local.manifest.engines.vscode, - productUpdateVersion.version, - productUpdateVersion.date, - ) + if (!runningExtension.isUnderDevelopment && (extension.version !== runningExtension.version || extension.local.targetPlatform !== runningExtension.targetPlatform)) { + const productCurrentVersion = this.getProductCurrentVersion(); + const productUpdateVersion = this.getProductUpdateVersion(); + if (productUpdateVersion + && !isEngineValid(extension.local.manifest.engines.vscode, productCurrentVersion.version, productCurrentVersion.date) + && isEngineValid(extension.local.manifest.engines.vscode, productUpdateVersion.version, productUpdateVersion.date) ) { const state = this.updateService.state; - if ( - state.type === - StateType.AvailableForDownload - ) { - return { - action: ExtensionRuntimeActionType.DownloadUpdate, - reason: nls.localize( - "postUpdateDownloadTooltip", - "Please update {0} to enable the updated extension.", - this.productService.nameLong, - ), - }; + if (state.type === StateType.AvailableForDownload) { + return { action: ExtensionRuntimeActionType.DownloadUpdate, reason: nls.localize('postUpdateDownloadTooltip', "Please update {0} to enable the updated extension.", this.productService.nameLong) }; } if (state.type === StateType.Downloaded) { - return { - action: ExtensionRuntimeActionType.ApplyUpdate, - reason: nls.localize( - "postUpdateUpdateTooltip", - "Please update {0} to enable the updated extension.", - this.productService.nameLong, - ), - }; + return { action: ExtensionRuntimeActionType.ApplyUpdate, reason: nls.localize('postUpdateUpdateTooltip', "Please update {0} to enable the updated extension.", this.productService.nameLong) }; } if (state.type === StateType.Ready) { - return { - action: ExtensionRuntimeActionType.QuitAndInstall, - reason: nls.localize( - "postUpdateRestartTooltip", - "Please restart {0} to enable the updated extension.", - this.productService.nameLong, - ), - }; + return { action: ExtensionRuntimeActionType.QuitAndInstall, reason: nls.localize('postUpdateRestartTooltip', "Please restart {0} to enable the updated extension.", this.productService.nameLong) }; } return undefined; } - return { - action: reloadAction, - reason: nls.localize( - "postUpdateTooltip", - "Please {0} to enable the updated extension.", - reloadActionLabel, - ), - }; + return { action: reloadAction, reason: nls.localize('postUpdateTooltip', "Please {0} to enable the updated extension.", reloadActionLabel) }; } if (this.extensionsServers.length > 1) { - const extensionInOtherServer = - this.installed.filter( - (e) => - areSameExtensions( - e.identifier, - extension.identifier, - ) && e.server !== extension.server, - )[0]; + const extensionInOtherServer = this.installed.filter(e => areSameExtensions(e.identifier, extension.identifier) && e.server !== extension.server)[0]; if (extensionInOtherServer) { // This extension prefers to run on UI/Local side but is running in remote - if ( - runningExtensionServer === - this.extensionManagementServerService - .remoteExtensionManagementServer && - this.extensionManifestPropertiesService.prefersExecuteOnUI( - extension.local.manifest, - ) && - extensionInOtherServer.server === - this.extensionManagementServerService - .localExtensionManagementServer - ) { - return { - action: reloadAction, - reason: nls.localize( - "enable locally", - "Please {0} to enable this extension locally.", - reloadActionLabel, - ), - }; + if (runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnUI(extension.local.manifest) && extensionInOtherServer.server === this.extensionManagementServerService.localExtensionManagementServer) { + return { action: reloadAction, reason: nls.localize('enable locally', "Please {0} to enable this extension locally.", reloadActionLabel) }; } // This extension prefers to run on Workspace/Remote side but is running in local - if ( - runningExtensionServer === - this.extensionManagementServerService - .localExtensionManagementServer && - this.extensionManifestPropertiesService.prefersExecuteOnWorkspace( - extension.local.manifest, - ) && - extensionInOtherServer.server === - this.extensionManagementServerService - .remoteExtensionManagementServer - ) { - return { - action: reloadAction, - reason: nls.localize( - "enable remote", - "Please {0} to enable this extension in {1}.", - reloadActionLabel, - this - .extensionManagementServerService - .remoteExtensionManagementServer - ?.label, - ), - }; + if (runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(extension.local.manifest) && extensionInOtherServer.server === this.extensionManagementServerService.remoteExtensionManagementServer) { + return { action: reloadAction, reason: nls.localize('enable remote', "Please {0} to enable this extension in {1}.", reloadActionLabel, this.extensionManagementServerService.remoteExtensionManagementServer?.label) }; } } } + } else { - if ( - extension.server === - this.extensionManagementServerService - .localExtensionManagementServer && - runningExtensionServer === - this.extensionManagementServerService - .remoteExtensionManagementServer - ) { + + if (extension.server === this.extensionManagementServerService.localExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) { // This extension prefers to run on UI/Local side but is running in remote - if ( - this.extensionManifestPropertiesService.prefersExecuteOnUI( - extension.local.manifest, - ) - ) { - return { - action: reloadAction, - reason: nls.localize( - "postEnableTooltip", - "Please {0} to enable this extension.", - reloadActionLabel, - ), - }; + if (this.extensionManifestPropertiesService.prefersExecuteOnUI(extension.local.manifest)) { + return { action: reloadAction, reason: nls.localize('postEnableTooltip', "Please {0} to enable this extension.", reloadActionLabel) }; } } - if ( - extension.server === - this.extensionManagementServerService - .remoteExtensionManagementServer && - runningExtensionServer === - this.extensionManagementServerService - .localExtensionManagementServer - ) { + if (extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer) { // This extension prefers to run on Workspace/Remote side but is running in local - if ( - this.extensionManifestPropertiesService.prefersExecuteOnWorkspace( - extension.local.manifest, - ) - ) { - return { - action: reloadAction, - reason: nls.localize( - "postEnableTooltip", - "Please {0} to enable this extension.", - reloadActionLabel, - ), - }; + if (this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(extension.local.manifest)) { + return { action: reloadAction, reason: nls.localize('postEnableTooltip', "Please {0} to enable this extension.", reloadActionLabel) }; } } } return undefined; } else { if (isSameExtensionRunning) { - return { - action: reloadAction, - reason: nls.localize( - "postDisableTooltip", - "Please {0} to disable this extension.", - reloadActionLabel, - ), - }; + return { action: reloadAction, reason: nls.localize('postDisableTooltip', "Please {0} to disable this extension.", reloadActionLabel) }; } } return undefined; @@ -2884,59 +1698,16 @@ export class ExtensionsWorkbenchService // Extension is not running else { - if ( - isEnabled && - !this.extensionService.canAddExtension( - toExtensionDescription(extension.local), - ) - ) { - return { - action: reloadAction, - reason: nls.localize( - "postEnableTooltip", - "Please {0} to enable this extension.", - reloadActionLabel, - ), - }; + if (isEnabled && !this.extensionService.canAddExtension(toExtensionDescription(extension.local))) { + return { action: reloadAction, reason: nls.localize('postEnableTooltip', "Please {0} to enable this extension.", reloadActionLabel) }; } - const otherServer = extension.server - ? extension.server === - this.extensionManagementServerService - .localExtensionManagementServer - ? this.extensionManagementServerService - .remoteExtensionManagementServer - : this.extensionManagementServerService - .localExtensionManagementServer - : null; - if ( - otherServer && - extension.enablementState === - EnablementState.DisabledByExtensionKind - ) { - const extensionInOtherServer = this.local.filter( - (e) => - areSameExtensions( - e.identifier, - extension.identifier, - ) && e.server === otherServer, - )[0]; + const otherServer = extension.server ? extension.server === this.extensionManagementServerService.localExtensionManagementServer ? this.extensionManagementServerService.remoteExtensionManagementServer : this.extensionManagementServerService.localExtensionManagementServer : null; + if (otherServer && extension.enablementState === EnablementState.DisabledByExtensionKind) { + const extensionInOtherServer = this.local.filter(e => areSameExtensions(e.identifier, extension.identifier) && e.server === otherServer)[0]; // Same extension in other server exists and - if ( - extensionInOtherServer && - extensionInOtherServer.local && - this.extensionEnablementService.isEnabled( - extensionInOtherServer.local, - ) - ) { - return { - action: reloadAction, - reason: nls.localize( - "postEnableTooltip", - "Please {0} to enable this extension.", - reloadActionLabel, - ), - }; + if (extensionInOtherServer && extensionInOtherServer.local && this.extensionEnablementService.isEnabled(extensionInOtherServer.local)) { + return { action: reloadAction, reason: nls.localize('postEnableTooltip', "Please {0} to enable this extension.", reloadActionLabel) }; } } } @@ -2949,20 +1720,13 @@ export class ExtensionsWorkbenchService return extensions[0]; } - const enabledExtensions = extensions.filter( - (e) => - e.local && this.extensionEnablementService.isEnabled(e.local), - ); + const enabledExtensions = extensions.filter(e => e.local && this.extensionEnablementService.isEnabled(e.local)); if (enabledExtensions.length === 1) { return enabledExtensions[0]; } - const extensionsToChoose = enabledExtensions.length - ? enabledExtensions - : extensions; - const manifest = extensionsToChoose.find( - (e) => e.local && e.local.manifest, - )?.local?.manifest; + const extensionsToChoose = enabledExtensions.length ? enabledExtensions : extensions; + const manifest = extensionsToChoose.find(e => e.local && e.local.manifest)?.local?.manifest; // Manifest is not found which should not happen. // In which case return the first extension. @@ -2970,39 +1734,26 @@ export class ExtensionsWorkbenchService return extensionsToChoose[0]; } - const extensionKinds = - this.extensionManifestPropertiesService.getExtensionKind(manifest); + const extensionKinds = this.extensionManifestPropertiesService.getExtensionKind(manifest); - let extension = extensionsToChoose.find((extension) => { + let extension = extensionsToChoose.find(extension => { for (const extensionKind of extensionKinds) { switch (extensionKind) { - case "ui": + case 'ui': /* UI extension is chosen only if it is installed locally */ - if ( - extension.server === - this.extensionManagementServerService - .localExtensionManagementServer - ) { + if (extension.server === this.extensionManagementServerService.localExtensionManagementServer) { return true; } return false; - case "workspace": + case 'workspace': /* Choose remote workspace extension if exists */ - if ( - extension.server === - this.extensionManagementServerService - .remoteExtensionManagementServer - ) { + if (extension.server === this.extensionManagementServerService.remoteExtensionManagementServer) { return true; } return false; - case "web": + case 'web': /* Choose web extension if exists */ - if ( - extension.server === - this.extensionManagementServerService - .webExtensionManagementServer - ) { + if (extension.server === this.extensionManagementServerService.webExtensionManagementServer) { return true; } return false; @@ -3011,30 +1762,19 @@ export class ExtensionsWorkbenchService return false; }); - if ( - !extension && - this.extensionManagementServerService.localExtensionManagementServer - ) { - extension = extensionsToChoose.find((extension) => { + if (!extension && this.extensionManagementServerService.localExtensionManagementServer) { + extension = extensionsToChoose.find(extension => { for (const extensionKind of extensionKinds) { switch (extensionKind) { - case "workspace": + case 'workspace': /* Choose local workspace extension if exists */ - if ( - extension.server === - this.extensionManagementServerService - .localExtensionManagementServer - ) { + if (extension.server === this.extensionManagementServerService.localExtensionManagementServer) { return true; } return false; - case "web": + case 'web': /* Choose local web extension if exists */ - if ( - extension.server === - this.extensionManagementServerService - .localExtensionManagementServer - ) { + if (extension.server === this.extensionManagementServerService.localExtensionManagementServer) { return true; } return false; @@ -3044,20 +1784,13 @@ export class ExtensionsWorkbenchService }); } - if ( - !extension && - this.extensionManagementServerService.webExtensionManagementServer - ) { - extension = extensionsToChoose.find((extension) => { + if (!extension && this.extensionManagementServerService.webExtensionManagementServer) { + extension = extensionsToChoose.find(extension => { for (const extensionKind of extensionKinds) { switch (extensionKind) { - case "web": + case 'web': /* Choose web extension if exists */ - if ( - extension.server === - this.extensionManagementServerService - .webExtensionManagementServer - ) { + if (extension.server === this.extensionManagementServerService.webExtensionManagementServer) { return true; } return false; @@ -3067,21 +1800,13 @@ export class ExtensionsWorkbenchService }); } - if ( - !extension && - this.extensionManagementServerService - .remoteExtensionManagementServer - ) { - extension = extensionsToChoose.find((extension) => { + if (!extension && this.extensionManagementServerService.remoteExtensionManagementServer) { + extension = extensionsToChoose.find(extension => { for (const extensionKind of extensionKinds) { switch (extensionKind) { - case "web": + case 'web': /* Choose remote web extension if exists */ - if ( - extension.server === - this.extensionManagementServerService - .remoteExtensionManagementServer - ) { + if (extension.server === this.extensionManagementServerService.remoteExtensionManagementServer) { return true; } return false; @@ -3095,13 +1820,7 @@ export class ExtensionsWorkbenchService } private getExtensionState(extension: Extension): ExtensionState { - if ( - this.installing.some( - (i) => - areSameExtensions(i.identifier, extension.identifier) && - (!extension.server || i.server === extension.server), - ) - ) { + if (this.installing.some(i => areSameExtensions(i.identifier, extension.identifier) && (!extension.server || i.server === extension.server))) { return ExtensionState.Installing; } if (this.remoteExtensions) { @@ -3145,58 +1864,31 @@ export class ExtensionsWorkbenchService // Skip if check updates only for builtin extensions and current extension is not builtin. continue; } - if ( - installed.isBuiltin && - !installed.local?.pinned && - (installed.type === ExtensionType.System || - !installed.local?.identifier.uuid) - ) { + if (installed.isBuiltin && !installed.local?.pinned && (installed.type === ExtensionType.System || !installed.local?.identifier.uuid)) { // Skip checking updates for a builtin extension if it is a system extension or if it does not has Marketplace identifier continue; } - if (installed.local?.source === "resource") { + if (installed.local?.source === 'resource') { continue; } - infos.push({ - ...installed.identifier, - preRelease: !!installed.local?.preRelease, - }); + infos.push({ ...installed.identifier, preRelease: !!installed.local?.preRelease }); } if (infos.length) { - const targetPlatform = - await extensions[0].server.extensionManagementService.getTargetPlatform(); + const targetPlatform = await extensions[0].server.extensionManagementService.getTargetPlatform(); type GalleryServiceUpdatesCheckClassification = { - owner: "sandy081"; - comment: "Report when a request is made to check for updates of extensions"; - count: { - classification: "SystemMetaData"; - purpose: "FeatureInsight"; - comment: "Number of extensions to check update"; - }; + owner: 'sandy081'; + comment: 'Report when a request is made to check for updates of extensions'; + count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of extensions to check update' }; }; type GalleryServiceUpdatesCheckEvent = { count: number; }; - this.telemetryService.publicLog2< - GalleryServiceUpdatesCheckEvent, - GalleryServiceUpdatesCheckClassification - >("galleryService:checkingForUpdates", { + this.telemetryService.publicLog2('galleryService:checkingForUpdates', { count: infos.length, }); - const galleryExtensions = await this.galleryService.getExtensions( - infos, - { - targetPlatform, - compatible: true, - productVersion: this.getProductVersion(), - preferResourceApi: true, - }, - CancellationToken.None, - ); + const galleryExtensions = await this.galleryService.getExtensions(infos, { targetPlatform, compatible: true, productVersion: this.getProductVersion(), preferResourceApi: true }, CancellationToken.None); if (galleryExtensions.length) { - await this.syncInstalledExtensionsWithGallery( - galleryExtensions, - ); + await this.syncInstalledExtensionsWithGallery(galleryExtensions); } } } @@ -3209,64 +1901,35 @@ export class ExtensionsWorkbenchService extension: extension.gallery, options: { operation: InstallOperation.Update, - installPreReleaseVersion: - extension.local?.isPreReleaseVersion, - profileLocation: - this.userDataProfileService.currentProfile - .extensionsResource, - isApplicationScoped: - extension.local?.isApplicationScoped, - }, + installPreReleaseVersion: extension.local?.isPreReleaseVersion, + profileLocation: this.userDataProfileService.currentProfile.extensionsResource, + isApplicationScoped: extension.local?.isApplicationScoped, + } }); } }); - return this.extensionManagementService.installGalleryExtensions( - toUpdate, - ); - } - - async downloadVSIX( - extensionId: string, - preRelease: boolean, - ): Promise { - let [galleryExtension] = await this.galleryService.getExtensions( - [{ id: extensionId, preRelease }], - { compatible: true }, - CancellationToken.None, - ); + return this.extensionManagementService.installGalleryExtensions(toUpdate); + } + + async downloadVSIX(extensionId: string, preRelease: boolean): Promise { + let [galleryExtension] = await this.galleryService.getExtensions([{ id: extensionId, preRelease }], { compatible: true }, CancellationToken.None); if (!galleryExtension) { - throw new Error( - nls.localize( - "extension not found", - "Extension '{0}' not found.", - extensionId, - ), - ); + throw new Error(nls.localize('extension not found', "Extension '{0}' not found.", extensionId)); } let targetPlatform = galleryExtension.properties.targetPlatform; const options = []; for (const targetPlatform of galleryExtension.allTargetPlatforms) { - if ( - targetPlatform !== TargetPlatform.UNDEFINED && - targetPlatform !== TargetPlatform.UNKNOWN && - targetPlatform !== TargetPlatform.UNIVERSAL - ) { + if (targetPlatform !== TargetPlatform.UNDEFINED && targetPlatform !== TargetPlatform.UNKNOWN && targetPlatform !== TargetPlatform.UNIVERSAL) { options.push({ label: TargetPlatformToString(targetPlatform), - id: targetPlatform, + id: targetPlatform }); } } if (options.length) { - const message = nls.localize( - "platform placeholder", - "Please select the platform for which you want to download the VSIX", - ); - const option = await this.quickInputService.pick( - options.sort((a, b) => a.label.localeCompare(b.label)), - { placeHolder: message }, - ); + const message = nls.localize('platform placeholder', "Please select the platform for which you want to download the VSIX"); + const option = await this.quickInputService.pick(options.sort((a, b) => a.label.localeCompare(b.label)), { placeHolder: message }); if (!option) { return; } @@ -3274,56 +1937,30 @@ export class ExtensionsWorkbenchService } if (targetPlatform !== galleryExtension.properties.targetPlatform) { - [galleryExtension] = await this.galleryService.getExtensions( - [{ id: extensionId, preRelease }], - { compatible: true, targetPlatform }, - CancellationToken.None, - ); + [galleryExtension] = await this.galleryService.getExtensions([{ id: extensionId, preRelease }], { compatible: true, targetPlatform }, CancellationToken.None); } const result = await this.fileDialogService.showOpenDialog({ - title: nls.localize( - "download title", - "Select folder to download the VSIX", - ), + title: nls.localize('download title', "Select folder to download the VSIX"), canSelectFiles: false, canSelectFolders: true, canSelectMany: false, - openLabel: nls.localize("download", "Download"), + openLabel: nls.localize('download', "Download"), }); if (!result?.[0]) { return; } - this.progressService.withProgress( - { location: ProgressLocation.Notification }, - async (progress) => { - progress.report({ - message: nls.localize( - "downloading...", - "Downlading VSIX...", - ), - }); - const name = `${galleryExtension.identifier.id}-${galleryExtension.version}${targetPlatform !== TargetPlatform.UNDEFINED && targetPlatform !== TargetPlatform.UNIVERSAL && targetPlatform !== TargetPlatform.UNKNOWN ? `-${targetPlatform}` : ""}.vsix`; - await this.galleryService.download( - galleryExtension, - this.uriIdentityService.extUri.joinPath(result[0], name), - InstallOperation.None, - ); - this.notificationService.info( - nls.localize( - "download.completed", - "Successfully downloaded the VSIX", - ), - ); - }, - ); - } - - private async syncInstalledExtensionsWithGallery( - gallery: IGalleryExtension[], - ): Promise { + this.progressService.withProgress({ location: ProgressLocation.Notification }, async progress => { + progress.report({ message: nls.localize('downloading...', "Downlading VSIX...") }); + const name = `${galleryExtension.identifier.id}-${galleryExtension.version}${targetPlatform !== TargetPlatform.UNDEFINED && targetPlatform !== TargetPlatform.UNIVERSAL && targetPlatform !== TargetPlatform.UNKNOWN ? `-${targetPlatform}` : ''}.vsix`; + await this.galleryService.download(galleryExtension, this.uriIdentityService.extUri.joinPath(result[0], name), InstallOperation.None); + this.notificationService.info(nls.localize('download.completed', "Successfully downloaded the VSIX")); + }); + } + + private async syncInstalledExtensionsWithGallery(gallery: IGalleryExtension[]): Promise { const extensions: Extensions[] = []; if (this.localExtensions) { extensions.push(this.localExtensions); @@ -3337,94 +1974,55 @@ export class ExtensionsWorkbenchService if (!extensions.length) { return; } - await Promise.allSettled( - extensions.map((extensions) => - extensions.syncInstalledExtensionsWithGallery( - gallery, - this.getProductVersion(), - ), - ), - ); + await Promise.allSettled(extensions.map(extensions => extensions.syncInstalledExtensionsWithGallery(gallery, this.getProductVersion()))); if (this.outdated.length) { this.eventuallyAutoUpdateExtensions(); } } private isAutoCheckUpdatesEnabled(): boolean { - return this.configurationService.getValue( - AutoCheckUpdatesConfigurationKey, - ); + return this.configurationService.getValue(AutoCheckUpdatesConfigurationKey); } private eventuallyCheckForUpdates(immediate = false): void { this.updatesCheckDelayer.cancel(); - this.updatesCheckDelayer - .trigger( - async () => { - if (this.isAutoCheckUpdatesEnabled()) { - await this.checkForUpdates(); - } - this.eventuallyCheckForUpdates(); - }, - immediate ? 0 : this.getUpdatesCheckInterval(), - ) - .then(undefined, (err) => null); + this.updatesCheckDelayer.trigger(async () => { + if (this.isAutoCheckUpdatesEnabled()) { + await this.checkForUpdates(); + } + this.eventuallyCheckForUpdates(); + }, immediate ? 0 : this.getUpdatesCheckInterval()).then(undefined, err => null); } private getUpdatesCheckInterval(): number { - if ( - this.productService.quality === "insider" && - this.getProductUpdateVersion() - ) { + if (this.productService.quality === 'insider' && this.getProductUpdateVersion()) { return 1000 * 60 * 60 * 1; // 1 hour } return ExtensionsWorkbenchService.UpdatesCheckInterval; } private eventuallyAutoUpdateExtensions(): void { - this.autoUpdateDelayer - .trigger(() => this.autoUpdateExtensions()) - .then(undefined, (err) => null); + this.autoUpdateDelayer.trigger(() => this.autoUpdateExtensions()) + .then(undefined, err => null); } private async autoUpdateBuiltinExtensions(): Promise { await this.checkForUpdates(true); - const toUpdate = this.outdated.filter((e) => e.isBuiltin); - await Promises.settled( - toUpdate.map((e) => - this.install( - e, - e.local?.preRelease - ? { installPreReleaseVersion: true } - : undefined, - ), - ), - ); + const toUpdate = this.outdated.filter(e => e.isBuiltin); + await Promises.settled(toUpdate.map(e => this.install(e, e.local?.preRelease ? { installPreReleaseVersion: true } : undefined))); } private async syncPinnedBuiltinExtensions(): Promise { const infos: IExtensionInfo[] = []; for (const installed of this.local) { - if ( - installed.isBuiltin && - installed.local?.pinned && - installed.local?.identifier.uuid - ) { - infos.push({ - ...installed.identifier, - version: installed.version, - }); + if (installed.isBuiltin && installed.local?.pinned && installed.local?.identifier.uuid) { + infos.push({ ...installed.identifier, version: installed.version }); } } if (infos.length) { - const galleryExtensions = await this.galleryService.getExtensions( - infos, - CancellationToken.None, - ); + const galleryExtensions = await this.galleryService.getExtensions(infos, CancellationToken.None); if (galleryExtensions.length) { - await this.syncInstalledExtensionsWithGallery( - galleryExtensions, - ); + await this.syncInstalledExtensionsWithGallery(galleryExtensions); } } } @@ -3446,29 +2044,15 @@ export class ExtensionsWorkbenchService } const productVersion = this.getProductVersion(); - await Promises.settled( - toUpdate.map((e) => - this.install( - e, - e.local?.preRelease - ? { installPreReleaseVersion: true, productVersion } - : { productVersion }, - ), - ), - ); + await Promises.settled(toUpdate.map(e => this.install(e, e.local?.preRelease ? { installPreReleaseVersion: true, productVersion } : { productVersion }))); } private getProductVersion(): IProductVersion { - return ( - this.getProductUpdateVersion() ?? this.getProductCurrentVersion() - ); + return this.getProductUpdateVersion() ?? this.getProductCurrentVersion(); } private getProductCurrentVersion(): IProductVersion { - return { - version: this.productService.version, - date: this.productService.date, - }; + return { version: this.productService.version, date: this.productService.date }; } private getProductUpdateVersion(): IProductVersion | undefined { @@ -3479,14 +2063,7 @@ export class ExtensionsWorkbenchService case StateType.Ready: { const version = this.updateService.state.update.productVersion; if (version && semver.valid(version)) { - return { - version, - date: this.updateService.state.update.timestamp - ? new Date( - this.updateService.state.update.timestamp, - ).toISOString() - : undefined, - }; + return { version, date: this.updateService.state.update.timestamp ? new Date(this.updateService.state.update.timestamp).toISOString() : undefined }; } } } @@ -3501,16 +2078,12 @@ export class ExtensionsWorkbenchService const autoUpdateValue = this.getAutoUpdateValue(); if (autoUpdateValue === false) { - const extensionsToAutoUpdate = - this.getEnabledAutoUpdateExtensions(); + const extensionsToAutoUpdate = this.getEnabledAutoUpdateExtensions(); const extensionId = extension.identifier.id.toLowerCase(); if (extensionsToAutoUpdate.includes(extensionId)) { return true; } - if ( - this.isAutoUpdateEnabledForPublisher(extension.publisher) && - !extensionsToAutoUpdate.includes(`-${extensionId}`) - ) { + if (this.isAutoUpdateEnabledForPublisher(extension.publisher) && !extensionsToAutoUpdate.includes(`-${extensionId}`)) { return true; } return false; @@ -3520,13 +2093,8 @@ export class ExtensionsWorkbenchService return false; } - const disabledAutoUpdateExtensions = - this.getDisabledAutoUpdateExtensions(); - if ( - disabledAutoUpdateExtensions.includes( - extension.identifier.id.toLowerCase(), - ) - ) { + const disabledAutoUpdateExtensions = this.getDisabledAutoUpdateExtensions(); + if (disabledAutoUpdateExtensions.includes(extension.identifier.id.toLowerCase())) { return false; } @@ -3534,26 +2102,19 @@ export class ExtensionsWorkbenchService return true; } - if (autoUpdateValue === "onlyEnabledExtensions") { - return this.extensionEnablementService.isEnabledEnablementState( - extension.enablementState, - ); + if (autoUpdateValue === 'onlyEnabledExtensions') { + return this.extensionEnablementService.isEnabledEnablementState(extension.enablementState); } return false; } - async shouldRequireConsentToUpdate( - extension: IExtension, - ): Promise { + async shouldRequireConsentToUpdate(extension: IExtension): Promise { if (!extension.outdated) { return; } - if ( - extension.local?.manifest.main || - extension.local?.manifest.browser - ) { + if (extension.local?.manifest.main || extension.local?.manifest.browser) { return; } @@ -3566,31 +2127,21 @@ export class ExtensionsWorkbenchService return; } } else { - const manifest = - extension instanceof Extension - ? await extension.getGalleryManifest() - : await this.galleryService.getManifest( - extension.gallery, - CancellationToken.None, - ); + const manifest = extension instanceof Extension + ? await extension.getGalleryManifest() + : await this.galleryService.getManifest(extension.gallery, CancellationToken.None); if (!manifest?.main && !manifest?.browser) { return; } } - return nls.localize( - "consentRequiredToUpdate", - "The update for {0} extension introduces executable code, which is not present in the currently installed version.", - extension.displayName, - ); + return nls.localize('consentRequiredToUpdate', "The update for {0} extension introduces executable code, which is not present in the currently installed version.", extension.displayName); } isAutoUpdateEnabledFor(extensionOrPublisher: IExtension | string): boolean { if (isString(extensionOrPublisher)) { if (EXTENSION_IDENTIFIER_REGEX.test(extensionOrPublisher)) { - throw new Error( - "Expected publisher string, found extension identifier", - ); + throw new Error('Expected publisher string, found extension identifier'); } if (this.isAutoUpdateEnabled()) { return true; @@ -3605,108 +2156,66 @@ export class ExtensionsWorkbenchService return publishersToAutoUpdate.includes(publisher.toLowerCase()); } - async updateAutoUpdateEnablementFor( - extensionOrPublisher: IExtension | string, - enable: boolean, - ): Promise { + async updateAutoUpdateEnablementFor(extensionOrPublisher: IExtension | string, enable: boolean): Promise { if (this.isAutoUpdateEnabled()) { if (isString(extensionOrPublisher)) { - throw new Error("Expected extension, found publisher string"); - } - const disabledAutoUpdateExtensions = - this.getDisabledAutoUpdateExtensions(); - const extensionId = - extensionOrPublisher.identifier.id.toLowerCase(); - const extensionIndex = - disabledAutoUpdateExtensions.indexOf(extensionId); + throw new Error('Expected extension, found publisher string'); + } + const disabledAutoUpdateExtensions = this.getDisabledAutoUpdateExtensions(); + const extensionId = extensionOrPublisher.identifier.id.toLowerCase(); + const extensionIndex = disabledAutoUpdateExtensions.indexOf(extensionId); if (enable) { if (extensionIndex !== -1) { disabledAutoUpdateExtensions.splice(extensionIndex, 1); } - } else { + } + else { if (extensionIndex === -1) { disabledAutoUpdateExtensions.push(extensionId); } } this.setDisabledAutoUpdateExtensions(disabledAutoUpdateExtensions); - if ( - enable && - extensionOrPublisher.local && - extensionOrPublisher.pinned - ) { - await this.extensionManagementService.updateMetadata( - extensionOrPublisher.local, - { pinned: false }, - ); + if (enable && extensionOrPublisher.local && extensionOrPublisher.pinned) { + await this.extensionManagementService.updateMetadata(extensionOrPublisher.local, { pinned: false }); } this._onChange.fire(extensionOrPublisher); - } else { - const enabledAutoUpdateExtensions = - this.getEnabledAutoUpdateExtensions(); + } + + else { + const enabledAutoUpdateExtensions = this.getEnabledAutoUpdateExtensions(); if (isString(extensionOrPublisher)) { if (EXTENSION_IDENTIFIER_REGEX.test(extensionOrPublisher)) { - throw new Error( - "Expected publisher string, found extension identifier", - ); + throw new Error('Expected publisher string, found extension identifier'); } extensionOrPublisher = extensionOrPublisher.toLowerCase(); - if ( - this.isAutoUpdateEnabledFor(extensionOrPublisher) !== enable - ) { + if (this.isAutoUpdateEnabledFor(extensionOrPublisher) !== enable) { if (enable) { enabledAutoUpdateExtensions.push(extensionOrPublisher); } else { - if ( - enabledAutoUpdateExtensions.includes( - extensionOrPublisher, - ) - ) { - enabledAutoUpdateExtensions.splice( - enabledAutoUpdateExtensions.indexOf( - extensionOrPublisher, - ), - 1, - ); + if (enabledAutoUpdateExtensions.includes(extensionOrPublisher)) { + enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(extensionOrPublisher), 1); } } } - this.setEnabledAutoUpdateExtensions( - enabledAutoUpdateExtensions, - ); + this.setEnabledAutoUpdateExtensions(enabledAutoUpdateExtensions); for (const e of this.installed) { if (e.publisher.toLowerCase() === extensionOrPublisher) { this._onChange.fire(e); } } } else { - const extensionId = - extensionOrPublisher.identifier.id.toLowerCase(); - const enableAutoUpdatesForPublisher = - this.isAutoUpdateEnabledFor( - extensionOrPublisher.publisher.toLowerCase(), - ); - const enableAutoUpdatesForExtension = - enabledAutoUpdateExtensions.includes(extensionId); - const disableAutoUpdatesForExtension = - enabledAutoUpdateExtensions.includes(`-${extensionId}`); + const extensionId = extensionOrPublisher.identifier.id.toLowerCase(); + const enableAutoUpdatesForPublisher = this.isAutoUpdateEnabledFor(extensionOrPublisher.publisher.toLowerCase()); + const enableAutoUpdatesForExtension = enabledAutoUpdateExtensions.includes(extensionId); + const disableAutoUpdatesForExtension = enabledAutoUpdateExtensions.includes(`-${extensionId}`); if (enable) { if (disableAutoUpdatesForExtension) { - enabledAutoUpdateExtensions.splice( - enabledAutoUpdateExtensions.indexOf( - `-${extensionId}`, - ), - 1, - ); + enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(`-${extensionId}`), 1); } if (enableAutoUpdatesForPublisher) { if (enableAutoUpdatesForExtension) { - enabledAutoUpdateExtensions.splice( - enabledAutoUpdateExtensions.indexOf( - extensionId, - ), - 1, - ); + enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(extensionId), 1); } } else { if (!enableAutoUpdatesForExtension) { @@ -3717,10 +2226,7 @@ export class ExtensionsWorkbenchService // Disable Auto Updates else { if (enableAutoUpdatesForExtension) { - enabledAutoUpdateExtensions.splice( - enabledAutoUpdateExtensions.indexOf(extensionId), - 1, - ); + enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(extensionId), 1); } if (enableAutoUpdatesForPublisher) { if (!disableAutoUpdatesForExtension) { @@ -3728,18 +2234,11 @@ export class ExtensionsWorkbenchService } } else { if (disableAutoUpdatesForExtension) { - enabledAutoUpdateExtensions.splice( - enabledAutoUpdateExtensions.indexOf( - `-${extensionId}`, - ), - 1, - ); + enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(`-${extensionId}`), 1); } } } - this.setEnabledAutoUpdateExtensions( - enabledAutoUpdateExtensions, - ); + this.setEnabledAutoUpdateExtensions(enabledAutoUpdateExtensions); this._onChange.fire(extensionOrPublisher); } } @@ -3751,12 +2250,10 @@ export class ExtensionsWorkbenchService private onDidSelectedExtensionToAutoUpdateValueChange(): void { if ( - this.enabledAuotUpdateExtensionsValue !== - this.getEnabledAutoUpdateExtensionsValue() /* This checks if current window changed the value or not */ || - this.disabledAutoUpdateExtensionsValue !== - this.getDisabledAutoUpdateExtensionsValue() /* This checks if current window changed the value or not */ + this.enabledAuotUpdateExtensionsValue !== this.getEnabledAutoUpdateExtensionsValue() /* This checks if current window changed the value or not */ + || this.disabledAutoUpdateExtensionsValue !== this.getDisabledAutoUpdateExtensionsValue() /* This checks if current window changed the value or not */ ) { - const userExtensions = this.installed.filter((e) => !e.isBuiltin); + const userExtensions = this.installed.filter(e => !e.isBuiltin); const groupBy = (extensions: IExtension[]): IExtension[][] => { const shouldAutoUpdate: IExtension[] = []; const shouldNotAutoUpdate: IExtension[] = []; @@ -3770,12 +2267,10 @@ export class ExtensionsWorkbenchService return [shouldAutoUpdate, shouldNotAutoUpdate]; }; - const [wasShouldAutoUpdate, wasShouldNotAutoUpdate] = - groupBy(userExtensions); + const [wasShouldAutoUpdate, wasShouldNotAutoUpdate] = groupBy(userExtensions); this._enabledAutoUpdateExtensionsValue = undefined; this._disabledAutoUpdateExtensionsValue = undefined; - const [shouldAutoUpdate, shouldNotAutoUpdate] = - groupBy(userExtensions); + const [shouldAutoUpdate, shouldNotAutoUpdate] = groupBy(userExtensions); for (const e of wasShouldAutoUpdate ?? []) { if (shouldNotAutoUpdate?.includes(e)) { @@ -3792,102 +2287,49 @@ export class ExtensionsWorkbenchService async canInstall(extension: IExtension): Promise { if (!(extension instanceof Extension)) { - return new MarkdownString().appendText( - nls.localize( - "not an extension", - "The provided object is not an extension.", - ), - ); + return new MarkdownString().appendText(nls.localize('not an extension', "The provided object is not an extension.")); } if (extension.isMalicious) { - return new MarkdownString().appendText( - nls.localize( - "malicious", - "This extension is reported to be problematic.", - ), - ); + return new MarkdownString().appendText(nls.localize('malicious', "This extension is reported to be problematic.")); } if (extension.deprecationInfo?.disallowInstall) { - return new MarkdownString().appendText( - nls.localize( - "disallowed", - "This extension is disallowed to be installed.", - ), - ); + return new MarkdownString().appendText(nls.localize('disallowed', "This extension is disallowed to be installed.")); } if (extension.gallery) { if (!extension.gallery.isSigned) { - return new MarkdownString().appendText( - nls.localize("not signed", "This extension is not signed."), - ); + return new MarkdownString().appendText(nls.localize('not signed', "This extension is not signed.")); } - const localResult = this.localExtensions - ? await this.localExtensions.canInstall(extension.gallery) - : undefined; + const localResult = this.localExtensions ? await this.localExtensions.canInstall(extension.gallery) : undefined; if (localResult === true) { return true; } - const remoteResult = this.remoteExtensions - ? await this.remoteExtensions.canInstall(extension.gallery) - : undefined; + const remoteResult = this.remoteExtensions ? await this.remoteExtensions.canInstall(extension.gallery) : undefined; if (remoteResult === true) { return true; } - const webResult = this.webExtensions - ? await this.webExtensions.canInstall(extension.gallery) - : undefined; + const webResult = this.webExtensions ? await this.webExtensions.canInstall(extension.gallery) : undefined; if (webResult === true) { return true; } - return ( - localResult ?? - remoteResult ?? - webResult ?? - new MarkdownString().appendText( - nls.localize( - "cannot be installed", - "Cannot install the '{0}' extension because it is not available in this setup.", - extension.displayName ?? extension.identifier.id, - ), - ) - ); + return localResult ?? remoteResult ?? webResult ?? new MarkdownString().appendText(nls.localize('cannot be installed', "Cannot install the '{0}' extension because it is not available in this setup.", extension.displayName ?? extension.identifier.id)); } - if ( - extension.resourceExtension && - (await this.extensionManagementService.canInstall( - extension.resourceExtension, - )) === true - ) { + if (extension.resourceExtension && await this.extensionManagementService.canInstall(extension.resourceExtension) === true) { return true; } - return new MarkdownString().appendText( - nls.localize( - "cannot be installed", - "Cannot install the '{0}' extension because it is not available in this setup.", - extension.displayName ?? extension.identifier.id, - ), - ); - } - - async install( - arg: string | URI | IExtension, - installOptions: InstallExtensionOptions = {}, - progressLocation?: ProgressLocation | string, - ): Promise { - let installable: - | URI - | IGalleryExtension - | IResourceExtension - | undefined; + return new MarkdownString().appendText(nls.localize('cannot be installed', "Cannot install the '{0}' extension because it is not available in this setup.", extension.displayName ?? extension.identifier.id)); + } + + async install(arg: string | URI | IExtension, installOptions: InstallExtensionOptions = {}, progressLocation?: ProgressLocation | string): Promise { + let installable: URI | IGalleryExtension | IResourceExtension | undefined; let extension: IExtension | undefined; if (arg instanceof URI) { @@ -3896,92 +2338,40 @@ export class ExtensionsWorkbenchService let installableInfo: IExtensionInfo | undefined; let gallery: IGalleryExtension | undefined; if (isString(arg)) { - extension = this.local.find((e) => - areSameExtensions(e.identifier, { id: arg }), - ); + extension = this.local.find(e => areSameExtensions(e.identifier, { id: arg })); if (!extension?.isBuiltin) { - installableInfo = { - id: arg, - version: installOptions.version, - preRelease: - installOptions.installPreReleaseVersion ?? - this.preferPreReleases, - }; + installableInfo = { id: arg, version: installOptions.version, preRelease: installOptions.installPreReleaseVersion ?? this.preferPreReleases }; } } else if (arg.gallery) { extension = arg; gallery = arg.gallery; - if ( - installOptions.version && - installOptions.version !== gallery?.version - ) { - installableInfo = { - id: extension.identifier.id, - version: installOptions.version, - }; + if (installOptions.version && installOptions.version !== gallery?.version) { + installableInfo = { id: extension.identifier.id, version: installOptions.version }; } } else if (arg.resourceExtension) { extension = arg; installable = arg.resourceExtension; } if (installableInfo) { - const targetPlatform = extension?.server - ? await extension.server.extensionManagementService.getTargetPlatform() - : undefined; - gallery = ( - await this.galleryService.getExtensions( - [installableInfo], - { targetPlatform }, - CancellationToken.None, - ) - ).at(0); + const targetPlatform = extension?.server ? await extension.server.extensionManagementService.getTargetPlatform() : undefined; + gallery = (await this.galleryService.getExtensions([installableInfo], { targetPlatform }, CancellationToken.None)).at(0); } if (!extension && gallery) { - extension = this.instantiationService.createInstance( - Extension, - (ext) => this.getExtensionState(ext), - (ext) => this.getRuntimeState(ext), - undefined, - undefined, - gallery, - undefined, - ); - (extension).setExtensionsControlManifest( - await this.extensionManagementService.getExtensionsControlManifest(), - ); + extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext), undefined, undefined, gallery, undefined); + (extension).setExtensionsControlManifest(await this.extensionManagementService.getExtensionsControlManifest()); } if (extension?.isMalicious) { - throw new Error( - nls.localize( - "malicious", - "This extension is reported to be problematic.", - ), - ); + throw new Error(nls.localize('malicious', "This extension is reported to be problematic.")); } // Do not install if requested to enable and extension is already installed if (!(installOptions.enable && extension?.local)) { if (!installable) { if (!gallery) { - const id = isString(arg) - ? arg - : (arg).identifier.id; + const id = isString(arg) ? arg : (arg).identifier.id; if (installOptions.version) { - throw new Error( - nls.localize( - "not found version", - "Unable to install extension '{0}' because the requested version '{1}' is not found.", - id, - installOptions.version, - ), - ); + throw new Error(nls.localize('not found version', "Unable to install extension '{0}' because the requested version '{1}' is not found.", id, installOptions.version)); } else { - throw new Error( - nls.localize( - "not found", - "Unable to install extension '{0}' because it is not found.", - id, - ), - ); + throw new Error(nls.localize('not found', "Unable to install extension '{0}' because it is not found.", id)); } } installable = gallery; @@ -3997,73 +2387,26 @@ export class ExtensionsWorkbenchService if (installable) { if (installOptions.justification) { - const syncCheck = - isUndefined(installOptions.isMachineScoped) && - this.userDataSyncEnablementService.isEnabled() && - this.userDataSyncEnablementService.isResourceEnabled( - SyncResource.Extensions, - ); + const syncCheck = isUndefined(installOptions.isMachineScoped) && this.userDataSyncEnablementService.isEnabled() && this.userDataSyncEnablementService.isResourceEnabled(SyncResource.Extensions); const buttons: IPromptButton[] = []; buttons.push({ - label: - isString(installOptions.justification) || - !installOptions.justification.action - ? nls.localize( - { - key: "installButtonLabel", - comment: ["&& denotes a mnemonic"], - }, - "&&Install Extension", - ) - : nls.localize( - { - key: "installButtonLabelWithAction", - comment: ["&& denotes a mnemonic"], - }, - "&&Install Extension and {0}", - installOptions.justification.action, - ), - run: () => true, + label: isString(installOptions.justification) || !installOptions.justification.action + ? nls.localize({ key: 'installButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Install Extension") + : nls.localize({ key: 'installButtonLabelWithAction', comment: ['&& denotes a mnemonic'] }, "&&Install Extension and {0}", installOptions.justification.action), run: () => true }); if (!extension) { - buttons.push({ - label: nls.localize("open", "Open Extension"), - run: () => { - this.open(extension!); - return false; - }, - }); + buttons.push({ label: nls.localize('open', "Open Extension"), run: () => { this.open(extension!); return false; } }); } const result = await this.dialogService.prompt({ - title: nls.localize( - "installExtensionTitle", - "Install Extension", - ), - message: extension - ? nls.localize( - "installExtensionMessage", - "Would you like to install '{0}' extension from '{1}'?", - extension.displayName, - extension.publisherDisplayName, - ) - : nls.localize( - "installVSIXMessage", - "Would you like to install the extension?", - ), - detail: isString(installOptions.justification) - ? installOptions.justification - : installOptions.justification.reason, + title: nls.localize('installExtensionTitle', "Install Extension"), + message: extension ? nls.localize('installExtensionMessage', "Would you like to install '{0}' extension from '{1}'?", extension.displayName, extension.publisherDisplayName) : nls.localize('installVSIXMessage', "Would you like to install the extension?"), + detail: isString(installOptions.justification) ? installOptions.justification : installOptions.justification.reason, cancelButton: true, buttons, - checkbox: syncCheck - ? { - label: nls.localize( - "sync extension", - "Sync this extension", - ), - checked: true, - } - : undefined, + checkbox: syncCheck ? { + label: nls.localize('sync extension', "Sync this extension"), + checked: true, + } : undefined, }); if (!result.result) { throw new CancellationError(); @@ -4073,91 +2416,34 @@ export class ExtensionsWorkbenchService } } if (installable instanceof URI) { - extension = await this.doInstall( - undefined, - () => this.installFromVSIX(installable, installOptions), - progressLocation, - ); + extension = await this.doInstall(undefined, () => this.installFromVSIX(installable, installOptions), progressLocation); } else if (extension) { if (extension.resourceExtension) { - extension = await this.doInstall( - extension, - () => - this.extensionManagementService.installResourceExtension( - installable as IResourceExtension, - installOptions, - ), - progressLocation, - ); + extension = await this.doInstall(extension, () => this.extensionManagementService.installResourceExtension(installable as IResourceExtension, installOptions), progressLocation); } else { - extension = await this.doInstall( - extension, - () => - this.installFromGallery( - extension!, - installable as IGalleryExtension, - installOptions, - ), - progressLocation, - ); + extension = await this.doInstall(extension, () => this.installFromGallery(extension!, installable as IGalleryExtension, installOptions), progressLocation); } } } if (!extension) { - throw new Error( - nls.localize("unknown", "Unable to install extension"), - ); + throw new Error(nls.localize('unknown', "Unable to install extension")); } if (installOptions.enable) { - if ( - extension.enablementState === - EnablementState.DisabledWorkspace || - extension.enablementState === EnablementState.DisabledGlobally - ) { + if (extension.enablementState === EnablementState.DisabledWorkspace || extension.enablementState === EnablementState.DisabledGlobally) { if (installOptions.justification) { const result = await this.dialogService.confirm({ - title: nls.localize( - "enableExtensionTitle", - "Enable Extension", - ), - message: nls.localize( - "enableExtensionMessage", - "Would you like to enable '{0}' extension?", - extension.displayName, - ), - detail: isString(installOptions.justification) - ? installOptions.justification - : installOptions.justification.reason, - primaryButton: isString(installOptions.justification) - ? nls.localize( - { - key: "enableButtonLabel", - comment: ["&& denotes a mnemonic"], - }, - "&&Enable Extension", - ) - : nls.localize( - { - key: "enableButtonLabelWithAction", - comment: ["&& denotes a mnemonic"], - }, - "&&Enable Extension and {0}", - installOptions.justification.action, - ), + title: nls.localize('enableExtensionTitle', "Enable Extension"), + message: nls.localize('enableExtensionMessage', "Would you like to enable '{0}' extension?", extension.displayName), + detail: isString(installOptions.justification) ? installOptions.justification : installOptions.justification.reason, + primaryButton: isString(installOptions.justification) ? nls.localize({ key: 'enableButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Enable Extension") : nls.localize({ key: 'enableButtonLabelWithAction', comment: ['&& denotes a mnemonic'] }, "&&Enable Extension and {0}", installOptions.justification.action), }); if (!result.confirmed) { throw new CancellationError(); } } - await this.setEnablement( - extension, - extension.enablementState === - EnablementState.DisabledWorkspace - ? EnablementState.EnabledWorkspace - : EnablementState.EnabledGlobally, - ); + await this.setEnablement(extension, extension.enablementState === EnablementState.DisabledWorkspace ? EnablementState.EnabledWorkspace : EnablementState.EnabledGlobally); } await this.waitUntilExtensionIsEnabled(extension); } @@ -4165,52 +2451,22 @@ export class ExtensionsWorkbenchService return extension; } - async installInServer( - extension: IExtension, - server: IExtensionManagementServer, - ): Promise { + async installInServer(extension: IExtension, server: IExtensionManagementServer): Promise { await this.doInstall(extension, async () => { const local = extension.local; if (!local) { - throw new Error("Extension not found"); + throw new Error('Extension not found'); } if (!extension.gallery) { - extension = - ( - await this.getExtensions( - [ - { - ...extension.identifier, - preRelease: local.preRelease, - }, - ], - CancellationToken.None, - ) - )[0] ?? extension; + extension = (await this.getExtensions([{ ...extension.identifier, preRelease: local.preRelease }], CancellationToken.None))[0] ?? extension; } if (extension.gallery) { - return server.extensionManagementService.installFromGallery( - extension.gallery, - { installPreReleaseVersion: local.preRelease }, - ); - } - - const targetPlatform = - await server.extensionManagementService.getTargetPlatform(); - if ( - !isTargetPlatformCompatible( - local.targetPlatform, - [local.targetPlatform], - targetPlatform, - ) - ) { - throw new Error( - nls.localize( - "incompatible", - "Can't install '{0}' extension because it is not compatible.", - extension.identifier.id, - ), - ); + return server.extensionManagementService.installFromGallery(extension.gallery, { installPreReleaseVersion: local.preRelease }); + } + + const targetPlatform = await server.extensionManagementService.getTargetPlatform(); + if (!isTargetPlatformCompatible(local.targetPlatform, [local.targetPlatform], targetPlatform)) { + throw new Error(nls.localize('incompatible', "Can't install '{0}' extension because it is not compatible.", extension.identifier.id)); } const vsix = await this.extensionManagementService.zip(local); @@ -4245,55 +2501,30 @@ export class ExtensionsWorkbenchService async setLanguage(extension: IExtension): Promise { if (!this.canSetLanguage(extension)) { - throw new Error("Can not set language"); + throw new Error('Can not set language'); } const locale = getLocale(extension.gallery!); if (locale === language) { return; } - const localizedLanguageName = - extension.gallery?.properties?.localizedLanguages?.[0]; - return this.localeService.setLocale({ - id: locale, - galleryExtension: extension.gallery, - extensionId: extension.identifier.id, - label: localizedLanguageName ?? extension.displayName, - }); + const localizedLanguageName = extension.gallery?.properties?.localizedLanguages?.[0]; + return this.localeService.setLocale({ id: locale, galleryExtension: extension.gallery, extensionId: extension.identifier.id, label: localizedLanguageName ?? extension.displayName }); } - setEnablement( - extensions: IExtension | IExtension[], - enablementState: EnablementState, - ): Promise { + setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise { extensions = Array.isArray(extensions) ? extensions : [extensions]; return this.promptAndSetEnablement(extensions, enablementState); } async uninstall(e: IExtension): Promise { - const extension = e.local - ? e - : this.local.find((local) => - areSameExtensions(local.identifier, e.identifier), - ); + const extension = e.local ? e : this.local.find(local => areSameExtensions(local.identifier, e.identifier)); if (!extension?.local) { - throw new Error("Missing local"); - } - - const extensionsToUninstall: UninstallExtensionInfo[] = [ - { extension: extension.local }, - ]; - for (const packExtension of this.getAllPackExtensionsToUninstall( - extension.local, - this.local, - )) { - if ( - !extensionsToUninstall.some((e) => - areSameExtensions( - e.extension.identifier, - packExtension.identifier, - ), - ) - ) { + throw new Error('Missing local'); + } + + const extensionsToUninstall: UninstallExtensionInfo[] = [{ extension: extension.local }]; + for (const packExtension of this.getAllPackExtensionsToUninstall(extension.local, this.local)) { + if (!extensionsToUninstall.some(e => areSameExtensions(e.extension.identifier, packExtension.identifier))) { extensionsToUninstall.push({ extension: packExtension }); } } @@ -4310,27 +2541,13 @@ export class ExtensionsWorkbenchService if (local.dependencies.length === 0) { continue; } - if ( - extension.manifest.extensionPack?.some((id) => - areSameExtensions({ id }, local.identifier), - ) - ) { + if (extension.manifest.extensionPack?.some(id => areSameExtensions({ id }, local.identifier))) { continue; } - if ( - dependents.some((d) => - d.extensionPack.some((id) => - areSameExtensions({ id }, local.identifier), - ), - ) - ) { + if (dependents.some(d => d.extensionPack.some(id => areSameExtensions({ id }, local.identifier)))) { continue; } - if ( - local.dependencies.some((dep) => - areSameExtensions(extension.identifier, { id: dep }), - ) - ) { + if (local.dependencies.some(dep => areSameExtensions(extension.identifier, { id: dep }))) { dependents.push(local); extensionsToUninstall.push({ extension: local.local }); } @@ -4339,57 +2556,31 @@ export class ExtensionsWorkbenchService if (dependents.length) { const { result } = await this.dialogService.prompt({ - title: nls.localize( - "uninstallDependents", - "Uninstall Extension with Dependents", - ), + title: nls.localize('uninstallDependents', "Uninstall Extension with Dependents"), type: Severity.Warning, - message: - this.getErrorMessageForUninstallingAnExtensionWithDependents( - extension, - dependents, - ), - buttons: [ - { - label: nls.localize("uninstallAll", "Uninstall All"), - run: () => true, - }, - ], + message: this.getErrorMessageForUninstallingAnExtensionWithDependents(extension, dependents), + buttons: [{ + label: nls.localize('uninstallAll', "Uninstall All"), + run: () => true + }], cancelButton: { - run: () => false, - }, + run: () => false + } }); if (!result) { throw new CancellationError(); } } - return this.withProgress( - { - location: ProgressLocation.Extensions, - title: nls.localize( - "uninstallingExtension", - "Uninstalling extension....", - ), - source: `${extension.identifier.id}`, - }, - () => - this.extensionManagementService - .uninstallExtensions(extensionsToUninstall) - .then(() => undefined), - ); - } - - private getAllPackExtensionsToUninstall( - extension: ILocalExtension, - installed: IExtension[], - checked: ILocalExtension[] = [], - ): ILocalExtension[] { - if ( - checked.some((e) => - areSameExtensions(e.identifier, extension.identifier), - ) - ) { + return this.withProgress({ + location: ProgressLocation.Extensions, + title: nls.localize('uninstallingExtension', 'Uninstalling extension....'), + source: `${extension.identifier.id}` + }, () => this.extensionManagementService.uninstallExtensions(extensionsToUninstall).then(() => undefined)); + } + + private getAllPackExtensionsToUninstall(extension: ILocalExtension, installed: IExtension[], checked: ILocalExtension[] = []): ILocalExtension[] { + if (checked.some(e => areSameExtensions(e.identifier, extension.identifier))) { return []; } checked.push(extension); @@ -4397,85 +2588,45 @@ export class ExtensionsWorkbenchService if (extensionsPack.length) { const packedExtensions: ILocalExtension[] = []; for (const i of installed) { - if ( - i.local && - !i.isBuiltin && - extensionsPack.some((id) => - areSameExtensions({ id }, i.identifier), - ) - ) { + if (i.local && !i.isBuiltin && extensionsPack.some(id => areSameExtensions({ id }, i.identifier))) { packedExtensions.push(i.local); } } const packOfPackedExtensions: ILocalExtension[] = []; for (const packedExtension of packedExtensions) { - packOfPackedExtensions.push( - ...this.getAllPackExtensionsToUninstall( - packedExtension, - installed, - checked, - ), - ); + packOfPackedExtensions.push(...this.getAllPackExtensionsToUninstall(packedExtension, installed, checked)); } return [...packedExtensions, ...packOfPackedExtensions]; } return []; } - private getErrorMessageForUninstallingAnExtensionWithDependents( - extension: IExtension, - dependents: IExtension[], - ): string { + private getErrorMessageForUninstallingAnExtensionWithDependents(extension: IExtension, dependents: IExtension[]): string { if (dependents.length === 1) { - return nls.localize( - "singleDependentUninstallError", - "Cannot uninstall '{0}' extension alone. '{1}' extension depends on this. Do you want to uninstall all these extensions?", - extension.displayName, - dependents[0].displayName, - ); + return nls.localize('singleDependentUninstallError', "Cannot uninstall '{0}' extension alone. '{1}' extension depends on this. Do you want to uninstall all these extensions?", extension.displayName, dependents[0].displayName); } if (dependents.length === 2) { - return nls.localize( - "twoDependentsUninstallError", - "Cannot uninstall '{0}' extension alone. '{1}' and '{2}' extensions depend on this. Do you want to uninstall all these extensions?", - extension.displayName, - dependents[0].displayName, - dependents[1].displayName, - ); - } - return nls.localize( - "multipleDependentsUninstallError", - "Cannot uninstall '{0}' extension alone. '{1}', '{2}' and other extensions depend on this. Do you want to uninstall all these extensions?", - extension.displayName, - dependents[0].displayName, - dependents[1].displayName, - ); + return nls.localize('twoDependentsUninstallError', "Cannot uninstall '{0}' extension alone. '{1}' and '{2}' extensions depend on this. Do you want to uninstall all these extensions?", + extension.displayName, dependents[0].displayName, dependents[1].displayName); + } + return nls.localize('multipleDependentsUninstallError', "Cannot uninstall '{0}' extension alone. '{1}', '{2}' and other extensions depend on this. Do you want to uninstall all these extensions?", + extension.displayName, dependents[0].displayName, dependents[1].displayName); } reinstall(extension: IExtension): Promise { return this.doInstall(extension, () => { - const ext = extension.local - ? extension - : this.local.filter((e) => - areSameExtensions(e.identifier, extension.identifier), - )[0]; - const toReinstall: ILocalExtension | null = - ext && ext.local ? ext.local : null; + const ext = extension.local ? extension : this.local.filter(e => areSameExtensions(e.identifier, extension.identifier))[0]; + const toReinstall: ILocalExtension | null = ext && ext.local ? ext.local : null; if (!toReinstall) { - throw new Error("Missing local"); + throw new Error('Missing local'); } - return this.extensionManagementService.reinstallFromGallery( - toReinstall, - ); + return this.extensionManagementService.reinstallFromGallery(toReinstall); }); } isExtensionIgnoredToSync(extension: IExtension): boolean { - return extension.local - ? !this.isInstalledExtensionSynced(extension.local) - : this.extensionsSyncManagementService.hasToNeverSyncExtension( - extension.identifier.id, - ); + return extension.local ? !this.isInstalledExtensionSynced(extension.local) + : this.extensionsSyncManagementService.hasToNeverSyncExtension(extension.identifier.id); } async togglePreRelease(extension: IExtension): Promise { @@ -4483,68 +2634,34 @@ export class ExtensionsWorkbenchService return; } if (extension.preRelease !== extension.isPreReleaseVersion) { - await this.extensionManagementService.updateMetadata( - extension.local, - { preRelease: !extension.preRelease }, - ); + await this.extensionManagementService.updateMetadata(extension.local, { preRelease: !extension.preRelease }); return; } - await this.install(extension, { - installPreReleaseVersion: !extension.preRelease, - preRelease: !extension.preRelease, - }); + await this.install(extension, { installPreReleaseVersion: !extension.preRelease, preRelease: !extension.preRelease }); } async toggleExtensionIgnoredToSync(extension: IExtension): Promise { const isIgnored = this.isExtensionIgnoredToSync(extension); if (extension.local && isIgnored) { - (extension).local = - await this.updateSynchronizingInstalledExtension( - extension.local, - true, - ); + (extension).local = await this.updateSynchronizingInstalledExtension(extension.local, true); this._onChange.fire(extension); } else { - this.extensionsSyncManagementService.updateIgnoredExtensions( - extension.identifier.id, - !isIgnored, - ); + this.extensionsSyncManagementService.updateIgnoredExtensions(extension.identifier.id, !isIgnored); } - await this.userDataAutoSyncService.triggerSync( - ["IgnoredExtensionsUpdated"], - false, - false, - ); + await this.userDataAutoSyncService.triggerSync(['IgnoredExtensionsUpdated'], false, false); } - async toggleApplyExtensionToAllProfiles( - extension: IExtension, - ): Promise { - if ( - !extension.local || - isApplicationScopedExtension(extension.local.manifest) || - extension.isBuiltin - ) { + async toggleApplyExtensionToAllProfiles(extension: IExtension): Promise { + if (!extension.local || isApplicationScopedExtension(extension.local.manifest) || extension.isBuiltin) { return; } const isApplicationScoped = extension.local.isApplicationScoped; - await Promise.all( - this.getAllExtensions().map(async (extensions) => { - const local = extensions.local.find((e) => - areSameExtensions(e.identifier, extension.identifier), - )?.local; - if ( - local && - local.isApplicationScoped === isApplicationScoped - ) { - await this.extensionManagementService.toggleAppliationScope( - local, - this.userDataProfileService.currentProfile - .extensionsResource, - ); - } - }), - ); + await Promise.all(this.getAllExtensions().map(async extensions => { + const local = extensions.local.find(e => areSameExtensions(e.identifier, extension.identifier))?.local; + if (local && local.isApplicationScoped === isApplicationScoped) { + await this.extensionManagementService.toggleAppliationScope(local, this.userDataProfileService.currentProfile.extensionsResource); + } + })); } private getAllExtensions(): Extensions[] { @@ -4565,442 +2682,221 @@ export class ExtensionsWorkbenchService if (extension.isMachineScoped) { return false; } - if ( - this.extensionsSyncManagementService.hasToAlwaysSyncExtension( - extension.identifier.id, - ) - ) { + if (this.extensionsSyncManagementService.hasToAlwaysSyncExtension(extension.identifier.id)) { return true; } - return !this.extensionsSyncManagementService.hasToNeverSyncExtension( - extension.identifier.id, - ); + return !this.extensionsSyncManagementService.hasToNeverSyncExtension(extension.identifier.id); } - async updateSynchronizingInstalledExtension( - extension: ILocalExtension, - sync: boolean, - ): Promise { + async updateSynchronizingInstalledExtension(extension: ILocalExtension, sync: boolean): Promise { const isMachineScoped = !sync; if (extension.isMachineScoped !== isMachineScoped) { - extension = await this.extensionManagementService.updateMetadata( - extension, - { isMachineScoped }, - ); + extension = await this.extensionManagementService.updateMetadata(extension, { isMachineScoped }); } if (sync) { - this.extensionsSyncManagementService.updateIgnoredExtensions( - extension.identifier.id, - false, - ); + this.extensionsSyncManagementService.updateIgnoredExtensions(extension.identifier.id, false); } return extension; } - private doInstall( - extension: IExtension | undefined, - installTask: () => Promise, - progressLocation?: ProgressLocation | string, - ): Promise { - const title = extension - ? nls.localize( - "installing named extension", - "Installing '{0}' extension....", - extension.displayName, - ) - : nls.localize("installing extension", "Installing extension...."); - return this.withProgress( - { - location: progressLocation ?? ProgressLocation.Extensions, - title, - }, - async () => { - try { - if (extension) { - this.installing.push(extension); - this._onChange.fire(extension); - } - const local = await installTask(); - return await this.waitAndGetInstalledExtension( - local.identifier, - ); - } finally { - if (extension) { - this.installing = this.installing.filter( - (e) => e !== extension, - ); - // Trigger the change without passing the extension because it is replaced by a new instance. - this._onChange.fire(undefined); - } + private doInstall(extension: IExtension | undefined, installTask: () => Promise, progressLocation?: ProgressLocation | string): Promise { + const title = extension ? nls.localize('installing named extension', "Installing '{0}' extension....", extension.displayName) : nls.localize('installing extension', 'Installing extension....'); + return this.withProgress({ + location: progressLocation ?? ProgressLocation.Extensions, + title + }, async () => { + try { + if (extension) { + this.installing.push(extension); + this._onChange.fire(extension); + } + const local = await installTask(); + return await this.waitAndGetInstalledExtension(local.identifier); + } finally { + if (extension) { + this.installing = this.installing.filter(e => e !== extension); + // Trigger the change without passing the extension because it is replaced by a new instance. + this._onChange.fire(undefined); } - }, - ); - } - - private async installFromVSIX( - vsix: URI, - installOptions: InstallOptions, - ): Promise { - const manifest = - await this.extensionManagementService.getManifest(vsix); - const existingExtension = this.local.find((local) => - areSameExtensions(local.identifier, { - id: getGalleryExtensionId(manifest.publisher, manifest.name), - }), - ); + } + }); + } + + private async installFromVSIX(vsix: URI, installOptions: InstallOptions): Promise { + const manifest = await this.extensionManagementService.getManifest(vsix); + const existingExtension = this.local.find(local => areSameExtensions(local.identifier, { id: getGalleryExtensionId(manifest.publisher, manifest.name) })); if (existingExtension) { installOptions = installOptions || {}; if (existingExtension.latestVersion === manifest.version) { - installOptions.pinned = - existingExtension.local?.pinned || - !this.shouldAutoUpdateExtension(existingExtension); + installOptions.pinned = existingExtension.local?.pinned || !this.shouldAutoUpdateExtension(existingExtension); } else { installOptions.installGivenVersion = true; } } - return this.extensionManagementService.installVSIX( - vsix, - manifest, - installOptions, - ); + return this.extensionManagementService.installVSIX(vsix, manifest, installOptions); } - private installFromGallery( - extension: IExtension, - gallery: IGalleryExtension, - installOptions?: InstallOptions, - ): Promise { + private installFromGallery(extension: IExtension, gallery: IGalleryExtension, installOptions?: InstallOptions): Promise { installOptions = installOptions ?? {}; - installOptions.pinned = - extension.local?.pinned || - !this.shouldAutoUpdateExtension(extension); + installOptions.pinned = extension.local?.pinned || !this.shouldAutoUpdateExtension(extension); if (extension.local) { installOptions.productVersion = this.getProductVersion(); installOptions.operation = InstallOperation.Update; - return this.extensionManagementService.updateFromGallery( - gallery, - extension.local, - installOptions, - ); + return this.extensionManagementService.updateFromGallery(gallery, extension.local, installOptions); } else { - return this.extensionManagementService.installFromGallery( - gallery, - installOptions, - ); + return this.extensionManagementService.installFromGallery(gallery, installOptions); } } - private async waitAndGetInstalledExtension( - identifier: IExtensionIdentifier, - ): Promise { - let installedExtension = this.local.find((local) => - areSameExtensions(local.identifier, identifier), - ); + private async waitAndGetInstalledExtension(identifier: IExtensionIdentifier): Promise { + let installedExtension = this.local.find(local => areSameExtensions(local.identifier, identifier)); if (!installedExtension) { - await Event.toPromise( - Event.filter( - this.onChange, - (e) => - !!e && - this.local.some((local) => - areSameExtensions(local.identifier, identifier), - ), - ), - ); - } - installedExtension = this.local.find((local) => - areSameExtensions(local.identifier, identifier), - ); + await Event.toPromise(Event.filter(this.onChange, e => !!e && this.local.some(local => areSameExtensions(local.identifier, identifier)))); + } + installedExtension = this.local.find(local => areSameExtensions(local.identifier, identifier)); if (!installedExtension) { // This should not happen - throw new Error("Extension should have been installed"); + throw new Error('Extension should have been installed'); } return installedExtension; } - private async waitUntilExtensionIsEnabled( - extension: IExtension, - ): Promise { - if ( - this.extensionService.extensions.find((e) => - ExtensionIdentifier.equals( - e.identifier, - extension.identifier.id, - ), - ) - ) { + private async waitUntilExtensionIsEnabled(extension: IExtension): Promise { + if (this.extensionService.extensions.find(e => ExtensionIdentifier.equals(e.identifier, extension.identifier.id))) { return; } - if ( - !extension.local || - !this.extensionService.canAddExtension( - toExtensionDescription(extension.local), - ) - ) { + if (!extension.local || !this.extensionService.canAddExtension(toExtensionDescription(extension.local))) { return; } await new Promise((c, e) => { - const disposable = this.extensionService.onDidChangeExtensions( - () => { - try { - if ( - this.extensionService.extensions.find((e) => - ExtensionIdentifier.equals( - e.identifier, - extension.identifier.id, - ), - ) - ) { - disposable.dispose(); - c(); - } - } catch (error) { - e(error); + const disposable = this.extensionService.onDidChangeExtensions(() => { + try { + if (this.extensionService.extensions.find(e => ExtensionIdentifier.equals(e.identifier, extension.identifier.id))) { + disposable.dispose(); + c(); } - }, - ); + } catch (error) { + e(error); + } + }); }); } - private promptAndSetEnablement( - extensions: IExtension[], - enablementState: EnablementState, - ): Promise { - const enable = - enablementState === EnablementState.EnabledGlobally || - enablementState === EnablementState.EnabledWorkspace; + private promptAndSetEnablement(extensions: IExtension[], enablementState: EnablementState): Promise { + const enable = enablementState === EnablementState.EnabledGlobally || enablementState === EnablementState.EnabledWorkspace; if (enable) { - const allDependenciesAndPackedExtensions = - this.getExtensionsRecursively( - extensions, - this.local, - enablementState, - { dependencies: true, pack: true }, - ); - return this.checkAndSetEnablement( - extensions, - allDependenciesAndPackedExtensions, - enablementState, - ); + const allDependenciesAndPackedExtensions = this.getExtensionsRecursively(extensions, this.local, enablementState, { dependencies: true, pack: true }); + return this.checkAndSetEnablement(extensions, allDependenciesAndPackedExtensions, enablementState); } else { - const packedExtensions = this.getExtensionsRecursively( - extensions, - this.local, - enablementState, - { dependencies: false, pack: true }, - ); + const packedExtensions = this.getExtensionsRecursively(extensions, this.local, enablementState, { dependencies: false, pack: true }); if (packedExtensions.length) { - return this.checkAndSetEnablement( - extensions, - packedExtensions, - enablementState, - ); + return this.checkAndSetEnablement(extensions, packedExtensions, enablementState); } return this.checkAndSetEnablement(extensions, [], enablementState); } } - private async checkAndSetEnablement( - extensions: IExtension[], - otherExtensions: IExtension[], - enablementState: EnablementState, - ): Promise { + private async checkAndSetEnablement(extensions: IExtension[], otherExtensions: IExtension[], enablementState: EnablementState): Promise { const allExtensions = [...extensions, ...otherExtensions]; - const enable = - enablementState === EnablementState.EnabledGlobally || - enablementState === EnablementState.EnabledWorkspace; + const enable = enablementState === EnablementState.EnabledGlobally || enablementState === EnablementState.EnabledWorkspace; if (!enable) { for (const extension of extensions) { - const dependents = this.getDependentsAfterDisablement( - extension, - allExtensions, - this.local, - ); + const dependents = this.getDependentsAfterDisablement(extension, allExtensions, this.local); if (dependents.length) { const { result } = await this.dialogService.prompt({ - title: nls.localize( - "disableDependents", - "Disable Extension with Dependents", - ), + title: nls.localize('disableDependents', "Disable Extension with Dependents"), type: Severity.Warning, - message: this.getDependentsErrorMessageForDisablement( - extension, - allExtensions, - dependents, - ), - buttons: [ - { - label: nls.localize( - "disable all", - "Disable All", - ), - run: () => true, - }, - ], + message: this.getDependentsErrorMessageForDisablement(extension, allExtensions, dependents), + buttons: [{ + label: nls.localize('disable all', 'Disable All'), + run: () => true + }], cancelButton: { - run: () => false, - }, + run: () => false + } }); if (!result) { throw new CancellationError(); } - await this.checkAndSetEnablement( - dependents, - [extension], - enablementState, - ); + await this.checkAndSetEnablement(dependents, [extension], enablementState); } } } return this.doSetEnablement(allExtensions, enablementState); } - private getExtensionsRecursively( - extensions: IExtension[], - installed: IExtension[], - enablementState: EnablementState, - options: { dependencies: boolean; pack: boolean }, - checked: IExtension[] = [], - ): IExtension[] { - const toCheck = extensions.filter((e) => checked.indexOf(e) === -1); + private getExtensionsRecursively(extensions: IExtension[], installed: IExtension[], enablementState: EnablementState, options: { dependencies: boolean; pack: boolean }, checked: IExtension[] = []): IExtension[] { + const toCheck = extensions.filter(e => checked.indexOf(e) === -1); if (toCheck.length) { for (const extension of toCheck) { checked.push(extension); } - const extensionsToEanbleOrDisable = installed.filter((i) => { + const extensionsToEanbleOrDisable = installed.filter(i => { if (checked.indexOf(i) !== -1) { return false; } - const enable = - enablementState === EnablementState.EnabledGlobally || - enablementState === EnablementState.EnabledWorkspace; - const isExtensionEnabled = - i.enablementState === EnablementState.EnabledGlobally || - i.enablementState === EnablementState.EnabledWorkspace; + const enable = enablementState === EnablementState.EnabledGlobally || enablementState === EnablementState.EnabledWorkspace; + const isExtensionEnabled = i.enablementState === EnablementState.EnabledGlobally || i.enablementState === EnablementState.EnabledWorkspace; if (enable === isExtensionEnabled) { return false; } - return ( - (enable || !i.isBuiltin) && // Include all Extensions for enablement and only non builtin extensions for disablement - (options.dependencies || options.pack) && - extensions.some( - (extension) => - (options.dependencies && - extension.dependencies.some((id) => - areSameExtensions({ id }, i.identifier), - )) || - (options.pack && - extension.extensionPack.some((id) => - areSameExtensions({ id }, i.identifier), - )), - ) - ); + return (enable || !i.isBuiltin) // Include all Extensions for enablement and only non builtin extensions for disablement + && (options.dependencies || options.pack) + && extensions.some(extension => + (options.dependencies && extension.dependencies.some(id => areSameExtensions({ id }, i.identifier))) + || (options.pack && extension.extensionPack.some(id => areSameExtensions({ id }, i.identifier))) + ); }); if (extensionsToEanbleOrDisable.length) { - extensionsToEanbleOrDisable.push( - ...this.getExtensionsRecursively( - extensionsToEanbleOrDisable, - installed, - enablementState, - options, - checked, - ), - ); + extensionsToEanbleOrDisable.push(...this.getExtensionsRecursively(extensionsToEanbleOrDisable, installed, enablementState, options, checked)); } return extensionsToEanbleOrDisable; } return []; } - private getDependentsAfterDisablement( - extension: IExtension, - extensionsToDisable: IExtension[], - installed: IExtension[], - ): IExtension[] { - return installed.filter((i) => { + private getDependentsAfterDisablement(extension: IExtension, extensionsToDisable: IExtension[], installed: IExtension[]): IExtension[] { + return installed.filter(i => { if (i.dependencies.length === 0) { return false; } if (i === extension) { return false; } - if ( - !this.extensionEnablementService.isEnabledEnablementState( - i.enablementState, - ) - ) { + if (!this.extensionEnablementService.isEnabledEnablementState(i.enablementState)) { return false; } if (extensionsToDisable.indexOf(i) !== -1) { return false; } - return i.dependencies.some((dep) => - [extension, ...extensionsToDisable].some((d) => - areSameExtensions(d.identifier, { id: dep }), - ), - ); + return i.dependencies.some(dep => [extension, ...extensionsToDisable].some(d => areSameExtensions(d.identifier, { id: dep }))); }); } - private getDependentsErrorMessageForDisablement( - extension: IExtension, - allDisabledExtensions: IExtension[], - dependents: IExtension[], - ): string { + private getDependentsErrorMessageForDisablement(extension: IExtension, allDisabledExtensions: IExtension[], dependents: IExtension[]): string { for (const e of [extension, ...allDisabledExtensions]) { - const dependentsOfTheExtension = dependents.filter((d) => - d.dependencies.some((id) => - areSameExtensions({ id }, e.identifier), - ), - ); + const dependentsOfTheExtension = dependents.filter(d => d.dependencies.some(id => areSameExtensions({ id }, e.identifier))); if (dependentsOfTheExtension.length) { - return this.getErrorMessageForDisablingAnExtensionWithDependents( - e, - dependentsOfTheExtension, - ); + return this.getErrorMessageForDisablingAnExtensionWithDependents(e, dependentsOfTheExtension); } } - return ""; + return ''; } - private getErrorMessageForDisablingAnExtensionWithDependents( - extension: IExtension, - dependents: IExtension[], - ): string { + private getErrorMessageForDisablingAnExtensionWithDependents(extension: IExtension, dependents: IExtension[]): string { if (dependents.length === 1) { - return nls.localize( - "singleDependentError", - "Cannot disable '{0}' extension alone. '{1}' extension depends on this. Do you want to disable all these extensions?", - extension.displayName, - dependents[0].displayName, - ); + return nls.localize('singleDependentError', "Cannot disable '{0}' extension alone. '{1}' extension depends on this. Do you want to disable all these extensions?", extension.displayName, dependents[0].displayName); } if (dependents.length === 2) { - return nls.localize( - "twoDependentsError", - "Cannot disable '{0}' extension alone. '{1}' and '{2}' extensions depend on this. Do you want to disable all these extensions?", - extension.displayName, - dependents[0].displayName, - dependents[1].displayName, - ); - } - return nls.localize( - "multipleDependentsError", - "Cannot disable '{0}' extension alone. '{1}', '{2}' and other extensions depend on this. Do you want to disable all these extensions?", - extension.displayName, - dependents[0].displayName, - dependents[1].displayName, - ); - } - - private async doSetEnablement( - extensions: IExtension[], - enablementState: EnablementState, - ): Promise { - const changed = await this.extensionEnablementService.setEnablement( - extensions.map((e) => e.local!), - enablementState, - ); + return nls.localize('twoDependentsError', "Cannot disable '{0}' extension alone. '{1}' and '{2}' extensions depend on this. Do you want to disable all these extensions?", + extension.displayName, dependents[0].displayName, dependents[1].displayName); + } + return nls.localize('multipleDependentsError', "Cannot disable '{0}' extension alone. '{1}', '{2}' and other extensions depend on this. Do you want to disable all these extensions?", + extension.displayName, dependents[0].displayName, dependents[1].displayName); + } + + private async doSetEnablement(extensions: IExtension[], enablementState: EnablementState): Promise { + const changed = await this.extensionEnablementService.setEnablement(extensions.map(e => e.local!), enablementState); for (let i = 0; i < changed.length; i++) { if (changed[i]) { /* __GDPR__ @@ -5019,13 +2915,7 @@ export class ExtensionsWorkbenchService ] } */ - this.telemetryService.publicLog( - enablementState === EnablementState.EnabledGlobally || - enablementState === EnablementState.EnabledWorkspace - ? "extension:enable" - : "extension:disable", - extensions[i].telemetryData, - ); + this.telemetryService.publicLog(enablementState === EnablementState.EnabledGlobally || enablementState === EnablementState.EnabledWorkspace ? 'extension:enable' : 'extension:disable', extensions[i].telemetryData); } } return changed; @@ -5036,21 +2926,9 @@ export class ExtensionsWorkbenchService // Since we cannot differentiate between the two, we report progress for all extension install/uninstall changes private _activityCallBack: ((value: void) => void) | undefined; private reportProgressFromOtherSources(): void { - if ( - this.installed.some( - (e) => - e.state === ExtensionState.Installing || - e.state === ExtensionState.Uninstalling, - ) - ) { + if (this.installed.some(e => e.state === ExtensionState.Installing || e.state === ExtensionState.Uninstalling)) { if (!this._activityCallBack) { - this.withProgress( - { location: ProgressLocation.Extensions }, - () => - new Promise( - (resolve) => (this._activityCallBack = resolve), - ), - ); + this.withProgress({ location: ProgressLocation.Extensions }, () => new Promise(resolve => this._activityCallBack = resolve)); } } else { this._activityCallBack?.(); @@ -5058,10 +2936,7 @@ export class ExtensionsWorkbenchService } } - private withProgress( - options: IProgressOptions, - task: () => Promise, - ): Promise { + private withProgress(options: IProgressOptions, task: () => Promise): Promise { return this.progressService.withProgress(options, async () => { const cancelableTask = createCancelablePromise(() => task()); this.tasksInProgress.push(cancelableTask); @@ -5081,13 +2956,9 @@ export class ExtensionsWorkbenchService return; } - const message = (err && err.message) || ""; + const message = err && err.message || ''; - if ( - /getaddrinfo ENOTFOUND|getaddrinfo ENOENT|connect EACCES|connect ECONNREFUSED/.test( - message, - ) - ) { + if (/getaddrinfo ENOTFOUND|getaddrinfo ENOENT|connect EACCES|connect ECONNREFUSED/.test(message)) { return; } @@ -5112,158 +2983,96 @@ export class ExtensionsWorkbenchService const extensionId = match[1]; - this.queryLocal() - .then(async (local) => { - let extension = local.find((local) => - areSameExtensions(local.identifier, { id: extensionId }), - ); - if (!extension) { - [extension] = await this.getExtensions( - [{ id: extensionId }], - { source: "uri" }, - CancellationToken.None, - ); - } - if (extension) { - await this.hostService.focus(mainWindow); - await this.open(extension); - } - }) - .then(undefined, (error) => this.onError(error)); + this.queryLocal().then(async local => { + let extension = local.find(local => areSameExtensions(local.identifier, { id: extensionId })); + if (!extension) { + [extension] = await this.getExtensions([{ id: extensionId }], { source: 'uri' }, CancellationToken.None); + } + if (extension) { + await this.hostService.focus(mainWindow); + await this.open(extension); + } + }).then(undefined, error => this.onError(error)); } private getPublishersToAutoUpdate(): string[] { - return this.getEnabledAutoUpdateExtensions().filter( - (id) => !EXTENSION_IDENTIFIER_REGEX.test(id), - ); + return this.getEnabledAutoUpdateExtensions().filter(id => !EXTENSION_IDENTIFIER_REGEX.test(id)); } getEnabledAutoUpdateExtensions(): string[] { try { - const parsedValue = JSON.parse( - this.enabledAuotUpdateExtensionsValue, - ); + const parsedValue = JSON.parse(this.enabledAuotUpdateExtensionsValue); if (Array.isArray(parsedValue)) { return parsedValue; } - } catch (e) { - /* Ignore */ - } + } catch (e) { /* Ignore */ } return []; } - private setEnabledAutoUpdateExtensions( - enabledAutoUpdateExtensions: string[], - ): void { - this.enabledAuotUpdateExtensionsValue = JSON.stringify( - enabledAutoUpdateExtensions, - ); + private setEnabledAutoUpdateExtensions(enabledAutoUpdateExtensions: string[]): void { + this.enabledAuotUpdateExtensionsValue = JSON.stringify(enabledAutoUpdateExtensions); } private _enabledAutoUpdateExtensionsValue: string | undefined; private get enabledAuotUpdateExtensionsValue(): string { if (!this._enabledAutoUpdateExtensionsValue) { - this._enabledAutoUpdateExtensionsValue = - this.getEnabledAutoUpdateExtensionsValue(); + this._enabledAutoUpdateExtensionsValue = this.getEnabledAutoUpdateExtensionsValue(); } return this._enabledAutoUpdateExtensionsValue; } - private set enabledAuotUpdateExtensionsValue( - enabledAuotUpdateExtensionsValue: string, - ) { - if ( - this.enabledAuotUpdateExtensionsValue !== - enabledAuotUpdateExtensionsValue - ) { - this._enabledAutoUpdateExtensionsValue = - enabledAuotUpdateExtensionsValue; - this.setEnabledAutoUpdateExtensionsValue( - enabledAuotUpdateExtensionsValue, - ); + private set enabledAuotUpdateExtensionsValue(enabledAuotUpdateExtensionsValue: string) { + if (this.enabledAuotUpdateExtensionsValue !== enabledAuotUpdateExtensionsValue) { + this._enabledAutoUpdateExtensionsValue = enabledAuotUpdateExtensionsValue; + this.setEnabledAutoUpdateExtensionsValue(enabledAuotUpdateExtensionsValue); } } private getEnabledAutoUpdateExtensionsValue(): string { - return this.storageService.get( - EXTENSIONS_AUTO_UPDATE_KEY, - StorageScope.APPLICATION, - "[]", - ); + return this.storageService.get(EXTENSIONS_AUTO_UPDATE_KEY, StorageScope.APPLICATION, '[]'); } private setEnabledAutoUpdateExtensionsValue(value: string): void { - this.storageService.store( - EXTENSIONS_AUTO_UPDATE_KEY, - value, - StorageScope.APPLICATION, - StorageTarget.USER, - ); + this.storageService.store(EXTENSIONS_AUTO_UPDATE_KEY, value, StorageScope.APPLICATION, StorageTarget.USER); } getDisabledAutoUpdateExtensions(): string[] { try { - const parsedValue = JSON.parse( - this.disabledAutoUpdateExtensionsValue, - ); + const parsedValue = JSON.parse(this.disabledAutoUpdateExtensionsValue); if (Array.isArray(parsedValue)) { return parsedValue; } - } catch (e) { - /* Ignore */ - } + } catch (e) { /* Ignore */ } return []; } - private setDisabledAutoUpdateExtensions( - disabledAutoUpdateExtensions: string[], - ): void { - this.disabledAutoUpdateExtensionsValue = JSON.stringify( - disabledAutoUpdateExtensions, - ); + private setDisabledAutoUpdateExtensions(disabledAutoUpdateExtensions: string[]): void { + this.disabledAutoUpdateExtensionsValue = JSON.stringify(disabledAutoUpdateExtensions); } private _disabledAutoUpdateExtensionsValue: string | undefined; private get disabledAutoUpdateExtensionsValue(): string { if (!this._disabledAutoUpdateExtensionsValue) { - this._disabledAutoUpdateExtensionsValue = - this.getDisabledAutoUpdateExtensionsValue(); + this._disabledAutoUpdateExtensionsValue = this.getDisabledAutoUpdateExtensionsValue(); } return this._disabledAutoUpdateExtensionsValue; } - private set disabledAutoUpdateExtensionsValue( - disabledAutoUpdateExtensionsValue: string, - ) { - if ( - this.disabledAutoUpdateExtensionsValue !== - disabledAutoUpdateExtensionsValue - ) { - this._disabledAutoUpdateExtensionsValue = - disabledAutoUpdateExtensionsValue; - this.setDisabledAutoUpdateExtensionsValue( - disabledAutoUpdateExtensionsValue, - ); + private set disabledAutoUpdateExtensionsValue(disabledAutoUpdateExtensionsValue: string) { + if (this.disabledAutoUpdateExtensionsValue !== disabledAutoUpdateExtensionsValue) { + this._disabledAutoUpdateExtensionsValue = disabledAutoUpdateExtensionsValue; + this.setDisabledAutoUpdateExtensionsValue(disabledAutoUpdateExtensionsValue); } } private getDisabledAutoUpdateExtensionsValue(): string { - return this.storageService.get( - EXTENSIONS_DONOT_AUTO_UPDATE_KEY, - StorageScope.APPLICATION, - "[]", - ); + return this.storageService.get(EXTENSIONS_DONOT_AUTO_UPDATE_KEY, StorageScope.APPLICATION, '[]'); } private setDisabledAutoUpdateExtensionsValue(value: string): void { - this.storageService.store( - EXTENSIONS_DONOT_AUTO_UPDATE_KEY, - value, - StorageScope.APPLICATION, - StorageTarget.USER, - ); + this.storageService.store(EXTENSIONS_DONOT_AUTO_UPDATE_KEY, value, StorageScope.APPLICATION, StorageTarget.USER); } private getDismissedNotifications(): string[] { @@ -5272,31 +3081,24 @@ export class ExtensionsWorkbenchService if (Array.isArray(parsedValue)) { return parsedValue; } - } catch (e) { - /* Ignore */ - } + } catch (e) { /* Ignore */ } return []; } private setDismissedNotifications(dismissedNotifications: string[]): void { - this.dismissedNotificationsValue = JSON.stringify( - dismissedNotifications, - ); + this.dismissedNotificationsValue = JSON.stringify(dismissedNotifications); } private _dismissedNotificationsValue: string | undefined; private get dismissedNotificationsValue(): string { if (!this._dismissedNotificationsValue) { - this._dismissedNotificationsValue = - this.getDismissedNotificationsValue(); + this._dismissedNotificationsValue = this.getDismissedNotificationsValue(); } return this._dismissedNotificationsValue; } - private set dismissedNotificationsValue( - dismissedNotificationsValue: string, - ) { + private set dismissedNotificationsValue(dismissedNotificationsValue: string) { if (this.dismissedNotificationsValue !== dismissedNotificationsValue) { this._dismissedNotificationsValue = dismissedNotificationsValue; this.setDismissedNotificationsValue(dismissedNotificationsValue); @@ -5304,19 +3106,11 @@ export class ExtensionsWorkbenchService } private getDismissedNotificationsValue(): string { - return this.storageService.get( - EXTENSIONS_DISMISSED_NOTIFICATIONS_KEY, - StorageScope.PROFILE, - "[]", - ); + return this.storageService.get(EXTENSIONS_DISMISSED_NOTIFICATIONS_KEY, StorageScope.PROFILE, '[]'); } private setDismissedNotificationsValue(value: string): void { - this.storageService.store( - EXTENSIONS_DISMISSED_NOTIFICATIONS_KEY, - value, - StorageScope.PROFILE, - StorageTarget.USER, - ); + this.storageService.store(EXTENSIONS_DISMISSED_NOTIFICATIONS_KEY, value, StorageScope.PROFILE, StorageTarget.USER); } + } diff --git a/Source/vs/workbench/contrib/extensions/common/extensions.ts b/Source/vs/workbench/contrib/extensions/common/extensions.ts index b398409a742f6..6207063200d71 100644 --- a/Source/vs/workbench/contrib/extensions/common/extensions.ts +++ b/Source/vs/workbench/contrib/extensions/common/extensions.ts @@ -3,43 +3,26 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken } from "../../../../base/common/cancellation.js"; -import { Event } from "../../../../base/common/event.js"; -import { IMarkdownString } from "../../../../base/common/htmlContent.js"; -import { Disposable, IDisposable } from "../../../../base/common/lifecycle.js"; -import { IPager } from "../../../../base/common/paging.js"; -import { URI } from "../../../../base/common/uri.js"; -import { MenuId } from "../../../../platform/actions/common/actions.js"; -import { RawContextKey } from "../../../../platform/contextkey/common/contextkey.js"; -import { - IDeprecationInfo, - IExtensionIdentifier, - IExtensionInfo, - IExtensionQueryOptions, - IGalleryExtension, - ILocalExtension, - InstallExtensionResult, - InstallOptions, - IQueryOptions, -} from "../../../../platform/extensionManagement/common/extensionManagement.js"; -import { areSameExtensions } from "../../../../platform/extensionManagement/common/extensionManagementUtil.js"; -import { - ExtensionType, - IExtensionManifest, -} from "../../../../platform/extensions/common/extensions.js"; -import { createDecorator } from "../../../../platform/instantiation/common/instantiation.js"; -import { Severity } from "../../../../platform/notification/common/notification.js"; -import { ProgressLocation } from "../../../../platform/progress/common/progress.js"; -import { IView, IViewPaneContainer } from "../../../common/views.js"; -import { - EnablementState, - IExtensionManagementServer, - IResourceExtension, -} from "../../../services/extensionManagement/common/extensionManagement.js"; -import { IExtensionsStatus as IExtensionRuntimeStatus } from "../../../services/extensions/common/extensions.js"; -import { IExtensionEditorOptions } from "./extensionsInput.js"; - -export const VIEWLET_ID = "workbench.view.extensions"; +import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; +import { Event } from '../../../../base/common/event.js'; +import { IPager } from '../../../../base/common/paging.js'; +import { IQueryOptions, ILocalExtension, IGalleryExtension, IExtensionIdentifier, IExtensionInfo, IExtensionQueryOptions, IDeprecationInfo, InstallExtensionResult } from '../../../../platform/extensionManagement/common/extensionManagement.js'; +import { EnablementState, IExtensionManagementServer, IResourceExtension, IWorkbenchInstallOptions } from '../../../services/extensionManagement/common/extensionManagement.js'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js'; +import { areSameExtensions } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js'; +import { IExtensionManifest, ExtensionType } from '../../../../platform/extensions/common/extensions.js'; +import { URI } from '../../../../base/common/uri.js'; +import { IView, IViewPaneContainer } from '../../../common/views.js'; +import { RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { IExtensionsStatus as IExtensionRuntimeStatus } from '../../../services/extensions/common/extensions.js'; +import { IExtensionEditorOptions } from './extensionsInput.js'; +import { MenuId } from '../../../../platform/actions/common/actions.js'; +import { ProgressLocation } from '../../../../platform/progress/common/progress.js'; +import { Severity } from '../../../../platform/notification/common/notification.js'; +import { IMarkdownString } from '../../../../base/common/htmlContent.js'; + +export const VIEWLET_ID = 'workbench.view.extensions'; export interface IExtensionsViewPaneContainer extends IViewPaneContainer { readonly searchValue: string | undefined; @@ -55,21 +38,18 @@ export const enum ExtensionState { Installing, Installed, Uninstalling, - Uninstalled, + Uninstalled } export const enum ExtensionRuntimeActionType { - ReloadWindow = "reloadWindow", - RestartExtensions = "restartExtensions", - DownloadUpdate = "downloadUpdate", - ApplyUpdate = "applyUpdate", - QuitAndInstall = "quitAndInstall", + ReloadWindow = 'reloadWindow', + RestartExtensions = 'restartExtensions', + DownloadUpdate = 'downloadUpdate', + ApplyUpdate = 'applyUpdate', + QuitAndInstall = 'quitAndInstall', } -export type ExtensionRuntimeState = { - action: ExtensionRuntimeActionType; - reason: string; -}; +export type ExtensionRuntimeState = { action: ExtensionRuntimeActionType; reason: string }; export interface IExtension { readonly type: ExtensionType; @@ -111,13 +91,10 @@ export interface IExtension { readonly extensionPack: string[]; readonly telemetryData: any; readonly preview: boolean; - getManifest(token: CancellationToken): Promise; hasReadme(): boolean; - getReadme(token: CancellationToken): Promise; hasChangelog(): boolean; - getChangelog(token: CancellationToken): Promise; readonly server?: IExtensionManagementServer; readonly local?: ILocalExtension; @@ -127,10 +104,9 @@ export interface IExtension { readonly deprecationInfo?: IDeprecationInfo; } -export const IExtensionsWorkbenchService = - createDecorator("extensionsWorkbenchService"); +export const IExtensionsWorkbenchService = createDecorator('extensionsWorkbenchService'); -export interface InstallExtensionOptions extends InstallOptions { +export interface InstallExtensionOptions extends IWorkbenchInstallOptions { version?: string; justification?: string | { reason: string; action: string }; enable?: boolean; @@ -154,87 +130,35 @@ export interface IExtensionsWorkbenchService { readonly whenInitialized: Promise; queryLocal(server?: IExtensionManagementServer): Promise; queryGallery(token: CancellationToken): Promise>; - queryGallery( - options: IQueryOptions, - token: CancellationToken, - ): Promise>; - - getExtensions( - extensionInfos: IExtensionInfo[], - token: CancellationToken, - ): Promise; - - getExtensions( - extensionInfos: IExtensionInfo[], - options: IExtensionQueryOptions, - token: CancellationToken, - ): Promise; - - getResourceExtensions( - locations: URI[], - isWorkspaceScoped: boolean, - ): Promise; + queryGallery(options: IQueryOptions, token: CancellationToken): Promise>; + getExtensions(extensionInfos: IExtensionInfo[], token: CancellationToken): Promise; + getExtensions(extensionInfos: IExtensionInfo[], options: IExtensionQueryOptions, token: CancellationToken): Promise; + getResourceExtensions(locations: URI[], isWorkspaceScoped: boolean): Promise; canInstall(extension: IExtension): Promise; - install( - id: string, - installOptions?: InstallExtensionOptions, - progressLocation?: ProgressLocation | string, - ): Promise; - install( - vsix: URI, - installOptions?: InstallExtensionOptions, - progressLocation?: ProgressLocation | string, - ): Promise; - install( - extension: IExtension, - installOptions?: InstallExtensionOptions, - progressLocation?: ProgressLocation | string, - ): Promise; - installInServer( - extension: IExtension, - server: IExtensionManagementServer, - ): Promise; - + install(id: string, installOptions?: InstallExtensionOptions, progressLocation?: ProgressLocation | string): Promise; + install(vsix: URI, installOptions?: InstallExtensionOptions, progressLocation?: ProgressLocation | string): Promise; + install(extension: IExtension, installOptions?: InstallExtensionOptions, progressLocation?: ProgressLocation | string): Promise; + installInServer(extension: IExtension, server: IExtensionManagementServer): Promise; downloadVSIX(extension: string, prerelease: boolean): Promise; uninstall(extension: IExtension): Promise; reinstall(extension: IExtension): Promise; togglePreRelease(extension: IExtension): Promise; canSetLanguage(extension: IExtension): boolean; - setLanguage(extension: IExtension): Promise; - - setEnablement( - extensions: IExtension | IExtension[], - enablementState: EnablementState, - ): Promise; + setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise; isAutoUpdateEnabledFor(extensionOrPublisher: IExtension | string): boolean; - updateAutoUpdateEnablementFor( - extensionOrPublisher: IExtension | string, - enable: boolean, - ): Promise; - shouldRequireConsentToUpdate( - extension: IExtension, - ): Promise; + updateAutoUpdateEnablementFor(extensionOrPublisher: IExtension | string, enable: boolean): Promise; + shouldRequireConsentToUpdate(extension: IExtension): Promise; updateAutoUpdateForAllExtensions(value: boolean): Promise; - open( - extension: IExtension | string, - options?: IExtensionEditorOptions, - ): Promise; + open(extension: IExtension | string, options?: IExtensionEditorOptions): Promise; openSearch(searchValue: string, focus?: boolean): Promise; - getAutoUpdateValue(): AutoUpdateConfigurationValue; checkForUpdates(): Promise; - - getExtensionRuntimeStatus( - extension: IExtension, - ): IExtensionRuntimeStatus | undefined; + getExtensionRuntimeStatus(extension: IExtension): IExtensionRuntimeStatus | undefined; updateAll(): Promise; updateRunningExtensions(): Promise; - readonly onDidChangeExtensionsNotification: Event< - IExtensionsNotification | undefined - >; - + readonly onDidChangeExtensionsNotification: Event; getExtensionsNotification(): IExtensionsNotification | undefined; // Sync APIs @@ -244,24 +168,20 @@ export interface IExtensionsWorkbenchService { } export const enum ExtensionEditorTab { - Readme = "readme", - Features = "features", - Changelog = "changelog", - Dependencies = "dependencies", - ExtensionPack = "extensionPack", + Readme = 'readme', + Features = 'features', + Changelog = 'changelog', + Dependencies = 'dependencies', + ExtensionPack = 'extensionPack', } -export const ConfigurationKey = "extensions"; -export const AutoUpdateConfigurationKey = "extensions.autoUpdate"; -export const AutoCheckUpdatesConfigurationKey = "extensions.autoCheckUpdates"; -export const CloseExtensionDetailsOnViewChangeKey = - "extensions.closeExtensionDetailsOnViewChange"; -export const AutoRestartConfigurationKey = "extensions.autoRestart"; +export const ConfigurationKey = 'extensions'; +export const AutoUpdateConfigurationKey = 'extensions.autoUpdate'; +export const AutoCheckUpdatesConfigurationKey = 'extensions.autoCheckUpdates'; +export const CloseExtensionDetailsOnViewChangeKey = 'extensions.closeExtensionDetailsOnViewChange'; +export const AutoRestartConfigurationKey = 'extensions.autoRestart'; -export type AutoUpdateConfigurationValue = - | boolean - | "onlyEnabledExtensions" - | "onlySelectedExtensions"; +export type AutoUpdateConfigurationValue = boolean | 'onlyEnabledExtensions' | 'onlySelectedExtensions'; export interface IExtensionsConfiguration { autoUpdate: boolean; @@ -277,33 +197,24 @@ export interface IExtensionContainer extends IDisposable { } export class ExtensionContainers extends Disposable { + constructor( private readonly containers: IExtensionContainer[], - @IExtensionsWorkbenchService - extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService ) { super(); this._register(extensionsWorkbenchService.onChange(this.update, this)); } set extension(extension: IExtension) { - this.containers.forEach((c) => (c.extension = extension)); + this.containers.forEach(c => c.extension = extension); } private update(extension: IExtension | undefined): void { for (const container of this.containers) { if (extension && container.extension) { - if ( - areSameExtensions( - container.extension.identifier, - extension.identifier, - ) - ) { - if ( - container.extension.server && - extension.server && - container.extension.server !== extension.server - ) { + if (areSameExtensions(container.extension.identifier, extension.identifier)) { + if (container.extension.server && extension.server && container.extension.server !== extension.server) { if (container.updateWhenCounterExtensionChanges) { container.update(); } @@ -318,38 +229,24 @@ export class ExtensionContainers extends Disposable { } } -export const WORKSPACE_RECOMMENDATIONS_VIEW_ID = - "workbench.views.extensions.workspaceRecommendations"; -export const OUTDATED_EXTENSIONS_VIEW_ID = - "workbench.views.extensions.searchOutdated"; -export const TOGGLE_IGNORE_EXTENSION_ACTION_ID = - "workbench.extensions.action.toggleIgnoreExtension"; -export const SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID = - "workbench.extensions.action.installVSIX"; -export const INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID = - "workbench.extensions.command.installFromVSIX"; +export const WORKSPACE_RECOMMENDATIONS_VIEW_ID = 'workbench.views.extensions.workspaceRecommendations'; +export const OUTDATED_EXTENSIONS_VIEW_ID = 'workbench.views.extensions.searchOutdated'; +export const TOGGLE_IGNORE_EXTENSION_ACTION_ID = 'workbench.extensions.action.toggleIgnoreExtension'; +export const SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID = 'workbench.extensions.action.installVSIX'; +export const INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID = 'workbench.extensions.command.installFromVSIX'; -export const LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID = - "workbench.extensions.action.listWorkspaceUnsupportedExtensions"; +export const LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID = 'workbench.extensions.action.listWorkspaceUnsupportedExtensions'; // Context Keys -export const HasOutdatedExtensionsContext = new RawContextKey( - "hasOutdatedExtensions", - false, -); -export const CONTEXT_HAS_GALLERY = new RawContextKey( - "hasGallery", - false, -); +export const HasOutdatedExtensionsContext = new RawContextKey('hasOutdatedExtensions', false); +export const CONTEXT_HAS_GALLERY = new RawContextKey('hasGallery', false); // Context Menu Groups -export const THEME_ACTIONS_GROUP = "_theme_"; -export const INSTALL_ACTIONS_GROUP = "0_install"; -export const UPDATE_ACTIONS_GROUP = "0_update"; +export const THEME_ACTIONS_GROUP = '_theme_'; +export const INSTALL_ACTIONS_GROUP = '0_install'; +export const UPDATE_ACTIONS_GROUP = '0_update'; -export const extensionsSearchActionsMenu = new MenuId( - "extensionsSearchActionsMenu", -); +export const extensionsSearchActionsMenu = new MenuId('extensionsSearchActionsMenu'); export interface IExtensionArg { id: string; diff --git a/Source/vs/workbench/contrib/files/browser/views/explorerView.ts b/Source/vs/workbench/contrib/files/browser/views/explorerView.ts index c5a1aa8d7261f..97cee3715ee7f 100644 --- a/Source/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/Source/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -3,155 +3,65 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DelayedDragHandler } from "../../../../../base/browser/dnd.js"; -import * as DOM from "../../../../../base/browser/dom.js"; -import { AbstractTreePart } from "../../../../../base/browser/ui/tree/abstractTree.js"; -import { IAsyncDataTreeViewState } from "../../../../../base/browser/ui/tree/asyncDataTree.js"; -import { - ITreeContextMenuEvent, - TreeVisibility, -} from "../../../../../base/browser/ui/tree/tree.js"; -import { - WorkbenchActionExecutedClassification, - WorkbenchActionExecutedEvent, -} from "../../../../../base/common/actions.js"; -import { Codicon } from "../../../../../base/common/codicons.js"; -import { memoize } from "../../../../../base/common/decorators.js"; -import { Event } from "../../../../../base/common/event.js"; -import { FuzzyScore } from "../../../../../base/common/filters.js"; -import { IDisposable } from "../../../../../base/common/lifecycle.js"; -import { ResourceMap } from "../../../../../base/common/map.js"; -import * as perf from "../../../../../base/common/performance.js"; -import { URI } from "../../../../../base/common/uri.js"; -import * as nls from "../../../../../nls.js"; -import { - Action2, - MenuId, - registerAction2, -} from "../../../../../platform/actions/common/actions.js"; -import { IClipboardService } from "../../../../../platform/clipboard/common/clipboardService.js"; -import { ICommandService } from "../../../../../platform/commands/common/commands.js"; -import { - IConfigurationChangeEvent, - IConfigurationService, -} from "../../../../../platform/configuration/common/configuration.js"; -import { - ContextKeyExpr, - IContextKey, - IContextKeyService, -} from "../../../../../platform/contextkey/common/contextkey.js"; -import { IContextMenuService } from "../../../../../platform/contextview/browser/contextView.js"; -import { EditorOpenSource } from "../../../../../platform/editor/common/editor.js"; -import { - FileSystemProviderCapabilities, - IFileService, -} from "../../../../../platform/files/common/files.js"; -import { IHoverService } from "../../../../../platform/hover/browser/hover.js"; -import { - IInstantiationService, - ServicesAccessor, -} from "../../../../../platform/instantiation/common/instantiation.js"; -import { IKeybindingService } from "../../../../../platform/keybinding/common/keybinding.js"; -import { ILabelService } from "../../../../../platform/label/common/label.js"; -import { WorkbenchCompressibleAsyncDataTree } from "../../../../../platform/list/browser/listService.js"; -import { IOpenerService } from "../../../../../platform/opener/common/opener.js"; -import { - IProgressService, - ProgressLocation, -} from "../../../../../platform/progress/common/progress.js"; -import { - IStorageService, - StorageScope, - StorageTarget, -} from "../../../../../platform/storage/common/storage.js"; -import { ITelemetryService } from "../../../../../platform/telemetry/common/telemetry.js"; -import { - IFileIconTheme, - IThemeService, -} from "../../../../../platform/theme/common/themeService.js"; -import { IUriIdentityService } from "../../../../../platform/uriIdentity/common/uriIdentity.js"; -import { - IWorkspaceContextService, - WorkbenchState, -} from "../../../../../platform/workspace/common/workspace.js"; -import { ResourceLabels } from "../../../../browser/labels.js"; -import { - IViewPaneOptions, - ViewPane, -} from "../../../../browser/parts/views/viewPane.js"; -import { ResourceContextKey } from "../../../../common/contextkeys.js"; -import { - EditorResourceAccessor, - SideBySideEditor, -} from "../../../../common/editor.js"; -import { IViewDescriptorService } from "../../../../common/views.js"; -import { IDecorationsService } from "../../../../services/decorations/common/decorations.js"; -import { IEditorResolverService } from "../../../../services/editor/common/editorResolverService.js"; -import { - ACTIVE_GROUP, - IEditorService, - SIDE_GROUP, -} from "../../../../services/editor/common/editorService.js"; -import { IWorkbenchLayoutService } from "../../../../services/layout/browser/layoutService.js"; -import { IWorkbenchThemeService } from "../../../../services/themes/common/workbenchThemeService.js"; -import { IViewsService } from "../../../../services/views/common/viewsService.js"; -import { ExplorerItem, NewExplorerItem } from "../../common/explorerModel.js"; -import { - ExplorerCompressedFirstFocusContext, - ExplorerCompressedFocusContext, - ExplorerCompressedLastFocusContext, - ExplorerFindProviderActive, - ExplorerFocusedContext, - ExplorerFolderContext, - ExplorerResourceAvailableEditorIdsContext, - ExplorerResourceCut, - ExplorerResourceMoveableToTrash, - ExplorerResourceNotReadonlyContext, - ExplorerResourceParentReadOnlyContext, - ExplorerResourceReadonlyContext, - ExplorerRootContext, - FilesExplorerFocusedContext, - FoldersViewVisibleContext, - IFilesConfiguration, - VIEW_ID, - ViewHasSomeCollapsibleRootItemContext, -} from "../../common/files.js"; -import { - FileCopiedContext, - NEW_FILE_COMMAND_ID, - NEW_FOLDER_COMMAND_ID, -} from "../fileActions.js"; -import { IExplorerService, IExplorerView } from "../files.js"; -import { ExplorerDecorationsProvider } from "./explorerDecorationsProvider.js"; -import { - ExplorerCompressionDelegate, - ExplorerDataSource, - ExplorerDelegate, - ExplorerFindProvider, - FileDragAndDrop, - FilesFilter, - FileSorter, - FilesRenderer, - ICompressedNavigationController, - isCompressedFolderName, -} from "./explorerViewer.js"; - -function hasExpandedRootChild( - tree: WorkbenchCompressibleAsyncDataTree< - ExplorerItem | ExplorerItem[], - ExplorerItem, - FuzzyScore - >, - treeInput: ExplorerItem[], -): boolean { +import * as nls from '../../../../../nls.js'; +import { URI } from '../../../../../base/common/uri.js'; +import * as perf from '../../../../../base/common/performance.js'; +import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from '../../../../../base/common/actions.js'; +import { memoize } from '../../../../../base/common/decorators.js'; +import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, ExplorerResourceAvailableEditorIdsContext, VIEW_ID, ExplorerResourceNotReadonlyContext, ViewHasSomeCollapsibleRootItemContext, FoldersViewVisibleContext, ExplorerResourceParentReadOnlyContext, ExplorerFindProviderActive } from '../../common/files.js'; +import { FileCopiedContext, NEW_FILE_COMMAND_ID, NEW_FOLDER_COMMAND_ID } from '../fileActions.js'; +import * as DOM from '../../../../../base/browser/dom.js'; +import { IWorkbenchLayoutService } from '../../../../services/layout/browser/layoutService.js'; +import { ExplorerDecorationsProvider } from './explorerDecorationsProvider.js'; +import { IWorkspaceContextService, WorkbenchState } from '../../../../../platform/workspace/common/workspace.js'; +import { IConfigurationService, IConfigurationChangeEvent } from '../../../../../platform/configuration/common/configuration.js'; +import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; +import { IProgressService, ProgressLocation } from '../../../../../platform/progress/common/progress.js'; +import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js'; +import { IContextKeyService, IContextKey, ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; +import { ResourceContextKey } from '../../../../common/contextkeys.js'; +import { IDecorationsService } from '../../../../services/decorations/common/decorations.js'; +import { WorkbenchCompressibleAsyncDataTree } from '../../../../../platform/list/browser/listService.js'; +import { DelayedDragHandler } from '../../../../../base/browser/dnd.js'; +import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from '../../../../services/editor/common/editorService.js'; +import { IViewPaneOptions, ViewPane } from '../../../../browser/parts/views/viewPane.js'; +import { ILabelService } from '../../../../../platform/label/common/label.js'; +import { ExplorerDelegate, ExplorerDataSource, FilesRenderer, ICompressedNavigationController, FilesFilter, FileSorter, FileDragAndDrop, ExplorerCompressionDelegate, isCompressedFolderName, ExplorerFindProvider } from './explorerViewer.js'; +import { IThemeService, IFileIconTheme } from '../../../../../platform/theme/common/themeService.js'; +import { IWorkbenchThemeService } from '../../../../services/themes/common/workbenchThemeService.js'; +import { ITreeContextMenuEvent, TreeVisibility } from '../../../../../base/browser/ui/tree/tree.js'; +import { MenuId, Action2, registerAction2 } from '../../../../../platform/actions/common/actions.js'; +import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; +import { ExplorerItem, NewExplorerItem } from '../../common/explorerModel.js'; +import { ResourceLabels } from '../../../../browser/labels.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; +import { IAsyncDataTreeViewState } from '../../../../../base/browser/ui/tree/asyncDataTree.js'; +import { FuzzyScore } from '../../../../../base/common/filters.js'; +import { IClipboardService } from '../../../../../platform/clipboard/common/clipboardService.js'; +import { IFileService, FileSystemProviderCapabilities } from '../../../../../platform/files/common/files.js'; +import { IDisposable } from '../../../../../base/common/lifecycle.js'; +import { Event } from '../../../../../base/common/event.js'; +import { IViewDescriptorService } from '../../../../common/views.js'; +import { IViewsService } from '../../../../services/views/common/viewsService.js'; +import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; +import { IUriIdentityService } from '../../../../../platform/uriIdentity/common/uriIdentity.js'; +import { EditorResourceAccessor, SideBySideEditor } from '../../../../common/editor.js'; +import { IExplorerService, IExplorerView } from '../files.js'; +import { Codicon } from '../../../../../base/common/codicons.js'; +import { ICommandService } from '../../../../../platform/commands/common/commands.js'; +import { IEditorResolverService } from '../../../../services/editor/common/editorResolverService.js'; +import { EditorOpenSource } from '../../../../../platform/editor/common/editor.js'; +import { ResourceMap } from '../../../../../base/common/map.js'; +import { AbstractTreePart } from '../../../../../base/browser/ui/tree/abstractTree.js'; +import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; + + +function hasExpandedRootChild(tree: WorkbenchCompressibleAsyncDataTree, treeInput: ExplorerItem[]): boolean { for (const folder of treeInput) { if (tree.hasNode(folder) && !tree.isCollapsed(folder)) { for (const [, child] of folder.children.entries()) { - if ( - tree.hasNode(child) && - tree.isCollapsible(child) && - !tree.isCollapsed(child) - ) { + if (tree.hasNode(child) && tree.isCollapsible(child) && !tree.isCollapsed(child)) { return true; } } @@ -163,14 +73,7 @@ function hasExpandedRootChild( /** * Whether or not any of the nodes in the tree are expanded */ -function hasExpandedNode( - tree: WorkbenchCompressibleAsyncDataTree< - ExplorerItem | ExplorerItem[], - ExplorerItem, - FuzzyScore - >, - treeInput: ExplorerItem[], -): boolean { +function hasExpandedNode(tree: WorkbenchCompressibleAsyncDataTree, treeInput: ExplorerItem[]): boolean { for (const folder of treeInput) { if (tree.hasNode(folder) && !tree.isCollapsed(folder)) { return true; @@ -186,19 +89,12 @@ const identityProvider = { } return stat.getId(); - }, + } }; -export function getContext( - focus: ExplorerItem[], - selection: ExplorerItem[], - respectMultiSelection: boolean, - compressedNavigationControllerProvider: { - getCompressedNavigationController( - stat: ExplorerItem, - ): ICompressedNavigationController[] | undefined; - }, -): ExplorerItem[] { +export function getContext(focus: ExplorerItem[], selection: ExplorerItem[], respectMultiSelection: boolean, + compressedNavigationControllerProvider: { getCompressedNavigationController(stat: ExplorerItem): ICompressedNavigationController[] | undefined }): ExplorerItem[] { + let focusedStat: ExplorerItem | undefined; focusedStat = focus.length ? focus[0] : undefined; @@ -207,37 +103,16 @@ export function getContext( focusedStat = undefined; } - const compressedNavigationControllers = - focusedStat && - compressedNavigationControllerProvider.getCompressedNavigationController( - focusedStat, - ); - - const compressedNavigationController = - compressedNavigationControllers && - compressedNavigationControllers.length - ? compressedNavigationControllers[0] - : undefined; - focusedStat = compressedNavigationController - ? compressedNavigationController.current - : focusedStat; + const compressedNavigationControllers = focusedStat && compressedNavigationControllerProvider.getCompressedNavigationController(focusedStat); + const compressedNavigationController = compressedNavigationControllers && compressedNavigationControllers.length ? compressedNavigationControllers[0] : undefined; + focusedStat = compressedNavigationController ? compressedNavigationController.current : focusedStat; const selectedStats: ExplorerItem[] = []; for (const stat of selection) { - const controllers = - compressedNavigationControllerProvider.getCompressedNavigationController( - stat, - ); - - const controller = - controllers && controllers.length ? controllers[0] : undefined; - - if ( - controller && - focusedStat && - controller === compressedNavigationController - ) { + const controllers = compressedNavigationControllerProvider.getCompressedNavigationController(stat); + const controller = controllers && controllers.length ? controllers[0] : undefined; + if (controller && focusedStat && controller === compressedNavigationController) { if (stat === focusedStat) { selectedStats.push(stat); } @@ -276,14 +151,9 @@ export interface IExplorerViewPaneOptions extends IViewPaneOptions { } export class ExplorerView extends ViewPane implements IExplorerView { - static readonly TREE_VIEW_STATE_STORAGE_KEY: string = - "workbench.explorer.treeViewState"; - - private tree!: WorkbenchCompressibleAsyncDataTree< - ExplorerItem | ExplorerItem[], - ExplorerItem, - FuzzyScore - >; + static readonly TREE_VIEW_STATE_STORAGE_KEY: string = 'workbench.explorer.treeViewState'; + + private tree!: WorkbenchCompressibleAsyncDataTree; private filter!: FilesFilter; private findProvider!: ExplorerFindProvider; @@ -311,7 +181,7 @@ export class ExplorerView extends ViewPane implements IExplorerView { private horizontalScrolling: boolean | undefined; private dragHandler!: DelayedDragHandler; - private _autoReveal: boolean | "force" | "focusNoScroll" = false; + private _autoReveal: boolean | 'force' | 'focusNoScroll' = false; private decorationsProvider: ExplorerDecorationsProvider | undefined; private readonly delegate: IExplorerViewContainerDelegate | undefined; @@ -320,19 +190,15 @@ export class ExplorerView extends ViewPane implements IExplorerView { @IContextMenuService contextMenuService: IContextMenuService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IInstantiationService instantiationService: IInstantiationService, - @IWorkspaceContextService - private readonly contextService: IWorkspaceContextService, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IProgressService private readonly progressService: IProgressService, @IEditorService private readonly editorService: IEditorService, - @IEditorResolverService - private readonly editorResolverService: IEditorResolverService, - @IWorkbenchLayoutService - private readonly layoutService: IWorkbenchLayoutService, + @IEditorResolverService private readonly editorResolverService: IEditorResolverService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IKeybindingService keybindingService: IKeybindingService, @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService configurationService: IConfigurationService, - @IDecorationsService - private readonly decorationService: IDecorationsService, + @IDecorationsService private readonly decorationService: IDecorationsService, @ILabelService private readonly labelService: ILabelService, @IThemeService themeService: IWorkbenchThemeService, @ITelemetryService telemetryService: ITelemetryService, @@ -341,50 +207,28 @@ export class ExplorerView extends ViewPane implements IExplorerView { @IStorageService private readonly storageService: IStorageService, @IClipboardService private clipboardService: IClipboardService, @IFileService private readonly fileService: IFileService, - @IUriIdentityService - private readonly uriIdentityService: IUriIdentityService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @ICommandService private readonly commandService: ICommandService, - @IOpenerService openerService: IOpenerService, + @IOpenerService openerService: IOpenerService ) { - super( - options, - keybindingService, - contextMenuService, - configurationService, - contextKeyService, - viewDescriptorService, - instantiationService, - openerService, - themeService, - telemetryService, - hoverService, - ); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService); this.delegate = options.delegate; - this.resourceContext = - instantiationService.createInstance(ResourceContextKey); + this.resourceContext = instantiationService.createInstance(ResourceContextKey); this._register(this.resourceContext); - this.parentReadonlyContext = - ExplorerResourceParentReadOnlyContext.bindTo(contextKeyService); + this.parentReadonlyContext = ExplorerResourceParentReadOnlyContext.bindTo(contextKeyService); this.folderContext = ExplorerFolderContext.bindTo(contextKeyService); - this.readonlyContext = - ExplorerResourceReadonlyContext.bindTo(contextKeyService); - this.availableEditorIdsContext = - ExplorerResourceAvailableEditorIdsContext.bindTo(contextKeyService); + this.readonlyContext = ExplorerResourceReadonlyContext.bindTo(contextKeyService); + this.availableEditorIdsContext = ExplorerResourceAvailableEditorIdsContext.bindTo(contextKeyService); this.rootContext = ExplorerRootContext.bindTo(contextKeyService); - this.resourceMoveableToTrash = - ExplorerResourceMoveableToTrash.bindTo(contextKeyService); - this.compressedFocusContext = - ExplorerCompressedFocusContext.bindTo(contextKeyService); - this.compressedFocusFirstContext = - ExplorerCompressedFirstFocusContext.bindTo(contextKeyService); - this.compressedFocusLastContext = - ExplorerCompressedLastFocusContext.bindTo(contextKeyService); - this.viewHasSomeCollapsibleRootItem = - ViewHasSomeCollapsibleRootItemContext.bindTo(contextKeyService); - this.viewVisibleContextKey = - FoldersViewVisibleContext.bindTo(contextKeyService); + this.resourceMoveableToTrash = ExplorerResourceMoveableToTrash.bindTo(contextKeyService); + this.compressedFocusContext = ExplorerCompressedFocusContext.bindTo(contextKeyService); + this.compressedFocusFirstContext = ExplorerCompressedFirstFocusContext.bindTo(contextKeyService); + this.compressedFocusLastContext = ExplorerCompressedLastFocusContext.bindTo(contextKeyService); + this.viewHasSomeCollapsibleRootItem = ViewHasSomeCollapsibleRootItemContext.bindTo(contextKeyService); + this.viewVisibleContextKey = FoldersViewVisibleContext.bindTo(contextKeyService); + this.explorerService.registerView(this); } @@ -393,14 +237,12 @@ export class ExplorerView extends ViewPane implements IExplorerView { return this._autoReveal; } - set autoReveal(autoReveal: boolean | "force" | "focusNoScroll") { + set autoReveal(autoReveal: boolean | 'force' | 'focusNoScroll') { this._autoReveal = autoReveal; } get name(): string { - return this.labelService.getWorkspaceLabel( - this.contextService.getWorkspace(), - ); + return this.labelService.getWorkspaceLabel(this.contextService.getWorkspace()); } override get title(): string { @@ -413,7 +255,6 @@ export class ExplorerView extends ViewPane implements IExplorerView { override setVisible(visible: boolean): void { this.viewVisibleContextKey.set(visible); - super.setVisible(visible); } @@ -431,26 +272,18 @@ export class ExplorerView extends ViewPane implements IExplorerView { super.renderHeader(container); // Expand on drag over - this.dragHandler = new DelayedDragHandler(container, () => - this.setExpanded(true), - ); - - const titleElement = container.querySelector(".title") as HTMLElement; + this.dragHandler = new DelayedDragHandler(container, () => this.setExpanded(true)); + const titleElement = container.querySelector('.title') as HTMLElement; const setHeader = () => { titleElement.textContent = this.name; this.updateTitle(this.name); - this.ariaHeaderLabel = nls.localize( - "explorerSection", - "Explorer Section: {0}", - this.name, - ); - titleElement.setAttribute("aria-label", this.ariaHeaderLabel); + this.ariaHeaderLabel = nls.localize('explorerSection', "Explorer Section: {0}", this.name); + titleElement.setAttribute('aria-label', this.ariaHeaderLabel); }; this._register(this.contextService.onDidChangeWorkspaceName(setHeader)); this._register(this.labelService.onDidChangeFormatters(setHeader)); - setHeader(); } @@ -463,67 +296,45 @@ export class ExplorerView extends ViewPane implements IExplorerView { super.renderBody(container); this.container = container; - this.treeContainer = DOM.append( - container, - DOM.$(".explorer-folders-view"), - ); + this.treeContainer = DOM.append(container, DOM.$('.explorer-folders-view')); this.createTree(this.treeContainer); - this._register( - this.labelService.onDidChangeFormatters(() => { - this._onDidChangeTitleArea.fire(); - }), - ); + this._register(this.labelService.onDidChangeFormatters(() => { + this._onDidChangeTitleArea.fire(); + })); // Update configuration this.onConfigurationUpdated(undefined); // When the explorer viewer is loaded, listen to changes to the editor input - this._register( - this.editorService.onDidActiveEditorChange(() => { - this.selectActiveFile(); - }), - ); + this._register(this.editorService.onDidActiveEditorChange(() => { + this.selectActiveFile(); + })); // Also handle configuration updates - this._register( - this.configurationService.onDidChangeConfiguration((e) => - this.onConfigurationUpdated(e), - ), - ); - - this._register( - this.onDidChangeBodyVisibility(async (visible) => { - if (visible) { - // Always refresh explorer when it becomes visible to compensate for missing file events #126817 - await this.setTreeInput(); - // Update the collapse / expand button state - this.updateAnyCollapsedContext(); - // Find resource to focus from active editor input if set - this.selectActiveFile(true); - } - }), - ); + this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e))); + + this._register(this.onDidChangeBodyVisibility(async visible => { + if (visible) { + // Always refresh explorer when it becomes visible to compensate for missing file events #126817 + await this.setTreeInput(); + // Update the collapse / expand button state + this.updateAnyCollapsedContext(); + // Find resource to focus from active editor input if set + this.selectActiveFile(true); + } + })); // Support for paste of files into explorer - this._register( - DOM.addDisposableListener( - DOM.getWindow(this.container), - DOM.EventType.PASTE, - async (event) => { - if (!this.hasFocus() || this.readonlyContext.get()) { - return; - } - if (event.clipboardData?.files?.length) { - await this.commandService.executeCommand( - "filesExplorer.paste", - event.clipboardData?.files, - ); - } - }, - ), - ); + this._register(DOM.addDisposableListener(DOM.getWindow(this.container), DOM.EventType.PASTE, async event => { + if (!this.hasFocus() || this.readonlyContext.get()) { + return; + } + if (event.clipboardData?.files?.length) { + await this.commandService.executeCommand('filesExplorer.paste', event.clipboardData?.files); + } + })); } override focus(): void { @@ -532,7 +343,6 @@ export class ExplorerView extends ViewPane implements IExplorerView { if (this.tree.getFocusedPart() === AbstractTreePart.Tree) { const focused = this.tree.getFocus(); - if (focused.length === 1 && this._autoReveal) { this.tree.reveal(focused[0], 0.5); } @@ -556,17 +366,10 @@ export class ExplorerView extends ViewPane implements IExplorerView { } getContext(respectMultiSelection: boolean): ExplorerItem[] { - const focusedItems = - this.tree.getFocusedPart() === AbstractTreePart.StickyScroll - ? this.tree.getStickyScrollFocus() - : this.tree.getFocus(); - - return getContext( - focusedItems, - this.tree.getSelection(), - respectMultiSelection, - this.renderer, - ); + const focusedItems = this.tree.getFocusedPart() === AbstractTreePart.StickyScroll ? + this.tree.getStickyScrollFocus() : + this.tree.getFocus(); + return getContext(focusedItems, this.tree.getSelection(), respectMultiSelection, this.renderer); } isItemVisible(item: ExplorerItem): boolean { @@ -592,19 +395,17 @@ export class ExplorerView extends ViewPane implements IExplorerView { await this.tree.expand(stat.parent!); } else { if (this.horizontalScrolling !== undefined) { - this.tree.updateOptions({ - horizontalScrolling: this.horizontalScrolling, - }); + this.tree.updateOptions({ horizontalScrolling: this.horizontalScrolling }); } this.horizontalScrolling = undefined; - this.treeContainer.classList.remove("highlight"); + this.treeContainer.classList.remove('highlight'); } await this.refresh(false, stat.parent, false); if (isEditing) { - this.treeContainer.classList.add("highlight"); + this.treeContainer.classList.add('highlight'); this.tree.reveal(stat); } else { this.tree.domFocus(); @@ -613,28 +414,12 @@ export class ExplorerView extends ViewPane implements IExplorerView { private async selectActiveFile(reveal = this._autoReveal): Promise { if (this._autoReveal) { - const activeFile = EditorResourceAccessor.getCanonicalUri( - this.editorService.activeEditor, - { supportSideBySide: SideBySideEditor.PRIMARY }, - ); + const activeFile = EditorResourceAccessor.getCanonicalUri(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY }); if (activeFile) { const focus = this.tree.getFocus(); - const selection = this.tree.getSelection(); - - if ( - focus.length === 1 && - this.uriIdentityService.extUri.isEqual( - focus[0].resource, - activeFile, - ) && - selection.length === 1 && - this.uriIdentityService.extUri.isEqual( - selection[0].resource, - activeFile, - ) - ) { + if (focus.length === 1 && this.uriIdentityService.extUri.isEqual(focus[0].resource, activeFile) && selection.length === 1 && this.uriIdentityService.extUri.isEqual(selection[0].resource, activeFile)) { // No action needed, active file is already focused and selected return; } @@ -647,299 +432,168 @@ export class ExplorerView extends ViewPane implements IExplorerView { this.filter = this.instantiationService.createInstance(FilesFilter); this._register(this.filter); this._register(this.filter.onDidChange(() => this.refresh(true))); - - const explorerLabels = this.instantiationService.createInstance( - ResourceLabels, - { onDidChangeVisibility: this.onDidChangeBodyVisibility }, - ); + const explorerLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); this._register(explorerLabels); - this.findProvider = this.instantiationService.createInstance( - ExplorerFindProvider, - () => this.tree, - ); + this.findProvider = this.instantiationService.createInstance(ExplorerFindProvider, () => this.tree); const updateWidth = (stat: ExplorerItem) => this.tree.updateWidth(stat); - this.renderer = this.instantiationService.createInstance( - FilesRenderer, - container, - explorerLabels, - this.findProvider.highlightTree, - updateWidth, - ); + this.renderer = this.instantiationService.createInstance(FilesRenderer, container, explorerLabels, this.findProvider.highlightTree, updateWidth); this._register(this.renderer); - this._register( - createFileIconThemableTreeContainerScope( - container, - this.themeService, - ), - ); - - const isCompressionEnabled = () => - this.configurationService.getValue( - "explorer.compactFolders", - ); - - const getFileNestingSettings = (item?: ExplorerItem) => - this.configurationService.getValue({ - resource: item?.root.resource, - }).explorer.fileNesting; - - this.tree = < - WorkbenchCompressibleAsyncDataTree< - ExplorerItem | ExplorerItem[], - ExplorerItem, - FuzzyScore - > - >this.instantiationService.createInstance( - WorkbenchCompressibleAsyncDataTree, - "FileExplorer", - container, - new ExplorerDelegate(), - new ExplorerCompressionDelegate(), - [this.renderer], - this.instantiationService.createInstance( - ExplorerDataSource, - this.filter, - this.findProvider, - ), - { - compressionEnabled: isCompressionEnabled(), - accessibilityProvider: this.renderer, - identityProvider, - keyboardNavigationLabelProvider: { - getKeyboardNavigationLabel: (stat: ExplorerItem) => { - if (this.explorerService.isEditable(stat)) { - return undefined; - } + this._register(createFileIconThemableTreeContainerScope(container, this.themeService)); - return stat.name; - }, - getCompressedNodeKeyboardNavigationLabel: ( - stats: ExplorerItem[], - ) => { - if ( - stats.some((stat) => - this.explorerService.isEditable(stat), - ) - ) { - return undefined; - } + const isCompressionEnabled = () => this.configurationService.getValue('explorer.compactFolders'); - return stats.map((stat) => stat.name).join("/"); - }, - }, - multipleSelectionSupport: true, - filter: this.filter, - sorter: this.instantiationService.createInstance(FileSorter), - dnd: this.instantiationService.createInstance( - FileDragAndDrop, - (item) => this.isItemCollapsed(item), - ), - collapseByDefault: (e) => { - if (e instanceof ExplorerItem) { - if (e.hasNests && getFileNestingSettings(e).expand) { - return false; - } - if (this.findProvider.isShowingFilterResults()) { - return false; - } + const getFileNestingSettings = (item?: ExplorerItem) => this.configurationService.getValue({ resource: item?.root.resource }).explorer.fileNesting; + + this.tree = this.instantiationService.createInstance(WorkbenchCompressibleAsyncDataTree, 'FileExplorer', container, new ExplorerDelegate(), new ExplorerCompressionDelegate(), [this.renderer], + this.instantiationService.createInstance(ExplorerDataSource, this.filter, this.findProvider), { + compressionEnabled: isCompressionEnabled(), + accessibilityProvider: this.renderer, + identityProvider, + keyboardNavigationLabelProvider: { + getKeyboardNavigationLabel: (stat: ExplorerItem) => { + if (this.explorerService.isEditable(stat)) { + return undefined; } - return true; + + return stat.name; }, - autoExpandSingleChildren: true, - expandOnlyOnTwistieClick: (e: unknown) => { - if (e instanceof ExplorerItem) { - if (e.hasNests) { - return true; - } else if ( - this.configurationService.getValue< - "singleClick" | "doubleClick" - >("workbench.tree.expandMode") === "doubleClick" - ) { - return true; - } + getCompressedNodeKeyboardNavigationLabel: (stats: ExplorerItem[]) => { + if (stats.some(stat => this.explorerService.isEditable(stat))) { + return undefined; } - return false; - }, - paddingBottom: ExplorerDelegate.ITEM_HEIGHT, - overrideStyles: - this.getLocationBasedColors().listOverrideStyles, - findProvider: this.findProvider, + + return stats.map(stat => stat.name).join('/'); + } + }, + multipleSelectionSupport: true, + filter: this.filter, + sorter: this.instantiationService.createInstance(FileSorter), + dnd: this.instantiationService.createInstance(FileDragAndDrop, (item) => this.isItemCollapsed(item)), + collapseByDefault: (e) => { + if (e instanceof ExplorerItem) { + if (e.hasNests && getFileNestingSettings(e).expand) { + return false; + } + if (this.findProvider.isShowingFilterResults()) { + return false; + } + } + return true; + }, + autoExpandSingleChildren: true, + expandOnlyOnTwistieClick: (e: unknown) => { + if (e instanceof ExplorerItem) { + if (e.hasNests) { + return true; + } + else if (this.configurationService.getValue<'singleClick' | 'doubleClick'>('workbench.tree.expandMode') === 'doubleClick') { + return true; + } + } + return false; }, - ); + paddingBottom: ExplorerDelegate.ITEM_HEIGHT, + overrideStyles: this.getLocationBasedColors().listOverrideStyles, + findProvider: this.findProvider, + }); this._register(this.tree); - this._register( - this.themeService.onDidColorThemeChange(() => this.tree.rerender()), - ); + this._register(this.themeService.onDidColorThemeChange(() => this.tree.rerender())); // Bind configuration - const onDidChangeCompressionConfiguration = Event.filter( - this.configurationService.onDidChangeConfiguration, - (e) => e.affectsConfiguration("explorer.compactFolders"), - ); - this._register( - onDidChangeCompressionConfiguration((_) => - this.tree.updateOptions({ - compressionEnabled: isCompressionEnabled(), - }), - ), - ); + const onDidChangeCompressionConfiguration = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('explorer.compactFolders')); + this._register(onDidChangeCompressionConfiguration(_ => this.tree.updateOptions({ compressionEnabled: isCompressionEnabled() }))); // Bind context keys FilesExplorerFocusedContext.bindTo(this.tree.contextKeyService); ExplorerFocusedContext.bindTo(this.tree.contextKeyService); // Update resource context based on focused element - this._register( - this.tree.onDidChangeFocus((e) => this.onFocusChanged(e.elements)), - ); + this._register(this.tree.onDidChangeFocus(e => this.onFocusChanged(e.elements))); this.onFocusChanged([]); // Open when selecting via keyboard - this._register( - this.tree.onDidOpen(async (e) => { - const element = e.element; - - if (!element) { + this._register(this.tree.onDidOpen(async e => { + const element = e.element; + if (!element) { + return; + } + // Do not react if the user is expanding selection via keyboard. + // Check if the item was previously also selected, if yes the user is simply expanding / collapsing current selection #66589. + const shiftDown = DOM.isKeyboardEvent(e.browserEvent) && e.browserEvent.shiftKey; + if (!shiftDown) { + if (element.isDirectory || this.explorerService.isEditable(undefined)) { + // Do not react if user is clicking on explorer items while some are being edited #70276 + // Do not react if clicking on directories return; } - // Do not react if the user is expanding selection via keyboard. - // Check if the item was previously also selected, if yes the user is simply expanding / collapsing current selection #66589. - const shiftDown = - DOM.isKeyboardEvent(e.browserEvent) && - e.browserEvent.shiftKey; - - if (!shiftDown) { - if ( - element.isDirectory || - this.explorerService.isEditable(undefined) - ) { - // Do not react if user is clicking on explorer items while some are being edited #70276 - // Do not react if clicking on directories - return; - } - this.telemetryService.publicLog2< - WorkbenchActionExecutedEvent, - WorkbenchActionExecutedClassification - >("workbenchActionExecuted", { - id: "workbench.files.openFile", - from: "explorer", - }); - - try { - this.delegate?.willOpenElement(e.browserEvent); - await this.editorService.openEditor( - { - resource: element.resource, - options: { - preserveFocus: - e.editorOptions.preserveFocus, - pinned: e.editorOptions.pinned, - source: EditorOpenSource.USER, - }, - }, - e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP, - ); - } finally { - this.delegate?.didOpenElement(); - } + this.telemetryService.publicLog2('workbenchActionExecuted', { id: 'workbench.files.openFile', from: 'explorer' }); + try { + this.delegate?.willOpenElement(e.browserEvent); + await this.editorService.openEditor({ resource: element.resource, options: { preserveFocus: e.editorOptions.preserveFocus, pinned: e.editorOptions.pinned, source: EditorOpenSource.USER } }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + } finally { + this.delegate?.didOpenElement(); } - }), - ); + } + })); - this._register(this.tree.onContextMenu((e) => this.onContextMenu(e))); + this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); - this._register( - this.tree.onDidScroll(async (e) => { - const editable = this.explorerService.getEditable(); + this._register(this.tree.onDidScroll(async e => { + const editable = this.explorerService.getEditable(); + if (e.scrollTopChanged && editable && this.tree.getRelativeTop(editable.stat) === null) { + await editable.data.onFinish('', false); + } + })); - if ( - e.scrollTopChanged && - editable && - this.tree.getRelativeTop(editable.stat) === null - ) { - await editable.data.onFinish("", false); - } - }), - ); - - this._register( - this.tree.onDidChangeCollapseState((e) => { - const element = e.node.element?.element; - - if (element) { - const navigationControllers = - this.renderer.getCompressedNavigationController( - element instanceof Array ? element[0] : element, - ); - navigationControllers?.forEach((controller) => - controller.updateCollapsed(e.node.collapsed), - ); - } - // Update showing expand / collapse button - this.updateAnyCollapsedContext(); - }), - ); + this._register(this.tree.onDidChangeCollapseState(e => { + const element = e.node.element?.element; + if (element) { + const navigationControllers = this.renderer.getCompressedNavigationController(element instanceof Array ? element[0] : element); + navigationControllers?.forEach(controller => controller.updateCollapsed(e.node.collapsed)); + } + // Update showing expand / collapse button + this.updateAnyCollapsedContext(); + })); this.updateAnyCollapsedContext(); - this._register( - this.tree.onMouseDblClick((e) => { - // If empty space is clicked, and not scrolling by page enabled #173261 - const scrollingByPage = - this.configurationService.getValue( - "workbench.list.scrollByPage", - ); - - if (e.element === null && !scrollingByPage) { - // click in empty area -> create a new file #116676 - this.commandService.executeCommand(NEW_FILE_COMMAND_ID); - } - }), - ); + this._register(this.tree.onMouseDblClick(e => { + // If empty space is clicked, and not scrolling by page enabled #173261 + const scrollingByPage = this.configurationService.getValue('workbench.list.scrollByPage'); + if (e.element === null && !scrollingByPage) { + // click in empty area -> create a new file #116676 + this.commandService.executeCommand(NEW_FILE_COMMAND_ID); + } + })); // save view state - this._register( - this.storageService.onWillSaveState(() => { - this.storeTreeViewState(); - }), - ); + this._register(this.storageService.onWillSaveState(() => { + this.storeTreeViewState(); + })); } // React on events - private onConfigurationUpdated( - event: IConfigurationChangeEvent | undefined, - ): void { - if (!event || event.affectsConfiguration("explorer.autoReveal")) { - const configuration = - this.configurationService.getValue(); + private onConfigurationUpdated(event: IConfigurationChangeEvent | undefined): void { + if (!event || event.affectsConfiguration('explorer.autoReveal')) { + const configuration = this.configurationService.getValue(); this._autoReveal = configuration?.explorer?.autoReveal; } // Push down config updates to components of viewer - if ( - event && - (event.affectsConfiguration("explorer.decorations.colors") || - event.affectsConfiguration("explorer.decorations.badges")) - ) { + if (event && (event.affectsConfiguration('explorer.decorations.colors') || event.affectsConfiguration('explorer.decorations.badges'))) { this.refresh(true); } } private storeTreeViewState() { - this.storageService.store( - ExplorerView.TREE_VIEW_STATE_STORAGE_KEY, - JSON.stringify(this.tree.getViewState()), - StorageScope.WORKSPACE, - StorageTarget.MACHINE, - ); + this.storageService.store(ExplorerView.TREE_VIEW_STATE_STORAGE_KEY, JSON.stringify(this.tree.getViewState()), StorageScope.WORKSPACE, StorageTarget.MACHINE); } private setContextKeys(stat: ExplorerItem | null | undefined): void { const folders = this.contextService.getWorkspace().folders; - const resource = stat ? stat.resource : folders[folders.length - 1].uri; stat = stat || this.explorerService.findClosest(resource); this.resourceContext.set(resource); @@ -949,65 +603,47 @@ export class ExplorerView extends ViewPane implements IExplorerView { this.rootContext.set(!!stat && stat.isRoot); if (resource) { - const overrides = resource - ? this.editorResolverService - .getEditors(resource) - .map((editor) => editor.id) - : []; - this.availableEditorIdsContext.set(overrides.join(",")); + const overrides = resource ? this.editorResolverService.getEditors(resource).map(editor => editor.id) : []; + this.availableEditorIdsContext.set(overrides.join(',')); } else { this.availableEditorIdsContext.reset(); } } - private async onContextMenu( - e: ITreeContextMenuEvent, - ): Promise { + private async onContextMenu(e: ITreeContextMenuEvent): Promise { if (DOM.isEditableElement(e.browserEvent.target as HTMLElement)) { return; } const stat = e.element; - let anchor = e.anchor; // Adjust for compressed folders (except when mouse is used) if (DOM.isHTMLElement(anchor)) { if (stat) { - const controllers = - this.renderer.getCompressedNavigationController(stat); + const controllers = this.renderer.getCompressedNavigationController(stat); if (controllers && controllers.length > 0) { - if ( - DOM.isKeyboardEvent(e.browserEvent) || - isCompressedFolderName(e.browserEvent.target) - ) { + if (DOM.isKeyboardEvent(e.browserEvent) || isCompressedFolderName(e.browserEvent.target)) { anchor = controllers[0].labels[controllers[0].index]; } else { - controllers.forEach((controller) => controller.last()); + controllers.forEach(controller => controller.last()); } } } } // update dynamic contexts - this.fileCopiedContextKey.set( - await this.clipboardService.hasResources(), - ); + this.fileCopiedContextKey.set(await this.clipboardService.hasResources()); this.setContextKeys(stat); const selection = this.tree.getSelection(); const roots = this.explorerService.roots; // If the click is outside of the elements pass the root resource if there is only one root. If there are multiple roots pass empty object. let arg: URI | {}; - if (stat instanceof ExplorerItem) { - const compressedControllers = - this.renderer.getCompressedNavigationController(stat); - arg = - compressedControllers && compressedControllers.length - ? compressedControllers[0].current.resource - : stat.resource; + const compressedControllers = this.renderer.getCompressedNavigationController(stat); + arg = compressedControllers && compressedControllers.length ? compressedControllers[0].current.resource : stat.resource; } else { arg = roots.length === 1 ? roots[0].resource : {}; } @@ -1022,12 +658,9 @@ export class ExplorerView extends ViewPane implements IExplorerView { this.tree.domFocus(); } }, - getActionsContext: () => - stat && selection && selection.indexOf(stat) >= 0 - ? selection.map((fs: ExplorerItem) => fs.resource) - : stat instanceof ExplorerItem - ? [stat.resource] - : [], + getActionsContext: () => stat && selection && selection.indexOf(stat) >= 0 + ? selection.map((fs: ExplorerItem) => fs.resource) + : stat instanceof ExplorerItem ? [stat.resource] : [] }); } @@ -1036,31 +669,22 @@ export class ExplorerView extends ViewPane implements IExplorerView { this.setContextKeys(stat); if (stat) { - const enableTrash = Boolean( - this.configurationService.getValue().files - ?.enableTrash, - ); - - const hasCapability = this.fileService.hasCapability( - stat.resource, - FileSystemProviderCapabilities.Trash, - ); + const enableTrash = Boolean(this.configurationService.getValue().files?.enableTrash); + const hasCapability = this.fileService.hasCapability(stat.resource, FileSystemProviderCapabilities.Trash); this.resourceMoveableToTrash.set(enableTrash && hasCapability); } else { this.resourceMoveableToTrash.reset(); } - const compressedNavigationControllers = - stat && this.renderer.getCompressedNavigationController(stat); + const compressedNavigationControllers = stat && this.renderer.getCompressedNavigationController(stat); if (!compressedNavigationControllers) { this.compressedFocusContext.set(false); - return; } this.compressedFocusContext.set(true); - compressedNavigationControllers.forEach((controller) => { + compressedNavigationControllers.forEach(controller => { this.updateCompressedNavigationContextKeys(controller); }); } @@ -1071,17 +695,8 @@ export class ExplorerView extends ViewPane implements IExplorerView { * Refresh the contents of the explorer to get up to date data from the disk about the file structure. * If the item is passed we refresh only that level of the tree, otherwise we do a full refresh. */ - refresh( - recursive: boolean, - item?: ExplorerItem, - cancelEditing: boolean = true, - ): Promise { - if ( - !this.tree || - !this.isBodyVisible() || - (item && !this.tree.hasNode(item)) || - (this.findProvider?.isShowingFilterResults() && recursive) - ) { + refresh(recursive: boolean, item?: ExplorerItem, cancelEditing: boolean = true): Promise { + if (!this.tree || !this.isBodyVisible() || (item && !this.tree.hasNode(item)) || (this.findProvider?.isShowingFilterResults() && recursive)) { // Tree node doesn't exist yet, when it becomes visible we will refresh return Promise.resolve(undefined); } @@ -1091,16 +706,12 @@ export class ExplorerView extends ViewPane implements IExplorerView { } const toRefresh = item || this.tree.getInput(); - return this.tree.updateChildren(toRefresh, recursive, !!item); } override getOptimalWidth(): number { const parentNode = this.tree.getHTMLElement(); - - const childNodes = ([] as HTMLElement[]).slice.call( - parentNode.querySelectorAll(".explorer-item .label-name"), - ); // select all file labels + const childNodes = ([] as HTMLElement[]).slice.call(parentNode.querySelectorAll('.explorer-item .label-name')); // select all file labels return DOM.getLargestChildWidth(parentNode, childNodes); } @@ -1116,112 +727,74 @@ export class ExplorerView extends ViewPane implements IExplorerView { } const initialInputSetup = !this.tree.getInput(); - if (initialInputSetup) { - perf.mark("code/willResolveExplorer"); + perf.mark('code/willResolveExplorer'); } const roots = this.explorerService.roots; - let input: ExplorerItem | ExplorerItem[] = roots[0]; - - if ( - this.contextService.getWorkbenchState() !== WorkbenchState.FOLDER || - roots[0].error - ) { + if (this.contextService.getWorkbenchState() !== WorkbenchState.FOLDER || roots[0].error) { // Display roots only when multi folder workspace input = roots; } let viewState: IAsyncDataTreeViewState | undefined; - if (this.tree && this.tree.getInput()) { viewState = this.tree.getViewState(); } else { - const rawViewState = this.storageService.get( - ExplorerView.TREE_VIEW_STATE_STORAGE_KEY, - StorageScope.WORKSPACE, - ); - + const rawViewState = this.storageService.get(ExplorerView.TREE_VIEW_STATE_STORAGE_KEY, StorageScope.WORKSPACE); if (rawViewState) { viewState = JSON.parse(rawViewState); } } const previousInput = this.tree.getInput(); + const promise = this.setTreeInputPromise = this.tree.setInput(input, viewState).then(async () => { + if (Array.isArray(input)) { + if (!viewState || previousInput instanceof ExplorerItem) { + // There is no view state for this workspace (we transitioned from a folder workspace?), expand up to five roots. + // If there are many roots in a workspace, expanding them all would can cause performance issues #176226 + for (let i = 0; i < Math.min(input.length, 5); i++) { + try { + await this.tree.expand(input[i]); + } catch (e) { } + } + } + // Reloaded or transitioned from an empty workspace, but only have a single folder in the workspace. + if (!previousInput && input.length === 1 && this.configurationService.getValue().explorer.expandSingleFolderWorkspaces) { + await this.tree.expand(input[0]).catch(() => { }); + } + if (Array.isArray(previousInput)) { + const previousRoots = new ResourceMap(); + previousInput.forEach(previousRoot => previousRoots.set(previousRoot.resource, true)); - const promise = (this.setTreeInputPromise = this.tree - .setInput(input, viewState) - .then(async () => { - if (Array.isArray(input)) { - if (!viewState || previousInput instanceof ExplorerItem) { - // There is no view state for this workspace (we transitioned from a folder workspace?), expand up to five roots. - // If there are many roots in a workspace, expanding them all would can cause performance issues #176226 - for (let i = 0; i < Math.min(input.length, 5); i++) { + // Roots added to the explorer -> expand them. + await Promise.all(input.map(async item => { + if (!previousRoots.has(item.resource)) { try { - await this.tree.expand(input[i]); - } catch (e) {} + await this.tree.expand(item); + } catch (e) { } } - } - // Reloaded or transitioned from an empty workspace, but only have a single folder in the workspace. - if ( - !previousInput && - input.length === 1 && - this.configurationService.getValue() - .explorer.expandSingleFolderWorkspaces - ) { - await this.tree.expand(input[0]).catch(() => {}); - } - if (Array.isArray(previousInput)) { - const previousRoots = new ResourceMap(); - previousInput.forEach((previousRoot) => - previousRoots.set(previousRoot.resource, true), - ); - - // Roots added to the explorer -> expand them. - await Promise.all( - input.map(async (item) => { - if (!previousRoots.has(item.resource)) { - try { - await this.tree.expand(item); - } catch (e) {} - } - }), - ); - } + })); } - if (initialInputSetup) { - perf.mark("code/didResolveExplorer"); - } - })); + } + if (initialInputSetup) { + perf.mark('code/didResolveExplorer'); + } + }); - this.progressService.withProgress( - { - location: ProgressLocation.Explorer, - delay: this.layoutService.isRestored() ? 800 : 1500, // reduce progress visibility when still restoring - }, - (_progress) => promise, - ); + this.progressService.withProgress({ + location: ProgressLocation.Explorer, + delay: this.layoutService.isRestored() ? 800 : 1500 // reduce progress visibility when still restoring + }, _progress => promise); await promise; - if (!this.decorationsProvider) { - this.decorationsProvider = new ExplorerDecorationsProvider( - this.explorerService, - this.contextService, - ); - this._register( - this.decorationService.registerDecorationsProvider( - this.decorationsProvider, - ), - ); + this.decorationsProvider = new ExplorerDecorationsProvider(this.explorerService, this.contextService); + this._register(this.decorationService.registerDecorationsProvider(this.decorationsProvider)); } } - public async selectResource( - resource: URI | undefined, - reveal = this._autoReveal, - retry = 0, - ): Promise { + public async selectResource(resource: URI | undefined, reveal = this._autoReveal, retry = 0): Promise { // do no retry more than once to prevent infinite loops in cases of inconsistent model if (retry === 2) { return; @@ -1237,8 +810,7 @@ export class ExplorerView extends ViewPane implements IExplorerView { } // Expand all stats in the parent chain. - let item: ExplorerItem | null = - this.explorerService.findClosestRoot(resource); + let item: ExplorerItem | null = this.explorerService.findClosestRoot(resource); while (item && item.resource.toString() !== resource.toString()) { try { @@ -1250,14 +822,8 @@ export class ExplorerView extends ViewPane implements IExplorerView { item = null; } else { for (const child of item.children.values()) { - if ( - this.uriIdentityService.extUri.isEqualOrParent( - resource, - child.resource, - ) - ) { + if (this.uriIdentityService.extUri.isEqualOrParent(resource, child.resource)) { item = child; - break; } item = null; @@ -1269,7 +835,6 @@ export class ExplorerView extends ViewPane implements IExplorerView { if (item === this.tree.getInput()) { this.tree.setFocus([]); this.tree.setSelection([]); - return; } @@ -1279,10 +844,7 @@ export class ExplorerView extends ViewPane implements IExplorerView { await this.tree.expand(item.nestedParent); } - if ( - (reveal === true || reveal === "force") && - this.tree.getRelativeTop(item) === null - ) { + if ((reveal === true || reveal === 'force') && this.tree.getRelativeTop(item) === null) { // Don't scroll to the item if it's already visible, or if set not to. this.tree.reveal(item, 0.5); } @@ -1296,17 +858,12 @@ export class ExplorerView extends ViewPane implements IExplorerView { } } - itemsCopied( - stats: ExplorerItem[], - cut: boolean, - previousCut: ExplorerItem[] | undefined, - ): void { + itemsCopied(stats: ExplorerItem[], cut: boolean, previousCut: ExplorerItem[] | undefined): void { this.fileCopiedContextKey.set(stats.length > 0); this.resourceCutContextKey.set(cut && stats.length > 0); - previousCut?.forEach((item) => this.tree.rerender(item)); - + previousCut?.forEach(item => this.tree.rerender(item)); if (cut) { - stats.forEach((s) => this.tree.rerender(s)); + stats.forEach(s => this.tree.rerender(s)); } } @@ -1324,15 +881,10 @@ export class ExplorerView extends ViewPane implements IExplorerView { } const treeInput = this.tree.getInput(); - if (Array.isArray(treeInput)) { if (hasExpandedRootChild(this.tree, treeInput)) { - treeInput.forEach((folder) => { - folder.children.forEach( - (child) => - this.tree.hasNode(child) && - this.tree.collapse(child, true), - ); + treeInput.forEach(folder => { + folder.children.forEach(child => this.tree.hasNode(child) && this.tree.collapse(child, true)); }); return; @@ -1344,14 +896,12 @@ export class ExplorerView extends ViewPane implements IExplorerView { previousCompressedStat(): void { const focused = this.tree.getFocus(); - if (!focused.length) { return; } - const compressedNavigationControllers = - this.renderer.getCompressedNavigationController(focused[0])!; - compressedNavigationControllers.forEach((controller) => { + const compressedNavigationControllers = this.renderer.getCompressedNavigationController(focused[0])!; + compressedNavigationControllers.forEach(controller => { controller.previous(); this.updateCompressedNavigationContextKeys(controller); }); @@ -1359,14 +909,12 @@ export class ExplorerView extends ViewPane implements IExplorerView { nextCompressedStat(): void { const focused = this.tree.getFocus(); - if (!focused.length) { return; } - const compressedNavigationControllers = - this.renderer.getCompressedNavigationController(focused[0])!; - compressedNavigationControllers.forEach((controller) => { + const compressedNavigationControllers = this.renderer.getCompressedNavigationController(focused[0])!; + compressedNavigationControllers.forEach(controller => { controller.next(); this.updateCompressedNavigationContextKeys(controller); }); @@ -1374,14 +922,12 @@ export class ExplorerView extends ViewPane implements IExplorerView { firstCompressedStat(): void { const focused = this.tree.getFocus(); - if (!focused.length) { return; } - const compressedNavigationControllers = - this.renderer.getCompressedNavigationController(focused[0])!; - compressedNavigationControllers.forEach((controller) => { + const compressedNavigationControllers = this.renderer.getCompressedNavigationController(focused[0])!; + compressedNavigationControllers.forEach(controller => { controller.first(); this.updateCompressedNavigationContextKeys(controller); }); @@ -1389,41 +935,30 @@ export class ExplorerView extends ViewPane implements IExplorerView { lastCompressedStat(): void { const focused = this.tree.getFocus(); - if (!focused.length) { return; } - const compressedNavigationControllers = - this.renderer.getCompressedNavigationController(focused[0])!; - compressedNavigationControllers.forEach((controller) => { + const compressedNavigationControllers = this.renderer.getCompressedNavigationController(focused[0])!; + compressedNavigationControllers.forEach(controller => { controller.last(); this.updateCompressedNavigationContextKeys(controller); }); } - private updateCompressedNavigationContextKeys( - controller: ICompressedNavigationController, - ): void { + private updateCompressedNavigationContextKeys(controller: ICompressedNavigationController): void { this.compressedFocusFirstContext.set(controller.index === 0); - this.compressedFocusLastContext.set( - controller.index === controller.count - 1, - ); + this.compressedFocusLastContext.set(controller.index === controller.count - 1); } private updateAnyCollapsedContext(): void { const treeInput = this.tree.getInput(); - if (treeInput === undefined) { return; } - const treeInputArray = Array.isArray(treeInput) - ? treeInput - : Array.from(treeInput.children.values()); + const treeInputArray = Array.isArray(treeInput) ? treeInput : Array.from(treeInput.children.values()); // Has collapsible root when anything is expanded - this.viewHasSomeCollapsibleRootItem.set( - hasExpandedNode(this.tree, treeInputArray), - ); + this.viewHasSomeCollapsibleRootItem.set(hasExpandedNode(this.tree, treeInputArray)); // synchronize state to cache this.storeTreeViewState(); } @@ -1434,166 +969,129 @@ export class ExplorerView extends ViewPane implements IExplorerView { override dispose(): void { this.dragHandler?.dispose(); - super.dispose(); } } -export function createFileIconThemableTreeContainerScope( - container: HTMLElement, - themeService: IThemeService, -): IDisposable { - container.classList.add("file-icon-themable-tree"); - container.classList.add("show-file-icons"); +export function createFileIconThemableTreeContainerScope(container: HTMLElement, themeService: IThemeService): IDisposable { + container.classList.add('file-icon-themable-tree'); + container.classList.add('show-file-icons'); const onDidChangeFileIconTheme = (theme: IFileIconTheme) => { - container.classList.toggle( - "align-icons-and-twisties", - theme.hasFileIcons && !theme.hasFolderIcons, - ); - container.classList.toggle( - "hide-arrows", - theme.hidesExplorerArrows === true, - ); + container.classList.toggle('align-icons-and-twisties', theme.hasFileIcons && !theme.hasFolderIcons); + container.classList.toggle('hide-arrows', theme.hidesExplorerArrows === true); }; onDidChangeFileIconTheme(themeService.getFileIconTheme()); - return themeService.onDidFileIconThemeChange(onDidChangeFileIconTheme); } const CanCreateContext = ContextKeyExpr.or( // Folder: can create unless readonly - ContextKeyExpr.and( - ExplorerFolderContext, - ExplorerResourceNotReadonlyContext, - ), + ContextKeyExpr.and(ExplorerFolderContext, ExplorerResourceNotReadonlyContext), // File: can create unless parent is readonly - ContextKeyExpr.and( - ExplorerFolderContext.toNegated(), - ExplorerResourceParentReadOnlyContext.toNegated(), - ), -); - -registerAction2( - class extends Action2 { - constructor() { - super({ - id: "workbench.files.action.createFileFromExplorer", - title: nls.localize("createNewFile", "New File..."), - f1: false, - icon: Codicon.newFile, - precondition: CanCreateContext, - menu: { - id: MenuId.ViewTitle, - group: "navigation", - when: ContextKeyExpr.equals("view", VIEW_ID), - order: 10, - }, - }); - } - - run(accessor: ServicesAccessor): void { - const commandService = accessor.get(ICommandService); - commandService.executeCommand(NEW_FILE_COMMAND_ID); - } - }, -); - -registerAction2( - class extends Action2 { - constructor() { - super({ - id: "workbench.files.action.createFolderFromExplorer", - title: nls.localize("createNewFolder", "New Folder..."), - f1: false, - icon: Codicon.newFolder, - precondition: CanCreateContext, - menu: { - id: MenuId.ViewTitle, - group: "navigation", - when: ContextKeyExpr.equals("view", VIEW_ID), - order: 20, - }, - }); - } - - run(accessor: ServicesAccessor): void { - const commandService = accessor.get(ICommandService); - commandService.executeCommand(NEW_FOLDER_COMMAND_ID); - } - }, + ContextKeyExpr.and(ExplorerFolderContext.toNegated(), ExplorerResourceParentReadOnlyContext.toNegated()) ); -registerAction2( - class extends Action2 { - constructor() { - super({ - id: "workbench.files.action.refreshFilesExplorer", - title: nls.localize2("refreshExplorer", "Refresh Explorer"), - f1: true, - icon: Codicon.refresh, - menu: { - id: MenuId.ViewTitle, - group: "navigation", - when: ContextKeyExpr.equals("view", VIEW_ID), - order: 30, - }, - metadata: { - description: nls.localize2( - "refreshExplorerMetadata", - "Forces a refresh of the Explorer.", - ), - }, - precondition: ExplorerFindProviderActive.negate(), - }); - } - - async run(accessor: ServicesAccessor): Promise { - const viewsService = accessor.get(IViewsService); - - const explorerService = accessor.get(IExplorerService); - await viewsService.openView(VIEW_ID); - await explorerService.refresh(); - } - }, -); - -registerAction2( - class extends Action2 { - constructor() { - super({ - id: "workbench.files.action.collapseExplorerFolders", - title: nls.localize2( - "collapseExplorerFolders", - "Collapse Folders in Explorer", - ), - f1: true, - icon: Codicon.collapseAll, - menu: { - id: MenuId.ViewTitle, - group: "navigation", - when: ContextKeyExpr.equals("view", VIEW_ID), - order: 40, - }, - metadata: { - description: nls.localize2( - "collapseExplorerFoldersMetadata", - "Folds all folders in the Explorer.", - ), - }, - }); - } +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.files.action.createFileFromExplorer', + title: nls.localize('createNewFile', "New File..."), + f1: false, + icon: Codicon.newFile, + precondition: CanCreateContext, + menu: { + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyExpr.equals('view', VIEW_ID), + order: 10 + } + }); + } - run(accessor: ServicesAccessor) { - const viewsService = accessor.get(IViewsService); + run(accessor: ServicesAccessor): void { + const commandService = accessor.get(ICommandService); + commandService.executeCommand(NEW_FILE_COMMAND_ID); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.files.action.createFolderFromExplorer', + title: nls.localize('createNewFolder', "New Folder..."), + f1: false, + icon: Codicon.newFolder, + precondition: CanCreateContext, + menu: { + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyExpr.equals('view', VIEW_ID), + order: 20 + } + }); + } - const view = viewsService.getViewWithId(VIEW_ID); + run(accessor: ServicesAccessor): void { + const commandService = accessor.get(ICommandService); + commandService.executeCommand(NEW_FOLDER_COMMAND_ID); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.files.action.refreshFilesExplorer', + title: nls.localize2('refreshExplorer', "Refresh Explorer"), + f1: true, + icon: Codicon.refresh, + menu: { + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyExpr.equals('view', VIEW_ID), + order: 30, + }, + metadata: { + description: nls.localize2('refreshExplorerMetadata', "Forces a refresh of the Explorer.") + }, + precondition: ExplorerFindProviderActive.negate() + }); + } - if (view !== null) { - const explorerView = view as ExplorerView; - explorerView.collapseAll(); + async run(accessor: ServicesAccessor): Promise { + const viewsService = accessor.get(IViewsService); + const explorerService = accessor.get(IExplorerService); + await viewsService.openView(VIEW_ID); + await explorerService.refresh(); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.files.action.collapseExplorerFolders', + title: nls.localize2('collapseExplorerFolders', "Collapse Folders in Explorer"), + f1: true, + icon: Codicon.collapseAll, + menu: { + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyExpr.equals('view', VIEW_ID), + order: 40 + }, + metadata: { + description: nls.localize2('collapseExplorerFoldersMetadata', "Folds all folders in the Explorer.") } + }); + } + + run(accessor: ServicesAccessor) { + const viewsService = accessor.get(IViewsService); + const view = viewsService.getViewWithId(VIEW_ID); + if (view !== null) { + const explorerView = view as ExplorerView; + explorerView.collapseAll(); } - }, -); + } +}); diff --git a/Source/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/Source/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 517c52573e155..11e8513cee70d 100644 --- a/Source/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/Source/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -3,146 +3,106 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Codicon } from "../../../../base/common/codicons.js"; -import { KeyChord, KeyCode, KeyMod } from "../../../../base/common/keyCodes.js"; -import { - ICodeEditor, - isCodeEditor, - isDiffEditor, -} from "../../../../editor/browser/editorBrowser.js"; -import { EditorAction2 } from "../../../../editor/browser/editorExtensions.js"; -import { ICodeEditorService } from "../../../../editor/browser/services/codeEditorService.js"; -import { EmbeddedCodeEditorWidget } from "../../../../editor/browser/widget/codeEditor/embeddedCodeEditorWidget.js"; -import { EmbeddedDiffEditorWidget } from "../../../../editor/browser/widget/diffEditor/embeddedDiffEditorWidget.js"; -import { EditorContextKeys } from "../../../../editor/common/editorContextKeys.js"; -import { localize, localize2 } from "../../../../nls.js"; -import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from "../../../../platform/accessibility/common/accessibility.js"; -import { - Action2, - IAction2Options, - MenuId, -} from "../../../../platform/actions/common/actions.js"; -import { CommandsRegistry } from "../../../../platform/commands/common/commands.js"; -import { ContextKeyExpr } from "../../../../platform/contextkey/common/contextkey.js"; -import { - IInstantiationService, - ServicesAccessor, -} from "../../../../platform/instantiation/common/instantiation.js"; -import { KeybindingWeight } from "../../../../platform/keybinding/common/keybindingsRegistry.js"; -import { ILogService } from "../../../../platform/log/common/log.js"; -import { registerIcon } from "../../../../platform/theme/common/iconRegistry.js"; -import { IEditorService } from "../../../services/editor/common/editorService.js"; -import { IPreferencesService } from "../../../services/preferences/common/preferences.js"; -import { IChatWidgetService } from "../../chat/browser/chat.js"; -import { ChatContextKeys } from "../../chat/common/chatContextKeys.js"; -import { IChatService } from "../../chat/common/chatService.js"; -import { - ACTION_ACCEPT_CHANGES, - ACTION_DISCARD_CHANGES, - ACTION_REGENERATE_RESPONSE, - ACTION_START, - ACTION_TOGGLE_DIFF, - ACTION_VIEW_IN_CHAT, - CTX_INLINE_CHAT_CHANGE_HAS_DIFF, - CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, - CTX_INLINE_CHAT_DOCUMENT_CHANGED, - CTX_INLINE_CHAT_EDIT_MODE, - CTX_INLINE_CHAT_FOCUSED, - CTX_INLINE_CHAT_HAS_AGENT, - CTX_INLINE_CHAT_HAS_STASHED_SESSION, - CTX_INLINE_CHAT_INNER_CURSOR_FIRST, - CTX_INLINE_CHAT_INNER_CURSOR_LAST, - CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, - CTX_INLINE_CHAT_POSSIBLE, - CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, - CTX_INLINE_CHAT_RESPONSE_TYPE, - CTX_INLINE_CHAT_VISIBLE, - EditMode, - InlineChatResponseType, - MENU_INLINE_CHAT_WIDGET_STATUS, - MENU_INLINE_CHAT_ZONE, -} from "../common/inlineChat.js"; -import { - InlineChatController, - InlineChatRunOptions, -} from "./inlineChatController.js"; -import { HunkInformation } from "./inlineChatSession.js"; - -CommandsRegistry.registerCommandAlias( - "interactiveEditor.start", - "inlineChat.start", -); -CommandsRegistry.registerCommandAlias( - "interactive.acceptChanges", - ACTION_ACCEPT_CHANGES, -); - -export const START_INLINE_CHAT = registerIcon( - "start-inline-chat", - Codicon.sparkle, - localize( - "startInlineChat", - "Icon which spawns the inline chat from the editor toolbar.", - ), -); +import { Codicon } from '../../../../base/common/codicons.js'; +import { KeyChord, KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; +import { ICodeEditor, isCodeEditor, isDiffEditor } from '../../../../editor/browser/editorBrowser.js'; +import { EditorAction2 } from '../../../../editor/browser/editorExtensions.js'; +import { EmbeddedDiffEditorWidget } from '../../../../editor/browser/widget/diffEditor/embeddedDiffEditorWidget.js'; +import { EmbeddedCodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/embeddedCodeEditorWidget.js'; +import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; +import { InlineChatController, InlineChatRunOptions } from './inlineChatController.js'; +import { ACTION_ACCEPT_CHANGES, CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_EDIT_MODE, EditMode, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, InlineChatResponseType, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, ACTION_TOGGLE_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, MENU_INLINE_CHAT_ZONE, ACTION_DISCARD_CHANGES, CTX_INLINE_CHAT_POSSIBLE, ACTION_START } from '../common/inlineChat.js'; +import { localize, localize2 } from '../../../../nls.js'; +import { Action2, IAction2Options, MenuId } from '../../../../platform/actions/common/actions.js'; +import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { IEditorService } from '../../../services/editor/common/editorService.js'; +import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; +import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from '../../../../platform/accessibility/common/accessibility.js'; +import { CommandsRegistry } from '../../../../platform/commands/common/commands.js'; +import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js'; +import { IPreferencesService } from '../../../services/preferences/common/preferences.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; +import { IChatService } from '../../chat/common/chatService.js'; +import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; +import { HunkInformation } from './inlineChatSession.js'; +import { IChatWidgetService } from '../../chat/browser/chat.js'; + +CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start'); +CommandsRegistry.registerCommandAlias('interactive.acceptChanges', ACTION_ACCEPT_CHANGES); + + +export const START_INLINE_CHAT = registerIcon('start-inline-chat', Codicon.sparkle, localize('startInlineChat', 'Icon which spawns the inline chat from the editor toolbar.')); // some gymnastics to enable hold for speech without moving the StartSessionAction into the electron-layer export interface IHoldForSpeech { - ( - accessor: ServicesAccessor, - controller: InlineChatController, - source: Action2, - ): void; + (accessor: ServicesAccessor, controller: InlineChatController, source: Action2): void; } let _holdForSpeech: IHoldForSpeech | undefined = undefined; export function setHoldForSpeech(holdForSpeech: IHoldForSpeech) { _holdForSpeech = holdForSpeech; } -export class StartSessionAction extends EditorAction2 { +export class StartSessionAction extends Action2 { + constructor() { super({ id: ACTION_START, - title: localize2("run", "Editor Inline Chat"), + title: localize2('run', 'Editor Inline Chat'), category: AbstractInlineChatAction.category, f1: true, precondition: ContextKeyExpr.and( CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_POSSIBLE, EditorContextKeys.writable, + EditorContextKeys.editorSimpleInput.negate() ), keybinding: { when: EditorContextKeys.focus, weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.KeyI, - secondary: [ - KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyI), - ], + secondary: [KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyI)], }, icon: START_INLINE_CHAT, menu: { id: MenuId.ChatCommandCenter, - group: "d_inlineChat", + group: 'd_inlineChat', order: 10, - }, + } + }); + } + override run(accessor: ServicesAccessor, ...args: any[]): void { + const codeEditorService = accessor.get(ICodeEditorService); + const editor = codeEditorService.getActiveCodeEditor(); + if (!editor || editor.isSimpleWidget) { + // well, at least we tried... + return; + } + + // precondition does hold + return editor.invokeWithinContext((editorAccessor) => { + const kbService = editorAccessor.get(IContextKeyService); + const logService = editorAccessor.get(ILogService); + const enabled = kbService.contextMatchesRules(this.desc.precondition ?? undefined); + if (!enabled) { + logService.debug(`[EditorAction2] NOT running command because its precondition is FALSE`, this.desc.id, this.desc.precondition?.serialize()); + return; + } + return this._runEditorCommand(editorAccessor, editor, ...args); }); } - override runEditorCommand( - accessor: ServicesAccessor, - editor: ICodeEditor, - ..._args: any[] - ) { + private _runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { + const ctrl = InlineChatController.get(editor); if (!ctrl) { return; } if (_holdForSpeech) { - accessor - .get(IInstantiationService) - .invokeFunction(_holdForSpeech, ctrl, this); + accessor.get(IInstantiationService).invokeFunction(_holdForSpeech, ctrl, this); } let options: InlineChatRunOptions | undefined; @@ -157,32 +117,25 @@ export class StartSessionAction extends EditorAction2 { export class UnstashSessionAction extends EditorAction2 { constructor() { super({ - id: "inlineChat.unstash", - title: localize2("unstash", "Resume Last Dismissed Inline Chat"), + id: 'inlineChat.unstash', + title: localize2('unstash', "Resume Last Dismissed Inline Chat"), category: AbstractInlineChatAction.category, - precondition: ContextKeyExpr.and( - CTX_INLINE_CHAT_HAS_STASHED_SESSION, - EditorContextKeys.writable, - ), + precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_STASHED_SESSION, EditorContextKeys.writable), keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.KeyZ, - }, + } }); } - override async runEditorCommand( - _accessor: ServicesAccessor, - editor: ICodeEditor, - ..._args: any[] - ) { + override async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { const ctrl = InlineChatController.get(editor); if (ctrl) { const session = ctrl.unstashLastSession(); if (session) { ctrl.run({ existingSession: session, - isUnstashed: true, + isUnstashed: true }); } } @@ -190,24 +143,18 @@ export class UnstashSessionAction extends EditorAction2 { } export abstract class AbstractInlineChatAction extends EditorAction2 { - static readonly category = localize2("cat", "Inline Chat"); + + static readonly category = localize2('cat', "Inline Chat"); constructor(desc: IAction2Options) { super({ ...desc, category: AbstractInlineChatAction.category, - precondition: ContextKeyExpr.and( - CTX_INLINE_CHAT_HAS_AGENT, - desc.precondition, - ), + precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_AGENT, desc.precondition) }); } - override runEditorCommand( - accessor: ServicesAccessor, - editor: ICodeEditor, - ..._args: any[] - ) { + override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { const editorService = accessor.get(IEditorService); const logService = accessor.get(ILogService); @@ -223,11 +170,7 @@ export abstract class AbstractInlineChatAction extends EditorAction2 { } if (!ctrl) { - logService.warn( - "[IE] NO controller found for action", - this.desc.id, - editor.getModel()?.uri, - ); + logService.warn('[IE] NO controller found for action', this.desc.id, editor.getModel()?.uri); return; } @@ -235,19 +178,10 @@ export abstract class AbstractInlineChatAction extends EditorAction2 { editor = editor.getParentEditor(); } if (!ctrl) { - for (const diffEditor of accessor - .get(ICodeEditorService) - .listDiffEditors()) { - if ( - diffEditor.getOriginalEditor() === editor || - diffEditor.getModifiedEditor() === editor - ) { + for (const diffEditor of accessor.get(ICodeEditorService).listDiffEditors()) { + if (diffEditor.getOriginalEditor() === editor || diffEditor.getModifiedEditor() === editor) { if (diffEditor instanceof EmbeddedDiffEditorWidget) { - this.runEditorCommand( - accessor, - diffEditor.getParentEditor(), - ..._args, - ); + this.runEditorCommand(accessor, diffEditor.getParentEditor(), ..._args); } } } @@ -256,38 +190,23 @@ export abstract class AbstractInlineChatAction extends EditorAction2 { this.runInlineChatCommand(accessor, ctrl, editor, ..._args); } - abstract runInlineChatCommand( - accessor: ServicesAccessor, - ctrl: InlineChatController, - editor: ICodeEditor, - ...args: any[] - ): void; + abstract runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController, editor: ICodeEditor, ...args: any[]): void; } export class ArrowOutUpAction extends AbstractInlineChatAction { constructor() { super({ - id: "inlineChat.arrowOutUp", - title: localize("arrowUp", "Cursor Up"), - precondition: ContextKeyExpr.and( - CTX_INLINE_CHAT_FOCUSED, - CTX_INLINE_CHAT_INNER_CURSOR_FIRST, - EditorContextKeys.isEmbeddedDiffEditor.negate(), - CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate(), - ), + id: 'inlineChat.arrowOutUp', + title: localize('arrowUp', 'Cursor Up'), + precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, EditorContextKeys.isEmbeddedDiffEditor.negate(), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), keybinding: { weight: KeybindingWeight.EditorCore, - primary: KeyMod.CtrlCmd | KeyCode.UpArrow, - }, + primary: KeyMod.CtrlCmd | KeyCode.UpArrow + } }); } - runInlineChatCommand( - _accessor: ServicesAccessor, - ctrl: InlineChatController, - _editor: ICodeEditor, - ..._args: any[] - ): void { + runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): void { ctrl.arrowOut(true); } } @@ -295,174 +214,116 @@ export class ArrowOutUpAction extends AbstractInlineChatAction { export class ArrowOutDownAction extends AbstractInlineChatAction { constructor() { super({ - id: "inlineChat.arrowOutDown", - title: localize("arrowDown", "Cursor Down"), - precondition: ContextKeyExpr.and( - CTX_INLINE_CHAT_FOCUSED, - CTX_INLINE_CHAT_INNER_CURSOR_LAST, - EditorContextKeys.isEmbeddedDiffEditor.negate(), - CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate(), - ), + id: 'inlineChat.arrowOutDown', + title: localize('arrowDown', 'Cursor Down'), + precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_LAST, EditorContextKeys.isEmbeddedDiffEditor.negate(), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), keybinding: { weight: KeybindingWeight.EditorCore, - primary: KeyMod.CtrlCmd | KeyCode.DownArrow, - }, + primary: KeyMod.CtrlCmd | KeyCode.DownArrow + } }); } - runInlineChatCommand( - _accessor: ServicesAccessor, - ctrl: InlineChatController, - _editor: ICodeEditor, - ..._args: any[] - ): void { + runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): void { ctrl.arrowOut(false); } } export class FocusInlineChat extends EditorAction2 { + constructor() { super({ - id: "inlineChat.focus", - title: localize2("focus", "Focus Input"), + id: 'inlineChat.focus', + title: localize2('focus', "Focus Input"), f1: true, category: AbstractInlineChatAction.category, - precondition: ContextKeyExpr.and( - EditorContextKeys.editorTextFocus, - CTX_INLINE_CHAT_VISIBLE, - CTX_INLINE_CHAT_FOCUSED.negate(), - CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate(), - ), - keybinding: [ - { - weight: KeybindingWeight.EditorCore + 10, // win against core_command - when: ContextKeyExpr.and( - CTX_INLINE_CHAT_OUTER_CURSOR_POSITION.isEqualTo( - "above", - ), - EditorContextKeys.isEmbeddedDiffEditor.negate(), - ), - primary: KeyMod.CtrlCmd | KeyCode.DownArrow, - }, - { - weight: KeybindingWeight.EditorCore + 10, // win against core_command - when: ContextKeyExpr.and( - CTX_INLINE_CHAT_OUTER_CURSOR_POSITION.isEqualTo( - "below", - ), - EditorContextKeys.isEmbeddedDiffEditor.negate(), - ), - primary: KeyMod.CtrlCmd | KeyCode.UpArrow, - }, - ], + precondition: ContextKeyExpr.and(EditorContextKeys.editorTextFocus, CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_FOCUSED.negate(), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), + keybinding: [{ + weight: KeybindingWeight.EditorCore + 10, // win against core_command + when: ContextKeyExpr.and(CTX_INLINE_CHAT_OUTER_CURSOR_POSITION.isEqualTo('above'), EditorContextKeys.isEmbeddedDiffEditor.negate()), + primary: KeyMod.CtrlCmd | KeyCode.DownArrow, + }, { + weight: KeybindingWeight.EditorCore + 10, // win against core_command + when: ContextKeyExpr.and(CTX_INLINE_CHAT_OUTER_CURSOR_POSITION.isEqualTo('below'), EditorContextKeys.isEmbeddedDiffEditor.negate()), + primary: KeyMod.CtrlCmd | KeyCode.UpArrow, + }] }); } - override runEditorCommand( - _accessor: ServicesAccessor, - editor: ICodeEditor, - ..._args: any[] - ) { + override runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { InlineChatController.get(editor)?.focus(); } } + export class AcceptChanges extends AbstractInlineChatAction { + constructor() { super({ id: ACTION_ACCEPT_CHANGES, - title: localize2("apply1", "Accept Changes"), - shortTitle: localize("apply2", "Accept"), + title: localize2('apply1', "Accept Changes"), + shortTitle: localize('apply2', 'Accept'), icon: Codicon.check, f1: true, - precondition: ContextKeyExpr.and( - CTX_INLINE_CHAT_VISIBLE, - ContextKeyExpr.or( - CTX_INLINE_CHAT_DOCUMENT_CHANGED.toNegated(), - CTX_INLINE_CHAT_EDIT_MODE.notEqualsTo(EditMode.Preview), + precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE, ContextKeyExpr.or(CTX_INLINE_CHAT_DOCUMENT_CHANGED.toNegated(), CTX_INLINE_CHAT_EDIT_MODE.notEqualsTo(EditMode.Preview))), + keybinding: [{ + weight: KeybindingWeight.WorkbenchContrib + 10, + primary: KeyMod.CtrlCmd | KeyCode.Enter, + }], + menu: [{ + id: MENU_INLINE_CHAT_WIDGET_STATUS, + group: '0_main', + order: 1, + when: ContextKeyExpr.and( + ChatContextKeys.inputHasText.toNegated(), + CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.toNegated(), + CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.MessagesAndEdits) ), - ), - keybinding: [ - { - weight: KeybindingWeight.WorkbenchContrib + 10, - primary: KeyMod.CtrlCmd | KeyCode.Enter, - }, - ], - menu: [ - { - id: MENU_INLINE_CHAT_WIDGET_STATUS, - group: "0_main", - order: 1, - when: ContextKeyExpr.and( - ChatContextKeys.inputHasText.toNegated(), - CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.toNegated(), - CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo( - InlineChatResponseType.MessagesAndEdits, - ), - ), - }, - { - id: MENU_INLINE_CHAT_ZONE, - group: "navigation", - order: 1, - }, - ], + }, { + id: MENU_INLINE_CHAT_ZONE, + group: 'navigation', + order: 1, + }] }); } - override async runInlineChatCommand( - _accessor: ServicesAccessor, - ctrl: InlineChatController, - _editor: ICodeEditor, - hunk?: HunkInformation | any, - ): Promise { + override async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, hunk?: HunkInformation | any): Promise { ctrl.acceptHunk(hunk); } } export class DiscardHunkAction extends AbstractInlineChatAction { + constructor() { super({ id: ACTION_DISCARD_CHANGES, - title: localize("discard", "Discard"), + title: localize('discard', 'Discard'), icon: Codicon.chromeClose, precondition: CTX_INLINE_CHAT_VISIBLE, - menu: [ - { - id: MENU_INLINE_CHAT_WIDGET_STATUS, - group: "0_main", - order: 2, - when: ContextKeyExpr.and( - ChatContextKeys.inputHasText.toNegated(), - CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.negate(), - CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo( - InlineChatResponseType.MessagesAndEdits, - ), - CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Live), - ), - }, - { - id: MENU_INLINE_CHAT_ZONE, - group: "navigation", - order: 2, - }, - ], + menu: [{ + id: MENU_INLINE_CHAT_WIDGET_STATUS, + group: '0_main', + order: 2, + when: ContextKeyExpr.and( + ChatContextKeys.inputHasText.toNegated(), + CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.negate(), + CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.MessagesAndEdits), + CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Live) + ), + }, { + id: MENU_INLINE_CHAT_ZONE, + group: 'navigation', + order: 2 + }], keybinding: { weight: KeybindingWeight.EditorContrib, primary: KeyCode.Escape, - when: CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo( - InlineChatResponseType.MessagesAndEdits, - ), - }, + when: CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.MessagesAndEdits) + } }); } - async runInlineChatCommand( - _accessor: ServicesAccessor, - ctrl: InlineChatController, - _editor: ICodeEditor, - hunk?: HunkInformation | any, - ): Promise { + async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, hunk?: HunkInformation | any): Promise { return ctrl.discardHunk(hunk); } } @@ -471,36 +332,29 @@ export class RerunAction extends AbstractInlineChatAction { constructor() { super({ id: ACTION_REGENERATE_RESPONSE, - title: localize2("chat.rerun.label", "Rerun Request"), - shortTitle: localize("rerun", "Rerun"), + title: localize2('chat.rerun.label', "Rerun Request"), + shortTitle: localize('rerun', 'Rerun'), f1: false, icon: Codicon.refresh, precondition: CTX_INLINE_CHAT_VISIBLE, menu: { id: MENU_INLINE_CHAT_WIDGET_STATUS, - group: "0_main", + group: '0_main', order: 5, when: ContextKeyExpr.and( ChatContextKeys.inputHasText.toNegated(), CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.negate(), - CTX_INLINE_CHAT_RESPONSE_TYPE.notEqualsTo( - InlineChatResponseType.None, - ), - ), + CTX_INLINE_CHAT_RESPONSE_TYPE.notEqualsTo(InlineChatResponseType.None) + ) }, keybinding: { weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyCode.KeyR, - }, + primary: KeyMod.CtrlCmd | KeyCode.KeyR + } }); } - override async runInlineChatCommand( - accessor: ServicesAccessor, - ctrl: InlineChatController, - _editor: ICodeEditor, - ..._args: any[] - ): Promise { + override async runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): Promise { const chatService = accessor.get(IChatService); const chatWidgetService = accessor.get(IChatWidgetService); const model = ctrl.chatWidget.viewModel?.model; @@ -510,57 +364,45 @@ export class RerunAction extends AbstractInlineChatAction { const lastRequest = model.getRequests().at(-1); if (lastRequest) { - const widget = chatWidgetService.getWidgetBySessionId( - model.sessionId, - ); + const widget = chatWidgetService.getWidgetBySessionId(model.sessionId); await chatService.resendRequest(lastRequest, { noCommandDetection: false, attempt: lastRequest.attempt + 1, location: ctrl.chatWidget.location, - userSelectedModelId: widget?.input.currentLanguageModel, + userSelectedModelId: widget?.input.currentLanguageModel }); } } } export class CloseAction extends AbstractInlineChatAction { + constructor() { super({ - id: "inlineChat.close", - title: localize("close", "Close"), + id: 'inlineChat.close', + title: localize('close', 'Close'), icon: Codicon.close, precondition: CTX_INLINE_CHAT_VISIBLE, keybinding: { weight: KeybindingWeight.EditorContrib + 1, primary: KeyCode.Escape, }, - menu: [ - { - id: MENU_INLINE_CHAT_WIDGET_STATUS, - group: "0_main", - order: 1, - when: ContextKeyExpr.and( - CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.negate(), - ContextKeyExpr.or( - CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo( - InlineChatResponseType.Messages, - ), - CTX_INLINE_CHAT_EDIT_MODE.isEqualTo( - EditMode.Preview, - ), - ), - ), - }, - ], + menu: [{ + id: MENU_INLINE_CHAT_WIDGET_STATUS, + group: '0_main', + order: 1, + when: ContextKeyExpr.and( + CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.negate(), + ContextKeyExpr.or( + CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.Messages), + CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Preview) + ) + ), + }] }); } - async runInlineChatCommand( - _accessor: ServicesAccessor, - ctrl: InlineChatController, - _editor: ICodeEditor, - ..._args: any[] - ): Promise { + async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): Promise { ctrl.cancelSession(); } } @@ -568,73 +410,60 @@ export class CloseAction extends AbstractInlineChatAction { export class ConfigureInlineChatAction extends AbstractInlineChatAction { constructor() { super({ - id: "inlineChat.configure", - title: localize2("configure", "Configure Inline Chat"), + id: 'inlineChat.configure', + title: localize2('configure', 'Configure Inline Chat'), icon: Codicon.settingsGear, precondition: CTX_INLINE_CHAT_VISIBLE, f1: true, menu: { id: MENU_INLINE_CHAT_WIDGET_STATUS, - group: "zzz", - order: 5, - }, + group: 'zzz', + order: 5 + } }); } - async runInlineChatCommand( - accessor: ServicesAccessor, - ctrl: InlineChatController, - _editor: ICodeEditor, - ..._args: any[] - ): Promise { - accessor.get(IPreferencesService).openSettings({ query: "inlineChat" }); + async runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): Promise { + accessor.get(IPreferencesService).openSettings({ query: 'inlineChat' }); } } export class MoveToNextHunk extends AbstractInlineChatAction { + constructor() { super({ - id: "inlineChat.moveToNextHunk", - title: localize2("moveToNextHunk", "Move to Next Change"), + id: 'inlineChat.moveToNextHunk', + title: localize2('moveToNextHunk', 'Move to Next Change'), precondition: CTX_INLINE_CHAT_VISIBLE, f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, - primary: KeyCode.F7, - }, + primary: KeyCode.F7 + } }); } - override runInlineChatCommand( - accessor: ServicesAccessor, - ctrl: InlineChatController, - editor: ICodeEditor, - ...args: any[] - ): void { + override runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController, editor: ICodeEditor, ...args: any[]): void { ctrl.moveHunk(true); } } export class MoveToPreviousHunk extends AbstractInlineChatAction { + constructor() { super({ - id: "inlineChat.moveToPreviousHunk", - title: localize2("moveToPreviousHunk", "Move to Previous Change"), + id: 'inlineChat.moveToPreviousHunk', + title: localize2('moveToPreviousHunk', 'Move to Previous Change'), f1: true, precondition: CTX_INLINE_CHAT_VISIBLE, keybinding: { weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.Shift | KeyCode.F7, - }, + primary: KeyMod.Shift | KeyCode.F7 + } }); } - override runInlineChatCommand( - accessor: ServicesAccessor, - ctrl: InlineChatController, - editor: ICodeEditor, - ...args: any[] - ): void { + override runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController, editor: ICodeEditor, ...args: any[]): void { ctrl.moveHunk(false); } } @@ -643,87 +472,62 @@ export class ViewInChatAction extends AbstractInlineChatAction { constructor() { super({ id: ACTION_VIEW_IN_CHAT, - title: localize("viewInChat", "View in Chat"), + title: localize('viewInChat', 'View in Chat'), icon: Codicon.commentDiscussion, precondition: CTX_INLINE_CHAT_VISIBLE, - menu: [ - { - id: MENU_INLINE_CHAT_WIDGET_STATUS, - group: "more", - order: 1, - when: CTX_INLINE_CHAT_RESPONSE_TYPE.notEqualsTo( - InlineChatResponseType.Messages, - ), - }, - { - id: MENU_INLINE_CHAT_WIDGET_STATUS, - group: "0_main", - order: 1, - when: ContextKeyExpr.and( - ChatContextKeys.inputHasText.toNegated(), - CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo( - InlineChatResponseType.Messages, - ), - CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.negate(), - ), - }, - ], + menu: [{ + id: MENU_INLINE_CHAT_WIDGET_STATUS, + group: 'more', + order: 1, + when: CTX_INLINE_CHAT_RESPONSE_TYPE.notEqualsTo(InlineChatResponseType.Messages) + }, { + id: MENU_INLINE_CHAT_WIDGET_STATUS, + group: '0_main', + order: 1, + when: ContextKeyExpr.and( + ChatContextKeys.inputHasText.toNegated(), + CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.Messages), + CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.negate() + ) + }], keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.DownArrow, - when: ChatContextKeys.inChatInput, - }, + when: ChatContextKeys.inChatInput + } }); } - override runInlineChatCommand( - _accessor: ServicesAccessor, - ctrl: InlineChatController, - _editor: ICodeEditor, - ..._args: any[] - ) { + override runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]) { return ctrl.viewInChat(); } } export class ToggleDiffForChange extends AbstractInlineChatAction { + constructor() { super({ id: ACTION_TOGGLE_DIFF, - precondition: ContextKeyExpr.and( - CTX_INLINE_CHAT_VISIBLE, - CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Live), - CTX_INLINE_CHAT_CHANGE_HAS_DIFF, - ), - title: localize2("showChanges", "Toggle Changes"), + precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Live), CTX_INLINE_CHAT_CHANGE_HAS_DIFF), + title: localize2('showChanges', 'Toggle Changes'), icon: Codicon.diffSingle, toggled: { condition: CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, }, - menu: [ - { - id: MENU_INLINE_CHAT_WIDGET_STATUS, - group: "zzz", - when: ContextKeyExpr.and( - CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Live), - ), - order: 1, - }, - { - id: MENU_INLINE_CHAT_ZONE, - group: "navigation", - when: CTX_INLINE_CHAT_CHANGE_HAS_DIFF, - order: 2, - }, - ], + menu: [{ + id: MENU_INLINE_CHAT_WIDGET_STATUS, + group: 'zzz', + when: ContextKeyExpr.and(CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Live)), + order: 1, + }, { + id: MENU_INLINE_CHAT_ZONE, + group: 'navigation', + when: CTX_INLINE_CHAT_CHANGE_HAS_DIFF, + order: 2 + }] }); } - override runInlineChatCommand( - _accessor: ServicesAccessor, - ctrl: InlineChatController, - _editor: ICodeEditor, - hunkInfo: HunkInformation | any, - ): void { + override runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, hunkInfo: HunkInformation | any): void { ctrl.toggleDiff(hunkInfo); } } diff --git a/Source/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts b/Source/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts index 9e907af184ceb..8aa9b86969aed 100644 --- a/Source/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts +++ b/Source/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts @@ -3,97 +3,56 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyCode, KeyMod } from "../../../../base/common/keyCodes.js"; -import { Disposable } from "../../../../base/common/lifecycle.js"; -import { - autorun, - observableFromEvent, - observableValue, -} from "../../../../base/common/observable.js"; -import { isEqual } from "../../../../base/common/resources.js"; -import { URI } from "../../../../base/common/uri.js"; -import { - ICodeEditor, - MouseTargetType, -} from "../../../../editor/browser/editorBrowser.js"; -import { - EditorAction2, - ServicesAccessor, -} from "../../../../editor/browser/editorExtensions.js"; -import { EditOperation } from "../../../../editor/common/core/editOperation.js"; -import { - IPosition, - Position, -} from "../../../../editor/common/core/position.js"; -import { Range } from "../../../../editor/common/core/range.js"; -import { IEditorContribution } from "../../../../editor/common/editorCommon.js"; -import { EditorContextKeys } from "../../../../editor/common/editorContextKeys.js"; -import { StandardTokenType } from "../../../../editor/common/encodedTokenAttributes.js"; -import { - InjectedTextCursorStops, - IValidEditOperation, - TrackedRangeStickiness, -} from "../../../../editor/common/model.js"; -import { localize, localize2 } from "../../../../nls.js"; -import { - ContextKeyExpr, - IContextKey, - IContextKeyService, - RawContextKey, -} from "../../../../platform/contextkey/common/contextkey.js"; -import { KeybindingWeight } from "../../../../platform/keybinding/common/keybindingsRegistry.js"; -import { - ACTION_START, - CTX_INLINE_CHAT_HAS_AGENT, - CTX_INLINE_CHAT_VISIBLE, -} from "../common/inlineChat.js"; -import { AbstractInlineChatAction } from "./inlineChatActions.js"; -import { InlineChatController, State } from "./inlineChatController.js"; - -import "./media/inlineChat.css"; - -import { InlineCompletionsController } from "../../../../editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.js"; -import { ICommandService } from "../../../../platform/commands/common/commands.js"; -import { IKeybindingService } from "../../../../platform/keybinding/common/keybinding.js"; - -export const CTX_INLINE_CHAT_SHOWING_HINT = new RawContextKey( - "inlineChatShowingHint", - false, - localize( - "inlineChatShowingHint", - "Whether inline chat shows a contextual hint", - ), -); - -const _inlineChatActionId = "inlineChat.startWithCurrentLine"; +import { Disposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; +import { ICodeEditor, MouseTargetType } from '../../../../editor/browser/editorBrowser.js'; +import { IEditorContribution } from '../../../../editor/common/editorCommon.js'; +import { localize, localize2 } from '../../../../nls.js'; +import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { InlineChatController, State } from './inlineChatController.js'; +import { ACTION_START, CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_VISIBLE } from '../common/inlineChat.js'; +import { EditorAction2, ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; +import { EditOperation } from '../../../../editor/common/core/editOperation.js'; +import { Range } from '../../../../editor/common/core/range.js'; +import { IPosition, Position } from '../../../../editor/common/core/position.js'; +import { AbstractInlineChatAction } from './inlineChatActions.js'; +import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; +import { InjectedTextCursorStops, IValidEditOperation, TrackedRangeStickiness } from '../../../../editor/common/model.js'; +import { URI } from '../../../../base/common/uri.js'; +import { isEqual } from '../../../../base/common/resources.js'; +import { StandardTokenType } from '../../../../editor/common/encodedTokenAttributes.js'; +import { autorun, observableFromEvent, observableValue } from '../../../../base/common/observable.js'; +import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; +import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; +import './media/inlineChat.css'; +import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { InlineCompletionsController } from '../../../../editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.js'; +import { ChatAgentLocation, IChatAgentService } from '../../chat/common/chatAgents.js'; +import { MarkdownString } from '../../../../base/common/htmlContent.js'; +import { IMarkerDecorationsService } from '../../../../editor/common/services/markerDecorations.js'; + +export const CTX_INLINE_CHAT_SHOWING_HINT = new RawContextKey('inlineChatShowingHint', false, localize('inlineChatShowingHint', "Whether inline chat shows a contextual hint")); + +const _inlineChatActionId = 'inlineChat.startWithCurrentLine'; export class InlineChatExpandLineAction extends EditorAction2 { + constructor() { super({ id: _inlineChatActionId, category: AbstractInlineChatAction.category, - title: localize2( - "startWithCurrentLine", - "Start in Editor with Current Line", - ), + title: localize2('startWithCurrentLine', "Start in Editor with Current Line"), f1: true, - precondition: ContextKeyExpr.and( - CTX_INLINE_CHAT_VISIBLE.negate(), - CTX_INLINE_CHAT_HAS_AGENT, - EditorContextKeys.writable, - ), + precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE.negate(), CTX_INLINE_CHAT_HAS_AGENT, EditorContextKeys.writable), keybinding: { when: CTX_INLINE_CHAT_SHOWING_HINT, weight: KeybindingWeight.WorkbenchContrib + 1, - primary: KeyMod.CtrlCmd | KeyCode.KeyI, - }, + primary: KeyMod.CtrlCmd | KeyCode.KeyI + } }); } - override async runEditorCommand( - _accessor: ServicesAccessor, - editor: ICodeEditor, - ) { + override async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor) { const ctrl = InlineChatController.get(editor); if (!ctrl || !editor.hasModel()) { return; @@ -108,30 +67,22 @@ export class InlineChatExpandLineAction extends EditorAction2 { // clear the line let undoEdits: IValidEditOperation[] = []; - model.pushEditOperations( - null, - [ - EditOperation.replace( - new Range(lineNumber, startColumn, lineNumber, endColumn), - "", - ), - ], - (edits) => { - undoEdits = edits; - return null; - }, - ); + model.pushEditOperations(null, [EditOperation.replace(new Range(lineNumber, startColumn, lineNumber, endColumn), '')], (edits) => { + undoEdits = edits; + return null; + }); let lastState: State | undefined; - const d = ctrl.onDidEnterState((e) => (lastState = e)); + const d = ctrl.onDidEnterState(e => lastState = e); try { // trigger chat await ctrl.run({ autoSend: true, message: lineContent.trim(), - position: new Position(lineNumber, startColumn), + position: new Position(lineNumber, startColumn) }); + } finally { d.dispose(); } @@ -143,25 +94,18 @@ export class InlineChatExpandLineAction extends EditorAction2 { } export class ShowInlineChatHintAction extends EditorAction2 { + constructor() { super({ - id: "inlineChat.showHint", + id: 'inlineChat.showHint', category: AbstractInlineChatAction.category, - title: localize2("showHint", "Show Inline Chat Hint"), + title: localize2('showHint', "Show Inline Chat Hint"), f1: false, - precondition: ContextKeyExpr.and( - CTX_INLINE_CHAT_VISIBLE.negate(), - CTX_INLINE_CHAT_HAS_AGENT, - EditorContextKeys.writable, - ), + precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE.negate(), CTX_INLINE_CHAT_HAS_AGENT, EditorContextKeys.writable), }); } - override async runEditorCommand( - _accessor: ServicesAccessor, - editor: ICodeEditor, - ...args: [uri: URI, position: IPosition, ...rest: any[]] - ) { + override async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ...args: [uri: URI, position: IPosition, ...rest: any[]]) { if (!editor.hasModel()) { return; } @@ -196,16 +140,12 @@ export class ShowInlineChatHintAction extends EditorAction2 { } } -export class InlineChatHintsController - extends Disposable - implements IEditorContribution -{ - public static readonly ID = "editor.contrib.inlineChatHints"; +export class InlineChatHintsController extends Disposable implements IEditorContribution { + + public static readonly ID = 'editor.contrib.inlineChatHints'; static get(editor: ICodeEditor): InlineChatHintsController | null { - return editor.getContribution( - InlineChatHintsController.ID, - ); + return editor.getContribution(InlineChatHintsController.ID); } private readonly _editor: ICodeEditor; @@ -217,95 +157,90 @@ export class InlineChatHintsController @IContextKeyService contextKeyService: IContextKeyService, @ICommandService commandService: ICommandService, @IKeybindingService keybindingService: IKeybindingService, + @IChatAgentService chatAgentService: IChatAgentService, + @IMarkerDecorationsService markerDecorationService: IMarkerDecorationsService ) { super(); this._editor = editor; - this._ctxShowingHint = - CTX_INLINE_CHAT_SHOWING_HINT.bindTo(contextKeyService); + this._ctxShowingHint = CTX_INLINE_CHAT_SHOWING_HINT.bindTo(contextKeyService); + const ghostCtrl = InlineCompletionsController.get(editor); - this._store.add( - this._editor.onMouseDown((e) => { - if (e.target.type !== MouseTargetType.CONTENT_TEXT) { - return; - } - if ( - e.target.detail.injectedText?.options.attachedData !== this - ) { - return; - } - commandService.executeCommand(_inlineChatActionId); + this._store.add(this._editor.onMouseDown(e => { + if (e.target.type !== MouseTargetType.CONTENT_TEXT) { + return; + } + if (e.target.detail.injectedText?.options.attachedData !== this) { + return; + } + commandService.executeCommand(_inlineChatActionId); + this.hide(); + })); + + this._store.add(commandService.onWillExecuteCommand(e => { + if (e.commandId === _inlineChatActionId) { this.hide(); - }), - ); - - this._store.add( - commandService.onWillExecuteCommand((e) => { - if (e.commandId === _inlineChatActionId) { - this.hide(); - } - }), - ); - - const posObs = observableFromEvent( - editor.onDidChangeCursorPosition, - () => editor.getPosition(), - ); + } + })); + const markerSuppression = this._store.add(new MutableDisposable()); const decos = this._editor.createDecorationsCollection(); - const keyObs = observableFromEvent( - keybindingService.onDidUpdateKeybindings, - (_) => keybindingService.lookupKeybinding(ACTION_START)?.getLabel(), - ); - - this._store.add( - autorun((r) => { - const ghostState = ghostCtrl?.model.read(r)?.state.read(r); - const visible = this._visibilityObs.read(r); - const kb = keyObs.read(r); - const position = posObs.read(r); - - // update context key - this._ctxShowingHint.set(visible); - - if (!visible || !kb || !position || ghostState !== undefined) { - decos.clear(); - return; - } - - const column = this._editor - .getModel() - ?.getLineMaxColumn(position.lineNumber); - if (!column) { - return; + const modelObs = observableFromEvent(editor.onDidChangeModel, () => editor.getModel()); + const posObs = observableFromEvent(editor.onDidChangeCursorPosition, () => editor.getPosition()); + const keyObs = observableFromEvent(keybindingService.onDidUpdateKeybindings, _ => keybindingService.lookupKeybinding(ACTION_START)?.getLabel()); + + this._store.add(autorun(r => { + + const ghostState = ghostCtrl?.model.read(r)?.state.read(r); + const visible = this._visibilityObs.read(r); + const kb = keyObs.read(r); + const position = posObs.read(r); + const model = modelObs.read(r); + + // update context key + this._ctxShowingHint.set(visible); + + if (!visible || !kb || !position || ghostState !== undefined || !model) { + decos.clear(); + markerSuppression.clear(); + return; + } + + const agentName = chatAgentService.getDefaultAgent(ChatAgentLocation.Editor)?.fullName ?? localize('defaultTitle', "Chat"); + const isEol = model.getLineMaxColumn(position.lineNumber) === position.column; + + let content: string; + let inlineClassName: string; + + if (isEol) { + content = '\u00A0' + localize('title', "{0} to continue with {1}...", kb, agentName); + inlineClassName = `inline-chat-hint${decos.length === 0 ? ' first' : ''}`; + } else { + content = '\u200a' + kb + '\u200a'; + inlineClassName = 'inline-chat-hint embedded'; + } + + decos.set([{ + range: Range.fromPositions(position), + options: { + description: 'inline-chat-hint-line', + showIfCollapsed: true, + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + hoverMessage: new MarkdownString(localize('toolttip', "Continue this with {0}...", agentName)), + after: { + content, + inlineClassName, + inlineClassNameAffectsLetterSpacing: true, + cursorStops: InjectedTextCursorStops.Both, + attachedData: this + } } + }]); - const range = Range.fromPositions(position); - - decos.set([ - { - range, - options: { - description: "inline-chat-hint-line", - showIfCollapsed: true, - stickiness: - TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - after: { - inlineClassName: "inline-chat-hint", - content: - "\u00A0" + - localize("ddd", "{0} to chat", kb), - inlineClassNameAffectsLetterSpacing: true, - cursorStops: InjectedTextCursorStops.Both, - attachedData: this, - }, - }, - }, - ]); - }), - ); + markerSuppression.value = markerDecorationService.addMarkerSuppression(model.uri, model.validateRange(new Range(position.lineNumber, 1, position.lineNumber, Number.MAX_SAFE_INTEGER))); + })); } show(): void { @@ -318,22 +253,20 @@ export class InlineChatHintsController } export class HideInlineChatHintAction extends EditorAction2 { + constructor() { super({ - id: "inlineChat.hideHint", - title: localize2("hideHint", "Hide Inline Chat Hint"), + id: 'inlineChat.hideHint', + title: localize2('hideHint', "Hide Inline Chat Hint"), precondition: CTX_INLINE_CHAT_SHOWING_HINT, keybinding: { weight: KeybindingWeight.EditorContrib - 10, - primary: KeyCode.Escape, - }, + primary: KeyCode.Escape + } }); } - override async runEditorCommand( - _accessor: ServicesAccessor, - editor: ICodeEditor, - ): Promise { + override async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { InlineChatHintsController.get(editor)?.hide(); } } diff --git a/Source/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css b/Source/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css index 7d32b9467e5b0..c9129451eda56 100644 --- a/Source/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css +++ b/Source/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css @@ -437,9 +437,34 @@ /* HINT */ .monaco-workbench .monaco-editor .inline-chat-hint { - /* padding: 0 8px; */ cursor: pointer; color: var(--vscode-editorGhostText-foreground); - border: 1px solid var(--vscode-editorGhostText-border); + background-image: linear-gradient(45deg, var(--vscode-editorGhostText-foreground), 95%, transparent); + background-clip: text; + -webkit-text-fill-color: transparent; +} + +.monaco-workbench .monaco-editor .inline-chat-hint.embedded { + border: 1px solid var(--vscode-editorSuggestWidget-border); border-radius: 3px; } + +@property --inline-chat-hint-progress { + syntax: ''; + initial-value: 33%; + inherits: false; +} + +@keyframes ltr { + 0% { + --inline-chat-hint-progress: 33%; + } + 100% { + --inline-chat-hint-progress: 95%; + } +} + +.monaco-workbench .monaco-editor .inline-chat-hint.first { + background-image: linear-gradient(45deg, var(--vscode-editorGhostText-foreground), var(--inline-chat-hint-progress), transparent); + animation: 75ms ltr ease-in forwards; +} diff --git a/Source/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnosticEditorContrib.ts b/Source/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnosticEditorContrib.ts index 9dbeabe920a87..00b45c45d8228 100644 --- a/Source/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnosticEditorContrib.ts +++ b/Source/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnosticEditorContrib.ts @@ -3,39 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from "../../../../../../base/common/event.js"; -import { Iterable } from "../../../../../../base/common/iterator.js"; -import { - Disposable, - IDisposable, - toDisposable, -} from "../../../../../../base/common/lifecycle.js"; -import { IRange } from "../../../../../../editor/common/core/range.js"; -import { IConfigurationService } from "../../../../../../platform/configuration/common/configuration.js"; -import { - IMarkerData, - IMarkerService, -} from "../../../../../../platform/markers/common/markers.js"; -import { IChatAgentService } from "../../../../chat/common/chatAgents.js"; -import { CellKind, NotebookSetting } from "../../../common/notebookCommon.js"; -import { - ICellExecutionStateChangedEvent, - IExecutionStateChangedEvent, - INotebookExecutionStateService, - NotebookExecutionType, -} from "../../../common/notebookExecutionStateService.js"; -import { - INotebookEditor, - INotebookEditorContribution, -} from "../../notebookBrowser.js"; -import { registerNotebookContribution } from "../../notebookEditorExtensions.js"; -import { CodeCellViewModel } from "../../viewModel/codeCellViewModel.js"; - -export class CellDiagnostics - extends Disposable - implements INotebookEditorContribution -{ - static ID: string = "workbench.notebook.cellDiagnostics"; +import { Disposable, IDisposable, toDisposable } from '../../../../../../base/common/lifecycle.js'; +import { IMarkerData, IMarkerService } from '../../../../../../platform/markers/common/markers.js'; +import { IRange } from '../../../../../../editor/common/core/range.js'; +import { ICellExecutionStateChangedEvent, IExecutionStateChangedEvent, INotebookExecutionStateService, NotebookExecutionType } from '../../../common/notebookExecutionStateService.js'; +import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; +import { CellKind, NotebookSetting } from '../../../common/notebookCommon.js'; +import { INotebookEditor, INotebookEditorContribution } from '../../notebookBrowser.js'; +import { registerNotebookContribution } from '../../notebookEditorExtensions.js'; +import { Iterable } from '../../../../../../base/common/iterator.js'; +import { CodeCellViewModel } from '../../viewModel/codeCellViewModel.js'; +import { Event } from '../../../../../../base/common/event.js'; +import { IChatAgentService } from '../../../../chat/common/chatAgents.js'; + +export class CellDiagnostics extends Disposable implements INotebookEditorContribution { + + static ID: string = 'workbench.notebook.cellDiagnostics'; private enabled = false; private listening = false; @@ -43,90 +26,50 @@ export class CellDiagnostics constructor( private readonly notebookEditor: INotebookEditor, - @INotebookExecutionStateService - private readonly notebookExecutionStateService: INotebookExecutionStateService, + @INotebookExecutionStateService private readonly notebookExecutionStateService: INotebookExecutionStateService, @IMarkerService private readonly markerService: IMarkerService, @IChatAgentService private readonly chatAgentService: IChatAgentService, - @IConfigurationService - private readonly configurationService: IConfigurationService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(); this.updateEnabled(); - this._register( - chatAgentService.onDidChangeAgents(() => this.updateEnabled()), - ); - this._register( - configurationService.onDidChangeConfiguration((e) => { - if ( - e.affectsConfiguration( - NotebookSetting.cellFailureDiagnostics, - ) - ) { - this.updateEnabled(); - } - }), - ); + this._register(chatAgentService.onDidChangeAgents(() => this.updateEnabled())); + this._register(configurationService.onDidChangeConfiguration((e) => { + if (e.affectsConfiguration(NotebookSetting.cellFailureDiagnostics)) { + this.updateEnabled(); + } + })); } private updateEnabled() { - const settingEnabled = this.configurationService.getValue( - NotebookSetting.cellFailureDiagnostics, - ); - - if ( - this.enabled && - (!settingEnabled || - Iterable.isEmpty(this.chatAgentService.getAgents())) - ) { + const settingEnabled = this.configurationService.getValue(NotebookSetting.cellFailureDiagnostics); + if (this.enabled && (!settingEnabled || Iterable.isEmpty(this.chatAgentService.getAgents()))) { this.enabled = false; this.clearAll(); - } else if ( - !this.enabled && - settingEnabled && - !Iterable.isEmpty(this.chatAgentService.getAgents()) - ) { + } else if (!this.enabled && settingEnabled && !Iterable.isEmpty(this.chatAgentService.getAgents())) { this.enabled = true; - if (!this.listening) { this.listening = true; - this._register( - Event.accumulate< - | ICellExecutionStateChangedEvent - | IExecutionStateChangedEvent - >( - this.notebookExecutionStateService.onDidChangeExecution, - 200, - )((e) => this.handleChangeExecutionState(e)), - ); + this._register(Event.accumulate( + this.notebookExecutionStateService.onDidChangeExecution, 200 + )((e) => this.handleChangeExecutionState(e))); } } } - private handleChangeExecutionState( - changes: ( - | ICellExecutionStateChangedEvent - | IExecutionStateChangedEvent - )[], - ) { + private handleChangeExecutionState(changes: (ICellExecutionStateChangedEvent | IExecutionStateChangedEvent)[]) { if (!this.enabled) { return; } const handled = new Set(); - for (const e of changes.reverse()) { - const notebookUri = this.notebookEditor.textModel?.uri; - if ( - e.type === NotebookExecutionType.cell && - notebookUri && - e.affectsNotebook(notebookUri) && - !handled.has(e.cellHandle) - ) { + const notebookUri = this.notebookEditor.textModel?.uri; + if (e.type === NotebookExecutionType.cell && notebookUri && e.affectsNotebook(notebookUri) && !handled.has(e.cellHandle)) { handled.add(e.cellHandle); - if (!!e.changed) { // cell is running this.clear(e.cellHandle); @@ -145,7 +88,6 @@ export class CellDiagnostics public clear(cellHandle: number) { const disposables = this.diagnosticsByHandle.get(cellHandle); - if (disposables) { for (const disposable of disposables) { disposable.dispose(); @@ -161,52 +103,27 @@ export class CellDiagnostics } const cell = this.notebookEditor.getCellByHandle(cellHandle); - if (!cell || cell.cellKind !== CellKind.Code) { return; } const metadata = cell.model.internalMetadata; - - if ( - cell instanceof CodeCellViewModel && - !metadata.lastRunSuccess && - metadata?.error?.location - ) { + if (cell instanceof CodeCellViewModel && !metadata.lastRunSuccess && metadata?.error?.location) { const disposables: IDisposable[] = []; - - const errorLabel = metadata.error.name - ? `${metadata.error.name}: ${metadata.error.message}` - : metadata.error.message; - - const marker = this.createMarkerData( - errorLabel, - metadata.error.location, - ); - this.markerService.changeOne(CellDiagnostics.ID, cell.uri, [ - marker, - ]); - disposables.push( - toDisposable(() => - this.markerService.changeOne( - CellDiagnostics.ID, - cell.uri, - [], - ), - ), - ); - disposables.push( - cell.model.onDidChangeOutputs(() => { - if (cell.model.outputs.length === 0) { - this.clear(cellHandle); - } - }), - ); - disposables.push( - cell.model.onDidChangeContent(() => { + const errorLabel = metadata.error.name ? `${metadata.error.name}: ${metadata.error.message}` : metadata.error.message; + const marker = this.createMarkerData(errorLabel, metadata.error.location); + this.markerService.changeOne(CellDiagnostics.ID, cell.uri, [marker]); + disposables.push(toDisposable(() => this.markerService.changeOne(CellDiagnostics.ID, cell.uri, []))); + cell.executionErrorDiagnostic.set(metadata.error, undefined); + disposables.push(toDisposable(() => cell.executionErrorDiagnostic.set(undefined, undefined))); + disposables.push(cell.model.onDidChangeOutputs(() => { + if (cell.model.outputs.length === 0) { this.clear(cellHandle); - }), - ); + } + })); + disposables.push(cell.model.onDidChangeContent(() => { + this.clear(cellHandle); + })); this.diagnosticsByHandle.set(cellHandle, disposables); } } @@ -219,7 +136,7 @@ export class CellDiagnostics startColumn: location.startColumn + 1, endLineNumber: location.endLineNumber + 1, endColumn: location.endColumn + 1, - source: "Cell Execution Error", + source: 'Cell Execution Error' }; } @@ -227,6 +144,7 @@ export class CellDiagnostics super.dispose(); this.clearAll(); } + } registerNotebookContribution(CellDiagnostics.ID, CellDiagnostics); diff --git a/Source/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnosticsActions.ts b/Source/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnosticsActions.ts index 0d56c6decc6d1..d41ae0fcfd41f 100644 --- a/Source/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnosticsActions.ts +++ b/Source/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnosticsActions.ts @@ -3,200 +3,119 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyCode, KeyMod } from "../../../../../../base/common/keyCodes.js"; -import { ServicesAccessor } from "../../../../../../editor/browser/editorExtensions.js"; -import { Range } from "../../../../../../editor/common/core/range.js"; -import { CodeActionController } from "../../../../../../editor/contrib/codeAction/browser/codeActionController.js"; -import { - CodeActionKind, - CodeActionTriggerSource, -} from "../../../../../../editor/contrib/codeAction/common/types.js"; -import { localize, localize2 } from "../../../../../../nls.js"; -import { registerAction2 } from "../../../../../../platform/actions/common/actions.js"; -import { ContextKeyExpr } from "../../../../../../platform/contextkey/common/contextkey.js"; -import { KeybindingWeight } from "../../../../../../platform/keybinding/common/keybindingsRegistry.js"; -import { IViewsService } from "../../../../../services/views/common/viewsService.js"; -import { showChatView } from "../../../../chat/browser/chat.js"; -import { InlineChatController } from "../../../../inlineChat/browser/inlineChatController.js"; -import { - NOTEBOOK_CELL_EDITOR_FOCUSED, - NOTEBOOK_CELL_FOCUSED, - NOTEBOOK_CELL_HAS_ERROR_DIAGNOSTICS, -} from "../../../common/notebookContextKeys.js"; -import { - findTargetCellEditor, - INotebookCellActionContext, - NotebookCellAction, -} from "../../controller/coreActions.js"; -import { CodeCellViewModel } from "../../viewModel/codeCellViewModel.js"; - -export const OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID = - "notebook.cell.openFailureActions"; -export const FIX_CELL_ERROR_COMMAND_ID = "notebook.cell.chat.fixError"; -export const EXPLAIN_CELL_ERROR_COMMAND_ID = "notebook.cell.chat.explainError"; - -registerAction2( - class extends NotebookCellAction { - constructor() { - super({ - id: OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID, - title: localize2( - "notebookActions.cellFailureActions", - "Show Cell Failure Actions", - ), - precondition: ContextKeyExpr.and( - NOTEBOOK_CELL_FOCUSED, - NOTEBOOK_CELL_HAS_ERROR_DIAGNOSTICS, - NOTEBOOK_CELL_EDITOR_FOCUSED.toNegated(), - ), - f1: true, - keybinding: { - when: ContextKeyExpr.and( - NOTEBOOK_CELL_FOCUSED, - NOTEBOOK_CELL_HAS_ERROR_DIAGNOSTICS, - NOTEBOOK_CELL_EDITOR_FOCUSED.toNegated(), - ), - primary: KeyMod.CtrlCmd | KeyCode.Period, - weight: KeybindingWeight.WorkbenchContrib, - }, - }); - } - - async runWithContext( - accessor: ServicesAccessor, - context: INotebookCellActionContext, - ): Promise { - if (context.cell instanceof CodeCellViewModel) { - const error = context.cell.executionError.get(); - - if (error?.location) { - const location = Range.lift({ - startLineNumber: error.location.startLineNumber + 1, - startColumn: error.location.startColumn + 1, - endLineNumber: error.location.endLineNumber + 1, - endColumn: error.location.endColumn + 1, - }); - context.notebookEditor.setCellEditorSelection( - context.cell, - Range.lift(location), - ); - - const editor = findTargetCellEditor(context, context.cell); - - if (editor) { - const controller = CodeActionController.get(editor); - controller?.manualTriggerAtCurrentPosition( - localize( - "cellCommands.quickFix.noneMessage", - "No code actions available", - ), - CodeActionTriggerSource.Default, - { include: CodeActionKind.QuickFix }, - ); - } +import { KeyCode, KeyMod } from '../../../../../../base/common/keyCodes.js'; +import { ServicesAccessor } from '../../../../../../editor/browser/editorExtensions.js'; +import { Range } from '../../../../../../editor/common/core/range.js'; +import { CodeActionController } from '../../../../../../editor/contrib/codeAction/browser/codeActionController.js'; +import { CodeActionKind, CodeActionTriggerSource } from '../../../../../../editor/contrib/codeAction/common/types.js'; +import { localize, localize2 } from '../../../../../../nls.js'; +import { registerAction2 } from '../../../../../../platform/actions/common/actions.js'; +import { ContextKeyExpr } from '../../../../../../platform/contextkey/common/contextkey.js'; +import { KeybindingWeight } from '../../../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { INotebookCellActionContext, NotebookCellAction, findTargetCellEditor } from '../../controller/coreActions.js'; +import { CodeCellViewModel } from '../../viewModel/codeCellViewModel.js'; +import { NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_HAS_ERROR_DIAGNOSTICS } from '../../../common/notebookContextKeys.js'; +import { InlineChatController } from '../../../../inlineChat/browser/inlineChatController.js'; +import { showChatView } from '../../../../chat/browser/chat.js'; +import { IViewsService } from '../../../../../services/views/common/viewsService.js'; + +export const OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID = 'notebook.cell.openFailureActions'; +export const FIX_CELL_ERROR_COMMAND_ID = 'notebook.cell.chat.fixError'; +export const EXPLAIN_CELL_ERROR_COMMAND_ID = 'notebook.cell.chat.explainError'; + +registerAction2(class extends NotebookCellAction { + constructor() { + super({ + id: OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID, + title: localize2('notebookActions.cellFailureActions', "Show Cell Failure Actions"), + precondition: ContextKeyExpr.and(NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_HAS_ERROR_DIAGNOSTICS, NOTEBOOK_CELL_EDITOR_FOCUSED.toNegated()), + f1: true, + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_HAS_ERROR_DIAGNOSTICS, NOTEBOOK_CELL_EDITOR_FOCUSED.toNegated()), + primary: KeyMod.CtrlCmd | KeyCode.Period, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + if (context.cell instanceof CodeCellViewModel) { + const error = context.cell.executionErrorDiagnostic.get(); + if (error?.location) { + const location = Range.lift({ + startLineNumber: error.location.startLineNumber + 1, + startColumn: error.location.startColumn + 1, + endLineNumber: error.location.endLineNumber + 1, + endColumn: error.location.endColumn + 1 + }); + context.notebookEditor.setCellEditorSelection(context.cell, Range.lift(location)); + const editor = findTargetCellEditor(context, context.cell); + if (editor) { + const controller = CodeActionController.get(editor); + controller?.manualTriggerAtCurrentPosition( + localize('cellCommands.quickFix.noneMessage', "No code actions available"), + CodeActionTriggerSource.Default, + { include: CodeActionKind.QuickFix }); } } } - }, -); - -registerAction2( - class extends NotebookCellAction { - constructor() { - super({ - id: FIX_CELL_ERROR_COMMAND_ID, - title: localize2( - "notebookActions.chatFixCellError", - "Fix Cell Error", - ), - precondition: ContextKeyExpr.and( - NOTEBOOK_CELL_FOCUSED, - NOTEBOOK_CELL_HAS_ERROR_DIAGNOSTICS, - NOTEBOOK_CELL_EDITOR_FOCUSED.toNegated(), - ), - f1: true, - }); - } - - async runWithContext( - accessor: ServicesAccessor, - context: INotebookCellActionContext, - ): Promise { - if (context.cell instanceof CodeCellViewModel) { - const error = context.cell.executionError.get(); - - if (error?.location) { - const location = Range.lift({ - startLineNumber: error.location.startLineNumber + 1, - startColumn: error.location.startColumn + 1, - endLineNumber: error.location.endLineNumber + 1, - endColumn: error.location.endColumn + 1, - }); - context.notebookEditor.setCellEditorSelection( - context.cell, - Range.lift(location), - ); - - const editor = findTargetCellEditor(context, context.cell); - - if (editor) { - const controller = InlineChatController.get(editor); - - const message = error.name - ? `${error.name}: ${error.message}` - : error.message; - - if (controller) { - await controller.run({ - message: "/fix " + message, - initialRange: location, - autoSend: true, - }); - } + } +}); + +registerAction2(class extends NotebookCellAction { + constructor() { + super({ + id: FIX_CELL_ERROR_COMMAND_ID, + title: localize2('notebookActions.chatFixCellError', "Fix Cell Error"), + precondition: ContextKeyExpr.and(NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_HAS_ERROR_DIAGNOSTICS, NOTEBOOK_CELL_EDITOR_FOCUSED.toNegated()), + f1: true + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + if (context.cell instanceof CodeCellViewModel) { + const error = context.cell.executionErrorDiagnostic.get(); + if (error?.location) { + const location = Range.lift({ + startLineNumber: error.location.startLineNumber + 1, + startColumn: error.location.startColumn + 1, + endLineNumber: error.location.endLineNumber + 1, + endColumn: error.location.endColumn + 1 + }); + context.notebookEditor.setCellEditorSelection(context.cell, Range.lift(location)); + const editor = findTargetCellEditor(context, context.cell); + if (editor) { + const controller = InlineChatController.get(editor); + const message = error.name ? `${error.name}: ${error.message}` : error.message; + if (controller) { + await controller.run({ message: '/fix ' + message, initialRange: location, autoSend: true }); } } } } - }, -); - -registerAction2( - class extends NotebookCellAction { - constructor() { - super({ - id: EXPLAIN_CELL_ERROR_COMMAND_ID, - title: localize2( - "notebookActions.chatExplainCellError", - "Explain Cell Error", - ), - precondition: ContextKeyExpr.and( - NOTEBOOK_CELL_FOCUSED, - NOTEBOOK_CELL_HAS_ERROR_DIAGNOSTICS, - NOTEBOOK_CELL_EDITOR_FOCUSED.toNegated(), - ), - f1: true, - }); - } - - async runWithContext( - accessor: ServicesAccessor, - context: INotebookCellActionContext, - ): Promise { - if (context.cell instanceof CodeCellViewModel) { - const error = context.cell.executionError.get(); - - if (error?.message) { - const viewsService = accessor.get(IViewsService); - - const chatWidget = await showChatView(viewsService); - - const message = error.name - ? `${error.name}: ${error.message}` - : error.message; - // TODO: can we add special prompt instructions? e.g. use "%pip install" - chatWidget?.acceptInput("@workspace /explain " + message); - } + } +}); + +registerAction2(class extends NotebookCellAction { + constructor() { + super({ + id: EXPLAIN_CELL_ERROR_COMMAND_ID, + title: localize2('notebookActions.chatExplainCellError', "Explain Cell Error"), + precondition: ContextKeyExpr.and(NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_HAS_ERROR_DIAGNOSTICS, NOTEBOOK_CELL_EDITOR_FOCUSED.toNegated()), + f1: true + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + if (context.cell instanceof CodeCellViewModel) { + const error = context.cell.executionErrorDiagnostic.get(); + if (error?.message) { + const viewsService = accessor.get(IViewsService); + const chatWidget = await showChatView(viewsService); + const message = error.name ? `${error.name}: ${error.message}` : error.message; + // TODO: can we add special prompt instructions? e.g. use "%pip install" + chatWidget?.acceptInput('@workspace /explain ' + message,); } } - }, -); + } +}); diff --git a/Source/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/diagnosticCellStatusBarContrib.ts b/Source/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/diagnosticCellStatusBarContrib.ts index 50ccbebd983cf..0d7c0c3d00f1e 100644 --- a/Source/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/diagnosticCellStatusBarContrib.ts +++ b/Source/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/diagnosticCellStatusBarContrib.ts @@ -3,56 +3,38 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from "../../../../../../base/common/lifecycle.js"; -import { autorun } from "../../../../../../base/common/observable.js"; -import { localize } from "../../../../../../nls.js"; -import { IInstantiationService } from "../../../../../../platform/instantiation/common/instantiation.js"; -import { - CellStatusbarAlignment, - INotebookCellStatusBarItem, -} from "../../../common/notebookCommon.js"; -import { ICellExecutionError } from "../../../common/notebookExecutionStateService.js"; -import { - INotebookEditor, - INotebookEditorContribution, - INotebookViewModel, -} from "../../notebookBrowser.js"; -import { registerNotebookContribution } from "../../notebookEditorExtensions.js"; -import { CodeCellViewModel } from "../../viewModel/codeCellViewModel.js"; -import { NotebookStatusBarController } from "../cellStatusBar/executionStatusBarItemController.js"; -import { - EXPLAIN_CELL_ERROR_COMMAND_ID, - FIX_CELL_ERROR_COMMAND_ID, -} from "./cellDiagnosticsActions.js"; +import { Disposable } from '../../../../../../base/common/lifecycle.js'; +import { autorun } from '../../../../../../base/common/observable.js'; +import { localize } from '../../../../../../nls.js'; +import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; +import { IKeybindingService } from '../../../../../../platform/keybinding/common/keybinding.js'; +import { OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID } from './cellDiagnosticsActions.js'; +import { NotebookStatusBarController } from '../cellStatusBar/executionStatusBarItemController.js'; +import { INotebookEditor, INotebookEditorContribution, INotebookViewModel } from '../../notebookBrowser.js'; +import { registerNotebookContribution } from '../../notebookEditorExtensions.js'; +import { CodeCellViewModel } from '../../viewModel/codeCellViewModel.js'; +import { INotebookCellStatusBarItem, CellStatusbarAlignment } from '../../../common/notebookCommon.js'; +import { ICellExecutionError } from '../../../common/notebookExecutionStateService.js'; +import { IChatAgentService } from '../../../../chat/common/chatAgents.js'; +import { Iterable } from '../../../../../../base/common/iterator.js'; -export class DiagnosticCellStatusBarContrib - extends Disposable - implements INotebookEditorContribution -{ - static id: string = "workbench.notebook.statusBar.diagtnostic"; +export class DiagnosticCellStatusBarContrib extends Disposable implements INotebookEditorContribution { + static id: string = 'workbench.notebook.statusBar.diagtnostic'; constructor( notebookEditor: INotebookEditor, - @IInstantiationService instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService ) { super(); - this._register( - new NotebookStatusBarController(notebookEditor, (vm, cell) => - cell instanceof CodeCellViewModel - ? instantiationService.createInstance( - DiagnosticCellStatusBarItem, - vm, - cell, - ) - : Disposable.None, - ), - ); + this._register(new NotebookStatusBarController(notebookEditor, (vm, cell) => + cell instanceof CodeCellViewModel ? + instantiationService.createInstance(DiagnosticCellStatusBarItem, vm, cell) : + Disposable.None + )); } } -registerNotebookContribution( - DiagnosticCellStatusBarContrib.id, - DiagnosticCellStatusBarContrib, -); +registerNotebookContribution(DiagnosticCellStatusBarContrib.id, DiagnosticCellStatusBarContrib); + class DiagnosticCellStatusBarItem extends Disposable { private _currentItemIds: string[] = []; @@ -60,55 +42,35 @@ class DiagnosticCellStatusBarItem extends Disposable { constructor( private readonly _notebookViewModel: INotebookViewModel, private readonly cell: CodeCellViewModel, + @IKeybindingService private readonly keybindingService: IKeybindingService, + @IChatAgentService private readonly chatAgentService: IChatAgentService, ) { super(); - this._register( - autorun((reader) => - this.updateQuickActions( - reader.readObservable(cell.executionError), - ), - ), - ); + this._register(autorun((reader) => this.updateSparkleItem(reader.readObservable(cell.executionErrorDiagnostic)))); } - private async updateQuickActions(error: ICellExecutionError | undefined) { - let items: INotebookCellStatusBarItem[] = []; + private async updateSparkleItem(error: ICellExecutionError | undefined) { + let item: INotebookCellStatusBarItem | undefined; + + if (error?.location && !Iterable.isEmpty(this.chatAgentService.getAgents())) { + const keybinding = this.keybindingService.lookupKeybinding(OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID)?.getLabel(); + const tooltip = localize('notebook.cell.status.diagnostic', "Quick Actions {0}", `(${keybinding})`); - if (error?.location) { - items = [ - { - text: `$(sparkle) fix`, - tooltip: localize( - "notebook.cell.status.fix", - "Fix With Inline Chat", - ), - alignment: CellStatusbarAlignment.Left, - command: FIX_CELL_ERROR_COMMAND_ID, - priority: Number.MAX_SAFE_INTEGER - 1, - }, - { - text: `$(sparkle) explain`, - tooltip: localize( - "notebook.cell.status.explain", - "Explain With Chat", - ), - alignment: CellStatusbarAlignment.Left, - command: EXPLAIN_CELL_ERROR_COMMAND_ID, - priority: Number.MAX_SAFE_INTEGER - 1, - }, - ]; + item = { + text: `$(sparkle)`, + tooltip, + alignment: CellStatusbarAlignment.Left, + command: OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID, + priority: Number.MAX_SAFE_INTEGER - 1 + }; } - this._currentItemIds = this._notebookViewModel.deltaCellStatusBarItems( - this._currentItemIds, - [{ handle: this.cell.handle, items }], - ); + const items = item ? [item] : []; + this._currentItemIds = this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [{ handle: this.cell.handle, items }]); } override dispose() { super.dispose(); - this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [ - { handle: this.cell.handle, items: [] }, - ]); + this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [{ handle: this.cell.handle, items: [] }]); } } diff --git a/Source/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts b/Source/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts index dababc3ea1e97..f1df88b5028b8 100644 --- a/Source/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts +++ b/Source/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts @@ -3,94 +3,47 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ITreeContextMenuEvent } from "../../../../../../base/browser/ui/tree/tree.js"; -import { RunOnceScheduler } from "../../../../../../base/common/async.js"; -import { URI } from "../../../../../../base/common/uri.js"; -import * as nls from "../../../../../../nls.js"; -import { ILocalizedString } from "../../../../../../platform/action/common/action.js"; -import { getFlatContextMenuActions } from "../../../../../../platform/actions/browser/menuEntryActionViewItem.js"; -import { - IMenuService, - MenuId, -} from "../../../../../../platform/actions/common/actions.js"; -import { ICommandService } from "../../../../../../platform/commands/common/commands.js"; -import { IConfigurationService } from "../../../../../../platform/configuration/common/configuration.js"; -import { IContextKeyService } from "../../../../../../platform/contextkey/common/contextkey.js"; -import { IContextMenuService } from "../../../../../../platform/contextview/browser/contextView.js"; -import { IHoverService } from "../../../../../../platform/hover/browser/hover.js"; -import { IInstantiationService } from "../../../../../../platform/instantiation/common/instantiation.js"; -import { IKeybindingService } from "../../../../../../platform/keybinding/common/keybinding.js"; -import { WorkbenchAsyncDataTree } from "../../../../../../platform/list/browser/listService.js"; -import { IOpenerService } from "../../../../../../platform/opener/common/opener.js"; -import { IQuickInputService } from "../../../../../../platform/quickinput/common/quickInput.js"; -import { ITelemetryService } from "../../../../../../platform/telemetry/common/telemetry.js"; -import { IThemeService } from "../../../../../../platform/theme/common/themeService.js"; -import { - IViewPaneOptions, - ViewPane, -} from "../../../../../browser/parts/views/viewPane.js"; -import { - IEditorCloseEvent, - IEditorPane, -} from "../../../../../common/editor.js"; -import { IViewDescriptorService } from "../../../../../common/views.js"; -import { IEditorService } from "../../../../../services/editor/common/editorService.js"; -import { - CONTEXT_VARIABLE_EXTENSIONID, - CONTEXT_VARIABLE_INTERFACES, - CONTEXT_VARIABLE_LANGUAGE, - CONTEXT_VARIABLE_NAME, - CONTEXT_VARIABLE_TYPE, - CONTEXT_VARIABLE_VALUE, -} from "../../../../debug/common/debug.js"; -import { NotebookTextModel } from "../../../common/model/notebookTextModel.js"; -import { isCompositeNotebookEditorInput } from "../../../common/notebookEditorInput.js"; -import { - ICellExecutionStateChangedEvent, - IExecutionStateChangedEvent, - INotebookExecutionStateService, -} from "../../../common/notebookExecutionStateService.js"; -import { INotebookKernelService } from "../../../common/notebookKernelService.js"; -import { getNotebookEditorFromEditorPane } from "../../notebookBrowser.js"; -import { - IEmptyScope, - INotebookScope, - INotebookVariableElement, - NotebookVariableDataSource, -} from "./notebookVariablesDataSource.js"; -import { - NotebookVariableAccessibilityProvider, - NotebookVariableRenderer, - NotebookVariablesDelegate, -} from "./notebookVariablesTree.js"; - -export type contextMenuArg = { - source: string; - name: string; - type?: string; - value?: string; - expression?: string; - language?: string; - extensionId?: string; -}; +import { ITreeContextMenuEvent } from '../../../../../../base/browser/ui/tree/tree.js'; +import { RunOnceScheduler } from '../../../../../../base/common/async.js'; +import { URI } from '../../../../../../base/common/uri.js'; +import * as nls from '../../../../../../nls.js'; +import { ILocalizedString } from '../../../../../../platform/action/common/action.js'; +import { getFlatContextMenuActions } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { IMenuService, MenuId } from '../../../../../../platform/actions/common/actions.js'; +import { ICommandService } from '../../../../../../platform/commands/common/commands.js'; +import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; +import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; +import { IContextMenuService } from '../../../../../../platform/contextview/browser/contextView.js'; +import { IHoverService } from '../../../../../../platform/hover/browser/hover.js'; +import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; +import { IKeybindingService } from '../../../../../../platform/keybinding/common/keybinding.js'; +import { WorkbenchAsyncDataTree } from '../../../../../../platform/list/browser/listService.js'; +import { IOpenerService } from '../../../../../../platform/opener/common/opener.js'; +import { IQuickInputService } from '../../../../../../platform/quickinput/common/quickInput.js'; +import { ITelemetryService } from '../../../../../../platform/telemetry/common/telemetry.js'; +import { IThemeService } from '../../../../../../platform/theme/common/themeService.js'; +import { IViewPaneOptions, ViewPane } from '../../../../../browser/parts/views/viewPane.js'; +import { IViewDescriptorService } from '../../../../../common/views.js'; +import { CONTEXT_VARIABLE_EXTENSIONID, CONTEXT_VARIABLE_INTERFACES, CONTEXT_VARIABLE_LANGUAGE, CONTEXT_VARIABLE_NAME, CONTEXT_VARIABLE_TYPE, CONTEXT_VARIABLE_VALUE } from '../../../../debug/common/debug.js'; +import { IEmptyScope, INotebookScope, INotebookVariableElement, NotebookVariableDataSource } from './notebookVariablesDataSource.js'; +import { NotebookVariableAccessibilityProvider, NotebookVariableRenderer, NotebookVariablesDelegate } from './notebookVariablesTree.js'; +import { getNotebookEditorFromEditorPane } from '../../notebookBrowser.js'; +import { NotebookTextModel } from '../../../common/model/notebookTextModel.js'; +import { ICellExecutionStateChangedEvent, IExecutionStateChangedEvent, INotebookExecutionStateService } from '../../../common/notebookExecutionStateService.js'; +import { INotebookKernelService } from '../../../common/notebookKernelService.js'; +import { IEditorService } from '../../../../../services/editor/common/editorService.js'; +import { IEditorCloseEvent, IEditorPane } from '../../../../../common/editor.js'; +import { isCompositeNotebookEditorInput } from '../../../common/notebookEditorInput.js'; + +export type contextMenuArg = { source: string; name: string; type?: string; value?: string; expression?: string; language?: string; extensionId?: string }; export class NotebookVariablesView extends ViewPane { - static readonly ID = "notebookVariablesView"; - static readonly NOTEBOOK_TITLE: ILocalizedString = nls.localize2( - "notebook.notebookVariables", - "Notebook Variables", - ); - static readonly REPL_TITLE: ILocalizedString = nls.localize2( - "notebook.ReplVariables", - "REPL Variables", - ); - private tree: - | WorkbenchAsyncDataTree< - INotebookScope | IEmptyScope, - INotebookVariableElement - > - | undefined; + static readonly ID = 'notebookVariablesView'; + static readonly NOTEBOOK_TITLE: ILocalizedString = nls.localize2('notebook.notebookVariables', "Notebook Variables"); + static readonly REPL_TITLE: ILocalizedString = nls.localize2('notebook.ReplVariables', "REPL Variables"); + + private tree: WorkbenchAsyncDataTree | undefined; private activeNotebook: NotebookTextModel | undefined; private readonly dataSource: NotebookVariableDataSource; @@ -99,10 +52,8 @@ export class NotebookVariablesView extends ViewPane { constructor( options: IViewPaneOptions, @IEditorService private readonly editorService: IEditorService, - @INotebookKernelService - private readonly notebookKernelService: INotebookKernelService, - @INotebookExecutionStateService - private readonly notebookExecutionStateService: INotebookExecutionStateService, + @INotebookKernelService private readonly notebookKernelService: INotebookKernelService, + @INotebookExecutionStateService private readonly notebookExecutionStateService: INotebookExecutionStateService, @IKeybindingService keybindingService: IKeybindingService, @IContextMenuService contextMenuService: IContextMenuService, @IContextKeyService contextKeyService: IContextKeyService, @@ -115,95 +66,46 @@ export class NotebookVariablesView extends ViewPane { @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, @IHoverService hoverService: IHoverService, - @IMenuService private readonly menuService: IMenuService, + @IMenuService private readonly menuService: IMenuService ) { - super( - options, - keybindingService, - contextMenuService, - configurationService, - contextKeyService, - viewDescriptorService, - instantiationService, - openerService, - themeService, - telemetryService, - hoverService, - ); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService); - this._register( - this.editorService.onDidActiveEditorChange(() => - this.handleActiveEditorChange(), - ), - ); - this._register( - this.notebookKernelService.onDidNotebookVariablesUpdate( - this.handleVariablesChanged.bind(this), - ), - ); - this._register( - this.notebookExecutionStateService.onDidChangeExecution( - this.handleExecutionStateChange.bind(this), - ), - ); - this._register( - this.editorService.onDidCloseEditor((e) => - this.handleCloseEditor(e), - ), - ); + this._register(this.editorService.onDidActiveEditorChange(() => this.handleActiveEditorChange())); + this._register(this.notebookKernelService.onDidNotebookVariablesUpdate(this.handleVariablesChanged.bind(this))); + this._register(this.notebookExecutionStateService.onDidChangeExecution(this.handleExecutionStateChange.bind(this))); + this._register(this.editorService.onDidCloseEditor((e) => this.handleCloseEditor(e))); this.handleActiveEditorChange(false); - this.dataSource = new NotebookVariableDataSource( - this.notebookKernelService, - ); - this.updateScheduler = new RunOnceScheduler( - () => this.tree?.updateChildren(), - 100, - ); + this.dataSource = new NotebookVariableDataSource(this.notebookKernelService); + this.updateScheduler = new RunOnceScheduler(() => this.tree?.updateChildren(), 100); } protected override renderBody(container: HTMLElement): void { super.renderBody(container); - this.element.classList.add("debug-pane"); + this.element.classList.add('debug-pane'); - this.tree = < - WorkbenchAsyncDataTree< - INotebookScope | IEmptyScope, - INotebookVariableElement - > - >this.instantiationService.createInstance( - WorkbenchAsyncDataTree, - "notebookVariablesTree", + this.tree = this.instantiationService.createInstance( + WorkbenchAsyncDataTree, + 'notebookVariablesTree', container, new NotebookVariablesDelegate(), - [ - this.instantiationService.createInstance( - NotebookVariableRenderer, - ), - ], + [this.instantiationService.createInstance(NotebookVariableRenderer)], this.dataSource, { - accessibilityProvider: - new NotebookVariableAccessibilityProvider(), - identityProvider: { - getId: (e: INotebookVariableElement) => e.id, - }, - }, - ); + accessibilityProvider: new NotebookVariableAccessibilityProvider(), + identityProvider: { getId: (e: INotebookVariableElement) => e.id }, + }); this.tree.layout(); - if (this.activeNotebook) { - this.tree.setInput({ kind: "root", notebook: this.activeNotebook }); + this.tree.setInput({ kind: 'root', notebook: this.activeNotebook }); } - this._register(this.tree.onContextMenu((e) => this.onContextMenu(e))); + this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); } - private onContextMenu( - e: ITreeContextMenuEvent, - ): void { + private onContextMenu(e: ITreeContextMenuEvent): void { if (!e.element) { return; } @@ -216,7 +118,7 @@ export class NotebookVariablesView extends ViewPane { type: element.type, expression: element.expression, language: element.language, - extensionId: element.extensionId, + extensionId: element.extensionId }; const overlayedContext = this.contextKeyService.createOverlay([ @@ -225,19 +127,13 @@ export class NotebookVariablesView extends ViewPane { [CONTEXT_VARIABLE_TYPE.key, element.type], [CONTEXT_VARIABLE_INTERFACES.key, element.interfaces], [CONTEXT_VARIABLE_LANGUAGE.key, element.language], - [CONTEXT_VARIABLE_EXTENSIONID.key, element.extensionId], + [CONTEXT_VARIABLE_EXTENSIONID.key, element.extensionId] ]); - - const menuActions = this.menuService.getMenuActions( - MenuId.NotebookVariablesContext, - overlayedContext, - { arg, shouldForwardArgs: true }, - ); - + const menuActions = this.menuService.getMenuActions(MenuId.NotebookVariablesContext, overlayedContext, { arg, shouldForwardArgs: true }); const actions = getFlatContextMenuActions(menuActions); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, - getActions: () => actions, + getActions: () => actions }); } @@ -251,11 +147,7 @@ export class NotebookVariablesView extends ViewPane { this.tree?.layout(height, width); } - private setActiveNotebook( - notebookDocument: NotebookTextModel, - editor: IEditorPane, - doUpdate = true, - ) { + private setActiveNotebook(notebookDocument: NotebookTextModel, editor: IEditorPane, doUpdate = true) { this.activeNotebook = notebookDocument; if (isCompositeNotebookEditorInput(editor.input)) { @@ -265,70 +157,48 @@ export class NotebookVariablesView extends ViewPane { } if (doUpdate) { - this.tree?.setInput({ kind: "root", notebook: notebookDocument }); + this.tree?.setInput({ kind: 'root', notebook: notebookDocument }); this.updateScheduler.schedule(); } } private getActiveNotebook() { const notebookEditor = this.editorService.activeEditorPane; - - const notebookDocument = - getNotebookEditorFromEditorPane(notebookEditor)?.textModel; - - return notebookDocument && notebookEditor - ? { notebookDocument, notebookEditor } - : undefined; + const notebookDocument = getNotebookEditorFromEditorPane(notebookEditor)?.textModel; + return notebookDocument && notebookEditor ? { notebookDocument, notebookEditor } : undefined; } private handleCloseEditor(e: IEditorCloseEvent) { - if ( - e.editor.resource && - e.editor.resource.toString() === this.activeNotebook?.uri.toString() - ) { - this.tree?.setInput({ kind: "empty" }); + if (e.editor.resource && e.editor.resource.toString() === this.activeNotebook?.uri.toString()) { + this.tree?.setInput({ kind: 'empty' }); this.updateScheduler.schedule(); } } private handleActiveEditorChange(doUpdate = true) { const found = this.getActiveNotebook(); - if (found && found.notebookDocument !== this.activeNotebook) { - this.setActiveNotebook( - found.notebookDocument, - found.notebookEditor, - doUpdate, - ); + this.setActiveNotebook(found.notebookDocument, found.notebookEditor, doUpdate); } } - private handleExecutionStateChange( - event: ICellExecutionStateChangedEvent | IExecutionStateChangedEvent, - ) { - if ( - this.activeNotebook && - event.affectsNotebook(this.activeNotebook.uri) - ) { + private handleExecutionStateChange(event: ICellExecutionStateChangedEvent | IExecutionStateChangedEvent) { + if (this.activeNotebook && event.affectsNotebook(this.activeNotebook.uri)) { // new execution state means either new variables or the kernel is busy so we shouldn't ask this.dataSource.cancel(); // changed === undefined -> excecution ended if (event.changed === undefined) { this.updateScheduler.schedule(); - } else { + } + else { this.updateScheduler.cancel(); } } else if (!this.getActiveNotebook()) { // check if the updated variables are for a visible notebook - this.editorService.visibleEditorPanes.forEach((editor) => { - const notebookDocument = - getNotebookEditorFromEditorPane(editor)?.textModel; - - if ( - notebookDocument && - event.affectsNotebook(notebookDocument.uri) - ) { + this.editorService.visibleEditorPanes.forEach(editor => { + const notebookDocument = getNotebookEditorFromEditorPane(editor)?.textModel; + if (notebookDocument && event.affectsNotebook(notebookDocument.uri)) { this.setActiveNotebook(notebookDocument, editor); } }); @@ -336,21 +206,13 @@ export class NotebookVariablesView extends ViewPane { } private handleVariablesChanged(notebookUri: URI) { - if ( - this.activeNotebook && - notebookUri.toString() === this.activeNotebook.uri.toString() - ) { + if (this.activeNotebook && notebookUri.toString() === this.activeNotebook.uri.toString()) { this.updateScheduler.schedule(); } else if (!this.getActiveNotebook()) { // check if the updated variables are for a visible notebook - this.editorService.visibleEditorPanes.forEach((editor) => { - const notebookDocument = - getNotebookEditorFromEditorPane(editor)?.textModel; - - if ( - notebookDocument && - notebookDocument.uri.toString() === notebookUri.toString() - ) { + this.editorService.visibleEditorPanes.forEach(editor => { + const notebookDocument = getNotebookEditorFromEditorPane(editor)?.textModel; + if (notebookDocument && notebookDocument.uri.toString() === notebookUri.toString()) { this.setActiveNotebook(notebookDocument, editor); } }); diff --git a/Source/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts b/Source/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts index d7ecefe0caf4b..771aba2da23bb 100644 --- a/Source/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts +++ b/Source/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts @@ -174,16 +174,10 @@ export class CellContextKeyManager extends Disposable { ); if (element instanceof CodeCellViewModel) { - this.elementDisposables.add( - element.onDidChangeOutputs(() => this.updateForOutputs()), - ); - this.elementDisposables.add( - autorun((reader) => { - this.cellHasErrorDiagnostics.set( - !!reader.readObservable(element.executionError), - ); - }), - ); + this.elementDisposables.add(element.onDidChangeOutputs(() => this.updateForOutputs())); + this.elementDisposables.add(autorun(reader => { + this.cellHasErrorDiagnostics.set(!!reader.readObservable(element.executionErrorDiagnostic)); + })); } this.elementDisposables.add( diff --git a/Source/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts b/Source/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts index dd315da472ff4..0765526c8b305 100644 --- a/Source/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts +++ b/Source/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts @@ -3,116 +3,76 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { - Emitter, - Event, - PauseableEmitter, -} from "../../../../../base/common/event.js"; -import { dispose } from "../../../../../base/common/lifecycle.js"; -import { - IObservable, - observableValue, -} from "../../../../../base/common/observable.js"; -import * as UUID from "../../../../../base/common/uuid.js"; -import { ICodeEditorService } from "../../../../../editor/browser/services/codeEditorService.js"; -import * as editorCommon from "../../../../../editor/common/editorCommon.js"; -import { PrefixSumComputer } from "../../../../../editor/common/model/prefixSumComputer.js"; -import { ITextModelService } from "../../../../../editor/common/services/resolverService.js"; -import { IConfigurationService } from "../../../../../platform/configuration/common/configuration.js"; -import { IUndoRedoService } from "../../../../../platform/undoRedo/common/undoRedo.js"; -import { NotebookCellTextModel } from "../../common/model/notebookCellTextModel.js"; -import { - CellKind, - INotebookFindOptions, - NotebookCellOutputsSplice, -} from "../../common/notebookCommon.js"; -import { - ICellExecutionError, - ICellExecutionStateChangedEvent, -} from "../../common/notebookExecutionStateService.js"; -import { INotebookService } from "../../common/notebookService.js"; -import { - CellEditState, - CellFindMatch, - CellLayoutState, - CodeCellLayoutChangeEvent, - CodeCellLayoutInfo, - ICellOutputViewModel, - ICellViewModel, -} from "../notebookBrowser.js"; -import { NotebookOptionsChangeEvent } from "../notebookOptions.js"; -import { NotebookLayoutInfo } from "../notebookViewEvents.js"; -import { BaseCellViewModel } from "./baseCellViewModel.js"; -import { CellOutputViewModel } from "./cellOutputViewModel.js"; -import { ViewContext } from "./viewContext.js"; +import { Emitter, Event, PauseableEmitter } from '../../../../../base/common/event.js'; +import { dispose } from '../../../../../base/common/lifecycle.js'; +import { observableValue } from '../../../../../base/common/observable.js'; +import * as UUID from '../../../../../base/common/uuid.js'; +import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js'; +import * as editorCommon from '../../../../../editor/common/editorCommon.js'; +import { PrefixSumComputer } from '../../../../../editor/common/model/prefixSumComputer.js'; +import { ITextModelService } from '../../../../../editor/common/services/resolverService.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { IUndoRedoService } from '../../../../../platform/undoRedo/common/undoRedo.js'; +import { CellEditState, CellFindMatch, CellLayoutState, CodeCellLayoutChangeEvent, CodeCellLayoutInfo, ICellOutputViewModel, ICellViewModel } from '../notebookBrowser.js'; +import { NotebookOptionsChangeEvent } from '../notebookOptions.js'; +import { NotebookLayoutInfo } from '../notebookViewEvents.js'; +import { CellOutputViewModel } from './cellOutputViewModel.js'; +import { ViewContext } from './viewContext.js'; +import { NotebookCellTextModel } from '../../common/model/notebookCellTextModel.js'; +import { CellKind, INotebookFindOptions, NotebookCellOutputsSplice } from '../../common/notebookCommon.js'; +import { ICellExecutionError, ICellExecutionStateChangedEvent } from '../../common/notebookExecutionStateService.js'; +import { INotebookService } from '../../common/notebookService.js'; +import { BaseCellViewModel } from './baseCellViewModel.js'; export const outputDisplayLimit = 500; -export class CodeCellViewModel - extends BaseCellViewModel - implements ICellViewModel -{ +export class CodeCellViewModel extends BaseCellViewModel implements ICellViewModel { readonly cellKind = CellKind.Code; protected readonly _onLayoutInfoRead = this._register(new Emitter()); readonly onLayoutInfoRead = this._onLayoutInfoRead.event; - protected readonly _onDidStartExecution = this._register( - new Emitter(), - ); + protected readonly _onDidStartExecution = this._register(new Emitter()); readonly onDidStartExecution = this._onDidStartExecution.event; - protected readonly _onDidStopExecution = this._register( - new Emitter(), - ); + protected readonly _onDidStopExecution = this._register(new Emitter()); readonly onDidStopExecution = this._onDidStopExecution.event; - protected readonly _onDidChangeOutputs = this._register( - new Emitter(), - ); + protected readonly _onDidChangeOutputs = this._register(new Emitter()); readonly onDidChangeOutputs = this._onDidChangeOutputs.event; - private readonly _onDidRemoveOutputs = this._register( - new Emitter(), - ); + private readonly _onDidRemoveOutputs = this._register(new Emitter()); readonly onDidRemoveOutputs = this._onDidRemoveOutputs.event; private _outputCollection: number[] = []; private _outputsTop: PrefixSumComputer | null = null; - protected _pauseableEmitter = this._register( - new PauseableEmitter(), - ); + protected _pauseableEmitter = this._register(new PauseableEmitter()); readonly onDidChangeLayout = this._pauseableEmitter.event; private _editorHeight = 0; - set editorHeight(height: number) { if (this._editorHeight === height) { return; } this._editorHeight = height; - this.layoutChange( - { editorHeight: true }, - "CodeCellViewModel#editorHeight", - ); + this.layoutChange({ editorHeight: true }, 'CodeCellViewModel#editorHeight'); } get editorHeight() { - throw new Error("editorHeight is write-only"); + throw new Error('editorHeight is write-only'); } private _chatHeight = 0; - set chatHeight(height: number) { if (this._chatHeight === height) { return; } this._chatHeight = height; - this.layoutChange({ chatHeight: true }, "CodeCellViewModel#chatHeight"); + this.layoutChange({ chatHeight: true }, 'CodeCellViewModel#chatHeight'); } get chatHeight() { @@ -174,13 +134,7 @@ export class CodeCellViewModel return this._outputViewModels; } - get executionError(): IObservable { - return this._executionError; - } - - private readonly _executionError = observableValue< - ICellExecutionError | undefined - >("excecutionError", undefined); + readonly executionErrorDiagnostic = observableValue('excecutionError', undefined); constructor( viewType: string, @@ -191,78 +145,34 @@ export class CodeCellViewModel @INotebookService private readonly _notebookService: INotebookService, @ITextModelService modelService: ITextModelService, @IUndoRedoService undoRedoService: IUndoRedoService, - @ICodeEditorService codeEditorService: ICodeEditorService, + @ICodeEditorService codeEditorService: ICodeEditorService ) { - super( - viewType, - model, - UUID.generateUuid(), - viewContext, - configurationService, - modelService, - undoRedoService, - codeEditorService, - ); - this._outputViewModels = this.model.outputs.map( - (output) => - new CellOutputViewModel(this, output, this._notebookService), - ); - - this._register( - this.model.onDidChangeOutputs((splice) => { - const removedOutputs: ICellOutputViewModel[] = []; - - let outputLayoutChange = false; - - for ( - let i = splice.start; - i < splice.start + splice.deleteCount; - i++ - ) { - if ( - this._outputCollection[i] !== undefined && - this._outputCollection[i] !== 0 - ) { - outputLayoutChange = true; - } + super(viewType, model, UUID.generateUuid(), viewContext, configurationService, modelService, undoRedoService, codeEditorService); + this._outputViewModels = this.model.outputs.map(output => new CellOutputViewModel(this, output, this._notebookService)); + + this._register(this.model.onDidChangeOutputs((splice) => { + const removedOutputs: ICellOutputViewModel[] = []; + let outputLayoutChange = false; + for (let i = splice.start; i < splice.start + splice.deleteCount; i++) { + if (this._outputCollection[i] !== undefined && this._outputCollection[i] !== 0) { + outputLayoutChange = true; } + } - this._outputCollection.splice( - splice.start, - splice.deleteCount, - ...splice.newOutputs.map(() => 0), - ); - removedOutputs.push( - ...this._outputViewModels.splice( - splice.start, - splice.deleteCount, - ...splice.newOutputs.map( - (output) => - new CellOutputViewModel( - this, - output, - this._notebookService, - ), - ), - ), - ); - - this._outputsTop = null; - this._onDidChangeOutputs.fire(splice); - this._onDidRemoveOutputs.fire(removedOutputs); - - if (outputLayoutChange) { - this.layoutChange( - { outputHeight: true }, - "CodeCellViewModel#model.onDidChangeOutputs", - ); - } - if (!this._outputCollection.length) { - this._executionError.set(undefined, undefined); - } - dispose(removedOutputs); - }), - ); + this._outputCollection.splice(splice.start, splice.deleteCount, ...splice.newOutputs.map(() => 0)); + removedOutputs.push(...this._outputViewModels.splice(splice.start, splice.deleteCount, ...splice.newOutputs.map(output => new CellOutputViewModel(this, output, this._notebookService)))); + + this._outputsTop = null; + this._onDidChangeOutputs.fire(splice); + this._onDidRemoveOutputs.fire(removedOutputs); + if (outputLayoutChange) { + this.layoutChange({ outputHeight: true }, 'CodeCellViewModel#model.onDidChangeOutputs'); + } + if (!this._outputCollection.length) { + this.executionErrorDiagnostic.set(undefined, undefined); + } + dispose(removedOutputs); + })); this._outputCollection = new Array(this.model.outputs.length); @@ -270,9 +180,7 @@ export class CodeCellViewModel fontInfo: initialNotebookLayoutInfo?.fontInfo || null, editorHeight: 0, editorWidth: initialNotebookLayoutInfo - ? this.viewContext.notebookOptions.computeCodeCellEditorWidth( - initialNotebookLayoutInfo.width, - ) + ? this.viewContext.notebookOptions.computeCodeCellEditorWidth(initialNotebookLayoutInfo.width) : 0, chatHeight: 0, statusBarHeight: 0, @@ -287,35 +195,22 @@ export class CodeCellViewModel outputIndicatorHeight: 0, bottomToolbarOffset: 0, layoutState: CellLayoutState.Uninitialized, - estimatedHasHorizontalScrolling: false, + estimatedHasHorizontalScrolling: false }; } updateExecutionState(e: ICellExecutionStateChangedEvent) { if (e.changed) { - this._executionError.set(undefined, undefined); + this.executionErrorDiagnostic.set(undefined, undefined); this._onDidStartExecution.fire(e); } else { this._onDidStopExecution.fire(e); - - if ( - this.internalMetadata.lastRunSuccess === false && - this.internalMetadata.error - ) { - const metadata = this.internalMetadata; - this._executionError.set(metadata.error, undefined); - } } } override updateOptions(e: NotebookOptionsChangeEvent) { super.updateOptions(e); - - if ( - e.cellStatusBarVisibility || - e.insertToolbarPosition || - e.cellToolbarLocation - ) { + if (e.cellStatusBarVisibility || e.insertToolbarPosition || e.cellToolbarLocation) { this.layoutChange({}); } } @@ -331,127 +226,56 @@ export class CodeCellViewModel layoutChange(state: CodeCellLayoutChangeEvent, source?: string) { // recompute this._ensureOutputsTop(); - - const notebookLayoutConfiguration = - this.viewContext.notebookOptions.getLayoutConfiguration(); - - const bottomToolbarDimensions = - this.viewContext.notebookOptions.computeBottomToolbarDimensions( - this.viewType, - ); - - const outputShowMoreContainerHeight = - state.outputShowMoreContainerHeight - ? state.outputShowMoreContainerHeight - : this._layoutInfo.outputShowMoreContainerHeight; - - const outputTotalHeight = Math.max( - this._outputMinHeight, - this.isOutputCollapsed - ? notebookLayoutConfiguration.collapsedIndicatorHeight - : this._outputsTop!.getTotalSum(), - ); - - const commentHeight = state.commentHeight - ? this._commentHeight - : this._layoutInfo.commentHeight; + const notebookLayoutConfiguration = this.viewContext.notebookOptions.getLayoutConfiguration(); + const bottomToolbarDimensions = this.viewContext.notebookOptions.computeBottomToolbarDimensions(this.viewType); + const outputShowMoreContainerHeight = state.outputShowMoreContainerHeight ? state.outputShowMoreContainerHeight : this._layoutInfo.outputShowMoreContainerHeight; + const outputTotalHeight = Math.max(this._outputMinHeight, this.isOutputCollapsed ? notebookLayoutConfiguration.collapsedIndicatorHeight : this._outputsTop!.getTotalSum()); + const commentHeight = state.commentHeight ? this._commentHeight : this._layoutInfo.commentHeight; const originalLayout = this.layoutInfo; - if (!this.isInputCollapsed) { let newState: CellLayoutState; - let editorHeight: number; - let totalHeight: number; - let hasHorizontalScrolling = false; - - const chatHeight = state.chatHeight - ? this._chatHeight - : this._layoutInfo.chatHeight; - - if ( - !state.editorHeight && - this._layoutInfo.layoutState === CellLayoutState.FromCache && - !state.outputHeight - ) { + const chatHeight = state.chatHeight ? this._chatHeight : this._layoutInfo.chatHeight; + if (!state.editorHeight && this._layoutInfo.layoutState === CellLayoutState.FromCache && !state.outputHeight) { // No new editorHeight info - keep cached totalHeight and estimate editorHeight - const estimate = this.estimateEditorHeight( - state.font?.lineHeight ?? - this._layoutInfo.fontInfo?.lineHeight, - ); + const estimate = this.estimateEditorHeight(state.font?.lineHeight ?? this._layoutInfo.fontInfo?.lineHeight); editorHeight = estimate.editorHeight; hasHorizontalScrolling = estimate.hasHorizontalScrolling; totalHeight = this._layoutInfo.totalHeight; newState = CellLayoutState.FromCache; - } else if ( - state.editorHeight || - this._layoutInfo.layoutState === CellLayoutState.Measured - ) { + } else if (state.editorHeight || this._layoutInfo.layoutState === CellLayoutState.Measured) { // Editor has been measured editorHeight = this._editorHeight; - totalHeight = this.computeTotalHeight( - this._editorHeight, - outputTotalHeight, - outputShowMoreContainerHeight, - chatHeight, - ); + totalHeight = this.computeTotalHeight(this._editorHeight, outputTotalHeight, outputShowMoreContainerHeight, chatHeight); newState = CellLayoutState.Measured; - hasHorizontalScrolling = - this._layoutInfo.estimatedHasHorizontalScrolling; + hasHorizontalScrolling = this._layoutInfo.estimatedHasHorizontalScrolling; } else { - const estimate = this.estimateEditorHeight( - state.font?.lineHeight ?? - this._layoutInfo.fontInfo?.lineHeight, - ); + const estimate = this.estimateEditorHeight(state.font?.lineHeight ?? this._layoutInfo.fontInfo?.lineHeight); editorHeight = estimate.editorHeight; hasHorizontalScrolling = estimate.hasHorizontalScrolling; - totalHeight = this.computeTotalHeight( - editorHeight, - outputTotalHeight, - outputShowMoreContainerHeight, - chatHeight, - ); + totalHeight = this.computeTotalHeight(editorHeight, outputTotalHeight, outputShowMoreContainerHeight, chatHeight); newState = CellLayoutState.Estimated; } - const statusBarHeight = - this.viewContext.notebookOptions.computeEditorStatusbarHeight( - this.internalMetadata, - this.uri, - ); - + const statusBarHeight = this.viewContext.notebookOptions.computeEditorStatusbarHeight(this.internalMetadata, this.uri); const codeIndicatorHeight = editorHeight + statusBarHeight; - - const outputIndicatorHeight = - outputTotalHeight + outputShowMoreContainerHeight; - - const outputContainerOffset = - notebookLayoutConfiguration.editorToolbarHeight + - notebookLayoutConfiguration.cellTopMargin + // CELL_TOP_MARGIN - chatHeight + - editorHeight + - statusBarHeight; - - const outputShowMoreContainerOffset = - totalHeight - - bottomToolbarDimensions.bottomToolbarGap - - bottomToolbarDimensions.bottomToolbarHeight / 2 - - outputShowMoreContainerHeight; - - const bottomToolbarOffset = - this.viewContext.notebookOptions.computeBottomToolbarOffset( - totalHeight, - this.viewType, - ); - - const editorWidth = - state.outerWidth !== undefined - ? this.viewContext.notebookOptions.computeCodeCellEditorWidth( - state.outerWidth, - ) - : this._layoutInfo?.editorWidth; + const outputIndicatorHeight = outputTotalHeight + outputShowMoreContainerHeight; + const outputContainerOffset = notebookLayoutConfiguration.editorToolbarHeight + + notebookLayoutConfiguration.cellTopMargin // CELL_TOP_MARGIN + + chatHeight + + editorHeight + + statusBarHeight; + const outputShowMoreContainerOffset = totalHeight + - bottomToolbarDimensions.bottomToolbarGap + - bottomToolbarDimensions.bottomToolbarHeight / 2 + - outputShowMoreContainerHeight; + const bottomToolbarOffset = this.viewContext.notebookOptions.computeBottomToolbarOffset(totalHeight, this.viewType); + const editorWidth = state.outerWidth !== undefined + ? this.viewContext.notebookOptions.computeCodeCellEditorWidth(state.outerWidth) + : this._layoutInfo?.editorWidth; this._layoutInfo = { fontInfo: state.font ?? this._layoutInfo.fontInfo ?? null, @@ -470,51 +294,30 @@ export class CodeCellViewModel outputIndicatorHeight, bottomToolbarOffset, layoutState: newState, - estimatedHasHorizontalScrolling: hasHorizontalScrolling, + estimatedHasHorizontalScrolling: hasHorizontalScrolling }; } else { - const codeIndicatorHeight = - notebookLayoutConfiguration.collapsedIndicatorHeight; - - const outputIndicatorHeight = - outputTotalHeight + outputShowMoreContainerHeight; - - const chatHeight = state.chatHeight - ? this._chatHeight - : this._layoutInfo.chatHeight; - - const outputContainerOffset = - notebookLayoutConfiguration.cellTopMargin + - notebookLayoutConfiguration.collapsedIndicatorHeight; + const codeIndicatorHeight = notebookLayoutConfiguration.collapsedIndicatorHeight; + const outputIndicatorHeight = outputTotalHeight + outputShowMoreContainerHeight; + const chatHeight = state.chatHeight ? this._chatHeight : this._layoutInfo.chatHeight; + const outputContainerOffset = notebookLayoutConfiguration.cellTopMargin + notebookLayoutConfiguration.collapsedIndicatorHeight; const totalHeight = - notebookLayoutConfiguration.cellTopMargin + - notebookLayoutConfiguration.collapsedIndicatorHeight + - notebookLayoutConfiguration.cellBottomMargin + //CELL_BOTTOM_MARGIN - bottomToolbarDimensions.bottomToolbarGap + //BOTTOM_CELL_TOOLBAR_GAP - chatHeight + - commentHeight + - outputTotalHeight + - outputShowMoreContainerHeight; - - const outputShowMoreContainerOffset = - totalHeight - - bottomToolbarDimensions.bottomToolbarGap - - bottomToolbarDimensions.bottomToolbarHeight / 2 - - outputShowMoreContainerHeight; - - const bottomToolbarOffset = - this.viewContext.notebookOptions.computeBottomToolbarOffset( - totalHeight, - this.viewType, - ); - - const editorWidth = - state.outerWidth !== undefined - ? this.viewContext.notebookOptions.computeCodeCellEditorWidth( - state.outerWidth, - ) - : this._layoutInfo?.editorWidth; + notebookLayoutConfiguration.cellTopMargin + + notebookLayoutConfiguration.collapsedIndicatorHeight + + notebookLayoutConfiguration.cellBottomMargin //CELL_BOTTOM_MARGIN + + bottomToolbarDimensions.bottomToolbarGap //BOTTOM_CELL_TOOLBAR_GAP + + chatHeight + + commentHeight + + outputTotalHeight + outputShowMoreContainerHeight; + const outputShowMoreContainerOffset = totalHeight + - bottomToolbarDimensions.bottomToolbarGap + - bottomToolbarDimensions.bottomToolbarHeight / 2 + - outputShowMoreContainerHeight; + const bottomToolbarOffset = this.viewContext.notebookOptions.computeBottomToolbarOffset(totalHeight, this.viewType); + const editorWidth = state.outerWidth !== undefined + ? this.viewContext.notebookOptions.computeCodeCellEditorWidth(state.outerWidth) + : this._layoutInfo?.editorWidth; this._layoutInfo = { fontInfo: state.font ?? this._layoutInfo.fontInfo ?? null, @@ -533,14 +336,13 @@ export class CodeCellViewModel outputIndicatorHeight, bottomToolbarOffset, layoutState: this._layoutInfo.layoutState, - estimatedHasHorizontalScrolling: false, + estimatedHasHorizontalScrolling: false }; } this._fireOnDidChangeLayout({ ...state, - totalHeight: - this.layoutInfo.totalHeight !== originalLayout.totalHeight, + totalHeight: this.layoutInfo.totalHeight !== originalLayout.totalHeight, source, }); } @@ -549,16 +351,9 @@ export class CodeCellViewModel this._pauseableEmitter.fire(state); } - override restoreEditorViewState( - editorViewStates: editorCommon.ICodeEditorViewState | null, - totalHeight?: number, - ) { + override restoreEditorViewState(editorViewStates: editorCommon.ICodeEditorViewState | null, totalHeight?: number) { super.restoreEditorViewState(editorViewStates); - - if ( - totalHeight !== undefined && - this._layoutInfo.layoutState !== CellLayoutState.Measured - ) { + if (totalHeight !== undefined && this._layoutInfo.layoutState !== CellLayoutState.Measured) { this._layoutInfo = { ...this._layoutInfo, totalHeight: totalHeight, @@ -569,121 +364,72 @@ export class CodeCellViewModel getDynamicHeight() { this._onLayoutInfoRead.fire(); - return this._layoutInfo.totalHeight; } getHeight(lineHeight: number) { if (this._layoutInfo.layoutState === CellLayoutState.Uninitialized) { const estimate = this.estimateEditorHeight(lineHeight); - return this.computeTotalHeight(estimate.editorHeight, 0, 0, 0); } else { return this._layoutInfo.totalHeight; } } - private estimateEditorHeight(lineHeight: number | undefined = 20): { - editorHeight: number; - hasHorizontalScrolling: boolean; - } { + private estimateEditorHeight(lineHeight: number | undefined = 20): { editorHeight: number; hasHorizontalScrolling: boolean } { let hasHorizontalScrolling = false; - - const cellEditorOptions = this.viewContext.getBaseCellEditorOptions( - this.language, - ); - - if ( - this.layoutInfo.fontInfo && - cellEditorOptions.value.wordWrap === "off" - ) { + const cellEditorOptions = this.viewContext.getBaseCellEditorOptions(this.language); + if (this.layoutInfo.fontInfo && cellEditorOptions.value.wordWrap === 'off') { for (let i = 0; i < this.lineCount; i++) { - const max = this.textBuffer.getLineLastNonWhitespaceColumn( - i + 1, - ); - - const estimatedWidth = - max * - (this.layoutInfo.fontInfo.typicalHalfwidthCharacterWidth + - this.layoutInfo.fontInfo.letterSpacing); - + const max = this.textBuffer.getLineLastNonWhitespaceColumn(i + 1); + const estimatedWidth = max * (this.layoutInfo.fontInfo.typicalHalfwidthCharacterWidth + this.layoutInfo.fontInfo.letterSpacing); if (estimatedWidth > this.layoutInfo.editorWidth) { hasHorizontalScrolling = true; - break; } } } const verticalScrollbarHeight = hasHorizontalScrolling ? 12 : 0; // take zoom level into account - const editorPadding = - this.viewContext.notebookOptions.computeEditorPadding( - this.internalMetadata, - this.uri, - ); - - const editorHeight = - this.lineCount * lineHeight + - editorPadding.top + - editorPadding.bottom + // EDITOR_BOTTOM_PADDING - verticalScrollbarHeight; - + const editorPadding = this.viewContext.notebookOptions.computeEditorPadding(this.internalMetadata, this.uri); + const editorHeight = this.lineCount * lineHeight + + editorPadding.top + + editorPadding.bottom // EDITOR_BOTTOM_PADDING + + verticalScrollbarHeight; return { editorHeight, - hasHorizontalScrolling, + hasHorizontalScrolling }; } - private computeTotalHeight( - editorHeight: number, - outputsTotalHeight: number, - outputShowMoreContainerHeight: number, - chatHeight: number, - ): number { - const layoutConfiguration = - this.viewContext.notebookOptions.getLayoutConfiguration(); - - const { bottomToolbarGap } = - this.viewContext.notebookOptions.computeBottomToolbarDimensions( - this.viewType, - ); - - return ( - layoutConfiguration.editorToolbarHeight + - layoutConfiguration.cellTopMargin + - chatHeight + - editorHeight + - this.viewContext.notebookOptions.computeEditorStatusbarHeight( - this.internalMetadata, - this.uri, - ) + - this._commentHeight + - outputsTotalHeight + - outputShowMoreContainerHeight + - bottomToolbarGap + - layoutConfiguration.cellBottomMargin - ); + private computeTotalHeight(editorHeight: number, outputsTotalHeight: number, outputShowMoreContainerHeight: number, chatHeight: number): number { + const layoutConfiguration = this.viewContext.notebookOptions.getLayoutConfiguration(); + const { bottomToolbarGap } = this.viewContext.notebookOptions.computeBottomToolbarDimensions(this.viewType); + return layoutConfiguration.editorToolbarHeight + + layoutConfiguration.cellTopMargin + + chatHeight + + editorHeight + + this.viewContext.notebookOptions.computeEditorStatusbarHeight(this.internalMetadata, this.uri) + + this._commentHeight + + outputsTotalHeight + + outputShowMoreContainerHeight + + bottomToolbarGap + + layoutConfiguration.cellBottomMargin; } protected onDidChangeTextModelContent(): void { if (this.getEditState() !== CellEditState.Editing) { - this.updateEditState( - CellEditState.Editing, - "onDidChangeTextModelContent", - ); + this.updateEditState(CellEditState.Editing, 'onDidChangeTextModelContent'); this._onDidChangeState.fire({ contentChanged: true }); } } onDeselect() { - this.updateEditState(CellEditState.Preview, "onDeselect"); + this.updateEditState(CellEditState.Preview, 'onDeselect'); } updateOutputShowMoreContainerHeight(height: number) { - this.layoutChange( - { outputShowMoreContainerHeight: height }, - "CodeCellViewModel#updateOutputShowMoreContainerHeight", - ); + this.layoutChange({ outputShowMoreContainerHeight: height }, 'CodeCellViewModel#updateOutputShowMoreContainerHeight'); } updateOutputMinHeight(height: number) { @@ -697,7 +443,7 @@ export class CodeCellViewModel updateOutputHeight(index: number, height: number, source?: string) { if (index >= this._outputCollection.length) { - throw new Error("Output index out of range!"); + throw new Error('Output index out of range!'); } this._ensureOutputsTop(); @@ -709,10 +455,8 @@ export class CodeCellViewModel this._outputViewModels[index].setVisible(false); } } catch (e) { - const errorMessage = - `Failed to update output height for cell ${this.handle}, output ${index}. ` + - `this.outputCollection.length: ${this._outputCollection.length}, this._outputViewModels.length: ${this._outputViewModels.length}`; - + const errorMessage = `Failed to update output height for cell ${this.handle}, output ${index}. ` + + `this.outputCollection.length: ${this._outputCollection.length}, this._outputViewModels.length: ${this._outputViewModels.length}`; throw new Error(`${errorMessage}.\n Error: ${e.message}`); } @@ -721,7 +465,6 @@ export class CodeCellViewModel } this._outputCollection[index] = height; - if (this._outputsTop!.setValue(index, height)) { this.layoutChange({ outputHeight: true }, source); } @@ -731,27 +474,22 @@ export class CodeCellViewModel this._ensureOutputsTop(); if (index >= this._outputCollection.length) { - throw new Error("Output index out of range!"); + throw new Error('Output index out of range!'); } return this._outputsTop!.getPrefixSum(index - 1); } getOutputOffset(index: number): number { - return ( - this.layoutInfo.outputContainerOffset + - this.getOutputOffsetInContainer(index) - ); + return this.layoutInfo.outputContainerOffset + this.getOutputOffsetInContainer(index); } spliceOutputHeights(start: number, deleteCnt: number, heights: number[]) { this._ensureOutputsTop(); this._outputsTop!.removeValues(start, deleteCnt); - if (heights.length) { const values = new Uint32Array(heights.length); - for (let i = 0; i < heights.length; i++) { values[i] = heights[i]; } @@ -759,16 +497,12 @@ export class CodeCellViewModel this._outputsTop!.insertValues(start, values); } - this.layoutChange( - { outputHeight: true }, - "CodeCellViewModel#spliceOutputs", - ); + this.layoutChange({ outputHeight: true }, 'CodeCellViewModel#spliceOutputs'); } private _ensureOutputsTop(): void { if (!this._outputsTop) { const values = new Uint32Array(this._outputCollection.length); - for (let i = 0; i < this._outputCollection.length; i++) { values[i] = this._outputCollection[i]; } @@ -780,10 +514,7 @@ export class CodeCellViewModel private readonly _hasFindResult = this._register(new Emitter()); public readonly hasFindResult: Event = this._hasFindResult.event; - startFind( - value: string, - options: INotebookFindOptions, - ): CellFindMatch | null { + startFind(value: string, options: INotebookFindOptions): CellFindMatch | null { const matches = super.cellStartFind(value, options); if (matches === null) { @@ -792,7 +523,7 @@ export class CodeCellViewModel return { cell: this, - contentMatches: matches, + contentMatches: matches }; } diff --git a/Source/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts b/Source/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts index c804969754c60..a21a95750b018 100644 --- a/Source/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts +++ b/Source/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts @@ -2,90 +2,53 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken } from "../../../../base/common/cancellation.js"; -import { AsyncEmitter, Emitter, Event } from "../../../../base/common/event.js"; -import { - combinedDisposable, - DisposableStore, - dispose, - IDisposable, - IReference, - ReferenceCollection, - toDisposable, -} from "../../../../base/common/lifecycle.js"; -import { ResourceMap } from "../../../../base/common/map.js"; -import { Schemas } from "../../../../base/common/network.js"; -import { assertIsDefined } from "../../../../base/common/types.js"; -import { URI } from "../../../../base/common/uri.js"; -import { IConfigurationService } from "../../../../platform/configuration/common/configuration.js"; -import { IFileReadLimits } from "../../../../platform/files/common/files.js"; -import { IInstantiationService } from "../../../../platform/instantiation/common/instantiation.js"; -import { ITelemetryService } from "../../../../platform/telemetry/common/telemetry.js"; -import { IUriIdentityService } from "../../../../platform/uriIdentity/common/uriIdentity.js"; -import { IExtensionService } from "../../../services/extensions/common/extensions.js"; -import { - FileWorkingCopyManager, - IFileWorkingCopyManager, -} from "../../../services/workingCopy/common/fileWorkingCopyManager.js"; -import { - CellUri, - IResolvedNotebookEditorModel, - NotebookEditorModelCreationOptions, - NotebookSetting, - NotebookWorkingCopyTypeIdentifier, -} from "./notebookCommon.js"; -import { - NotebookFileWorkingCopyModel, - NotebookFileWorkingCopyModelFactory, - SimpleNotebookEditorModel, -} from "./notebookEditorModel.js"; -import { - INotebookConflictEvent, - INotebookEditorModelResolverService, - IUntitledNotebookResource, -} from "./notebookEditorModelResolverService.js"; -import { INotebookLoggingService } from "./notebookLoggingService.js"; -import { NotebookProviderInfo } from "./notebookProvider.js"; -import { INotebookService } from "./notebookService.js"; - -class NotebookModelReferenceCollection extends ReferenceCollection< - Promise -> { + +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { URI } from '../../../../base/common/uri.js'; +import { CellUri, IResolvedNotebookEditorModel, NotebookEditorModelCreationOptions, NotebookSetting, NotebookWorkingCopyTypeIdentifier } from './notebookCommon.js'; +import { NotebookFileWorkingCopyModel, NotebookFileWorkingCopyModelFactory, SimpleNotebookEditorModel } from './notebookEditorModel.js'; +import { combinedDisposable, DisposableStore, dispose, IDisposable, IReference, ReferenceCollection, toDisposable } from '../../../../base/common/lifecycle.js'; +import { INotebookService } from './notebookService.js'; +import { AsyncEmitter, Emitter, Event } from '../../../../base/common/event.js'; +import { IExtensionService } from '../../../services/extensions/common/extensions.js'; +import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; +import { INotebookConflictEvent, INotebookEditorModelResolverService, IUntitledNotebookResource } from './notebookEditorModelResolverService.js'; +import { ResourceMap } from '../../../../base/common/map.js'; +import { FileWorkingCopyManager, IFileWorkingCopyManager } from '../../../services/workingCopy/common/fileWorkingCopyManager.js'; +import { Schemas } from '../../../../base/common/network.js'; +import { NotebookProviderInfo } from './notebookProvider.js'; +import { assertIsDefined } from '../../../../base/common/types.js'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { IFileReadLimits } from '../../../../platform/files/common/files.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { INotebookLoggingService } from './notebookLoggingService.js'; + +class NotebookModelReferenceCollection extends ReferenceCollection> { + private readonly _disposables = new DisposableStore(); - private readonly _workingCopyManagers = new Map< - string, - IFileWorkingCopyManager< - NotebookFileWorkingCopyModel, - NotebookFileWorkingCopyModel - > - >(); - private readonly _modelListener = new Map< - IResolvedNotebookEditorModel, - IDisposable - >(); + private readonly _workingCopyManagers = new Map>(); + private readonly _modelListener = new Map(); + private readonly _onDidSaveNotebook = new Emitter(); readonly onDidSaveNotebook: Event = this._onDidSaveNotebook.event; - private readonly _onDidChangeDirty = - new Emitter(); - readonly onDidChangeDirty: Event = - this._onDidChangeDirty.event; + + private readonly _onDidChangeDirty = new Emitter(); + readonly onDidChangeDirty: Event = this._onDidChangeDirty.event; + private readonly _dirtyStates = new ResourceMap(); - private readonly modelsToDispose = new Set(); + private readonly modelsToDispose = new Set(); constructor( - @IInstantiationService - private readonly _instantiationService: IInstantiationService, - @INotebookService - private readonly _notebookService: INotebookService, - @IConfigurationService - private readonly _configurationService: IConfigurationService, - @ITelemetryService - private readonly _telemetryService: ITelemetryService, - @INotebookLoggingService - private readonly _notebookLoggingService: INotebookLoggingService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @INotebookService private readonly _notebookService: INotebookService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @ITelemetryService private readonly _telemetryService: ITelemetryService, + @INotebookLoggingService private readonly _notebookLoggingService: INotebookLoggingService, ) { super(); } + dispose(): void { this._disposables.dispose(); this._onDidSaveNotebook.dispose(); @@ -93,107 +56,65 @@ class NotebookModelReferenceCollection extends ReferenceCollection< dispose(this._modelListener.values()); dispose(this._workingCopyManagers.values()); } + isDirty(resource: URI): boolean { return this._dirtyStates.get(resource) ?? false; } - protected async createReferencedObject( - key: string, - notebookType: string, - hasAssociatedFilePath: boolean, - limits?: IFileReadLimits, - isScratchpad?: boolean, - viewType?: string, - ): Promise { + + protected async createReferencedObject(key: string, notebookType: string, hasAssociatedFilePath: boolean, limits?: IFileReadLimits, isScratchpad?: boolean, viewType?: string): Promise { // Untrack as being disposed this.modelsToDispose.delete(key); const uri = URI.parse(key); - const workingCopyTypeId = NotebookWorkingCopyTypeIdentifier.create( - notebookType, - viewType, - ); - - let workingCopyManager = - this._workingCopyManagers.get(workingCopyTypeId); - + const workingCopyTypeId = NotebookWorkingCopyTypeIdentifier.create(notebookType, viewType); + let workingCopyManager = this._workingCopyManagers.get(workingCopyTypeId); if (!workingCopyManager) { - const factory = new NotebookFileWorkingCopyModelFactory( - notebookType, - this._notebookService, - this._configurationService, - this._telemetryService, - this._notebookLoggingService, - ); - workingCopyManager = < - IFileWorkingCopyManager< - NotebookFileWorkingCopyModel, - NotebookFileWorkingCopyModel - > - >(( - this._instantiationService.createInstance( - FileWorkingCopyManager, - workingCopyTypeId, - factory, - factory, - ) - )); - this._workingCopyManagers.set( + const factory = new NotebookFileWorkingCopyModelFactory(notebookType, this._notebookService, this._configurationService, this._telemetryService, this._notebookLoggingService); + workingCopyManager = this._instantiationService.createInstance( + FileWorkingCopyManager, workingCopyTypeId, - workingCopyManager, + factory, + factory, ); + this._workingCopyManagers.set(workingCopyTypeId, workingCopyManager); } - const isScratchpadView = - isScratchpad || - (notebookType === "interactive" && - this._configurationService.getValue( - NotebookSetting.InteractiveWindowPromptToSave, - ) !== true); - - const model = this._instantiationService.createInstance( - SimpleNotebookEditorModel, - uri, - hasAssociatedFilePath, - notebookType, - workingCopyManager, - isScratchpadView, - ); + const isScratchpadView = isScratchpad || (notebookType === 'interactive' && this._configurationService.getValue(NotebookSetting.InteractiveWindowPromptToSave) !== true); + const model = this._instantiationService.createInstance(SimpleNotebookEditorModel, uri, hasAssociatedFilePath, notebookType, workingCopyManager, isScratchpadView); const result = await model.load({ limits }); + + // Whenever a notebook model is dirty we automatically reference it so that // we can ensure that at least one reference exists. That guarantees that // a model with unsaved changes is never disposed. let onDirtyAutoReference: IReference | undefined; - this._modelListener.set( - result, - combinedDisposable( - result.onDidSave(() => - this._onDidSaveNotebook.fire(result.resource), - ), - result.onDidChangeDirty(() => { - const isDirty = result.isDirty(); - this._dirtyStates.set(result.resource, isDirty); - // isDirty -> add reference - // !isDirty -> free reference - if (isDirty && !onDirtyAutoReference) { - onDirtyAutoReference = this.acquire(key, notebookType); - } else if (onDirtyAutoReference) { - onDirtyAutoReference.dispose(); - onDirtyAutoReference = undefined; - } - this._onDidChangeDirty.fire(result); - }), - toDisposable(() => onDirtyAutoReference?.dispose()), - ), - ); + this._modelListener.set(result, combinedDisposable( + result.onDidSave(() => this._onDidSaveNotebook.fire(result.resource)), + result.onDidChangeDirty(() => { + const isDirty = result.isDirty(); + this._dirtyStates.set(result.resource, isDirty); + + // isDirty -> add reference + // !isDirty -> free reference + if (isDirty && !onDirtyAutoReference) { + onDirtyAutoReference = this.acquire(key, notebookType); + } else if (onDirtyAutoReference) { + onDirtyAutoReference.dispose(); + onDirtyAutoReference = undefined; + } + + this._onDidChangeDirty.fire(result); + }), + toDisposable(() => onDirtyAutoReference?.dispose()), + )); return result; } - protected destroyReferencedObject( - key: string, - object: Promise, - ): void { + + protected destroyReferencedObject(key: string, object: Promise): void { this.modelsToDispose.add(key); + (async () => { try { const model = await object; @@ -202,212 +123,149 @@ class NotebookModelReferenceCollection extends ReferenceCollection< // return if model has been acquired again meanwhile return; } + if (model instanceof SimpleNotebookEditorModel) { await model.canDispose(); } + if (!this.modelsToDispose.has(key)) { // return if model has been acquired again meanwhile return; } + // Finally we can dispose the model this._modelListener.get(model)?.dispose(); this._modelListener.delete(model); model.dispose(); } catch (err) { - this._notebookLoggingService.error( - "NotebookModelCollection", - "FAILED to destory notebook - " + err, - ); + this._notebookLoggingService.error('NotebookModelCollection', 'FAILED to destory notebook - ' + err); } finally { this.modelsToDispose.delete(key); // Untrack as being disposed } })(); } } -export class NotebookModelResolverServiceImpl - implements INotebookEditorModelResolverService -{ + +export class NotebookModelResolverServiceImpl implements INotebookEditorModelResolverService { + readonly _serviceBrand: undefined; + private readonly _data: NotebookModelReferenceCollection; + readonly onDidSaveNotebook: Event; readonly onDidChangeDirty: Event; - private readonly _onWillFailWithConflict = - new AsyncEmitter(); + + private readonly _onWillFailWithConflict = new AsyncEmitter(); readonly onWillFailWithConflict = this._onWillFailWithConflict.event; constructor( - @IInstantiationService - instantiationService: IInstantiationService, - @INotebookService - private readonly _notebookService: INotebookService, - @IExtensionService - private readonly _extensionService: IExtensionService, - @IUriIdentityService - private readonly _uriIdentService: IUriIdentityService, + @IInstantiationService instantiationService: IInstantiationService, + @INotebookService private readonly _notebookService: INotebookService, + @IExtensionService private readonly _extensionService: IExtensionService, + @IUriIdentityService private readonly _uriIdentService: IUriIdentityService, ) { - this._data = instantiationService.createInstance( - NotebookModelReferenceCollection, - ); + this._data = instantiationService.createInstance(NotebookModelReferenceCollection); this.onDidSaveNotebook = this._data.onDidSaveNotebook; this.onDidChangeDirty = this._data.onDidChangeDirty; } + dispose() { this._data.dispose(); } + isDirty(resource: URI): boolean { return this._data.isDirty(resource); } - private createUntitledUri(notebookType: string) { - const info = this._notebookService.getContributedNotebookType( - assertIsDefined(notebookType), - ); + private createUntitledUri(notebookType: string) { + const info = this._notebookService.getContributedNotebookType(assertIsDefined(notebookType)); if (!info) { - throw new Error("UNKNOWN notebook type: " + notebookType); + throw new Error('UNKNOWN notebook type: ' + notebookType); } - const suffix = - NotebookProviderInfo.possibleFileEnding(info.selectors) ?? ""; + const suffix = NotebookProviderInfo.possibleFileEnding(info.selectors) ?? ''; for (let counter = 1; ; counter++) { - const candidate = URI.from({ - scheme: Schemas.untitled, - path: `Untitled-${counter}${suffix}`, - query: notebookType, - }); - + const candidate = URI.from({ scheme: Schemas.untitled, path: `Untitled-${counter}${suffix}`, query: notebookType }); if (!this._notebookService.getNotebookTextModel(candidate)) { return candidate; } } } - private async validateResourceViewType( - uri: URI | undefined, - viewType: string | undefined, - ) { + + private async validateResourceViewType(uri: URI | undefined, viewType: string | undefined) { if (!uri && !viewType) { - throw new Error( - "Must provide at least one of resource or viewType", - ); + throw new Error('Must provide at least one of resource or viewType'); } + if (uri?.scheme === CellUri.scheme) { - throw new Error( - `CANNOT open a cell-uri as notebook. Tried with ${uri.toString()}`, - ); + throw new Error(`CANNOT open a cell-uri as notebook. Tried with ${uri.toString()}`); } - const resource = this._uriIdentService.asCanonicalUri( - uri ?? this.createUntitledUri(viewType!), - ); - const existingNotebook = - this._notebookService.getNotebookTextModel(resource); + const resource = this._uriIdentService.asCanonicalUri(uri ?? this.createUntitledUri(viewType!)); + const existingNotebook = this._notebookService.getNotebookTextModel(resource); if (!viewType) { if (existingNotebook) { viewType = existingNotebook.viewType; } else { await this._extensionService.whenInstalledExtensionsRegistered(); - - const providers = - this._notebookService.getContributedNotebookTypes(resource); - viewType = - providers.find( - (provider) => provider.priority === "exclusive", - )?.id ?? - providers.find( - (provider) => provider.priority === "default", - )?.id ?? + const providers = this._notebookService.getContributedNotebookTypes(resource); + viewType = providers.find(provider => provider.priority === 'exclusive')?.id ?? + providers.find(provider => provider.priority === 'default')?.id ?? providers[0]?.id; } } + if (!viewType) { throw new Error(`Missing viewType for '${resource}'`); } + if (existingNotebook && existingNotebook.viewType !== viewType) { - await this._onWillFailWithConflict.fireAsync( - { resource: resource, viewType }, - CancellationToken.None, - ); - // check again, listener should have done cleanup - const existingViewType2 = - this._notebookService.getNotebookTextModel(resource)?.viewType; + await this._onWillFailWithConflict.fireAsync({ resource: resource, viewType }, CancellationToken.None); + + // check again, listener should have done cleanup + const existingViewType2 = this._notebookService.getNotebookTextModel(resource)?.viewType; if (existingViewType2 && existingViewType2 !== viewType) { - throw new Error( - `A notebook with view type '${existingViewType2}' already exists for '${resource}', CANNOT create another notebook with view type ${viewType}`, - ); + throw new Error(`A notebook with view type '${existingViewType2}' already exists for '${resource}', CANNOT create another notebook with view type ${viewType}`); } } return { resource, viewType }; } + public async createUntitledNotebookTextModel(viewType: string) { - const resource = this._uriIdentService.asCanonicalUri( - this.createUntitledUri(viewType), - ); - - return await this._notebookService.createNotebookTextModel( - viewType, - resource, - ); + const resource = this._uriIdentService.asCanonicalUri(this.createUntitledUri(viewType)); + + return (await this._notebookService.createNotebookTextModel(viewType, resource)); } - async resolve( - resource: URI, - viewType?: string, - options?: NotebookEditorModelCreationOptions, - ): Promise>; - - async resolve( - resource: IUntitledNotebookResource, - viewType: string, - options: NotebookEditorModelCreationOptions, - ): Promise>; - - async resolve( - arg0: URI | IUntitledNotebookResource, - viewType?: string, - options?: NotebookEditorModelCreationOptions, - ): Promise> { - let resource: URI | undefined; + async resolve(resource: URI, viewType?: string, options?: NotebookEditorModelCreationOptions): Promise>; + async resolve(resource: IUntitledNotebookResource, viewType: string, options: NotebookEditorModelCreationOptions): Promise>; + async resolve(arg0: URI | IUntitledNotebookResource, viewType?: string, options?: NotebookEditorModelCreationOptions): Promise> { + let resource: URI | undefined; let hasAssociatedFilePath; - if (URI.isUri(arg0)) { resource = arg0; } else if (arg0.untitledResource) { if (arg0.untitledResource.scheme === Schemas.untitled) { resource = arg0.untitledResource; } else { - resource = arg0.untitledResource.with({ - scheme: Schemas.untitled, - }); + resource = arg0.untitledResource.with({ scheme: Schemas.untitled }); hasAssociatedFilePath = true; } } - const validated = await this.validateResourceViewType( - resource, - viewType, - ); - - const reference = this._data.acquire( - validated.resource.toString(), - validated.viewType, - hasAssociatedFilePath, - options?.limits, - options?.scratchpad, - options?.viewType, - ); + const validated = await this.validateResourceViewType(resource, viewType); + + const reference = this._data.acquire(validated.resource.toString(), validated.viewType, hasAssociatedFilePath, options?.limits, options?.scratchpad, options?.viewType); try { const model = await reference.object; - return { object: model, - dispose() { - reference.dispose(); - }, + dispose() { reference.dispose(); } }; } catch (err) { reference.dispose(); - throw err; } } diff --git a/Source/vs/workbench/contrib/outline/browser/outlinePane.ts b/Source/vs/workbench/contrib/outline/browser/outlinePane.ts index c02f08eaa64b7..15091ceeec80c 100644 --- a/Source/vs/workbench/contrib/outline/browser/outlinePane.ts +++ b/Source/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -2,71 +2,49 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import "./outlinePane.css"; - -import * as dom from "../../../../base/browser/dom.js"; -import { ProgressBar } from "../../../../base/browser/ui/progressbar/progressbar.js"; -import { - AbstractTreeViewState, - IAbstractTreeViewState, - TreeFindMode, -} from "../../../../base/browser/ui/tree/abstractTree.js"; -import { ITreeSorter } from "../../../../base/browser/ui/tree/tree.js"; -import { timeout, TimeoutTimer } from "../../../../base/common/async.js"; -import { CancellationTokenSource } from "../../../../base/common/cancellation.js"; -import { Event } from "../../../../base/common/event.js"; -import { FuzzyScore } from "../../../../base/common/filters.js"; -import { - DisposableStore, - IDisposable, - MutableDisposable, - toDisposable, -} from "../../../../base/common/lifecycle.js"; -import { LRUCache } from "../../../../base/common/map.js"; -import { basename } from "../../../../base/common/resources.js"; -import { URI } from "../../../../base/common/uri.js"; -import { localize } from "../../../../nls.js"; -import { IConfigurationService } from "../../../../platform/configuration/common/configuration.js"; -import { - IContextKey, - IContextKeyService, -} from "../../../../platform/contextkey/common/contextkey.js"; -import { IContextMenuService } from "../../../../platform/contextview/browser/contextView.js"; -import { IHoverService } from "../../../../platform/hover/browser/hover.js"; -import { IInstantiationService } from "../../../../platform/instantiation/common/instantiation.js"; -import { IKeybindingService } from "../../../../platform/keybinding/common/keybinding.js"; -import { WorkbenchDataTree } from "../../../../platform/list/browser/listService.js"; -import { IOpenerService } from "../../../../platform/opener/common/opener.js"; -import { IStorageService } from "../../../../platform/storage/common/storage.js"; -import { ITelemetryService } from "../../../../platform/telemetry/common/telemetry.js"; -import { defaultProgressBarStyles } from "../../../../platform/theme/browser/defaultStyles.js"; -import { IThemeService } from "../../../../platform/theme/common/themeService.js"; -import { ViewPane } from "../../../browser/parts/views/viewPane.js"; -import { IViewletViewOptions } from "../../../browser/parts/views/viewsViewlet.js"; -import { EditorResourceAccessor, IEditorPane } from "../../../common/editor.js"; -import { IViewDescriptorService } from "../../../common/views.js"; -import { IEditorService } from "../../../services/editor/common/editorService.js"; -import { - IOutline, - IOutlineComparator, - IOutlineService, - OutlineTarget, -} from "../../../services/outline/browser/outline.js"; -import { - ctxAllCollapsed, - ctxFilterOnType, - ctxFollowsCursor, - ctxSortMode, - IOutlinePane, - OutlineSortOrder, -} from "./outline.js"; -import { OutlineViewState } from "./outlineViewState.js"; + +import './outlinePane.css'; +import * as dom from '../../../../base/browser/dom.js'; +import { ProgressBar } from '../../../../base/browser/ui/progressbar/progressbar.js'; +import { TimeoutTimer, timeout } from '../../../../base/common/async.js'; +import { IDisposable, toDisposable, DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js'; +import { LRUCache } from '../../../../base/common/map.js'; +import { localize } from '../../../../nls.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { WorkbenchDataTree } from '../../../../platform/list/browser/listService.js'; +import { IStorageService } from '../../../../platform/storage/common/storage.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { ViewPane } from '../../../browser/parts/views/viewPane.js'; +import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js'; +import { IEditorService } from '../../../services/editor/common/editorService.js'; +import { FuzzyScore } from '../../../../base/common/filters.js'; +import { basename } from '../../../../base/common/resources.js'; +import { IViewDescriptorService } from '../../../common/views.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { OutlineViewState } from './outlineViewState.js'; +import { IOutline, IOutlineComparator, IOutlineService, OutlineTarget } from '../../../services/outline/browser/outline.js'; +import { EditorResourceAccessor, IEditorPane } from '../../../common/editor.js'; +import { CancellationTokenSource } from '../../../../base/common/cancellation.js'; +import { Event } from '../../../../base/common/event.js'; +import { ITreeSorter } from '../../../../base/browser/ui/tree/tree.js'; +import { AbstractTreeViewState, IAbstractTreeViewState, TreeFindMode } from '../../../../base/browser/ui/tree/abstractTree.js'; +import { URI } from '../../../../base/common/uri.js'; +import { ctxAllCollapsed, ctxFilterOnType, ctxFollowsCursor, ctxSortMode, IOutlinePane, OutlineSortOrder } from './outline.js'; +import { defaultProgressBarStyles } from '../../../../platform/theme/browser/defaultStyles.js'; +import { IHoverService } from '../../../../platform/hover/browser/hover.js'; class OutlineTreeSorter implements ITreeSorter { + constructor( private _comparator: IOutlineComparator, - public order: OutlineSortOrder, - ) {} + public order: OutlineSortOrder + ) { } + compare(a: E, b: E): number { if (this.order === OutlineSortOrder.ByKind) { return this._comparator.compareByType(a, b); @@ -77,24 +55,27 @@ class OutlineTreeSorter implements ITreeSorter { } } } + export class OutlinePane extends ViewPane implements IOutlinePane { - static readonly Id = "outline"; + + static readonly Id = 'outline'; + private readonly _disposables = new DisposableStore(); + private readonly _editorControlDisposables = new DisposableStore(); private readonly _editorPaneDisposables = new DisposableStore(); private readonly _outlineViewState = new OutlineViewState(); + private readonly _editorListener = new MutableDisposable(); + private _domNode!: HTMLElement; private _message!: HTMLDivElement; private _progressBar!: ProgressBar; private _treeContainer!: HTMLElement; - private _tree?: WorkbenchDataTree< - IOutline | undefined, - any, - FuzzyScore - >; + private _tree?: WorkbenchDataTree | undefined, any, FuzzyScore>; private _treeDimensions?: dom.Dimension; private _treeStates = new LRUCache(10); + private _ctxFollowsCursor!: IContextKey; private _ctxFilterOnType!: IContextKey; private _ctxSortMode!: IContextKey; @@ -102,48 +83,24 @@ export class OutlinePane extends ViewPane implements IOutlinePane { constructor( options: IViewletViewOptions, - @IOutlineService - private readonly _outlineService: IOutlineService, - @IInstantiationService - private readonly _instantiationService: IInstantiationService, - @IViewDescriptorService - viewDescriptorService: IViewDescriptorService, - @IStorageService - private readonly _storageService: IStorageService, - @IEditorService - private readonly _editorService: IEditorService, - @IConfigurationService - configurationService: IConfigurationService, - @IKeybindingService - keybindingService: IKeybindingService, - @IContextKeyService - contextKeyService: IContextKeyService, - @IContextMenuService - contextMenuService: IContextMenuService, - @IOpenerService - openerService: IOpenerService, - @IThemeService - themeService: IThemeService, - @ITelemetryService - telemetryService: ITelemetryService, - @IHoverService - hoverService: IHoverService, + @IOutlineService private readonly _outlineService: IOutlineService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IStorageService private readonly _storageService: IStorageService, + @IEditorService private readonly _editorService: IEditorService, + @IConfigurationService configurationService: IConfigurationService, + @IKeybindingService keybindingService: IKeybindingService, + @IContextKeyService contextKeyService: IContextKeyService, + @IContextMenuService contextMenuService: IContextMenuService, + @IOpenerService openerService: IOpenerService, + @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, + @IHoverService hoverService: IHoverService, ) { - super( - options, - keybindingService, - contextMenuService, - configurationService, - contextKeyService, - viewDescriptorService, - _instantiationService, - openerService, - themeService, - telemetryService, - hoverService, - ); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, _instantiationService, openerService, themeService, telemetryService, hoverService); this._outlineViewState.restore(this._storageService); this._disposables.add(this._outlineViewState); + contextKeyService.bufferChangeEvents(() => { this._ctxFollowsCursor = ctxFollowsCursor.bindTo(contextKeyService); this._ctxFilterOnType = ctxFilterOnType.bindTo(contextKeyService); @@ -157,184 +114,146 @@ export class OutlinePane extends ViewPane implements IOutlinePane { this._ctxSortMode.set(this._outlineViewState.sortBy); }; updateContext(); - this._disposables.add( - this._outlineViewState.onDidChange(updateContext), - ); + this._disposables.add(this._outlineViewState.onDidChange(updateContext)); } + override dispose(): void { this._disposables.dispose(); this._editorPaneDisposables.dispose(); this._editorControlDisposables.dispose(); this._editorListener.dispose(); - super.dispose(); } + override focus(): void { super.focus(); this._tree?.domFocus(); } + protected override renderBody(container: HTMLElement): void { super.renderBody(container); + this._domNode = container; - container.classList.add("outline-pane"); + container.classList.add('outline-pane'); - const progressContainer = dom.$(".outline-progress"); - this._message = dom.$(".outline-message"); - this._progressBar = new ProgressBar( - progressContainer, - defaultProgressBarStyles, - ); - this._treeContainer = dom.$(".outline-tree"); + const progressContainer = dom.$('.outline-progress'); + this._message = dom.$('.outline-message'); - dom.append( - container, - progressContainer, - this._message, - this._treeContainer, - ); - this._disposables.add( - this.onDidChangeBodyVisibility((visible) => { - if (!visible) { - // stop everything when not visible - this._editorListener.clear(); - this._editorPaneDisposables.clear(); - this._editorControlDisposables.clear(); - } else if (!this._editorListener.value) { - const event = Event.any( - this._editorService.onDidActiveEditorChange, - this._outlineService.onDidChange, - ); - this._editorListener.value = event(() => - this._handleEditorChanged( - this._editorService.activeEditorPane, - ), - ); - this._handleEditorChanged( - this._editorService.activeEditorPane, - ); - } - }), - ); + this._progressBar = new ProgressBar(progressContainer, defaultProgressBarStyles); + + this._treeContainer = dom.$('.outline-tree'); + dom.append(container, progressContainer, this._message, this._treeContainer); + + this._disposables.add(this.onDidChangeBodyVisibility(visible => { + if (!visible) { + // stop everything when not visible + this._editorListener.clear(); + this._editorPaneDisposables.clear(); + this._editorControlDisposables.clear(); + + } else if (!this._editorListener.value) { + const event = Event.any(this._editorService.onDidActiveEditorChange, this._outlineService.onDidChange); + this._editorListener.value = event(() => this._handleEditorChanged(this._editorService.activeEditorPane)); + this._handleEditorChanged(this._editorService.activeEditorPane); + } + })); } + protected override layoutBody(height: number, width: number): void { super.layoutBody(height, width); this._tree?.layout(height, width); this._treeDimensions = new dom.Dimension(width, height); } + collapseAll(): void { this._tree?.collapseAll(); } + expandAll(): void { this._tree?.expandAll(); } + get outlineViewState() { return this._outlineViewState; } + private _showMessage(message: string) { - this._domNode.classList.add("message"); + this._domNode.classList.add('message'); this._progressBar.stop().hide(); this._message.innerText = message; } + private _captureViewState(uri?: URI): boolean { if (this._tree) { const oldOutline = this._tree.getInput(); - if (!uri) { uri = oldOutline?.uri; } if (oldOutline && uri) { - this._treeStates.set( - `${oldOutline.outlineKind}/${uri}`, - this._tree.getViewState(), - ); - + this._treeStates.set(`${oldOutline.outlineKind}/${uri}`, this._tree.getViewState()); return true; } } return false; } + private _handleEditorChanged(pane: IEditorPane | undefined): void { this._editorPaneDisposables.clear(); if (pane) { // react to control changes from within pane (https://github.com/microsoft/vscode/issues/134008) - this._editorPaneDisposables.add( - pane.onDidChangeControl(() => { - this._handleEditorControlChanged(pane); - }), - ); + this._editorPaneDisposables.add(pane.onDidChangeControl(() => { + this._handleEditorControlChanged(pane); + })); } + this._handleEditorControlChanged(pane); } - private async _handleEditorControlChanged( - pane: IEditorPane | undefined, - ): Promise { + + private async _handleEditorControlChanged(pane: IEditorPane | undefined): Promise { + // persist state const resource = EditorResourceAccessor.getOriginalUri(pane?.input); - const didCapture = this._captureViewState(); + this._editorControlDisposables.clear(); - if ( - !pane || - !this._outlineService.canCreateOutline(pane) || - !resource - ) { - return this._showMessage( - localize( - "no-editor", - "The active editor cannot provide outline information.", - ), - ); + if (!pane || !this._outlineService.canCreateOutline(pane) || !resource) { + return this._showMessage(localize('no-editor', "The active editor cannot provide outline information.")); } - let loadingMessage: IDisposable | undefined; + let loadingMessage: IDisposable | undefined; if (!didCapture) { loadingMessage = new TimeoutTimer(() => { - this._showMessage( - localize( - "loading", - "Loading document symbols for '{0}'...", - basename(resource), - ), - ); + this._showMessage(localize('loading', "Loading document symbols for '{0}'...", basename(resource))); }, 100); } + this._progressBar.infinite().show(500); const cts = new CancellationTokenSource(); - this._editorControlDisposables.add( - toDisposable(() => cts.dispose(true)), - ); + this._editorControlDisposables.add(toDisposable(() => cts.dispose(true))); - const newOutline = await this._outlineService.createOutline( - pane, - OutlineTarget.OutlinePane, - cts.token, - ); + const newOutline = await this._outlineService.createOutline(pane, OutlineTarget.OutlinePane, cts.token); loadingMessage?.dispose(); if (!newOutline) { return; } + if (cts.token.isCancellationRequested) { newOutline?.dispose(); - return; } + this._editorControlDisposables.add(newOutline); this._progressBar.stop().hide(); - const sorter = new OutlineTreeSorter( - newOutline.config.comparator, - this._outlineViewState.sortBy, - ); + const sorter = new OutlineTreeSorter(newOutline.config.comparator, this._outlineViewState.sortBy); - const tree = < - WorkbenchDataTree | undefined, any, FuzzyScore> - >this._instantiationService.createInstance( - WorkbenchDataTree, - "OutlinePane", + const tree = this._instantiationService.createInstance( + WorkbenchDataTree | undefined, any, FuzzyScore>, + 'OutlinePane', this._treeContainer, newOutline.config.delegate, newOutline.config.renderers, @@ -346,105 +265,68 @@ export class OutlinePane extends ViewPane implements IOutlinePane { expandOnlyOnTwistieClick: true, multipleSelectionSupport: false, hideTwistiesOfChildlessElements: true, - defaultFindMode: this._outlineViewState.filterOnType - ? TreeFindMode.Filter - : TreeFindMode.Highlight, - overrideStyles: - this.getLocationBasedColors().listOverrideStyles, - }, + defaultFindMode: this._outlineViewState.filterOnType ? TreeFindMode.Filter : TreeFindMode.Highlight, + overrideStyles: this.getLocationBasedColors().listOverrideStyles + } ); + // update tree, listen to changes const updateTree = () => { if (newOutline.isEmpty) { // no more elements - this._showMessage( - localize( - "no-symbols", - "No symbols found in document '{0}'", - basename(resource), - ), - ); + this._showMessage(localize('no-symbols', "No symbols found in document '{0}'", basename(resource))); this._captureViewState(resource); tree.setInput(undefined); + } else if (!tree.getInput()) { // first: init tree - this._domNode.classList.remove("message"); - - const state = this._treeStates.get( - `${newOutline.outlineKind}/${newOutline.uri}`, - ); - tree.setInput( - newOutline, - state && AbstractTreeViewState.lift(state), - ); + this._domNode.classList.remove('message'); + const state = this._treeStates.get(`${newOutline.outlineKind}/${newOutline.uri}`); + tree.setInput(newOutline, state && AbstractTreeViewState.lift(state)); + } else { // update: refresh tree - this._domNode.classList.remove("message"); + this._domNode.classList.remove('message'); tree.updateChildren(); } }; updateTree(); this._editorControlDisposables.add(newOutline.onDidChange(updateTree)); - tree.findMode = this._outlineViewState.filterOnType - ? TreeFindMode.Filter - : TreeFindMode.Highlight; + tree.findMode = this._outlineViewState.filterOnType ? TreeFindMode.Filter : TreeFindMode.Highlight; + // feature: apply panel background to tree - this._editorControlDisposables.add( - this.viewDescriptorService.onDidChangeLocation(({ views }) => { - if (views.some((v) => v.id === this.id)) { - tree.updateOptions({ - overrideStyles: - this.getLocationBasedColors().listOverrideStyles, - }); - } - }), - ); + this._editorControlDisposables.add(this.viewDescriptorService.onDidChangeLocation(({ views }) => { + if (views.some(v => v.id === this.id)) { + tree.updateOptions({ overrideStyles: this.getLocationBasedColors().listOverrideStyles }); + } + })); + // feature: filter on type - keep tree and menu in sync - this._editorControlDisposables.add( - tree.onDidChangeFindMode( - (mode) => - (this._outlineViewState.filterOnType = - mode === TreeFindMode.Filter), - ), - ); + this._editorControlDisposables.add(tree.onDidChangeFindMode(mode => this._outlineViewState.filterOnType = mode === TreeFindMode.Filter)); + // feature: reveal outline selection in editor // on change -> reveal/select defining range let idPool = 0; - this._editorControlDisposables.add( - tree.onDidOpen(async (e) => { - const myId = ++idPool; - - const isDoubleClick = e.browserEvent?.type === "dblclick"; - - if (!isDoubleClick) { - // workaround for https://github.com/microsoft/vscode/issues/206424 - await timeout(150); - - if (myId !== idPool) { - return; - } + this._editorControlDisposables.add(tree.onDidOpen(async e => { + const myId = ++idPool; + const isDoubleClick = e.browserEvent?.type === 'dblclick'; + if (!isDoubleClick) { + // workaround for https://github.com/microsoft/vscode/issues/206424 + await timeout(150); + if (myId !== idPool) { + return; } - await newOutline.reveal( - e.element, - e.editorOptions, - e.sideBySide, - isDoubleClick, - ); - }), - ); + } + await newOutline.reveal(e.element, e.editorOptions, e.sideBySide, isDoubleClick); + })); // feature: reveal editor selection in outline const revealActiveElement = () => { - if ( - !this._outlineViewState.followCursor || - !newOutline.activeElement - ) { + if (!this._outlineViewState.followCursor || !newOutline.activeElement) { return; } let item = newOutline.activeElement; - while (item) { const top = tree.getRelativeTop(item); - if (top === null) { // not visible -> reveal tree.reveal(item, 0.5); @@ -452,7 +334,6 @@ export class OutlinePane extends ViewPane implements IOutlinePane { if (tree.getRelativeTop(item) !== null) { tree.setFocus([item]); tree.setSelection([item]); - break; } // STILL not visible -> try parent @@ -460,75 +341,52 @@ export class OutlinePane extends ViewPane implements IOutlinePane { } }; revealActiveElement(); - this._editorControlDisposables.add( - newOutline.onDidChange(revealActiveElement), - ); + this._editorControlDisposables.add(newOutline.onDidChange(revealActiveElement)); + // feature: update view when user state changes - this._editorControlDisposables.add( - this._outlineViewState.onDidChange( - (e: { - followCursor?: boolean; - sortBy?: boolean; - filterOnType?: boolean; - }) => { - this._outlineViewState.persist(this._storageService); - - if (e.filterOnType) { - tree.findMode = this._outlineViewState.filterOnType - ? TreeFindMode.Filter - : TreeFindMode.Highlight; - } - if (e.followCursor) { - revealActiveElement(); - } - if (e.sortBy) { - sorter.order = this._outlineViewState.sortBy; - tree.resort(); - } - }, - ), - ); + this._editorControlDisposables.add(this._outlineViewState.onDidChange((e: { followCursor?: boolean; sortBy?: boolean; filterOnType?: boolean }) => { + this._outlineViewState.persist(this._storageService); + if (e.filterOnType) { + tree.findMode = this._outlineViewState.filterOnType ? TreeFindMode.Filter : TreeFindMode.Highlight; + } + if (e.followCursor) { + revealActiveElement(); + } + if (e.sortBy) { + sorter.order = this._outlineViewState.sortBy; + tree.resort(); + } + })); + // feature: expand all nodes when filtering (not when finding) let viewState: AbstractTreeViewState | undefined; - this._editorControlDisposables.add( - tree.onDidChangeFindPattern((pattern) => { - if (tree.findMode === TreeFindMode.Highlight) { - return; - } - if (!viewState && pattern) { - viewState = tree.getViewState(); - tree.expandAll(); - } else if (!pattern && viewState) { - tree.setInput(tree.getInput()!, viewState); - viewState = undefined; - } - }), - ); + this._editorControlDisposables.add(tree.onDidChangeFindPattern(pattern => { + if (tree.findMode === TreeFindMode.Highlight) { + return; + } + if (!viewState && pattern) { + viewState = tree.getViewState(); + tree.expandAll(); + } else if (!pattern && viewState) { + tree.setInput(tree.getInput()!, viewState); + viewState = undefined; + } + })); + // feature: update all-collapsed context key const updateAllCollapsedCtx = () => { - this._ctxAllCollapsed.set( - tree - .getNode(null) - .children.every( - (node) => !node.collapsible || node.collapsed, - ), - ); + this._ctxAllCollapsed.set(tree.getNode(null).children.every(node => !node.collapsible || node.collapsed)); }; - this._editorControlDisposables.add( - tree.onDidChangeCollapseState(updateAllCollapsedCtx), - ); - this._editorControlDisposables.add( - tree.onDidChangeModel(updateAllCollapsedCtx), - ); + this._editorControlDisposables.add(tree.onDidChangeCollapseState(updateAllCollapsedCtx)); + this._editorControlDisposables.add(tree.onDidChangeModel(updateAllCollapsedCtx)); updateAllCollapsedCtx(); + // last: set tree property and wire it up to one of our context keys tree.layout(this._treeDimensions?.height, this._treeDimensions?.width); this._tree = tree; - this._editorControlDisposables.add( - toDisposable(() => { - tree.dispose(); - this._tree = undefined; - }), - ); + this._editorControlDisposables.add(toDisposable(() => { + tree.dispose(); + this._tree = undefined; + })); } } diff --git a/Source/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/Source/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index 22e9739cae000..4c3a8c71dd678 100644 --- a/Source/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/Source/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -2,95 +2,50 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { - EventHelper, - getDomNodePagePosition, -} from "../../../../base/browser/dom.js"; -import { IAction, SubmenuAction } from "../../../../base/common/actions.js"; -import { Delayer } from "../../../../base/common/async.js"; -import { CancellationToken } from "../../../../base/common/cancellation.js"; -import { IStringDictionary } from "../../../../base/common/collections.js"; -import { Emitter, Event } from "../../../../base/common/event.js"; -import { IJSONSchema } from "../../../../base/common/jsonSchema.js"; -import { Disposable, IDisposable } from "../../../../base/common/lifecycle.js"; -import { ResourceMap } from "../../../../base/common/map.js"; -import { isEqual } from "../../../../base/common/resources.js"; -import { ThemeIcon } from "../../../../base/common/themables.js"; -import { - ICodeEditor, - IEditorMouseEvent, - MouseTargetType, -} from "../../../../editor/browser/editorBrowser.js"; -import { EditorOption } from "../../../../editor/common/config/editorOptions.js"; -import { Position } from "../../../../editor/common/core/position.js"; -import { IRange, Range } from "../../../../editor/common/core/range.js"; -import { Selection } from "../../../../editor/common/core/selection.js"; -import { ICursorPositionChangedEvent } from "../../../../editor/common/cursorEvents.js"; -import * as editorCommon from "../../../../editor/common/editorCommon.js"; -import * as languages from "../../../../editor/common/languages.js"; -import { - IModelDeltaDecoration, - ITextModel, - TrackedRangeStickiness, -} from "../../../../editor/common/model.js"; -import { ModelDecorationOptions } from "../../../../editor/common/model/textModel.js"; -import { ILanguageFeaturesService } from "../../../../editor/common/services/languageFeatures.js"; -import { CodeActionKind } from "../../../../editor/contrib/codeAction/common/types.js"; -import * as nls from "../../../../nls.js"; -import { - ConfigurationTarget, - IConfigurationService, -} from "../../../../platform/configuration/common/configuration.js"; -import { - Extensions as ConfigurationExtensions, - ConfigurationScope, - IConfigurationPropertySchema, - IConfigurationRegistry, - IRegisteredConfigurationPropertySchema, - OVERRIDE_PROPERTY_REGEX, - overrideIdentifiersFromKey, -} from "../../../../platform/configuration/common/configurationRegistry.js"; -import { IContextMenuService } from "../../../../platform/contextview/browser/contextView.js"; -import { IInstantiationService } from "../../../../platform/instantiation/common/instantiation.js"; -import { - IMarkerData, - IMarkerService, - MarkerSeverity, - MarkerTag, -} from "../../../../platform/markers/common/markers.js"; -import { Registry } from "../../../../platform/registry/common/platform.js"; -import { IUriIdentityService } from "../../../../platform/uriIdentity/common/uriIdentity.js"; -import { IUserDataProfilesService } from "../../../../platform/userDataProfile/common/userDataProfile.js"; -import { - IWorkspaceContextService, - WorkbenchState, -} from "../../../../platform/workspace/common/workspace.js"; -import { IWorkspaceTrustManagementService } from "../../../../platform/workspace/common/workspaceTrust.js"; -import { RangeHighlightDecorations } from "../../../browser/codeeditor.js"; -import { - APPLY_ALL_PROFILES_SETTING, - IWorkbenchConfigurationService, -} from "../../../services/configuration/common/configuration.js"; -import { IWorkbenchEnvironmentService } from "../../../services/environment/common/environmentService.js"; -import { - IPreferencesEditorModel, - IPreferencesService, - ISetting, - ISettingsEditorModel, - ISettingsGroup, -} from "../../../services/preferences/common/preferences.js"; -import { - DefaultSettingsEditorModel, - SettingsEditorModel, - WorkspaceConfigurationEditorModel, -} from "../../../services/preferences/common/preferencesModels.js"; -import { IUserDataProfileService } from "../../../services/userDataProfile/common/userDataProfile.js"; -import { - EXPERIMENTAL_INDICATOR_DESCRIPTION, - PREVIEW_INDICATOR_DESCRIPTION, -} from "../common/preferences.js"; -import { settingsEditIcon } from "./preferencesIcons.js"; -import { EditPreferenceWidget } from "./preferencesWidgets.js"; + +import { EventHelper, getDomNodePagePosition } from '../../../../base/browser/dom.js'; +import { IAction, SubmenuAction } from '../../../../base/common/actions.js'; +import { Delayer } from '../../../../base/common/async.js'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { IStringDictionary } from '../../../../base/common/collections.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; +import { IJSONSchema } from '../../../../base/common/jsonSchema.js'; +import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js'; +import { ResourceMap } from '../../../../base/common/map.js'; +import { isEqual } from '../../../../base/common/resources.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from '../../../../editor/browser/editorBrowser.js'; +import { EditorOption } from '../../../../editor/common/config/editorOptions.js'; +import { Position } from '../../../../editor/common/core/position.js'; +import { IRange, Range } from '../../../../editor/common/core/range.js'; +import { Selection } from '../../../../editor/common/core/selection.js'; +import { ICursorPositionChangedEvent } from '../../../../editor/common/cursorEvents.js'; +import * as editorCommon from '../../../../editor/common/editorCommon.js'; +import * as languages from '../../../../editor/common/languages.js'; +import { IModelDeltaDecoration, ITextModel, TrackedRangeStickiness } from '../../../../editor/common/model.js'; +import { ModelDecorationOptions } from '../../../../editor/common/model/textModel.js'; +import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js'; +import { CodeActionKind } from '../../../../editor/contrib/codeAction/common/types.js'; +import * as nls from '../../../../nls.js'; +import { ConfigurationTarget, IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationPropertySchema, IConfigurationRegistry, IRegisteredConfigurationPropertySchema, OVERRIDE_PROPERTY_REGEX, overrideIdentifiersFromKey } from '../../../../platform/configuration/common/configurationRegistry.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { IMarkerData, IMarkerService, MarkerSeverity, MarkerTag } from '../../../../platform/markers/common/markers.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; +import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js'; +import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js'; +import { IWorkspaceTrustManagementService } from '../../../../platform/workspace/common/workspaceTrust.js'; +import { RangeHighlightDecorations } from '../../../browser/codeeditor.js'; +import { settingsEditIcon } from './preferencesIcons.js'; +import { EditPreferenceWidget } from './preferencesWidgets.js'; +import { APPLY_ALL_PROFILES_SETTING, IWorkbenchConfigurationService } from '../../../services/configuration/common/configuration.js'; +import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js'; +import { IPreferencesEditorModel, IPreferencesService, ISetting, ISettingsEditorModel, ISettingsGroup } from '../../../services/preferences/common/preferences.js'; +import { DefaultSettingsEditorModel, SettingsEditorModel, WorkspaceConfigurationEditorModel } from '../../../services/preferences/common/preferencesModels.js'; +import { IUserDataProfileService } from '../../../services/userDataProfile/common/userDataProfile.js'; +import { EXPERIMENTAL_INDICATOR_DESCRIPTION, PREVIEW_INDICATOR_DESCRIPTION } from '../common/preferences.js'; export interface IPreferencesRenderer extends IDisposable { render(): void; @@ -99,83 +54,41 @@ export interface IPreferencesRenderer extends IDisposable { clearFocus(setting: ISetting): void; editPreference(setting: ISetting): boolean; } -export class UserSettingsRenderer - extends Disposable - implements IPreferencesRenderer -{ + +export class UserSettingsRenderer extends Disposable implements IPreferencesRenderer { + private settingHighlighter: SettingHighlighter; private editSettingActionRenderer: EditSettingRenderer; private modelChangeDelayer: Delayer = new Delayer(200); private associatedPreferencesModel!: IPreferencesEditorModel; + private unsupportedSettingsRenderer: UnsupportedSettingsRenderer; - constructor( - protected editor: ICodeEditor, - readonly preferencesModel: SettingsEditorModel, - @IPreferencesService - protected preferencesService: IPreferencesService, - @IConfigurationService - private readonly configurationService: IConfigurationService, - @IInstantiationService - protected instantiationService: IInstantiationService, + constructor(protected editor: ICodeEditor, readonly preferencesModel: SettingsEditorModel, + @IPreferencesService protected preferencesService: IPreferencesService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IInstantiationService protected instantiationService: IInstantiationService ) { super(); - this.settingHighlighter = this._register( - instantiationService.createInstance(SettingHighlighter, editor), - ); - this.editSettingActionRenderer = this._register( - this.instantiationService.createInstance( - EditSettingRenderer, - this.editor, - this.preferencesModel, - this.settingHighlighter, - ), - ); - this._register( - this.editSettingActionRenderer.onUpdateSetting( - ({ key, value, source }) => - this.updatePreference(key, value, source), - ), - ); - this._register( - this.editor - .getModel()! - .onDidChangeContent(() => - this.modelChangeDelayer.trigger(() => - this.onModelChanged(), - ), - ), - ); - this.unsupportedSettingsRenderer = this._register( - instantiationService.createInstance( - UnsupportedSettingsRenderer, - editor, - preferencesModel, - ), - ); + this.settingHighlighter = this._register(instantiationService.createInstance(SettingHighlighter, editor)); + this.editSettingActionRenderer = this._register(this.instantiationService.createInstance(EditSettingRenderer, this.editor, this.preferencesModel, this.settingHighlighter)); + this._register(this.editSettingActionRenderer.onUpdateSetting(({ key, value, source }) => this.updatePreference(key, value, source))); + this._register(this.editor.getModel()!.onDidChangeContent(() => this.modelChangeDelayer.trigger(() => this.onModelChanged()))); + this.unsupportedSettingsRenderer = this._register(instantiationService.createInstance(UnsupportedSettingsRenderer, editor, preferencesModel)); } + render(): void { - this.editSettingActionRenderer.render( - this.preferencesModel.settingsGroups, - this.associatedPreferencesModel, - ); + this.editSettingActionRenderer.render(this.preferencesModel.settingsGroups, this.associatedPreferencesModel); this.unsupportedSettingsRenderer.render(); } - updatePreference(key: string, value: any, source: IIndexedSetting): void { - const overrideIdentifiers = source.overrideOf - ? overrideIdentifiersFromKey(source.overrideOf.key) - : null; + updatePreference(key: string, value: any, source: IIndexedSetting): void { + const overrideIdentifiers = source.overrideOf ? overrideIdentifiersFromKey(source.overrideOf.key) : null; const resource = this.preferencesModel.uri; - this.configurationService - .updateValue( - key, - value, - { overrideIdentifiers, resource }, - this.preferencesModel.configurationTarget, - ) + this.configurationService.updateValue(key, value, { overrideIdentifiers, resource }, this.preferencesModel.configurationTarget) .then(() => this.onSettingUpdated(source)); } + private onModelChanged(): void { if (!this.editor.hasModel()) { // model could have been disposed during the delay @@ -183,23 +96,21 @@ export class UserSettingsRenderer } this.render(); } + private onSettingUpdated(setting: ISetting) { this.editor.focus(); - setting = this.getSetting(setting)!; - if (setting) { // TODO:@sandy Selection range should be template range this.editor.setSelection(setting.valueRange); this.settingHighlighter.highlight(setting, true); } } + private getSetting(setting: ISetting): ISetting | undefined { const { key, overrideOf } = setting; - if (overrideOf) { const setting = this.getSetting(overrideOf); - for (const override of setting!.overrides!) { if (override.key === key) { return override; @@ -207,334 +118,193 @@ export class UserSettingsRenderer } return undefined; } + return this.preferencesModel.getPreference(key); } + focusPreference(setting: ISetting): void { const s = this.getSetting(setting); - if (s) { this.settingHighlighter.highlight(s, true); - this.editor.setPosition({ - lineNumber: s.keyRange.startLineNumber, - column: s.keyRange.startColumn, - }); + this.editor.setPosition({ lineNumber: s.keyRange.startLineNumber, column: s.keyRange.startColumn }); } else { this.settingHighlighter.clear(true); } } + clearFocus(setting: ISetting): void { this.settingHighlighter.clear(true); } + editPreference(setting: ISetting): boolean { const editableSetting = this.getSetting(setting); - - return !!( - editableSetting && - this.editSettingActionRenderer.activateOnSetting(editableSetting) - ); + return !!(editableSetting && this.editSettingActionRenderer.activateOnSetting(editableSetting)); } + } -export class WorkspaceSettingsRenderer - extends UserSettingsRenderer - implements IPreferencesRenderer -{ + +export class WorkspaceSettingsRenderer extends UserSettingsRenderer implements IPreferencesRenderer { + private workspaceConfigurationRenderer: WorkspaceConfigurationRenderer; - constructor( - editor: ICodeEditor, - preferencesModel: SettingsEditorModel, - @IPreferencesService - preferencesService: IPreferencesService, - @IConfigurationService - configurationService: IConfigurationService, - @IInstantiationService - instantiationService: IInstantiationService, + constructor(editor: ICodeEditor, preferencesModel: SettingsEditorModel, + @IPreferencesService preferencesService: IPreferencesService, + @IConfigurationService configurationService: IConfigurationService, + @IInstantiationService instantiationService: IInstantiationService ) { - super( - editor, - preferencesModel, - preferencesService, - configurationService, - instantiationService, - ); - this.workspaceConfigurationRenderer = this._register( - instantiationService.createInstance( - WorkspaceConfigurationRenderer, - editor, - preferencesModel, - ), - ); + super(editor, preferencesModel, preferencesService, configurationService, instantiationService); + this.workspaceConfigurationRenderer = this._register(instantiationService.createInstance(WorkspaceConfigurationRenderer, editor, preferencesModel)); } + override render(): void { super.render(); this.workspaceConfigurationRenderer.render(); } } + export interface IIndexedSetting extends ISetting { index: number; groupId: string; } + class EditSettingRenderer extends Disposable { + private editPreferenceWidgetForCursorPosition: EditPreferenceWidget; private editPreferenceWidgetForMouseMove: EditPreferenceWidget; + private settingsGroups: ISettingsGroup[] = []; associatedPreferencesModel!: IPreferencesEditorModel; private toggleEditPreferencesForMouseMoveDelayer: Delayer; - private readonly _onUpdateSetting: Emitter<{ - key: string; - value: any; - source: IIndexedSetting; - }> = new Emitter<{ - key: string; - value: any; - source: IIndexedSetting; - }>(); - readonly onUpdateSetting: Event<{ - key: string; - value: any; - source: IIndexedSetting; - }> = this._onUpdateSetting.event; - constructor( - private editor: ICodeEditor, - private primarySettingsModel: ISettingsEditorModel, + private readonly _onUpdateSetting: Emitter<{ key: string; value: any; source: IIndexedSetting }> = new Emitter<{ key: string; value: any; source: IIndexedSetting }>(); + readonly onUpdateSetting: Event<{ key: string; value: any; source: IIndexedSetting }> = this._onUpdateSetting.event; + + constructor(private editor: ICodeEditor, private primarySettingsModel: ISettingsEditorModel, private settingHighlighter: SettingHighlighter, - @IConfigurationService - private readonly configurationService: IConfigurationService, - @IInstantiationService - private readonly instantiationService: IInstantiationService, - @IContextMenuService - private readonly contextMenuService: IContextMenuService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IContextMenuService private readonly contextMenuService: IContextMenuService ) { super(); - this.editPreferenceWidgetForCursorPosition = < - EditPreferenceWidget - >this._register( - this.instantiationService.createInstance( - EditPreferenceWidget, - editor, - ), - ); - this.editPreferenceWidgetForMouseMove = < - EditPreferenceWidget - >this._register( - this.instantiationService.createInstance( - EditPreferenceWidget, - editor, - ), - ); + + this.editPreferenceWidgetForCursorPosition = this._register(this.instantiationService.createInstance(EditPreferenceWidget, editor)); + this.editPreferenceWidgetForMouseMove = this._register(this.instantiationService.createInstance(EditPreferenceWidget, editor)); this.toggleEditPreferencesForMouseMoveDelayer = new Delayer(75); - this._register( - this.editPreferenceWidgetForCursorPosition.onClick((e) => - this.onEditSettingClicked( - this.editPreferenceWidgetForCursorPosition, - e, - ), - ), - ); - this._register( - this.editPreferenceWidgetForMouseMove.onClick((e) => - this.onEditSettingClicked( - this.editPreferenceWidgetForMouseMove, - e, - ), - ), - ); - this._register( - this.editor.onDidChangeCursorPosition((positionChangeEvent) => - this.onPositionChanged(positionChangeEvent), - ), - ); - this._register( - this.editor.onMouseMove((mouseMoveEvent) => - this.onMouseMoved(mouseMoveEvent), - ), - ); - this._register( - this.editor.onDidChangeConfiguration(() => - this.onConfigurationChanged(), - ), - ); + + this._register(this.editPreferenceWidgetForCursorPosition.onClick(e => this.onEditSettingClicked(this.editPreferenceWidgetForCursorPosition, e))); + this._register(this.editPreferenceWidgetForMouseMove.onClick(e => this.onEditSettingClicked(this.editPreferenceWidgetForMouseMove, e))); + + this._register(this.editor.onDidChangeCursorPosition(positionChangeEvent => this.onPositionChanged(positionChangeEvent))); + this._register(this.editor.onMouseMove(mouseMoveEvent => this.onMouseMoved(mouseMoveEvent))); + this._register(this.editor.onDidChangeConfiguration(() => this.onConfigurationChanged())); } - render( - settingsGroups: ISettingsGroup[], - associatedPreferencesModel: IPreferencesEditorModel, - ): void { + + render(settingsGroups: ISettingsGroup[], associatedPreferencesModel: IPreferencesEditorModel): void { this.editPreferenceWidgetForCursorPosition.hide(); this.editPreferenceWidgetForMouseMove.hide(); this.settingsGroups = settingsGroups; this.associatedPreferencesModel = associatedPreferencesModel; - const settings = this.getSettings( - this.editor.getPosition()!.lineNumber, - ); - + const settings = this.getSettings(this.editor.getPosition()!.lineNumber); if (settings.length) { - this.showEditPreferencesWidget( - this.editPreferenceWidgetForCursorPosition, - settings, - ); + this.showEditPreferencesWidget(this.editPreferenceWidgetForCursorPosition, settings); } } + private isDefaultSettings(): boolean { return this.primarySettingsModel instanceof DefaultSettingsEditorModel; } + private onConfigurationChanged(): void { if (!this.editor.getOption(EditorOption.glyphMargin)) { this.editPreferenceWidgetForCursorPosition.hide(); this.editPreferenceWidgetForMouseMove.hide(); } } - private onPositionChanged( - positionChangeEvent: ICursorPositionChangedEvent, - ) { - this.editPreferenceWidgetForMouseMove.hide(); - - const settings = this.getSettings( - positionChangeEvent.position.lineNumber, - ); + private onPositionChanged(positionChangeEvent: ICursorPositionChangedEvent) { + this.editPreferenceWidgetForMouseMove.hide(); + const settings = this.getSettings(positionChangeEvent.position.lineNumber); if (settings.length) { - this.showEditPreferencesWidget( - this.editPreferenceWidgetForCursorPosition, - settings, - ); + this.showEditPreferencesWidget(this.editPreferenceWidgetForCursorPosition, settings); } else { this.editPreferenceWidgetForCursorPosition.hide(); } } - private onMouseMoved(mouseMoveEvent: IEditorMouseEvent): void { - const editPreferenceWidget = - this.getEditPreferenceWidgetUnderMouse(mouseMoveEvent); + private onMouseMoved(mouseMoveEvent: IEditorMouseEvent): void { + const editPreferenceWidget = this.getEditPreferenceWidgetUnderMouse(mouseMoveEvent); if (editPreferenceWidget) { this.onMouseOver(editPreferenceWidget); - return; } this.settingHighlighter.clear(); - this.toggleEditPreferencesForMouseMoveDelayer.trigger(() => - this.toggleEditPreferenceWidgetForMouseMove(mouseMoveEvent), - ); + this.toggleEditPreferencesForMouseMoveDelayer.trigger(() => this.toggleEditPreferenceWidgetForMouseMove(mouseMoveEvent)); } - private getEditPreferenceWidgetUnderMouse( - mouseMoveEvent: IEditorMouseEvent, - ): EditPreferenceWidget | undefined { - if ( - mouseMoveEvent.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN - ) { - const line = mouseMoveEvent.target.position.lineNumber; - if ( - this.editPreferenceWidgetForMouseMove.getLine() === line && - this.editPreferenceWidgetForMouseMove.isVisible() - ) { + private getEditPreferenceWidgetUnderMouse(mouseMoveEvent: IEditorMouseEvent): EditPreferenceWidget | undefined { + if (mouseMoveEvent.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN) { + const line = mouseMoveEvent.target.position.lineNumber; + if (this.editPreferenceWidgetForMouseMove.getLine() === line && this.editPreferenceWidgetForMouseMove.isVisible()) { return this.editPreferenceWidgetForMouseMove; } - if ( - this.editPreferenceWidgetForCursorPosition.getLine() === line && - this.editPreferenceWidgetForCursorPosition.isVisible() - ) { + if (this.editPreferenceWidgetForCursorPosition.getLine() === line && this.editPreferenceWidgetForCursorPosition.isVisible()) { return this.editPreferenceWidgetForCursorPosition; } } return undefined; } - private toggleEditPreferenceWidgetForMouseMove( - mouseMoveEvent: IEditorMouseEvent, - ): void { - const settings = mouseMoveEvent.target.position - ? this.getSettings(mouseMoveEvent.target.position.lineNumber) - : null; + private toggleEditPreferenceWidgetForMouseMove(mouseMoveEvent: IEditorMouseEvent): void { + const settings = mouseMoveEvent.target.position ? this.getSettings(mouseMoveEvent.target.position.lineNumber) : null; if (settings && settings.length) { - this.showEditPreferencesWidget( - this.editPreferenceWidgetForMouseMove, - settings, - ); + this.showEditPreferencesWidget(this.editPreferenceWidgetForMouseMove, settings); } else { this.editPreferenceWidgetForMouseMove.hide(); } } - private showEditPreferencesWidget( - editPreferencesWidget: EditPreferenceWidget, - settings: IIndexedSetting[], - ) { - const line = settings[0].valueRange.startLineNumber; - if ( - this.editor.getOption(EditorOption.glyphMargin) && - this.marginFreeFromOtherDecorations(line) - ) { - editPreferencesWidget.show( - line, - nls.localize("editTtile", "Edit"), - settings, - ); - - const editPreferenceWidgetToHide = - editPreferencesWidget === - this.editPreferenceWidgetForCursorPosition - ? this.editPreferenceWidgetForMouseMove - : this.editPreferenceWidgetForCursorPosition; + private showEditPreferencesWidget(editPreferencesWidget: EditPreferenceWidget, settings: IIndexedSetting[]) { + const line = settings[0].valueRange.startLineNumber; + if (this.editor.getOption(EditorOption.glyphMargin) && this.marginFreeFromOtherDecorations(line)) { + editPreferencesWidget.show(line, nls.localize('editTtile', "Edit"), settings); + const editPreferenceWidgetToHide = editPreferencesWidget === this.editPreferenceWidgetForCursorPosition ? this.editPreferenceWidgetForMouseMove : this.editPreferenceWidgetForCursorPosition; editPreferenceWidgetToHide.hide(); } } + private marginFreeFromOtherDecorations(line: number): boolean { const decorations = this.editor.getLineDecorations(line); - if (decorations) { for (const { options } of decorations) { - if ( - options.glyphMarginClassName && - options.glyphMarginClassName.indexOf( - ThemeIcon.asClassName(settingsEditIcon), - ) === -1 - ) { + if (options.glyphMarginClassName && options.glyphMarginClassName.indexOf(ThemeIcon.asClassName(settingsEditIcon)) === -1) { return false; } } } return true; } + private getSettings(lineNumber: number): IIndexedSetting[] { const configurationMap = this.getConfigurationsMap(); - - return this.getSettingsAtLineNumber(lineNumber).filter((setting) => { + return this.getSettingsAtLineNumber(lineNumber).filter(setting => { const configurationNode = configurationMap[setting.key]; - if (configurationNode) { - if ( - configurationNode.policy && - this.configurationService.inspect(setting.key) - .policyValue !== undefined - ) { + if (configurationNode.policy && this.configurationService.inspect(setting.key).policyValue !== undefined) { return false; } if (this.isDefaultSettings()) { - if (setting.key === "launch") { + if (setting.key === 'launch') { // Do not show because of https://github.com/microsoft/vscode/issues/32593 return false; } return true; } - if ( - configurationNode.type === "boolean" || - configurationNode.enum - ) { - if ( - (this.primarySettingsModel) - .configurationTarget !== - ConfigurationTarget.WORKSPACE_FOLDER - ) { + if (configurationNode.type === 'boolean' || configurationNode.enum) { + if ((this.primarySettingsModel).configurationTarget !== ConfigurationTarget.WORKSPACE_FOLDER) { return true; } - if ( - configurationNode.scope === - ConfigurationScope.RESOURCE || - configurationNode.scope === - ConfigurationScope.LANGUAGE_OVERRIDABLE - ) { + if (configurationNode.scope === ConfigurationScope.RESOURCE || configurationNode.scope === ConfigurationScope.LANGUAGE_OVERRIDABLE) { return true; } } @@ -542,57 +312,35 @@ class EditSettingRenderer extends Disposable { return false; }); } + private getSettingsAtLineNumber(lineNumber: number): IIndexedSetting[] { // index of setting, across all groups/sections let index = 0; const settings: IIndexedSetting[] = []; - for (const group of this.settingsGroups) { if (group.range.startLineNumber > lineNumber) { break; } - if ( - lineNumber >= group.range.startLineNumber && - lineNumber <= group.range.endLineNumber - ) { + if (lineNumber >= group.range.startLineNumber && lineNumber <= group.range.endLineNumber) { for (const section of group.sections) { for (const setting of section.settings) { if (setting.range.startLineNumber > lineNumber) { break; } - if ( - lineNumber >= setting.range.startLineNumber && - lineNumber <= setting.range.endLineNumber - ) { - if ( - !this.isDefaultSettings() && - setting.overrides!.length - ) { + if (lineNumber >= setting.range.startLineNumber && lineNumber <= setting.range.endLineNumber) { + if (!this.isDefaultSettings() && setting.overrides!.length) { // Only one level because override settings cannot have override settings for (const overrideSetting of setting.overrides!) { - if ( - lineNumber >= - overrideSetting.range - .startLineNumber && - lineNumber <= - overrideSetting.range.endLineNumber - ) { - settings.push({ - ...overrideSetting, - index, - groupId: group.id, - }); + if (lineNumber >= overrideSetting.range.startLineNumber && lineNumber <= overrideSetting.range.endLineNumber) { + settings.push({ ...overrideSetting, index, groupId: group.id }); } } } else { - settings.push({ - ...setting, - index, - groupId: group.id, - }); + settings.push({ ...setting, index, groupId: group.id }); } } + index++; } } @@ -600,288 +348,179 @@ class EditSettingRenderer extends Disposable { } return settings; } - private onMouseOver( - editPreferenceWidget: EditPreferenceWidget, - ): void { + + private onMouseOver(editPreferenceWidget: EditPreferenceWidget): void { this.settingHighlighter.highlight(editPreferenceWidget.preferences[0]); } - private onEditSettingClicked( - editPreferenceWidget: EditPreferenceWidget, - e: IEditorMouseEvent, - ): void { + + private onEditSettingClicked(editPreferenceWidget: EditPreferenceWidget, e: IEditorMouseEvent): void { EventHelper.stop(e.event, true); - const actions = - this.getSettings(editPreferenceWidget.getLine()).length === 1 - ? this.getActions( - editPreferenceWidget.preferences[0], - this.getConfigurationsMap()[ - editPreferenceWidget.preferences[0].key - ], - ) - : editPreferenceWidget.preferences.map( - (setting) => - new SubmenuAction( - `preferences.submenu.${setting.key}`, - setting.key, - this.getActions( - setting, - this.getConfigurationsMap()[setting.key], - ), - ), - ); + const actions = this.getSettings(editPreferenceWidget.getLine()).length === 1 ? this.getActions(editPreferenceWidget.preferences[0], this.getConfigurationsMap()[editPreferenceWidget.preferences[0].key]) + : editPreferenceWidget.preferences.map(setting => new SubmenuAction(`preferences.submenu.${setting.key}`, setting.key, this.getActions(setting, this.getConfigurationsMap()[setting.key]))); this.contextMenuService.showContextMenu({ getAnchor: () => e.event, - getActions: () => actions, + getActions: () => actions }); } + activateOnSetting(setting: ISetting): boolean { const startLine = setting.keyRange.startLineNumber; - const settings = this.getSettings(startLine); - if (!settings.length) { return false; } - this.editPreferenceWidgetForMouseMove.show(startLine, "", settings); - - const actions = this.getActions( - this.editPreferenceWidgetForMouseMove.preferences[0], - this.getConfigurationsMap()[ - this.editPreferenceWidgetForMouseMove.preferences[0].key - ], - ); + + this.editPreferenceWidgetForMouseMove.show(startLine, '', settings); + const actions = this.getActions(this.editPreferenceWidgetForMouseMove.preferences[0], this.getConfigurationsMap()[this.editPreferenceWidgetForMouseMove.preferences[0].key]); this.contextMenuService.showContextMenu({ getAnchor: () => this.toAbsoluteCoords(new Position(startLine, 1)), - getActions: () => actions, + getActions: () => actions }); return true; } - private toAbsoluteCoords(position: Position): { - x: number; - y: number; - } { - const positionCoords = this.editor.getScrolledVisiblePosition(position); + private toAbsoluteCoords(position: Position): { x: number; y: number } { + const positionCoords = this.editor.getScrolledVisiblePosition(position); const editorCoords = getDomNodePagePosition(this.editor.getDomNode()!); - const x = editorCoords.left + positionCoords!.left; - - const y = - editorCoords.top + positionCoords!.top + positionCoords!.height; + const y = editorCoords.top + positionCoords!.top + positionCoords!.height; return { x, y: y + 10 }; } - private getConfigurationsMap(): { - [qualifiedKey: string]: IConfigurationPropertySchema; - } { - return Registry.as( - ConfigurationExtensions.Configuration, - ).getConfigurationProperties(); - } - private getActions( - setting: IIndexedSetting, - jsonSchema: IJSONSchema, - ): IAction[] { - if (jsonSchema.type === "boolean") { - return [ - { - id: "truthyValue", - label: "true", - tooltip: "true", - enabled: true, - run: () => this.updateSetting(setting.key, true, setting), - class: undefined, - }, - { - id: "falsyValue", - label: "false", - tooltip: "false", - enabled: true, - run: () => this.updateSetting(setting.key, false, setting), - class: undefined, - }, - ]; + + private getConfigurationsMap(): { [qualifiedKey: string]: IConfigurationPropertySchema } { + return Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); + } + + private getActions(setting: IIndexedSetting, jsonSchema: IJSONSchema): IAction[] { + if (jsonSchema.type === 'boolean') { + return [{ + id: 'truthyValue', + label: 'true', + tooltip: 'true', + enabled: true, + run: () => this.updateSetting(setting.key, true, setting), + class: undefined + }, { + id: 'falsyValue', + label: 'false', + tooltip: 'false', + enabled: true, + run: () => this.updateSetting(setting.key, false, setting), + class: undefined + }]; } if (jsonSchema.enum) { - return jsonSchema.enum.map((value) => { + return jsonSchema.enum.map(value => { return { id: value, label: JSON.stringify(value), tooltip: JSON.stringify(value), enabled: true, run: () => this.updateSetting(setting.key, value, setting), - class: undefined, + class: undefined }; }); } return this.getDefaultActions(setting); } + private getDefaultActions(setting: IIndexedSetting): IAction[] { if (this.isDefaultSettings()) { - const settingInOtherModel = - this.associatedPreferencesModel.getPreference(setting.key); - - return [ - { - id: "setDefaultValue", - label: settingInOtherModel - ? nls.localize( - "replaceDefaultValue", - "Replace in Settings", - ) - : nls.localize("copyDefaultValue", "Copy to Settings"), - tooltip: settingInOtherModel - ? nls.localize( - "replaceDefaultValue", - "Replace in Settings", - ) - : nls.localize("copyDefaultValue", "Copy to Settings"), - enabled: true, - run: () => - this.updateSetting(setting.key, setting.value, setting), - class: undefined, - }, - ]; + const settingInOtherModel = this.associatedPreferencesModel.getPreference(setting.key); + return [{ + id: 'setDefaultValue', + label: settingInOtherModel ? nls.localize('replaceDefaultValue', "Replace in Settings") : nls.localize('copyDefaultValue', "Copy to Settings"), + tooltip: settingInOtherModel ? nls.localize('replaceDefaultValue', "Replace in Settings") : nls.localize('copyDefaultValue', "Copy to Settings"), + enabled: true, + run: () => this.updateSetting(setting.key, setting.value, setting), + class: undefined + }]; } return []; } - private updateSetting( - key: string, - value: any, - source: IIndexedSetting, - ): void { + + private updateSetting(key: string, value: any, source: IIndexedSetting): void { this._onUpdateSetting.fire({ key, value, source }); } } + class SettingHighlighter extends Disposable { + private fixedHighlighter: RangeHighlightDecorations; private volatileHighlighter: RangeHighlightDecorations; - constructor( - private editor: ICodeEditor, - @IInstantiationService - instantiationService: IInstantiationService, - ) { + constructor(private editor: ICodeEditor, @IInstantiationService instantiationService: IInstantiationService) { super(); - this.fixedHighlighter = this._register( - instantiationService.createInstance(RangeHighlightDecorations), - ); - this.volatileHighlighter = this._register( - instantiationService.createInstance(RangeHighlightDecorations), - ); + this.fixedHighlighter = this._register(instantiationService.createInstance(RangeHighlightDecorations)); + this.volatileHighlighter = this._register(instantiationService.createInstance(RangeHighlightDecorations)); } + highlight(setting: ISetting, fix: boolean = false) { this.volatileHighlighter.removeHighlightRange(); this.fixedHighlighter.removeHighlightRange(); - const highlighter = fix - ? this.fixedHighlighter - : this.volatileHighlighter; - highlighter.highlightRange( - { - range: setting.valueRange, - resource: this.editor.getModel()!.uri, - }, - this.editor, - ); - this.editor.revealLineInCenterIfOutsideViewport( - setting.valueRange.startLineNumber, - editorCommon.ScrollType.Smooth, - ); + const highlighter = fix ? this.fixedHighlighter : this.volatileHighlighter; + highlighter.highlightRange({ + range: setting.valueRange, + resource: this.editor.getModel()!.uri + }, this.editor); + + this.editor.revealLineInCenterIfOutsideViewport(setting.valueRange.startLineNumber, editorCommon.ScrollType.Smooth); } + clear(fix: boolean = false): void { this.volatileHighlighter.removeHighlightRange(); - if (fix) { this.fixedHighlighter.removeHighlightRange(); } } } -class UnsupportedSettingsRenderer - extends Disposable - implements languages.CodeActionProvider -{ + +class UnsupportedSettingsRenderer extends Disposable implements languages.CodeActionProvider { + private renderingDelayer: Delayer = new Delayer(200); - private readonly codeActions = new ResourceMap< - [Range, languages.CodeAction[]][] - >((uri) => this.uriIdentityService.extUri.getComparisonKey(uri)); + + private readonly codeActions = new ResourceMap<[Range, languages.CodeAction[]][]>(uri => this.uriIdentityService.extUri.getComparisonKey(uri)); constructor( private readonly editor: ICodeEditor, private readonly settingsEditorModel: SettingsEditorModel, - @IMarkerService - private readonly markerService: IMarkerService, - @IWorkbenchEnvironmentService - private readonly environmentService: IWorkbenchEnvironmentService, - @IWorkbenchConfigurationService - private readonly configurationService: IWorkbenchConfigurationService, - @IWorkspaceTrustManagementService - private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService, - @IUriIdentityService - private readonly uriIdentityService: IUriIdentityService, - @ILanguageFeaturesService - languageFeaturesService: ILanguageFeaturesService, - @IUserDataProfileService - private readonly userDataProfileService: IUserDataProfileService, - @IUserDataProfilesService - private readonly userDataProfilesService: IUserDataProfilesService, + @IMarkerService private readonly markerService: IMarkerService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IWorkbenchConfigurationService private readonly configurationService: IWorkbenchConfigurationService, + @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, ) { super(); - this._register( - this.editor - .getModel()! - .onDidChangeContent(() => this.delayedRender()), - ); - this._register( - Event.filter( - this.configurationService.onDidChangeConfiguration, - (e) => e.source === ConfigurationTarget.DEFAULT, - )(() => this.delayedRender()), - ); - this._register( - languageFeaturesService.codeActionProvider.register( - { pattern: settingsEditorModel.uri.path }, - this, - ), - ); - this._register( - userDataProfileService.onDidChangeCurrentProfile(() => - this.delayedRender(), - ), - ); + this._register(this.editor.getModel()!.onDidChangeContent(() => this.delayedRender())); + this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.source === ConfigurationTarget.DEFAULT)(() => this.delayedRender())); + this._register(languageFeaturesService.codeActionProvider.register({ pattern: settingsEditorModel.uri.path }, this)); + this._register(userDataProfileService.onDidChangeCurrentProfile(() => this.delayedRender())); } + private delayedRender(): void { this.renderingDelayer.trigger(() => this.render()); } + public render(): void { this.codeActions.clear(); - const markerData: IMarkerData[] = this.generateMarkerData(); - if (markerData.length) { - this.markerService.changeOne( - "UnsupportedSettingsRenderer", - this.settingsEditorModel.uri, - markerData, - ); + this.markerService.changeOne('UnsupportedSettingsRenderer', this.settingsEditorModel.uri, markerData); } else { - this.markerService.remove("UnsupportedSettingsRenderer", [ - this.settingsEditorModel.uri, - ]); + this.markerService.remove('UnsupportedSettingsRenderer', [this.settingsEditorModel.uri]); } } - async provideCodeActions( - model: ITextModel, - range: Range | Selection, - context: languages.CodeActionContext, - token: CancellationToken, - ): Promise { - const actions: languages.CodeAction[] = []; + async provideCodeActions(model: ITextModel, range: Range | Selection, context: languages.CodeActionContext, token: CancellationToken): Promise { + const actions: languages.CodeAction[] = []; const codeActionsByRange = this.codeActions.get(model.uri); - if (codeActionsByRange) { for (const [codeActionsRange, codeActions] of codeActionsByRange) { if (codeActionsRange.containsRange(range)) { @@ -891,523 +530,317 @@ class UnsupportedSettingsRenderer } return { actions, - dispose: () => {}, + dispose: () => { } }; } + private generateMarkerData(): IMarkerData[] { const markerData: IMarkerData[] = []; - - const configurationRegistry = Registry.as( - ConfigurationExtensions.Configuration, - ).getConfigurationProperties(); - + const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); for (const settingsGroup of this.settingsEditorModel.settingsGroups) { for (const section of settingsGroup.sections) { for (const setting of section.settings) { if (OVERRIDE_PROPERTY_REGEX.test(setting.key)) { if (setting.overrides) { - this.handleOverrides( - setting.overrides, - configurationRegistry, - markerData, - ); + this.handleOverrides(setting.overrides, configurationRegistry, markerData); } continue; } const configuration = configurationRegistry[setting.key]; - if (configuration) { - this.handleUnstableSettingConfiguration( - setting, - configuration, - markerData, - ); - - if ( - this.handlePolicyConfiguration( - setting, - configuration, - markerData, - ) - ) { + this.handleUnstableSettingConfiguration(setting, configuration, markerData); + if (this.handlePolicyConfiguration(setting, configuration, markerData)) { continue; } switch (this.settingsEditorModel.configurationTarget) { case ConfigurationTarget.USER_LOCAL: - this.handleLocalUserConfiguration( - setting, - configuration, - markerData, - ); - + this.handleLocalUserConfiguration(setting, configuration, markerData); break; - case ConfigurationTarget.USER_REMOTE: - this.handleRemoteUserConfiguration( - setting, - configuration, - markerData, - ); - + this.handleRemoteUserConfiguration(setting, configuration, markerData); break; - case ConfigurationTarget.WORKSPACE: - this.handleWorkspaceConfiguration( - setting, - configuration, - markerData, - ); - + this.handleWorkspaceConfiguration(setting, configuration, markerData); break; - case ConfigurationTarget.WORKSPACE_FOLDER: - this.handleWorkspaceFolderConfiguration( - setting, - configuration, - markerData, - ); - + this.handleWorkspaceFolderConfiguration(setting, configuration, markerData); break; } } else { - markerData.push( - this.generateUnknownConfigurationMarker(setting), - ); + markerData.push(this.generateUnknownConfigurationMarker(setting)); } } } } return markerData; } - private handlePolicyConfiguration( - setting: ISetting, - configuration: IConfigurationPropertySchema, - markerData: IMarkerData[], - ): boolean { + + private handlePolicyConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): boolean { if (!configuration.policy) { return false; } - if ( - this.configurationService.inspect(setting.key).policyValue === - undefined - ) { + if (this.configurationService.inspect(setting.key).policyValue === undefined) { return false; } - if ( - this.settingsEditorModel.configurationTarget === - ConfigurationTarget.DEFAULT - ) { + if (this.settingsEditorModel.configurationTarget === ConfigurationTarget.DEFAULT) { return false; } markerData.push({ severity: MarkerSeverity.Hint, tags: [MarkerTag.Unnecessary], ...setting.range, - message: nls.localize( - "unsupportedPolicySetting", - "This setting cannot be applied because it is configured in the system policy.", - ), + message: nls.localize('unsupportedPolicySetting', "This setting cannot be applied because it is configured in the system policy.") }); - return true; } - private handleOverrides( - overrides: ISetting[], - configurationRegistry: IStringDictionary, - markerData: IMarkerData[], - ): void { + + private handleOverrides(overrides: ISetting[], configurationRegistry: IStringDictionary, markerData: IMarkerData[]): void { for (const setting of overrides || []) { const configuration = configurationRegistry[setting.key]; - if (configuration) { - if ( - configuration.scope !== - ConfigurationScope.LANGUAGE_OVERRIDABLE - ) { + if (configuration.scope !== ConfigurationScope.LANGUAGE_OVERRIDABLE) { markerData.push({ severity: MarkerSeverity.Hint, tags: [MarkerTag.Unnecessary], ...setting.range, - message: nls.localize( - "unsupportLanguageOverrideSetting", - "This setting cannot be applied because it is not registered as language override setting.", - ), + message: nls.localize('unsupportLanguageOverrideSetting', "This setting cannot be applied because it is not registered as language override setting.") }); } } else { - markerData.push( - this.generateUnknownConfigurationMarker(setting), - ); + markerData.push(this.generateUnknownConfigurationMarker(setting)); } } } - private handleLocalUserConfiguration( - setting: ISetting, - configuration: IConfigurationPropertySchema, - markerData: IMarkerData[], - ): void { - if ( - !this.userDataProfileService.currentProfile.isDefault && - !this.userDataProfileService.currentProfile.useDefaultFlags - ?.settings - ) { - if ( - isEqual( - this.userDataProfilesService.defaultProfile - .settingsResource, - this.settingsEditorModel.uri, - ) && - !this.configurationService.isSettingAppliedForAllProfiles( - setting.key, - ) - ) { + + private handleLocalUserConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): void { + if (!this.userDataProfileService.currentProfile.isDefault && !this.userDataProfileService.currentProfile.useDefaultFlags?.settings) { + if (isEqual(this.userDataProfilesService.defaultProfile.settingsResource, this.settingsEditorModel.uri) && !this.configurationService.isSettingAppliedForAllProfiles(setting.key)) { // If we're in the default profile setting file, and the setting cannot be applied in all profiles markerData.push({ severity: MarkerSeverity.Hint, tags: [MarkerTag.Unnecessary], ...setting.range, - message: nls.localize( - "defaultProfileSettingWhileNonDefaultActive", - "This setting cannot be applied while a non-default profile is active. It will be applied when the default profile is active.", - ), + message: nls.localize('defaultProfileSettingWhileNonDefaultActive', "This setting cannot be applied while a non-default profile is active. It will be applied when the default profile is active.") }); - } else if ( - isEqual( - this.userDataProfileService.currentProfile.settingsResource, - this.settingsEditorModel.uri, - ) - ) { + } else if (isEqual(this.userDataProfileService.currentProfile.settingsResource, this.settingsEditorModel.uri)) { if (configuration.scope === ConfigurationScope.APPLICATION) { // If we're in a profile setting file, and the setting is application-scoped, fade it out. - markerData.push( - this.generateUnsupportedApplicationSettingMarker( - setting, - ), - ); - } else if ( - this.configurationService.isSettingAppliedForAllProfiles( - setting.key, - ) - ) { + markerData.push(this.generateUnsupportedApplicationSettingMarker(setting)); + } else if (this.configurationService.isSettingAppliedForAllProfiles(setting.key)) { // If we're in the non-default profile setting file, and the setting can be applied in all profiles, fade it out. markerData.push({ severity: MarkerSeverity.Hint, tags: [MarkerTag.Unnecessary], ...setting.range, - message: nls.localize( - "allProfileSettingWhileInNonDefaultProfileSetting", - "This setting cannot be applied because it is configured to be applied in all profiles using setting {0}. Value from the default profile will be used instead.", - APPLY_ALL_PROFILES_SETTING, - ), + message: nls.localize('allProfileSettingWhileInNonDefaultProfileSetting', "This setting cannot be applied because it is configured to be applied in all profiles using setting {0}. Value from the default profile will be used instead.", APPLY_ALL_PROFILES_SETTING) }); } } } - if ( - this.environmentService.remoteAuthority && - (configuration.scope === ConfigurationScope.MACHINE || - configuration.scope === ConfigurationScope.MACHINE_OVERRIDABLE) - ) { + if (this.environmentService.remoteAuthority && (configuration.scope === ConfigurationScope.MACHINE || configuration.scope === ConfigurationScope.MACHINE_OVERRIDABLE)) { markerData.push({ severity: MarkerSeverity.Hint, tags: [MarkerTag.Unnecessary], ...setting.range, - message: nls.localize( - "unsupportedRemoteMachineSetting", - "This setting cannot be applied in this window. It will be applied when you open a local window.", - ), + message: nls.localize('unsupportedRemoteMachineSetting', "This setting cannot be applied in this window. It will be applied when you open a local window.") }); } } - private handleRemoteUserConfiguration( - setting: ISetting, - configuration: IConfigurationPropertySchema, - markerData: IMarkerData[], - ): void { + + private handleRemoteUserConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): void { if (configuration.scope === ConfigurationScope.APPLICATION) { - markerData.push( - this.generateUnsupportedApplicationSettingMarker(setting), - ); + markerData.push(this.generateUnsupportedApplicationSettingMarker(setting)); } } - private handleWorkspaceConfiguration( - setting: ISetting, - configuration: IConfigurationPropertySchema, - markerData: IMarkerData[], - ): void { + + private handleWorkspaceConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): void { if (configuration.scope === ConfigurationScope.APPLICATION) { - markerData.push( - this.generateUnsupportedApplicationSettingMarker(setting), - ); + markerData.push(this.generateUnsupportedApplicationSettingMarker(setting)); } + if (configuration.scope === ConfigurationScope.MACHINE) { - markerData.push( - this.generateUnsupportedMachineSettingMarker(setting), - ); + markerData.push(this.generateUnsupportedMachineSettingMarker(setting)); } - if ( - !this.workspaceTrustManagementService.isWorkspaceTrusted() && - configuration.restricted - ) { + + if (!this.workspaceTrustManagementService.isWorkspaceTrusted() && configuration.restricted) { const marker = this.generateUntrustedSettingMarker(setting); markerData.push(marker); - - const codeActions = this.generateUntrustedSettingCodeActions([ - marker, - ]); + const codeActions = this.generateUntrustedSettingCodeActions([marker]); this.addCodeActions(marker, codeActions); } } - private handleWorkspaceFolderConfiguration( - setting: ISetting, - configuration: IConfigurationPropertySchema, - markerData: IMarkerData[], - ): void { + + private handleWorkspaceFolderConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): void { if (configuration.scope === ConfigurationScope.APPLICATION) { - markerData.push( - this.generateUnsupportedApplicationSettingMarker(setting), - ); + markerData.push(this.generateUnsupportedApplicationSettingMarker(setting)); } + if (configuration.scope === ConfigurationScope.MACHINE) { - markerData.push( - this.generateUnsupportedMachineSettingMarker(setting), - ); + markerData.push(this.generateUnsupportedMachineSettingMarker(setting)); } + if (configuration.scope === ConfigurationScope.WINDOW) { markerData.push({ severity: MarkerSeverity.Hint, tags: [MarkerTag.Unnecessary], ...setting.range, - message: nls.localize( - "unsupportedWindowSetting", - "This setting cannot be applied in this workspace. It will be applied when you open the containing workspace folder directly.", - ), + message: nls.localize('unsupportedWindowSetting', "This setting cannot be applied in this workspace. It will be applied when you open the containing workspace folder directly.") }); } - if ( - !this.workspaceTrustManagementService.isWorkspaceTrusted() && - configuration.restricted - ) { + + if (!this.workspaceTrustManagementService.isWorkspaceTrusted() && configuration.restricted) { const marker = this.generateUntrustedSettingMarker(setting); markerData.push(marker); - - const codeActions = this.generateUntrustedSettingCodeActions([ - marker, - ]); + const codeActions = this.generateUntrustedSettingCodeActions([marker]); this.addCodeActions(marker, codeActions); } } - private handleUnstableSettingConfiguration( - setting: ISetting, - configuration: IConfigurationPropertySchema, - markerData: IMarkerData[], - ): void { - if (configuration.tags?.includes("preview")) { + + private handleUnstableSettingConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): void { + if (configuration.tags?.includes('preview')) { markerData.push(this.generatePreviewSettingMarker(setting)); - } else if (configuration.tags?.includes("experimental")) { + } else if (configuration.tags?.includes('experimental')) { markerData.push(this.generateExperimentalSettingMarker(setting)); } } - private generateUnsupportedApplicationSettingMarker( - setting: ISetting, - ): IMarkerData { + + private generateUnsupportedApplicationSettingMarker(setting: ISetting): IMarkerData { return { severity: MarkerSeverity.Hint, tags: [MarkerTag.Unnecessary], ...setting.range, - message: nls.localize( - "unsupportedApplicationSetting", - "This setting has an application scope and can be set only in the user settings file.", - ), + message: nls.localize('unsupportedApplicationSetting', "This setting has an application scope and can be set only in the user settings file.") }; } - private generateUnsupportedMachineSettingMarker( - setting: ISetting, - ): IMarkerData { + + private generateUnsupportedMachineSettingMarker(setting: ISetting): IMarkerData { return { severity: MarkerSeverity.Hint, tags: [MarkerTag.Unnecessary], ...setting.range, - message: nls.localize( - "unsupportedMachineSetting", - "This setting can only be applied in user settings in local window or in remote settings in remote window.", - ), + message: nls.localize('unsupportedMachineSetting', "This setting can only be applied in user settings in local window or in remote settings in remote window.") }; } + private generateUntrustedSettingMarker(setting: ISetting): IMarkerData { return { severity: MarkerSeverity.Warning, ...setting.range, - message: nls.localize( - "untrustedSetting", - "This setting can only be applied in a trusted workspace.", - ), + message: nls.localize('untrustedSetting', "This setting can only be applied in a trusted workspace.") }; } + private generateUnknownConfigurationMarker(setting: ISetting): IMarkerData { return { severity: MarkerSeverity.Hint, tags: [MarkerTag.Unnecessary], ...setting.range, - message: nls.localize( - "unknown configuration setting", - "Unknown Configuration Setting", - ), + message: nls.localize('unknown configuration setting', "Unknown Configuration Setting") }; } - private generateUntrustedSettingCodeActions( - diagnostics: IMarkerData[], - ): languages.CodeAction[] { - return [ - { - title: nls.localize( - "manage workspace trust", - "Manage Workspace Trust", - ), - command: { - id: "workbench.trust.manage", - title: nls.localize( - "manage workspace trust", - "Manage Workspace Trust", - ), - }, - diagnostics, - kind: CodeActionKind.QuickFix.value, + + private generateUntrustedSettingCodeActions(diagnostics: IMarkerData[]): languages.CodeAction[] { + return [{ + title: nls.localize('manage workspace trust', "Manage Workspace Trust"), + command: { + id: 'workbench.trust.manage', + title: nls.localize('manage workspace trust', "Manage Workspace Trust") }, - ]; + diagnostics, + kind: CodeActionKind.QuickFix.value + }]; } + private generatePreviewSettingMarker(setting: ISetting): IMarkerData { return { severity: MarkerSeverity.Hint, ...setting.range, - message: PREVIEW_INDICATOR_DESCRIPTION, + message: PREVIEW_INDICATOR_DESCRIPTION }; } + private generateExperimentalSettingMarker(setting: ISetting): IMarkerData { return { severity: MarkerSeverity.Hint, ...setting.range, - message: EXPERIMENTAL_INDICATOR_DESCRIPTION, + message: EXPERIMENTAL_INDICATOR_DESCRIPTION }; } - private addCodeActions( - range: IRange, - codeActions: languages.CodeAction[], - ): void { - let actions = this.codeActions.get(this.settingsEditorModel.uri); + private addCodeActions(range: IRange, codeActions: languages.CodeAction[]): void { + let actions = this.codeActions.get(this.settingsEditorModel.uri); if (!actions) { actions = []; this.codeActions.set(this.settingsEditorModel.uri, actions); } actions.push([Range.lift(range), codeActions]); } + public override dispose(): void { - this.markerService.remove("UnsupportedSettingsRenderer", [ - this.settingsEditorModel.uri, - ]); + this.markerService.remove('UnsupportedSettingsRenderer', [this.settingsEditorModel.uri]); this.codeActions.clear(); - super.dispose(); } + } + class WorkspaceConfigurationRenderer extends Disposable { - private static readonly supportedKeys = [ - "folders", - "tasks", - "launch", - "extensions", - "settings", - "remoteAuthority", - "transient", - ]; + private static readonly supportedKeys = ['folders', 'tasks', 'launch', 'extensions', 'settings', 'remoteAuthority', 'transient']; + private readonly decorations = this.editor.createDecorationsCollection(); private renderingDelayer: Delayer = new Delayer(200); - constructor( - private editor: ICodeEditor, - private workspaceSettingsEditorModel: SettingsEditorModel, - @IWorkspaceContextService - private readonly workspaceContextService: IWorkspaceContextService, - @IMarkerService - private readonly markerService: IMarkerService, + constructor(private editor: ICodeEditor, private workspaceSettingsEditorModel: SettingsEditorModel, + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, + @IMarkerService private readonly markerService: IMarkerService ) { super(); - this._register( - this.editor - .getModel()! - .onDidChangeContent(() => - this.renderingDelayer.trigger(() => this.render()), - ), - ); + this._register(this.editor.getModel()!.onDidChangeContent(() => this.renderingDelayer.trigger(() => this.render()))); } + render(): void { const markerData: IMarkerData[] = []; - - if ( - this.workspaceContextService.getWorkbenchState() === - WorkbenchState.WORKSPACE && - this.workspaceSettingsEditorModel instanceof - WorkspaceConfigurationEditorModel - ) { + if (this.workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE && this.workspaceSettingsEditorModel instanceof WorkspaceConfigurationEditorModel) { const ranges: IRange[] = []; - - for (const settingsGroup of this.workspaceSettingsEditorModel - .configurationGroups) { + for (const settingsGroup of this.workspaceSettingsEditorModel.configurationGroups) { for (const section of settingsGroup.sections) { for (const setting of section.settings) { - if ( - !WorkspaceConfigurationRenderer.supportedKeys.includes( - setting.key, - ) - ) { + if (!WorkspaceConfigurationRenderer.supportedKeys.includes(setting.key)) { markerData.push({ severity: MarkerSeverity.Hint, tags: [MarkerTag.Unnecessary], ...setting.range, - message: nls.localize( - "unsupportedProperty", - "Unsupported Property", - ), + message: nls.localize('unsupportedProperty', "Unsupported Property") }); } } } } - this.decorations.set( - ranges.map((range) => this.createDecoration(range)), - ); + this.decorations.set(ranges.map(range => this.createDecoration(range))); } if (markerData.length) { - this.markerService.changeOne( - "WorkspaceConfigurationRenderer", - this.workspaceSettingsEditorModel.uri, - markerData, - ); + this.markerService.changeOne('WorkspaceConfigurationRenderer', this.workspaceSettingsEditorModel.uri, markerData); } else { - this.markerService.remove("WorkspaceConfigurationRenderer", [ - this.workspaceSettingsEditorModel.uri, - ]); + this.markerService.remove('WorkspaceConfigurationRenderer', [this.workspaceSettingsEditorModel.uri]); } } - private static readonly _DIM_CONFIGURATION_ = - ModelDecorationOptions.register({ - description: "dim-configuration", - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - inlineClassName: "dim-configuration", - }); + + private static readonly _DIM_CONFIGURATION_ = ModelDecorationOptions.register({ + description: 'dim-configuration', + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + inlineClassName: 'dim-configuration' + }); + private createDecoration(range: IRange): IModelDeltaDecoration { return { range, - options: WorkspaceConfigurationRenderer._DIM_CONFIGURATION_, + options: WorkspaceConfigurationRenderer._DIM_CONFIGURATION_ }; } + override dispose(): void { - this.markerService.remove("WorkspaceConfigurationRenderer", [ - this.workspaceSettingsEditorModel.uri, - ]); + this.markerService.remove('WorkspaceConfigurationRenderer', [this.workspaceSettingsEditorModel.uri]); this.decorations.clear(); - super.dispose(); } } diff --git a/Source/vs/workbench/contrib/remote/browser/remote.ts b/Source/vs/workbench/contrib/remote/browser/remote.ts index 6b55fbbbb6cbd..8fef141d4e7f0 100644 --- a/Source/vs/workbench/contrib/remote/browser/remote.ts +++ b/Source/vs/workbench/contrib/remote/browser/remote.ts @@ -2,145 +2,113 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import "./media/remoteViewlet.css"; - -import * as dom from "../../../../base/browser/dom.js"; -import { IListVirtualDelegate } from "../../../../base/browser/ui/list/list.js"; -import { - IAsyncDataSource, - ITreeNode, - ITreeRenderer, -} from "../../../../base/browser/ui/tree/tree.js"; -import { mainWindow } from "../../../../base/browser/window.js"; -import { Emitter, Event } from "../../../../base/common/event.js"; -import { Disposable, IDisposable } from "../../../../base/common/lifecycle.js"; -import { Schemas } from "../../../../base/common/network.js"; -import Severity from "../../../../base/common/severity.js"; -import { ThemeIcon } from "../../../../base/common/themables.js"; -import { isStringArray } from "../../../../base/common/types.js"; -import { URI } from "../../../../base/common/uri.js"; -import * as nls from "../../../../nls.js"; -import { ICommandService } from "../../../../platform/commands/common/commands.js"; -import { IConfigurationService } from "../../../../platform/configuration/common/configuration.js"; -import { IContextKeyService } from "../../../../platform/contextkey/common/contextkey.js"; -import { IContextMenuService } from "../../../../platform/contextview/browser/contextView.js"; -import { IDialogService } from "../../../../platform/dialogs/common/dialogs.js"; -import { IExtensionDescription } from "../../../../platform/extensions/common/extensions.js"; -import { IHoverService } from "../../../../platform/hover/browser/hover.js"; -import { SyncDescriptor } from "../../../../platform/instantiation/common/descriptors.js"; -import { IInstantiationService } from "../../../../platform/instantiation/common/instantiation.js"; -import { IKeybindingService } from "../../../../platform/keybinding/common/keybinding.js"; -import { WorkbenchAsyncDataTree } from "../../../../platform/list/browser/listService.js"; -import { ILogService } from "../../../../platform/log/common/log.js"; -import { IOpenerService } from "../../../../platform/opener/common/opener.js"; -import { - IProgress, - IProgressService, - IProgressStep, - ProgressLocation, -} from "../../../../platform/progress/common/progress.js"; -import { IQuickInputService } from "../../../../platform/quickinput/common/quickInput.js"; -import { Registry } from "../../../../platform/registry/common/platform.js"; -import { - PersistentConnectionEventType, - ReconnectionWaitEvent, -} from "../../../../platform/remote/common/remoteAgentConnection.js"; -import { getRemoteName } from "../../../../platform/remote/common/remoteHosts.js"; -import { IStorageService } from "../../../../platform/storage/common/storage.js"; -import { ITelemetryService } from "../../../../platform/telemetry/common/telemetry.js"; -import { IThemeService } from "../../../../platform/theme/common/themeService.js"; -import { getVirtualWorkspaceLocation } from "../../../../platform/workspace/common/virtualWorkspace.js"; -import { IWorkspaceContextService } from "../../../../platform/workspace/common/workspace.js"; -import { ReloadWindowAction } from "../../../browser/actions/windowActions.js"; -import { - IViewPaneOptions, - ViewPane, -} from "../../../browser/parts/views/viewPane.js"; -import { FilterViewPaneContainer } from "../../../browser/parts/views/viewsViewlet.js"; -import { IWorkbenchContribution } from "../../../common/contributions.js"; -import { - Extensions, - IViewContainersRegistry, - IViewDescriptor, - IViewDescriptorService, - IViewsRegistry, - ViewContainerLocation, -} from "../../../common/views.js"; -import { IWorkbenchEnvironmentService } from "../../../services/environment/common/environmentService.js"; -import { - IExtensionService, - isProposedApiEnabled, -} from "../../../services/extensions/common/extensions.js"; -import { IExtensionPointUser } from "../../../services/extensions/common/extensionsRegistry.js"; -import { IWorkbenchLayoutService } from "../../../services/layout/browser/layoutService.js"; -import { IRemoteAgentService } from "../../../services/remote/common/remoteAgentService.js"; -import { - HelpInformation, - IRemoteExplorerService, -} from "../../../services/remote/common/remoteExplorerService.js"; -import { ITimerService } from "../../../services/timer/browser/timerService.js"; -import { IWalkthroughsService } from "../../welcomeGettingStarted/browser/gettingStartedService.js"; -import { SwitchRemoteViewItem } from "./explorerViewItems.js"; -import { VIEWLET_ID } from "./remoteExplorer.js"; -import * as icons from "./remoteIcons.js"; + +import './media/remoteViewlet.css'; +import * as nls from '../../../../nls.js'; +import * as dom from '../../../../base/browser/dom.js'; +import { URI } from '../../../../base/common/uri.js'; +import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; +import { IStorageService } from '../../../../platform/storage/common/storage.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; +import { IExtensionService, isProposedApiEnabled } from '../../../services/extensions/common/extensions.js'; +import { FilterViewPaneContainer } from '../../../browser/parts/views/viewsViewlet.js'; +import { VIEWLET_ID } from './remoteExplorer.js'; +import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IViewDescriptor, IViewsRegistry, Extensions, ViewContainerLocation, IViewContainersRegistry, IViewDescriptorService } from '../../../common/views.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { IExtensionDescription } from '../../../../platform/extensions/common/extensions.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; +import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js'; +import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { IProgress, IProgressStep, IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js'; +import { IWorkbenchContribution } from '../../../common/contributions.js'; +import { IRemoteAgentService } from '../../../services/remote/common/remoteAgentService.js'; +import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; +import { ReconnectionWaitEvent, PersistentConnectionEventType } from '../../../../platform/remote/common/remoteAgentConnection.js'; +import Severity from '../../../../base/common/severity.js'; +import { ReloadWindowAction } from '../../../browser/actions/windowActions.js'; +import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js'; +import { SwitchRemoteViewItem } from './explorerViewItems.js'; +import { isStringArray } from '../../../../base/common/types.js'; +import { HelpInformation, IRemoteExplorerService } from '../../../services/remote/common/remoteExplorerService.js'; +import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js'; +import { ViewPane, IViewPaneOptions } from '../../../browser/parts/views/viewPane.js'; +import { IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js'; +import { ITreeRenderer, ITreeNode, IAsyncDataSource } from '../../../../base/browser/ui/tree/tree.js'; +import { WorkbenchAsyncDataTree } from '../../../../platform/list/browser/listService.js'; +import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { Event, Emitter } from '../../../../base/common/event.js'; +import { IExtensionPointUser } from '../../../services/extensions/common/extensionsRegistry.js'; +import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; +import * as icons from './remoteIcons.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; +import { ITimerService } from '../../../services/timer/browser/timerService.js'; +import { getRemoteName } from '../../../../platform/remote/common/remoteHosts.js'; +import { getVirtualWorkspaceLocation } from '../../../../platform/workspace/common/virtualWorkspace.js'; +import { IWalkthroughsService } from '../../welcomeGettingStarted/browser/gettingStartedService.js'; +import { Schemas } from '../../../../base/common/network.js'; +import { mainWindow } from '../../../../base/browser/window.js'; +import { IHoverService } from '../../../../platform/hover/browser/hover.js'; interface IViewModel { onDidChangeHelpInformation: Event; helpInformation: HelpInformation[]; } + class HelpTreeVirtualDelegate implements IListVirtualDelegate { getHeight(element: IHelpItem): number { return 22; } + getTemplateId(element: IHelpItem): string { - return "HelpItemTemplate"; + return 'HelpItemTemplate'; } } + interface IHelpItemTemplateData { parent: HTMLElement; icon: HTMLElement; } -class HelpTreeRenderer - implements - ITreeRenderer -{ - templateId: string = "HelpItemTemplate"; - renderTemplate(container: HTMLElement): IHelpItemTemplateData { - container.classList.add("remote-help-tree-node-item"); - const icon = dom.append( - container, - dom.$(".remote-help-tree-node-item-icon"), - ); +class HelpTreeRenderer implements ITreeRenderer { + templateId: string = 'HelpItemTemplate'; + renderTemplate(container: HTMLElement): IHelpItemTemplateData { + container.classList.add('remote-help-tree-node-item'); + const icon = dom.append(container, dom.$('.remote-help-tree-node-item-icon')); const parent = container; - return { parent, icon }; } - renderElement( - element: ITreeNode, - index: number, - templateData: IHelpItemTemplateData, - height: number | undefined, - ): void { - const container = templateData.parent; + renderElement(element: ITreeNode, index: number, templateData: IHelpItemTemplateData, height: number | undefined): void { + const container = templateData.parent; dom.append(container, templateData.icon); templateData.icon.classList.add(...element.element.iconClasses); - - const labelContainer = dom.append(container, dom.$(".help-item-label")); + const labelContainer = dom.append(container, dom.$('.help-item-label')); labelContainer.innerText = element.element.label; } - disposeTemplate(templateData: IHelpItemTemplateData): void {} + + disposeTemplate(templateData: IHelpItemTemplateData): void { + + } } + class HelpDataSource implements IAsyncDataSource { hasChildren(element: HelpModel) { return element instanceof HelpModel; } + getChildren(element: HelpModel) { if (element instanceof HelpModel && element.items) { return element.items; } + return []; } } @@ -151,6 +119,7 @@ interface IHelpItem { values: HelpItemValue[]; handleClick(): Promise; } + class HelpModel { items: IHelpItem[] | undefined; @@ -162,211 +131,148 @@ class HelpModel { private remoteExplorerService: IRemoteExplorerService, private environmentService: IWorkbenchEnvironmentService, private workspaceContextService: IWorkspaceContextService, - private walkthroughsService: IWalkthroughsService, + private walkthroughsService: IWalkthroughsService ) { this.updateItems(); viewModel.onDidChangeHelpInformation(() => this.updateItems()); } - private createHelpItemValue( - info: HelpInformation, - infoKey: Exclude< - keyof HelpInformation, - "extensionDescription" | "remoteName" | "virtualWorkspace" - >, - ) { - return new HelpItemValue( - this.commandService, + + private createHelpItemValue(info: HelpInformation, infoKey: Exclude) { + return new HelpItemValue(this.commandService, this.walkthroughsService, info.extensionDescription, - typeof info.remoteName === "string" - ? [info.remoteName] - : info.remoteName, + (typeof info.remoteName === 'string') ? [info.remoteName] : info.remoteName, info.virtualWorkspace, - info[infoKey], - ); + info[infoKey]); } + private updateItems() { const helpItems: IHelpItem[] = []; - const getStarted = this.viewModel.helpInformation.filter( - (info) => info.getStarted, - ); - + const getStarted = this.viewModel.helpInformation.filter(info => info.getStarted); if (getStarted.length) { - const helpItemValues = getStarted.map((info: HelpInformation) => - this.createHelpItemValue(info, "getStarted"), + const helpItemValues = getStarted.map((info: HelpInformation) => this.createHelpItemValue(info, 'getStarted')); + const getStartedHelpItem = this.items?.find(item => item.icon === icons.getStartedIcon) ?? new GetStartedHelpItem( + icons.getStartedIcon, + nls.localize('remote.help.getStarted', "Get Started"), + helpItemValues, + this.quickInputService, + this.environmentService, + this.openerService, + this.remoteExplorerService, + this.workspaceContextService, + this.commandService ); - - const getStartedHelpItem = - this.items?.find( - (item) => item.icon === icons.getStartedIcon, - ) ?? - new GetStartedHelpItem( - icons.getStartedIcon, - nls.localize("remote.help.getStarted", "Get Started"), - helpItemValues, - this.quickInputService, - this.environmentService, - this.openerService, - this.remoteExplorerService, - this.workspaceContextService, - this.commandService, - ); - getStartedHelpItem.values = helpItemValues; helpItems.push(getStartedHelpItem); } - const documentation = this.viewModel.helpInformation.filter( - (info) => info.documentation, - ); + const documentation = this.viewModel.helpInformation.filter(info => info.documentation); if (documentation.length) { - const helpItemValues = documentation.map((info: HelpInformation) => - this.createHelpItemValue(info, "documentation"), + const helpItemValues = documentation.map((info: HelpInformation) => this.createHelpItemValue(info, 'documentation')); + const documentationHelpItem = this.items?.find(item => item.icon === icons.documentationIcon) ?? new HelpItem( + icons.documentationIcon, + nls.localize('remote.help.documentation', "Read Documentation"), + helpItemValues, + this.quickInputService, + this.environmentService, + this.openerService, + this.remoteExplorerService, + this.workspaceContextService ); - - const documentationHelpItem = - this.items?.find( - (item) => item.icon === icons.documentationIcon, - ) ?? - new HelpItem( - icons.documentationIcon, - nls.localize( - "remote.help.documentation", - "Read Documentation", - ), - helpItemValues, - this.quickInputService, - this.environmentService, - this.openerService, - this.remoteExplorerService, - this.workspaceContextService, - ); - documentationHelpItem.values = helpItemValues; helpItems.push(documentationHelpItem); } - const issues = this.viewModel.helpInformation.filter( - (info) => info.issues, - ); + const issues = this.viewModel.helpInformation.filter(info => info.issues); if (issues.length) { - const helpItemValues = issues.map((info: HelpInformation) => - this.createHelpItemValue(info, "issues"), + const helpItemValues = issues.map((info: HelpInformation) => this.createHelpItemValue(info, 'issues')); + const reviewIssuesHelpItem = this.items?.find(item => item.icon === icons.reviewIssuesIcon) ?? new HelpItem( + icons.reviewIssuesIcon, + nls.localize('remote.help.issues', "Review Issues"), + helpItemValues, + this.quickInputService, + this.environmentService, + this.openerService, + this.remoteExplorerService, + this.workspaceContextService ); - - const reviewIssuesHelpItem = - this.items?.find( - (item) => item.icon === icons.reviewIssuesIcon, - ) ?? - new HelpItem( - icons.reviewIssuesIcon, - nls.localize("remote.help.issues", "Review Issues"), - helpItemValues, - this.quickInputService, - this.environmentService, - this.openerService, - this.remoteExplorerService, - this.workspaceContextService, - ); reviewIssuesHelpItem.values = helpItemValues; helpItems.push(reviewIssuesHelpItem); } + if (helpItems.length) { - const helpItemValues = this.viewModel.helpInformation.map((info) => - this.createHelpItemValue(info, "reportIssue"), + const helpItemValues = this.viewModel.helpInformation.map(info => this.createHelpItemValue(info, 'reportIssue')); + const issueReporterItem = this.items?.find(item => item.icon === icons.reportIssuesIcon) ?? new IssueReporterItem( + icons.reportIssuesIcon, + nls.localize('remote.help.report', "Report Issue"), + helpItemValues, + this.quickInputService, + this.environmentService, + this.commandService, + this.openerService, + this.remoteExplorerService, + this.workspaceContextService ); - - const issueReporterItem = - this.items?.find( - (item) => item.icon === icons.reportIssuesIcon, - ) ?? - new IssueReporterItem( - icons.reportIssuesIcon, - nls.localize("remote.help.report", "Report Issue"), - helpItemValues, - this.quickInputService, - this.environmentService, - this.commandService, - this.openerService, - this.remoteExplorerService, - this.workspaceContextService, - ); issueReporterItem.values = helpItemValues; helpItems.push(issueReporterItem); } + if (helpItems.length) { this.items = helpItems; } } } + class HelpItemValue { private _url: string | undefined; private _description: string | undefined; - constructor( - private commandService: ICommandService, - private walkthroughService: IWalkthroughsService, - public extensionDescription: IExtensionDescription, - public readonly remoteAuthority: string[] | undefined, - public readonly virtualWorkspace: string | undefined, - private urlOrCommandOrId?: - | string - | { - id: string; - }, - ) {} + constructor(private commandService: ICommandService, private walkthroughService: IWalkthroughsService, public extensionDescription: IExtensionDescription, public readonly remoteAuthority: string[] | undefined, public readonly virtualWorkspace: string | undefined, private urlOrCommandOrId?: string | { id: string }) { + } + get description(): Promise { return this.getUrl().then(() => this._description); } + get url(): Promise { return this.getUrl(); } + private async getUrl(): Promise { if (this._url === undefined) { - if (typeof this.urlOrCommandOrId === "string") { + if (typeof this.urlOrCommandOrId === 'string') { const url = URI.parse(this.urlOrCommandOrId); - if (url.authority) { this._url = this.urlOrCommandOrId; } else { - const urlCommand: Promise = - this.commandService - .executeCommand(this.urlOrCommandOrId) - .then((result) => { - // if executing this command times out, cache its value whenever it eventually resolves - this._url = result; - - return this._url; - }); + const urlCommand: Promise = this.commandService.executeCommand(this.urlOrCommandOrId).then((result) => { + // if executing this command times out, cache its value whenever it eventually resolves + this._url = result; + return this._url; + }); // We must be defensive. The command may never return, meaning that no help at all is ever shown! - const emptyString: Promise = new Promise( - (resolve) => setTimeout(() => resolve(""), 500), - ); + const emptyString: Promise = new Promise(resolve => setTimeout(() => resolve(''), 500)); this._url = await Promise.race([urlCommand, emptyString]); } } else if (this.urlOrCommandOrId?.id) { try { const walkthroughId = `${this.extensionDescription.id}#${this.urlOrCommandOrId.id}`; - - const walkthrough = - await this.walkthroughService.getWalkthrough( - walkthroughId, - ); + const walkthrough = await this.walkthroughService.getWalkthrough(walkthroughId); this._description = walkthrough.title; this._url = walkthroughId; - } catch {} + } catch { } } } if (this._url === undefined) { - this._url = ""; + this._url = ''; } return this._url; } } + abstract class HelpItemBase implements IHelpItem { public iconClasses: string[] = []; - constructor( public icon: ThemeIcon, public label: string, @@ -374,58 +280,38 @@ abstract class HelpItemBase implements IHelpItem { private quickInputService: IQuickInputService, private environmentService: IWorkbenchEnvironmentService, private remoteExplorerService: IRemoteExplorerService, - private workspaceContextService: IWorkspaceContextService, + private workspaceContextService: IWorkspaceContextService ) { this.iconClasses.push(...ThemeIcon.asClassNameArray(icon)); - this.iconClasses.push("remote-help-tree-node-item-icon"); + this.iconClasses.push('remote-help-tree-node-item-icon'); } - protected async getActions(): Promise< - { - label: string; - url: string; - description: string; - extensionDescription: IExtensionDescription; - }[] - > { - return ( - await Promise.all( - this.values.map(async (value) => { - return { - label: - value.extensionDescription.displayName || - value.extensionDescription.identifier.value, - description: - (await value.description) ?? (await value.url), - url: await value.url, - extensionDescription: value.extensionDescription, - }; - }), - ) - ).filter((item) => item.description); + + protected async getActions(): Promise<{ + label: string; + url: string; + description: string; + extensionDescription: IExtensionDescription; + }[]> { + return (await Promise.all(this.values.map(async (value) => { + return { + label: value.extensionDescription.displayName || value.extensionDescription.identifier.value, + description: await value.description ?? await value.url, + url: await value.url, + extensionDescription: value.extensionDescription + }; + }))).filter(item => item.description); } + async handleClick() { const remoteAuthority = this.environmentService.remoteAuthority; - if (remoteAuthority) { - for ( - let i = 0; - i < this.remoteExplorerService.targetType.length; - i++ - ) { - if ( - remoteAuthority.startsWith( - this.remoteExplorerService.targetType[i], - ) - ) { + for (let i = 0; i < this.remoteExplorerService.targetType.length; i++) { + if (remoteAuthority.startsWith(this.remoteExplorerService.targetType[i])) { for (const value of this.values) { if (value.remoteAuthority) { for (const authority of value.remoteAuthority) { if (remoteAuthority.startsWith(authority)) { - await this.takeAction( - value.extensionDescription, - await value.url, - ); - + await this.takeAction(value.extensionDescription, await value.url); return; } } @@ -434,70 +320,42 @@ abstract class HelpItemBase implements IHelpItem { } } } else { - const virtualWorkspace = getVirtualWorkspaceLocation( - this.workspaceContextService.getWorkspace(), - )?.scheme; - + const virtualWorkspace = getVirtualWorkspaceLocation(this.workspaceContextService.getWorkspace())?.scheme; if (virtualWorkspace) { - for ( - let i = 0; - i < this.remoteExplorerService.targetType.length; - i++ - ) { + for (let i = 0; i < this.remoteExplorerService.targetType.length; i++) { for (const value of this.values) { if (value.virtualWorkspace && value.remoteAuthority) { for (const authority of value.remoteAuthority) { - if ( - this.remoteExplorerService.targetType[ - i - ].startsWith(authority) && - virtualWorkspace.startsWith( - value.virtualWorkspace, - ) - ) { - await this.takeAction( - value.extensionDescription, - await value.url, - ); - + if (this.remoteExplorerService.targetType[i].startsWith(authority) && virtualWorkspace.startsWith(value.virtualWorkspace)) { + await this.takeAction(value.extensionDescription, await value.url); return; } } } + } } } } + if (this.values.length > 1) { const actions = await this.getActions(); if (actions.length) { - const action = await this.quickInputService.pick(actions, { - placeHolder: nls.localize( - "pickRemoteExtension", - "Select url to open", - ), - }); - + const action = await this.quickInputService.pick(actions, { placeHolder: nls.localize('pickRemoteExtension', "Select url to open") }); if (action) { - await this.takeAction( - action.extensionDescription, - action.url, - ); + await this.takeAction(action.extensionDescription, action.url); } } } else { - await this.takeAction( - this.values[0].extensionDescription, - await this.values[0].url, - ); + await this.takeAction(this.values[0].extensionDescription, await this.values[0].url); } + } - protected abstract takeAction( - extensionDescription: IExtensionDescription, - url?: string, - ): Promise; + + protected abstract takeAction(extensionDescription: IExtensionDescription, url?: string): Promise; } + class GetStartedHelpItem extends HelpItemBase { constructor( icon: ThemeIcon, @@ -508,39 +366,21 @@ class GetStartedHelpItem extends HelpItemBase { private openerService: IOpenerService, remoteExplorerService: IRemoteExplorerService, workspaceContextService: IWorkspaceContextService, - private commandService: ICommandService, + private commandService: ICommandService ) { - super( - icon, - label, - values, - quickInputService, - environmentService, - remoteExplorerService, - workspaceContextService, - ); + super(icon, label, values, quickInputService, environmentService, remoteExplorerService, workspaceContextService); } - protected async takeAction( - extensionDescription: IExtensionDescription, - urlOrWalkthroughId: string, - ): Promise { - if ( - [Schemas.http, Schemas.https].includes( - URI.parse(urlOrWalkthroughId).scheme, - ) - ) { - this.openerService.open(urlOrWalkthroughId, { - allowCommands: true, - }); + protected async takeAction(extensionDescription: IExtensionDescription, urlOrWalkthroughId: string): Promise { + if ([Schemas.http, Schemas.https].includes(URI.parse(urlOrWalkthroughId).scheme)) { + this.openerService.open(urlOrWalkthroughId, { allowCommands: true }); return; } - this.commandService.executeCommand( - "workbench.action.openWalkthrough", - urlOrWalkthroughId, - ); + + this.commandService.executeCommand('workbench.action.openWalkthrough', urlOrWalkthroughId); } } + class HelpItem extends HelpItemBase { constructor( icon: ThemeIcon, @@ -550,25 +390,16 @@ class HelpItem extends HelpItemBase { environmentService: IWorkbenchEnvironmentService, private openerService: IOpenerService, remoteExplorerService: IRemoteExplorerService, - workspaceContextService: IWorkspaceContextService, + workspaceContextService: IWorkspaceContextService ) { - super( - icon, - label, - values, - quickInputService, - environmentService, - remoteExplorerService, - workspaceContextService, - ); + super(icon, label, values, quickInputService, environmentService, remoteExplorerService, workspaceContextService); } - protected async takeAction( - extensionDescription: IExtensionDescription, - url: string, - ): Promise { + + protected async takeAction(extensionDescription: IExtensionDescription, url: string): Promise { await this.openerService.open(URI.parse(url), { allowCommands: true }); } } + class IssueReporterItem extends HelpItemBase { constructor( icon: ThemeIcon, @@ -579,310 +410,198 @@ class IssueReporterItem extends HelpItemBase { private commandService: ICommandService, private openerService: IOpenerService, remoteExplorerService: IRemoteExplorerService, - workspaceContextService: IWorkspaceContextService, + workspaceContextService: IWorkspaceContextService ) { - super( - icon, - label, - values, - quickInputService, - environmentService, - remoteExplorerService, - workspaceContextService, - ); + super(icon, label, values, quickInputService, environmentService, remoteExplorerService, workspaceContextService); } - protected override async getActions(): Promise< - { - label: string; - description: string; - url: string; - extensionDescription: IExtensionDescription; - }[] - > { - return Promise.all( - this.values.map(async (value) => { - return { - label: - value.extensionDescription.displayName || - value.extensionDescription.identifier.value, - description: "", - url: await value.url, - extensionDescription: value.extensionDescription, - }; - }), - ); + + protected override async getActions(): Promise<{ + label: string; + description: string; + url: string; + extensionDescription: IExtensionDescription; + }[]> { + return Promise.all(this.values.map(async (value) => { + return { + label: value.extensionDescription.displayName || value.extensionDescription.identifier.value, + description: '', + url: await value.url, + extensionDescription: value.extensionDescription + }; + })); } - protected async takeAction( - extensionDescription: IExtensionDescription, - url: string, - ): Promise { + + protected async takeAction(extensionDescription: IExtensionDescription, url: string): Promise { if (!url) { - await this.commandService.executeCommand( - "workbench.action.openIssueReporter", - [extensionDescription.identifier.value], - ); + await this.commandService.executeCommand('workbench.action.openIssueReporter', [extensionDescription.identifier.value]); } else { await this.openerService.open(URI.parse(url)); } } } + class HelpPanel extends ViewPane { - static readonly ID = "~remote.helpPanel"; - static readonly TITLE = nls.localize2("remote.help", "Help and feedback"); + static readonly ID = '~remote.helpPanel'; + static readonly TITLE = nls.localize2('remote.help', "Help and feedback"); private tree!: WorkbenchAsyncDataTree; constructor( protected viewModel: IViewModel, options: IViewPaneOptions, - @IKeybindingService - keybindingService: IKeybindingService, - @IContextMenuService - contextMenuService: IContextMenuService, - @IContextKeyService - contextKeyService: IContextKeyService, - @IConfigurationService - configurationService: IConfigurationService, - @IInstantiationService - instantiationService: IInstantiationService, - @IViewDescriptorService - viewDescriptorService: IViewDescriptorService, - @IOpenerService - openerService: IOpenerService, - @IQuickInputService - protected quickInputService: IQuickInputService, - @ICommandService - protected commandService: ICommandService, - @IRemoteExplorerService - protected readonly remoteExplorerService: IRemoteExplorerService, - @IWorkbenchEnvironmentService - protected readonly environmentService: IWorkbenchEnvironmentService, - @IThemeService - themeService: IThemeService, - @ITelemetryService - telemetryService: ITelemetryService, - @IHoverService - hoverService: IHoverService, - @IWorkspaceContextService - private readonly workspaceContextService: IWorkspaceContextService, - @IWalkthroughsService - private readonly walkthroughsService: IWalkthroughsService, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IContextKeyService contextKeyService: IContextKeyService, + @IConfigurationService configurationService: IConfigurationService, + @IInstantiationService instantiationService: IInstantiationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IOpenerService openerService: IOpenerService, + @IQuickInputService protected quickInputService: IQuickInputService, + @ICommandService protected commandService: ICommandService, + @IRemoteExplorerService protected readonly remoteExplorerService: IRemoteExplorerService, + @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, + @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, + @IHoverService hoverService: IHoverService, + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, + @IWalkthroughsService private readonly walkthroughsService: IWalkthroughsService, ) { - super( - options, - keybindingService, - contextMenuService, - configurationService, - contextKeyService, - viewDescriptorService, - instantiationService, - openerService, - themeService, - telemetryService, - hoverService, - ); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService); } + protected override renderBody(container: HTMLElement): void { super.renderBody(container); - container.classList.add("remote-help"); - const treeContainer = document.createElement("div"); - treeContainer.classList.add("remote-help-content"); + container.classList.add('remote-help'); + const treeContainer = document.createElement('div'); + treeContainer.classList.add('remote-help-content'); container.appendChild(treeContainer); - this.tree = >( - this.instantiationService.createInstance( - WorkbenchAsyncDataTree, - "RemoteHelp", - treeContainer, - new HelpTreeVirtualDelegate(), - [new HelpTreeRenderer()], - new HelpDataSource(), - { - accessibilityProvider: { - getAriaLabel: (item: HelpItemBase) => { - return item.label; - }, - getWidgetAriaLabel: () => - nls.localize("remotehelp", "Remote Help"), + + this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, + 'RemoteHelp', + treeContainer, + new HelpTreeVirtualDelegate(), + [new HelpTreeRenderer()], + new HelpDataSource(), + { + accessibilityProvider: { + getAriaLabel: (item: HelpItemBase) => { + return item.label; }, - }, - ) + getWidgetAriaLabel: () => nls.localize('remotehelp', "Remote Help") + } + } ); - const model = new HelpModel( - this.viewModel, - this.openerService, - this.quickInputService, - this.commandService, - this.remoteExplorerService, - this.environmentService, - this.workspaceContextService, - this.walkthroughsService, - ); + const model = new HelpModel(this.viewModel, this.openerService, this.quickInputService, this.commandService, this.remoteExplorerService, this.environmentService, this.workspaceContextService, this.walkthroughsService); + this.tree.setInput(model); - this._register( - Event.debounce( - this.tree.onDidOpen, - (last, event) => event, - 75, - true, - )((e) => { - e.element?.handleClick(); - }), - ); + + this._register(Event.debounce(this.tree.onDidOpen, (last, event) => event, 75, true)(e => { + e.element?.handleClick(); + })); } + protected override layoutBody(height: number, width: number): void { super.layoutBody(height, width); this.tree.layout(height, width); } } + class HelpPanelDescriptor implements IViewDescriptor { readonly id = HelpPanel.ID; readonly name = HelpPanel.TITLE; readonly ctorDescriptor: SyncDescriptor; readonly canToggleVisibility = true; readonly hideByDefault = false; - readonly group = "help@50"; + readonly group = 'help@50'; readonly order = -10; constructor(viewModel: IViewModel) { this.ctorDescriptor = new SyncDescriptor(HelpPanel, [viewModel]); } } -class RemoteViewPaneContainer - extends FilterViewPaneContainer - implements IViewModel -{ + +class RemoteViewPaneContainer extends FilterViewPaneContainer implements IViewModel { private helpPanelDescriptor = new HelpPanelDescriptor(this); helpInformation: HelpInformation[] = []; private _onDidChangeHelpInformation = new Emitter(); - public onDidChangeHelpInformation: Event = - this._onDidChangeHelpInformation.event; + public onDidChangeHelpInformation: Event = this._onDidChangeHelpInformation.event; private hasRegisteredHelpView: boolean = false; private remoteSwitcher: SwitchRemoteViewItem | undefined; constructor( - @IWorkbenchLayoutService - layoutService: IWorkbenchLayoutService, - @ITelemetryService - telemetryService: ITelemetryService, - @IWorkspaceContextService - contextService: IWorkspaceContextService, - @IStorageService - storageService: IStorageService, - @IConfigurationService - configurationService: IConfigurationService, - @IInstantiationService - instantiationService: IInstantiationService, - @IThemeService - themeService: IThemeService, - @IContextMenuService - contextMenuService: IContextMenuService, - @IExtensionService - extensionService: IExtensionService, - @IRemoteExplorerService - private readonly remoteExplorerService: IRemoteExplorerService, - @IViewDescriptorService - viewDescriptorService: IViewDescriptorService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ITelemetryService telemetryService: ITelemetryService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IStorageService storageService: IStorageService, + @IConfigurationService configurationService: IConfigurationService, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService, + @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - super( - VIEWLET_ID, - remoteExplorerService.onDidChangeTargetType, - configurationService, - layoutService, - telemetryService, - storageService, - instantiationService, - themeService, - contextMenuService, - extensionService, - contextService, - viewDescriptorService, - ); + super(VIEWLET_ID, remoteExplorerService.onDidChangeTargetType, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, viewDescriptorService); this.addConstantViewDescriptors([this.helpPanelDescriptor]); - this._register( - (this.remoteSwitcher = - this.instantiationService.createInstance(SwitchRemoteViewItem)), - ); - this.remoteExplorerService.onDidChangeHelpInformation((extensions) => { + this._register(this.remoteSwitcher = this.instantiationService.createInstance(SwitchRemoteViewItem)); + this.remoteExplorerService.onDidChangeHelpInformation(extensions => { this._setHelpInformation(extensions); }); - this._setHelpInformation(this.remoteExplorerService.helpInformation); - - const viewsRegistry = Registry.as( - Extensions.ViewsRegistry, - ); - this.remoteSwitcher.createOptionItems( - viewsRegistry.getViews(this.viewContainer), - ); - this._register( - viewsRegistry.onViewsRegistered((e) => { - const remoteViews: IViewDescriptor[] = []; - for (const view of e) { - if (view.viewContainer.id === VIEWLET_ID) { - remoteViews.push(...view.views); - } - } - if (remoteViews.length > 0) { - this.remoteSwitcher!.createOptionItems(remoteViews); - } - }), - ); - this._register( - viewsRegistry.onViewsDeregistered((e) => { - if (e.viewContainer.id === VIEWLET_ID) { - this.remoteSwitcher!.removeOptionItems(e.views); + this._setHelpInformation(this.remoteExplorerService.helpInformation); + const viewsRegistry = Registry.as(Extensions.ViewsRegistry); + + this.remoteSwitcher.createOptionItems(viewsRegistry.getViews(this.viewContainer)); + this._register(viewsRegistry.onViewsRegistered(e => { + const remoteViews: IViewDescriptor[] = []; + for (const view of e) { + if (view.viewContainer.id === VIEWLET_ID) { + remoteViews.push(...view.views); } - }), - ); + } + if (remoteViews.length > 0) { + this.remoteSwitcher!.createOptionItems(remoteViews); + } + })); + this._register(viewsRegistry.onViewsDeregistered(e => { + if (e.viewContainer.id === VIEWLET_ID) { + this.remoteSwitcher!.removeOptionItems(e.views); + } + })); } - private _setHelpInformation( - extensions: readonly IExtensionPointUser[], - ) { - const helpInformation: HelpInformation[] = []; + private _setHelpInformation(extensions: readonly IExtensionPointUser[]) { + const helpInformation: HelpInformation[] = []; for (const extension of extensions) { this._handleRemoteInfoExtensionPoint(extension, helpInformation); } + this.helpInformation = helpInformation; this._onDidChangeHelpInformation.fire(); - const viewsRegistry = Registry.as( - Extensions.ViewsRegistry, - ); - + const viewsRegistry = Registry.as(Extensions.ViewsRegistry); if (this.helpInformation.length && !this.hasRegisteredHelpView) { const view = viewsRegistry.getView(this.helpPanelDescriptor.id); - if (!view) { - viewsRegistry.registerViews( - [this.helpPanelDescriptor], - this.viewContainer, - ); + viewsRegistry.registerViews([this.helpPanelDescriptor], this.viewContainer); } this.hasRegisteredHelpView = true; } else if (this.hasRegisteredHelpView) { - viewsRegistry.deregisterViews( - [this.helpPanelDescriptor], - this.viewContainer, - ); + viewsRegistry.deregisterViews([this.helpPanelDescriptor], this.viewContainer); this.hasRegisteredHelpView = false; } } - private _handleRemoteInfoExtensionPoint( - extension: IExtensionPointUser, - helpInformation: HelpInformation[], - ) { - if (!isProposedApiEnabled(extension.description, "contribRemoteHelp")) { + + private _handleRemoteInfoExtensionPoint(extension: IExtensionPointUser, helpInformation: HelpInformation[]) { + if (!isProposedApiEnabled(extension.description, 'contribRemoteHelp')) { return; } - if ( - !extension.value.documentation && - !extension.value.getStarted && - !extension.value.issues - ) { + + if (!extension.value.documentation && !extension.value.getStarted && !extension.value.issues) { return; } + helpInformation.push({ extensionDescription: extension.description, getStarted: extension.value.getStarted, @@ -890,33 +609,28 @@ class RemoteViewPaneContainer reportIssue: extension.value.reportIssue, issues: extension.value.issues, remoteName: extension.value.remoteName, - virtualWorkspace: extension.value.virtualWorkspace, + virtualWorkspace: extension.value.virtualWorkspace }); } + protected getFilterOn(viewDescriptor: IViewDescriptor): string | undefined { - return isStringArray(viewDescriptor.remoteAuthority) - ? viewDescriptor.remoteAuthority[0] - : viewDescriptor.remoteAuthority; + return isStringArray(viewDescriptor.remoteAuthority) ? viewDescriptor.remoteAuthority[0] : viewDescriptor.remoteAuthority; } + protected setFilter(viewDescriptor: IViewDescriptor): void { - this.remoteExplorerService.targetType = isStringArray( - viewDescriptor.remoteAuthority, - ) - ? viewDescriptor.remoteAuthority - : [viewDescriptor.remoteAuthority!]; + this.remoteExplorerService.targetType = isStringArray(viewDescriptor.remoteAuthority) ? viewDescriptor.remoteAuthority : [viewDescriptor.remoteAuthority!]; } - getTitle(): string { - const title = nls.localize("remote.explorer", "Remote Explorer"); + getTitle(): string { + const title = nls.localize('remote.explorer', "Remote Explorer"); return title; } } -Registry.as( - Extensions.ViewContainersRegistry, -).registerViewContainer( + +Registry.as(Extensions.ViewContainersRegistry).registerViewContainer( { id: VIEWLET_ID, - title: nls.localize2("remote.explorer", "Remote Explorer"), + title: nls.localize2('remote.explorer', "Remote Explorer"), ctorDescriptor: new SyncDescriptor(RemoteViewPaneContainer), hideIfEmpty: true, viewOrderDelegate: { @@ -924,63 +638,58 @@ Registry.as( if (!group) { return; } - let matches = /^targets@(\d+)$/.exec(group); + let matches = /^targets@(\d+)$/.exec(group); if (matches) { return -1000; } + matches = /^details(@(\d+))?$/.exec(group); if (matches) { return -500 + Number(matches[2]); } - matches = /^help(@(\d+))?$/.exec(group); + matches = /^help(@(\d+))?$/.exec(group); if (matches) { return -10; } + return; - }, + } }, icon: icons.remoteExplorerViewIcon, - order: 4, - }, - ViewContainerLocation.Sidebar, -); + order: 4 + }, ViewContainerLocation.Sidebar); + export class RemoteMarkers implements IWorkbenchContribution { + constructor( - @IRemoteAgentService - remoteAgentService: IRemoteAgentService, - @ITimerService - timerService: ITimerService, + @IRemoteAgentService remoteAgentService: IRemoteAgentService, + @ITimerService timerService: ITimerService, ) { - remoteAgentService.getEnvironment().then((remoteEnv) => { + remoteAgentService.getEnvironment().then(remoteEnv => { if (remoteEnv) { - timerService.setPerformanceMarks("server", remoteEnv.marks); + timerService.setPerformanceMarks('server', remoteEnv.marks); } }); } } + class VisibleProgress { + public readonly location: ProgressLocation; private _isDisposed: boolean; private _lastReport: string | null; private _currentProgressPromiseResolve: (() => void) | null; private _currentProgress: IProgress | null; private _currentTimer: ReconnectionTimer | null; + public get lastReport(): string | null { return this._lastReport; } - constructor( - progressService: IProgressService, - location: ProgressLocation, - initialReport: string | null, - buttons: string[], - onDidCancel: ( - choice: number | undefined, - lastReport: string | null, - ) => void, - ) { + + constructor(progressService: IProgressService, location: ProgressLocation, initialReport: string | null, buttons: string[], onDidCancel: (choice: number | undefined, lastReport: string | null) => void) { this.location = location; this._isDisposed = false; this._lastReport = initialReport; @@ -988,50 +697,47 @@ class VisibleProgress { this._currentProgress = null; this._currentTimer = null; - const promise = new Promise( - (resolve) => (this._currentProgressPromiseResolve = resolve), - ); + const promise = new Promise((resolve) => this._currentProgressPromiseResolve = resolve); + progressService.withProgress( { location: location, buttons: buttons }, - (progress) => { - if (!this._isDisposed) { - this._currentProgress = progress; - } - return promise; - }, - (choice) => onDidCancel(choice, this._lastReport), + (progress) => { if (!this._isDisposed) { this._currentProgress = progress; } return promise; }, + (choice) => onDidCancel(choice, this._lastReport) ); if (this._lastReport) { this.report(); } } + public dispose(): void { this._isDisposed = true; - if (this._currentProgressPromiseResolve) { this._currentProgressPromiseResolve(); this._currentProgressPromiseResolve = null; } this._currentProgress = null; - if (this._currentTimer) { this._currentTimer.dispose(); this._currentTimer = null; } } + public report(message?: string) { if (message) { this._lastReport = message; } + if (this._lastReport && this._currentProgress) { this._currentProgress.report({ message: this._lastReport }); } } + public startTimer(completionTime: number): void { this.stopTimer(); this._currentTimer = new ReconnectionTimer(this, completionTime); } + public stopTimer(): void { if (this._currentTimer) { this._currentTimer.dispose(); @@ -1039,6 +745,7 @@ class VisibleProgress { } } } + class ReconnectionTimer implements IDisposable { private readonly _parent: VisibleProgress; private readonly _completionTime: number; @@ -1047,178 +754,114 @@ class ReconnectionTimer implements IDisposable { constructor(parent: VisibleProgress, completionTime: number) { this._parent = parent; this._completionTime = completionTime; - this._renderInterval = dom.disposableWindowInterval( - mainWindow, - () => this._render(), - 1000, - ); + this._renderInterval = dom.disposableWindowInterval(mainWindow, () => this._render(), 1000); this._render(); } + public dispose(): void { this._renderInterval.dispose(); } + private _render() { const remainingTimeMs = this._completionTime - Date.now(); - if (remainingTimeMs < 0) { return; } const remainingTime = Math.ceil(remainingTimeMs / 1000); - if (remainingTime === 1) { - this._parent.report( - nls.localize( - "reconnectionWaitOne", - "Attempting to reconnect in {0} second...", - remainingTime, - ), - ); + this._parent.report(nls.localize('reconnectionWaitOne', "Attempting to reconnect in {0} second...", remainingTime)); } else { - this._parent.report( - nls.localize( - "reconnectionWaitMany", - "Attempting to reconnect in {0} seconds...", - remainingTime, - ), - ); + this._parent.report(nls.localize('reconnectionWaitMany', "Attempting to reconnect in {0} seconds...", remainingTime)); } } } + /** * The time when a prompt is shown to the user */ const DISCONNECT_PROMPT_TIME = 40 * 1000; // 40 seconds -export class RemoteAgentConnectionStatusListener - extends Disposable - implements IWorkbenchContribution -{ + +export class RemoteAgentConnectionStatusListener extends Disposable implements IWorkbenchContribution { + private _reloadWindowShown: boolean = false; constructor( - @IRemoteAgentService - remoteAgentService: IRemoteAgentService, - @IProgressService - progressService: IProgressService, - @IDialogService - dialogService: IDialogService, - @ICommandService - commandService: ICommandService, - @IQuickInputService - quickInputService: IQuickInputService, - @ILogService - logService: ILogService, - @IWorkbenchEnvironmentService - environmentService: IWorkbenchEnvironmentService, - @ITelemetryService - telemetryService: ITelemetryService, + @IRemoteAgentService remoteAgentService: IRemoteAgentService, + @IProgressService progressService: IProgressService, + @IDialogService dialogService: IDialogService, + @ICommandService commandService: ICommandService, + @IQuickInputService quickInputService: IQuickInputService, + @ILogService logService: ILogService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @ITelemetryService telemetryService: ITelemetryService ) { super(); - const connection = remoteAgentService.getConnection(); - if (connection) { let quickInputVisible = false; - this._register( - quickInputService.onShow(() => (quickInputVisible = true)), - ); - this._register( - quickInputService.onHide(() => (quickInputVisible = false)), - ); + this._register(quickInputService.onShow(() => quickInputVisible = true)); + this._register(quickInputService.onHide(() => quickInputVisible = false)); let visibleProgress: VisibleProgress | null = null; - let reconnectWaitEvent: ReconnectionWaitEvent | null = null; - let disposableListener: IDisposable | null = null; - function showProgress( - location: - | ProgressLocation.Dialog - | ProgressLocation.Notification - | null, - buttons: { - label: string; - callback: () => void; - }[], - initialReport: string | null = null, - ): VisibleProgress { + function showProgress(location: ProgressLocation.Dialog | ProgressLocation.Notification | null, buttons: { label: string; callback: () => void }[], initialReport: string | null = null): VisibleProgress { if (visibleProgress) { visibleProgress.dispose(); visibleProgress = null; } + if (!location) { - location = quickInputVisible - ? ProgressLocation.Notification - : ProgressLocation.Dialog; + location = quickInputVisible ? ProgressLocation.Notification : ProgressLocation.Dialog; } + return new VisibleProgress( - progressService, - location, - initialReport, - buttons.map((button) => button.label), + progressService, location, initialReport, buttons.map(button => button.label), (choice, lastReport) => { // Handle choice from dialog - if (typeof choice !== "undefined" && buttons[choice]) { + if (typeof choice !== 'undefined' && buttons[choice]) { buttons[choice].callback(); } else { if (location === ProgressLocation.Dialog) { - visibleProgress = showProgress( - ProgressLocation.Notification, - buttons, - lastReport, - ); + visibleProgress = showProgress(ProgressLocation.Notification, buttons, lastReport); } else { hideProgress(); } } - }, + } ); } + function hideProgress() { if (visibleProgress) { visibleProgress.dispose(); visibleProgress = null; } } - let reconnectionToken: string = ""; + let reconnectionToken: string = ''; let lastIncomingDataTime: number = 0; - let reconnectionAttempts: number = 0; const reconnectButton = { - label: nls.localize("reconnectNow", "Reconnect Now"), + label: nls.localize('reconnectNow', "Reconnect Now"), callback: () => { reconnectWaitEvent?.skipWait(); - }, + } }; const reloadButton = { - label: nls.localize("reloadWindow", "Reload Window"), + label: nls.localize('reloadWindow', "Reload Window"), callback: () => { + type ReconnectReloadClassification = { - owner: "alexdima"; - comment: "The reload button in the builtin permanent reconnection failure dialog was pressed"; - remoteName: { - classification: "SystemMetaData"; - purpose: "PerformanceAndHealth"; - comment: "The name of the resolver."; - }; - reconnectionToken: { - classification: "SystemMetaData"; - purpose: "PerformanceAndHealth"; - comment: "The identifier of the connection."; - }; - millisSinceLastIncomingData: { - classification: "SystemMetaData"; - purpose: "PerformanceAndHealth"; - comment: "Elapsed time (in ms) since data was last received."; - }; - attempt: { - classification: "SystemMetaData"; - purpose: "PerformanceAndHealth"; - comment: "The reconnection attempt counter."; - }; + owner: 'alexdima'; + comment: 'The reload button in the builtin permanent reconnection failure dialog was pressed'; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; + reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the connection.' }; + millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Elapsed time (in ms) since data was last received.' }; + attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The reconnection attempt counter.' }; }; type ReconnectReloadEvent = { remoteName: string | undefined; @@ -1226,26 +869,23 @@ export class RemoteAgentConnectionStatusListener millisSinceLastIncomingData: number; attempt: number; }; - telemetryService.publicLog2< - ReconnectReloadEvent, - ReconnectReloadClassification - >("remoteReconnectionReload", { - remoteName: getRemoteName( - environmentService.remoteAuthority, - ), + telemetryService.publicLog2('remoteReconnectionReload', { + remoteName: getRemoteName(environmentService.remoteAuthority), reconnectionToken: reconnectionToken, - millisSinceLastIncomingData: - Date.now() - lastIncomingDataTime, - attempt: reconnectionAttempts, + millisSinceLastIncomingData: Date.now() - lastIncomingDataTime, + attempt: reconnectionAttempts }); + commandService.executeCommand(ReloadWindowAction.ID); - }, + } }; + // Possible state transitions: // ConnectionGain -> ConnectionLost // ConnectionLost -> ReconnectionWait, ReconnectionRunning // ReconnectionWait -> ReconnectionRunning // ReconnectionRunning -> ConnectionGain, ReconnectionPermanentFailure + connection.onDidStateChange((e) => { visibleProgress?.stopTimer(); @@ -1256,98 +896,52 @@ export class RemoteAgentConnectionStatusListener switch (e.type) { case PersistentConnectionEventType.ConnectionLost: reconnectionToken = e.reconnectionToken; - lastIncomingDataTime = - Date.now() - e.millisSinceLastIncomingData; + lastIncomingDataTime = Date.now() - e.millisSinceLastIncomingData; reconnectionAttempts = 0; + type RemoteConnectionLostClassification = { - owner: "alexdima"; - comment: "The remote connection state is now `ConnectionLost`"; - remoteName: { - classification: "SystemMetaData"; - purpose: "PerformanceAndHealth"; - comment: "The name of the resolver."; - }; - reconnectionToken: { - classification: "SystemMetaData"; - purpose: "PerformanceAndHealth"; - comment: "The identifier of the connection."; - }; + owner: 'alexdima'; + comment: 'The remote connection state is now `ConnectionLost`'; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; + reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the connection.' }; }; type RemoteConnectionLostEvent = { remoteName: string | undefined; reconnectionToken: string; }; - telemetryService.publicLog2< - RemoteConnectionLostEvent, - RemoteConnectionLostClassification - >("remoteConnectionLost", { - remoteName: getRemoteName( - environmentService.remoteAuthority, - ), + telemetryService.publicLog2('remoteConnectionLost', { + remoteName: getRemoteName(environmentService.remoteAuthority), reconnectionToken: e.reconnectionToken, }); - if ( - visibleProgress || - e.millisSinceLastIncomingData > - DISCONNECT_PROMPT_TIME - ) { + if (visibleProgress || e.millisSinceLastIncomingData > DISCONNECT_PROMPT_TIME) { if (!visibleProgress) { - visibleProgress = showProgress(null, [ - reconnectButton, - reloadButton, - ]); + visibleProgress = showProgress(null, [reconnectButton, reloadButton]); } - visibleProgress.report( - nls.localize( - "connectionLost", - "Connection Lost", - ), - ); + visibleProgress.report(nls.localize('connectionLost', "Connection Lost")); } break; case PersistentConnectionEventType.ReconnectionWait: if (visibleProgress) { reconnectWaitEvent = e; - visibleProgress = showProgress(null, [ - reconnectButton, - reloadButton, - ]); - visibleProgress.startTimer( - Date.now() + 1000 * e.durationSeconds, - ); + visibleProgress = showProgress(null, [reconnectButton, reloadButton]); + visibleProgress.startTimer(Date.now() + 1000 * e.durationSeconds); } break; case PersistentConnectionEventType.ReconnectionRunning: reconnectionToken = e.reconnectionToken; - lastIncomingDataTime = - Date.now() - e.millisSinceLastIncomingData; + lastIncomingDataTime = Date.now() - e.millisSinceLastIncomingData; reconnectionAttempts = e.attempt; + type RemoteReconnectionRunningClassification = { - owner: "alexdima"; - comment: "The remote connection state is now `ReconnectionRunning`"; - remoteName: { - classification: "SystemMetaData"; - purpose: "PerformanceAndHealth"; - comment: "The name of the resolver."; - }; - reconnectionToken: { - classification: "SystemMetaData"; - purpose: "PerformanceAndHealth"; - comment: "The identifier of the connection."; - }; - millisSinceLastIncomingData: { - classification: "SystemMetaData"; - purpose: "PerformanceAndHealth"; - comment: "Elapsed time (in ms) since data was last received."; - }; - attempt: { - classification: "SystemMetaData"; - purpose: "PerformanceAndHealth"; - comment: "The reconnection attempt counter."; - }; + owner: 'alexdima'; + comment: 'The remote connection state is now `ReconnectionRunning`'; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; + reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the connection.' }; + millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Elapsed time (in ms) since data was last received.' }; + attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The reconnection attempt counter.' }; }; type RemoteReconnectionRunningEvent = { remoteName: string | undefined; @@ -1355,88 +949,42 @@ export class RemoteAgentConnectionStatusListener millisSinceLastIncomingData: number; attempt: number; }; - telemetryService.publicLog2< - RemoteReconnectionRunningEvent, - RemoteReconnectionRunningClassification - >("remoteReconnectionRunning", { - remoteName: getRemoteName( - environmentService.remoteAuthority, - ), + telemetryService.publicLog2('remoteReconnectionRunning', { + remoteName: getRemoteName(environmentService.remoteAuthority), reconnectionToken: e.reconnectionToken, - millisSinceLastIncomingData: - e.millisSinceLastIncomingData, - attempt: e.attempt, + millisSinceLastIncomingData: e.millisSinceLastIncomingData, + attempt: e.attempt }); - if ( - visibleProgress || - e.millisSinceLastIncomingData > - DISCONNECT_PROMPT_TIME - ) { - visibleProgress = showProgress(null, [ - reloadButton, - ]); - visibleProgress.report( - nls.localize( - "reconnectionRunning", - "Disconnected. Attempting to reconnect...", - ), - ); + if (visibleProgress || e.millisSinceLastIncomingData > DISCONNECT_PROMPT_TIME) { + visibleProgress = showProgress(null, [reloadButton]); + visibleProgress.report(nls.localize('reconnectionRunning', "Disconnected. Attempting to reconnect...")); + // Register to listen for quick input is opened - disposableListener = quickInputService.onShow( - () => { - // Need to move from dialog if being shown and user needs to type in a prompt - if ( - visibleProgress && - visibleProgress.location === - ProgressLocation.Dialog - ) { - visibleProgress = showProgress( - ProgressLocation.Notification, - [reloadButton], - visibleProgress.lastReport, - ); - } - }, - ); + disposableListener = quickInputService.onShow(() => { + // Need to move from dialog if being shown and user needs to type in a prompt + if (visibleProgress && visibleProgress.location === ProgressLocation.Dialog) { + visibleProgress = showProgress(ProgressLocation.Notification, [reloadButton], visibleProgress.lastReport); + } + }); } + break; case PersistentConnectionEventType.ReconnectionPermanentFailure: reconnectionToken = e.reconnectionToken; - lastIncomingDataTime = - Date.now() - e.millisSinceLastIncomingData; + lastIncomingDataTime = Date.now() - e.millisSinceLastIncomingData; reconnectionAttempts = e.attempt; - type RemoteReconnectionPermanentFailureClassification = - { - owner: "alexdima"; - comment: "The remote connection state is now `ReconnectionPermanentFailure`"; - remoteName: { - classification: "SystemMetaData"; - purpose: "PerformanceAndHealth"; - comment: "The name of the resolver."; - }; - reconnectionToken: { - classification: "SystemMetaData"; - purpose: "PerformanceAndHealth"; - comment: "The identifier of the connection."; - }; - millisSinceLastIncomingData: { - classification: "SystemMetaData"; - purpose: "PerformanceAndHealth"; - comment: "Elapsed time (in ms) since data was last received."; - }; - attempt: { - classification: "SystemMetaData"; - purpose: "PerformanceAndHealth"; - comment: "The reconnection attempt counter."; - }; - handled: { - classification: "SystemMetaData"; - purpose: "PerformanceAndHealth"; - comment: "The error was handled by the resolver."; - }; - }; + + type RemoteReconnectionPermanentFailureClassification = { + owner: 'alexdima'; + comment: 'The remote connection state is now `ReconnectionPermanentFailure`'; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; + reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the connection.' }; + millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Elapsed time (in ms) since data was last received.' }; + attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The reconnection attempt counter.' }; + handled: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error was handled by the resolver.' }; + }; type RemoteReconnectionPermanentFailureEvent = { remoteName: string | undefined; reconnectionToken: string; @@ -1444,83 +992,45 @@ export class RemoteAgentConnectionStatusListener attempt: number; handled: boolean; }; - telemetryService.publicLog2< - RemoteReconnectionPermanentFailureEvent, - RemoteReconnectionPermanentFailureClassification - >("remoteReconnectionPermanentFailure", { - remoteName: getRemoteName( - environmentService.remoteAuthority, - ), + telemetryService.publicLog2('remoteReconnectionPermanentFailure', { + remoteName: getRemoteName(environmentService.remoteAuthority), reconnectionToken: e.reconnectionToken, - millisSinceLastIncomingData: - e.millisSinceLastIncomingData, + millisSinceLastIncomingData: e.millisSinceLastIncomingData, attempt: e.attempt, - handled: e.handled, + handled: e.handled }); + hideProgress(); if (e.handled) { - logService.info( - `Error handled: Not showing a notification for the error.`, - ); - console.log( - `Error handled: Not showing a notification for the error.`, - ); + logService.info(`Error handled: Not showing a notification for the error.`); + console.log(`Error handled: Not showing a notification for the error.`); } else if (!this._reloadWindowShown) { this._reloadWindowShown = true; - dialogService - .confirm({ - type: Severity.Error, - message: nls.localize( - "reconnectionPermanentFailure", - "Cannot reconnect. Please reload the window.", - ), - primaryButton: nls.localize( - { - key: "reloadWindow.dialog", - comment: ["&& denotes a mnemonic"], - }, - "&&Reload Window", - ), - }) - .then((result) => { - if (result.confirmed) { - commandService.executeCommand( - ReloadWindowAction.ID, - ); - } - }); + dialogService.confirm({ + type: Severity.Error, + message: nls.localize('reconnectionPermanentFailure', "Cannot reconnect. Please reload the window."), + primaryButton: nls.localize({ key: 'reloadWindow.dialog', comment: ['&& denotes a mnemonic'] }, "&&Reload Window") + }).then(result => { + if (result.confirmed) { + commandService.executeCommand(ReloadWindowAction.ID); + } + }); } break; case PersistentConnectionEventType.ConnectionGain: reconnectionToken = e.reconnectionToken; - lastIncomingDataTime = - Date.now() - e.millisSinceLastIncomingData; + lastIncomingDataTime = Date.now() - e.millisSinceLastIncomingData; reconnectionAttempts = e.attempt; + type RemoteConnectionGainClassification = { - owner: "alexdima"; - comment: "The remote connection state is now `ConnectionGain`"; - remoteName: { - classification: "SystemMetaData"; - purpose: "PerformanceAndHealth"; - comment: "The name of the resolver."; - }; - reconnectionToken: { - classification: "SystemMetaData"; - purpose: "PerformanceAndHealth"; - comment: "The identifier of the connection."; - }; - millisSinceLastIncomingData: { - classification: "SystemMetaData"; - purpose: "PerformanceAndHealth"; - comment: "Elapsed time (in ms) since data was last received."; - }; - attempt: { - classification: "SystemMetaData"; - purpose: "PerformanceAndHealth"; - comment: "The reconnection attempt counter."; - }; + owner: 'alexdima'; + comment: 'The remote connection state is now `ConnectionGain`'; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; + reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the connection.' }; + millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Elapsed time (in ms) since data was last received.' }; + attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The reconnection attempt counter.' }; }; type RemoteConnectionGainEvent = { remoteName: string | undefined; @@ -1528,20 +1038,14 @@ export class RemoteAgentConnectionStatusListener millisSinceLastIncomingData: number; attempt: number; }; - telemetryService.publicLog2< - RemoteConnectionGainEvent, - RemoteConnectionGainClassification - >("remoteConnectionGain", { - remoteName: getRemoteName( - environmentService.remoteAuthority, - ), + telemetryService.publicLog2('remoteConnectionGain', { + remoteName: getRemoteName(environmentService.remoteAuthority), reconnectionToken: e.reconnectionToken, - millisSinceLastIncomingData: - e.millisSinceLastIncomingData, - attempt: e.attempt, + millisSinceLastIncomingData: e.millisSinceLastIncomingData, + attempt: e.attempt }); - hideProgress(); + hideProgress(); break; } }); diff --git a/Source/vs/workbench/contrib/scm/browser/scmViewPane.ts b/Source/vs/workbench/contrib/scm/browser/scmViewPane.ts index aa3a8f4319d4e..4ee1c3aa7bf32 100644 --- a/Source/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/Source/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -3,304 +3,124 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import "./media/scm.css"; - -import { asCSSUrl } from "../../../../base/browser/cssValue.js"; -import { IDragAndDropData } from "../../../../base/browser/dnd.js"; -import { - $, - append, - clearNode, - Dimension, - isActiveElement, - isPointerEvent, - trackFocus, -} from "../../../../base/browser/dom.js"; -import { DEFAULT_FONT_FAMILY } from "../../../../base/browser/fonts.js"; -import { - ActionBar, - IActionViewItemProvider, -} from "../../../../base/browser/ui/actionbar/actionbar.js"; -import { - Button, - ButtonWithDescription, - ButtonWithDropdown, -} from "../../../../base/browser/ui/button/button.js"; -import { AnchorAlignment } from "../../../../base/browser/ui/contextview/contextview.js"; -import { CountBadge } from "../../../../base/browser/ui/countBadge/countBadge.js"; -import { - IIdentityProvider, - IListVirtualDelegate, -} from "../../../../base/browser/ui/list/list.js"; -import { - ElementsDragAndDropData, - ListViewTargetSector, -} from "../../../../base/browser/ui/list/listView.js"; -import { IListAccessibilityProvider } from "../../../../base/browser/ui/list/listWidget.js"; -import { LabelFuzzyScore } from "../../../../base/browser/ui/tree/abstractTree.js"; -import { - IAsyncDataTreeViewState, - ITreeCompressionDelegate, -} from "../../../../base/browser/ui/tree/asyncDataTree.js"; -import { ICompressedTreeNode } from "../../../../base/browser/ui/tree/compressedObjectTreeModel.js"; -import { - ICompressibleKeyboardNavigationLabelProvider, - ICompressibleTreeRenderer, -} from "../../../../base/browser/ui/tree/objectTree.js"; -import { - IAsyncDataSource, - ITreeContextMenuEvent, - ITreeDragAndDrop, - ITreeDragOverReaction, - ITreeFilter, - ITreeNode, - ITreeSorter, -} from "../../../../base/browser/ui/tree/tree.js"; -import { - Action, - ActionRunner, - IAction, - IActionRunner, - Separator, -} from "../../../../base/common/actions.js"; -import { - disposableTimeout, - Sequencer, - ThrottledDelayer, - Throttler, -} from "../../../../base/common/async.js"; -import { CancellationTokenSource } from "../../../../base/common/cancellation.js"; -import { Codicon } from "../../../../base/common/codicons.js"; -import { - compareFileNames, - comparePaths, -} from "../../../../base/common/comparers.js"; -import { Emitter, Event } from "../../../../base/common/event.js"; -import { - createMatches, - FuzzyScore, - IMatch, -} from "../../../../base/common/filters.js"; -import { Iterable } from "../../../../base/common/iterator.js"; -import { KeyCode } from "../../../../base/common/keyCodes.js"; -import { - combinedDisposable, - Disposable, - DisposableMap, - DisposableStore, - dispose, - IDisposable, - MutableDisposable, - toDisposable, -} from "../../../../base/common/lifecycle.js"; -import { Schemas } from "../../../../base/common/network.js"; -import { clamp, rot } from "../../../../base/common/numbers.js"; -import { autorun } from "../../../../base/common/observable.js"; -import * as platform from "../../../../base/common/platform.js"; -import { basename, dirname } from "../../../../base/common/resources.js"; -import { - IResourceNode, - ResourceTree, -} from "../../../../base/common/resourceTree.js"; -import { compare, format } from "../../../../base/common/strings.js"; -import { ThemeIcon } from "../../../../base/common/themables.js"; -import { URI } from "../../../../base/common/uri.js"; -import { IEditorConstructionOptions } from "../../../../editor/browser/config/editorConfiguration.js"; -import { EditorExtensionsRegistry } from "../../../../editor/browser/editorExtensions.js"; -import { - CodeEditorWidget, - ICodeEditorWidgetOptions, -} from "../../../../editor/browser/widget/codeEditor/codeEditorWidget.js"; -import { - MarkdownRenderer, - openLinkFromMarkdown, -} from "../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js"; -import { - EditorOption, - EditorOptions, - IEditorOptions, -} from "../../../../editor/common/config/editorOptions.js"; -import { EditOperation } from "../../../../editor/common/core/editOperation.js"; -import { Selection } from "../../../../editor/common/core/selection.js"; -import { ITextModel } from "../../../../editor/common/model.js"; -import { IModelService } from "../../../../editor/common/services/model.js"; -import { CodeActionController } from "../../../../editor/contrib/codeAction/browser/codeActionController.js"; -import { ColorDetector } from "../../../../editor/contrib/colorPicker/browser/colorDetector.js"; -import { ContextMenuController } from "../../../../editor/contrib/contextmenu/browser/contextmenu.js"; -import { DragAndDropController } from "../../../../editor/contrib/dnd/browser/dnd.js"; -import { CopyPasteController } from "../../../../editor/contrib/dropOrPasteInto/browser/copyPasteController.js"; -import { DropIntoEditorController } from "../../../../editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.js"; -import { FormatOnType } from "../../../../editor/contrib/format/browser/formatActions.js"; -import { ContentHoverController } from "../../../../editor/contrib/hover/browser/contentHoverController.js"; -import { GlyphHoverController } from "../../../../editor/contrib/hover/browser/glyphHoverController.js"; -import { InlineCompletionsController } from "../../../../editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.js"; -import { LinkDetector } from "../../../../editor/contrib/links/browser/links.js"; -import { MessageController } from "../../../../editor/contrib/message/browser/messageController.js"; -import { PlaceholderTextContribution } from "../../../../editor/contrib/placeholderText/browser/placeholderTextContribution.js"; -import { SnippetController2 } from "../../../../editor/contrib/snippet/browser/snippetController2.js"; -import { SuggestController } from "../../../../editor/contrib/suggest/browser/suggestController.js"; -import { localize } from "../../../../nls.js"; -import { DropdownWithPrimaryActionViewItem } from "../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js"; -import { - createActionViewItem, - getFlatActionBarActions, - getFlatContextMenuActions, -} from "../../../../platform/actions/browser/menuEntryActionViewItem.js"; -import { - IMenuWorkbenchToolBarOptions, - WorkbenchToolBar, -} from "../../../../platform/actions/browser/toolbar.js"; -import { - Action2, - IAction2Options, - IMenu, - IMenuService, - MenuId, - MenuItemAction, - MenuRegistry, - registerAction2, -} from "../../../../platform/actions/common/actions.js"; -import { ICommandService } from "../../../../platform/commands/common/commands.js"; -import { - ConfigurationTarget, - IConfigurationService, -} from "../../../../platform/configuration/common/configuration.js"; -import { - ContextKeyExpr, - IContextKey, - IContextKeyService, - RawContextKey, -} from "../../../../platform/contextkey/common/contextkey.js"; -import { - IContextMenuService, - IContextViewService, - IOpenContextView, -} from "../../../../platform/contextview/browser/contextView.js"; -import { CodeDataTransfers } from "../../../../platform/dnd/browser/dnd.js"; -import { FileKind } from "../../../../platform/files/common/files.js"; -import { IHoverService } from "../../../../platform/hover/browser/hover.js"; -import { - IInstantiationService, - ServicesAccessor, -} from "../../../../platform/instantiation/common/instantiation.js"; -import { ServiceCollection } from "../../../../platform/instantiation/common/serviceCollection.js"; -import { IKeybindingService } from "../../../../platform/keybinding/common/keybinding.js"; -import { ILabelService } from "../../../../platform/label/common/label.js"; -import { - IOpenEvent, - WorkbenchCompressibleAsyncDataTree, -} from "../../../../platform/list/browser/listService.js"; -import { INotificationService } from "../../../../platform/notification/common/notification.js"; -import { observableConfigValue } from "../../../../platform/observable/common/platformObservableUtils.js"; -import { IOpenerService } from "../../../../platform/opener/common/opener.js"; -import { - IStorageService, - StorageScope, - StorageTarget, -} from "../../../../platform/storage/common/storage.js"; -import { ITelemetryService } from "../../../../platform/telemetry/common/telemetry.js"; -import { - defaultButtonStyles, - defaultCountBadgeStyles, -} from "../../../../platform/theme/browser/defaultStyles.js"; -import { ColorScheme } from "../../../../platform/theme/common/theme.js"; -import { - IFileIconTheme, - IThemeService, -} from "../../../../platform/theme/common/themeService.js"; -import { IUriIdentityService } from "../../../../platform/uriIdentity/common/uriIdentity.js"; -import { fillEditorsDragData } from "../../../browser/dnd.js"; -import { - IFileLabelOptions, - IResourceLabel, - ResourceLabels, -} from "../../../browser/labels.js"; -import { - API_OPEN_DIFF_EDITOR_COMMAND_ID, - API_OPEN_EDITOR_COMMAND_ID, -} from "../../../browser/parts/editor/editorCommands.js"; -import { - IViewPaneOptions, - ViewAction, - ViewPane, -} from "../../../browser/parts/views/viewPane.js"; -import { - EditorResourceAccessor, - SideBySideEditor, -} from "../../../common/editor.js"; -import { IViewDescriptorService } from "../../../common/views.js"; -import { IEditorService } from "../../../services/editor/common/editorService.js"; -import { EditorDictation } from "../../codeEditor/browser/dictation/editorDictation.js"; -import { MenuPreventer } from "../../codeEditor/browser/menuPreventer.js"; -import { SelectionClipboardContributionID } from "../../codeEditor/browser/selectionClipboard.js"; -import { - getSimpleEditorOptions, - setupSimpleEditorSelectionStyling, -} from "../../codeEditor/browser/simpleEditorOptions.js"; -import { OpenScmGroupAction } from "../../multiDiffEditor/browser/scmMultiDiffSourceResolver.js"; -import { - IInputValidation, - InputValidationType, - ISCMActionButton, - ISCMActionButtonDescriptor, - ISCMInput, - ISCMInputValueProviderContext, - ISCMRepository, - ISCMRepositorySortKey, - ISCMResource, - ISCMResourceGroup, - ISCMService, - ISCMViewService, - ISCMViewVisibleRepositoryChangeEvent, - SCMInputChangeReason, - VIEW_PANE_ID, -} from "../common/scm.js"; -import { - RepositoryActionRunner, - RepositoryRenderer, -} from "./scmRepositoryRenderer.js"; -import { RepositoryContextKeys } from "./scmViewService.js"; -import { - collectContextMenuActions, - connectPrimaryMenu, - getActionViewItemProvider, - isSCMActionButton, - isSCMInput, - isSCMRepository, - isSCMResource, - isSCMResourceGroup, - isSCMResourceNode, - isSCMViewService, -} from "./util.js"; - -type TreeElement = - | ISCMRepository - | ISCMInput - | ISCMActionButton - | ISCMResourceGroup - | ISCMResource - | IResourceNode; - -function processResourceFilterData( - uri: URI, - filterData: FuzzyScore | LabelFuzzyScore | undefined, -): [IMatch[] | undefined, IMatch[] | undefined] { +import './media/scm.css'; +import { Event, Emitter } from '../../../../base/common/event.js'; +import { basename, dirname } from '../../../../base/common/resources.js'; +import { IDisposable, Disposable, DisposableStore, combinedDisposable, dispose, toDisposable, MutableDisposable, DisposableMap } from '../../../../base/common/lifecycle.js'; +import { ViewPane, IViewPaneOptions, ViewAction } from '../../../browser/parts/views/viewPane.js'; +import { append, $, Dimension, trackFocus, clearNode, isPointerEvent, isActiveElement } from '../../../../base/browser/dom.js'; +import { asCSSUrl } from '../../../../base/browser/cssValue.js'; +import { IListVirtualDelegate, IIdentityProvider } from '../../../../base/browser/ui/list/list.js'; +import { ISCMResourceGroup, ISCMResource, InputValidationType, ISCMRepository, ISCMInput, IInputValidation, ISCMViewService, ISCMViewVisibleRepositoryChangeEvent, ISCMService, SCMInputChangeReason, VIEW_PANE_ID, ISCMActionButton, ISCMActionButtonDescriptor, ISCMRepositorySortKey, ISCMInputValueProviderContext } from '../common/scm.js'; +import { ResourceLabels, IResourceLabel, IFileLabelOptions } from '../../../browser/labels.js'; +import { CountBadge } from '../../../../base/browser/ui/countBadge/countBadge.js'; +import { IEditorService } from '../../../services/editor/common/editorService.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { IContextViewService, IContextMenuService, IOpenContextView } from '../../../../platform/contextview/browser/contextView.js'; +import { IContextKeyService, IContextKey, ContextKeyExpr, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { MenuItemAction, IMenuService, registerAction2, MenuId, IAction2Options, MenuRegistry, Action2, IMenu } from '../../../../platform/actions/common/actions.js'; +import { IAction, ActionRunner, Action, Separator, IActionRunner } from '../../../../base/common/actions.js'; +import { ActionBar, IActionViewItemProvider } from '../../../../base/browser/ui/actionbar/actionbar.js'; +import { IThemeService, IFileIconTheme } from '../../../../platform/theme/common/themeService.js'; +import { isSCMResource, isSCMResourceGroup, isSCMRepository, isSCMInput, collectContextMenuActions, getActionViewItemProvider, isSCMActionButton, isSCMViewService, isSCMResourceNode, connectPrimaryMenu } from './util.js'; +import { WorkbenchCompressibleAsyncDataTree, IOpenEvent } from '../../../../platform/list/browser/listService.js'; +import { IConfigurationService, ConfigurationTarget } from '../../../../platform/configuration/common/configuration.js'; +import { disposableTimeout, Sequencer, ThrottledDelayer, Throttler } from '../../../../base/common/async.js'; +import { ITreeNode, ITreeFilter, ITreeSorter, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction, IAsyncDataSource } from '../../../../base/browser/ui/tree/tree.js'; +import { ResourceTree, IResourceNode } from '../../../../base/common/resourceTree.js'; +import { ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider } from '../../../../base/browser/ui/tree/objectTree.js'; +import { Iterable } from '../../../../base/common/iterator.js'; +import { ICompressedTreeNode } from '../../../../base/browser/ui/tree/compressedObjectTreeModel.js'; +import { URI } from '../../../../base/common/uri.js'; +import { FileKind } from '../../../../platform/files/common/files.js'; +import { compareFileNames, comparePaths } from '../../../../base/common/comparers.js'; +import { FuzzyScore, createMatches, IMatch } from '../../../../base/common/filters.js'; +import { IViewDescriptorService } from '../../../common/views.js'; +import { localize } from '../../../../nls.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; +import { EditorResourceAccessor, SideBySideEditor } from '../../../common/editor.js'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from '../../../../editor/browser/widget/codeEditor/codeEditorWidget.js'; +import { IEditorConstructionOptions } from '../../../../editor/browser/config/editorConfiguration.js'; +import { getSimpleEditorOptions, setupSimpleEditorSelectionStyling } from '../../codeEditor/browser/simpleEditorOptions.js'; +import { IModelService } from '../../../../editor/common/services/model.js'; +import { EditorExtensionsRegistry } from '../../../../editor/browser/editorExtensions.js'; +import { MenuPreventer } from '../../codeEditor/browser/menuPreventer.js'; +import { SelectionClipboardContributionID } from '../../codeEditor/browser/selectionClipboard.js'; +import { EditorDictation } from '../../codeEditor/browser/dictation/editorDictation.js'; +import { ContextMenuController } from '../../../../editor/contrib/contextmenu/browser/contextmenu.js'; +import * as platform from '../../../../base/common/platform.js'; +import { compare, format } from '../../../../base/common/strings.js'; +import { SuggestController } from '../../../../editor/contrib/suggest/browser/suggestController.js'; +import { SnippetController2 } from '../../../../editor/contrib/snippet/browser/snippetController2.js'; +import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; +import { ColorDetector } from '../../../../editor/contrib/colorPicker/browser/colorDetector.js'; +import { LinkDetector } from '../../../../editor/contrib/links/browser/links.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js'; +import { ILabelService } from '../../../../platform/label/common/label.js'; +import { KeyCode } from '../../../../base/common/keyCodes.js'; +import { DEFAULT_FONT_FAMILY } from '../../../../base/browser/fonts.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { AnchorAlignment } from '../../../../base/browser/ui/contextview/contextview.js'; +import { RepositoryActionRunner, RepositoryRenderer } from './scmRepositoryRenderer.js'; +import { ColorScheme } from '../../../../platform/theme/common/theme.js'; +import { LabelFuzzyScore } from '../../../../base/browser/ui/tree/abstractTree.js'; +import { Selection } from '../../../../editor/common/core/selection.js'; +import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from '../../../browser/parts/editor/editorCommands.js'; +import { createActionViewItem, getFlatActionBarActions, getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { MarkdownRenderer, openLinkFromMarkdown } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { Button, ButtonWithDescription, ButtonWithDropdown } from '../../../../base/browser/ui/button/button.js'; +import { INotificationService } from '../../../../platform/notification/common/notification.js'; +import { RepositoryContextKeys } from './scmViewService.js'; +import { DragAndDropController } from '../../../../editor/contrib/dnd/browser/dnd.js'; +import { CopyPasteController } from '../../../../editor/contrib/dropOrPasteInto/browser/copyPasteController.js'; +import { DropIntoEditorController } from '../../../../editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.js'; +import { MessageController } from '../../../../editor/contrib/message/browser/messageController.js'; +import { defaultButtonStyles, defaultCountBadgeStyles } from '../../../../platform/theme/browser/defaultStyles.js'; +import { InlineCompletionsController } from '../../../../editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.js'; +import { CodeActionController } from '../../../../editor/contrib/codeAction/browser/codeActionController.js'; +import { Schemas } from '../../../../base/common/network.js'; +import { IDragAndDropData } from '../../../../base/browser/dnd.js'; +import { fillEditorsDragData } from '../../../browser/dnd.js'; +import { ElementsDragAndDropData, ListViewTargetSector } from '../../../../base/browser/ui/list/listView.js'; +import { CodeDataTransfers } from '../../../../platform/dnd/browser/dnd.js'; +import { FormatOnType } from '../../../../editor/contrib/format/browser/formatActions.js'; +import { EditorOption, EditorOptions, IEditorOptions } from '../../../../editor/common/config/editorOptions.js'; +import { IAsyncDataTreeViewState, ITreeCompressionDelegate } from '../../../../base/browser/ui/tree/asyncDataTree.js'; +import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; +import { EditOperation } from '../../../../editor/common/core/editOperation.js'; +import { IMenuWorkbenchToolBarOptions, WorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; +import { CancellationTokenSource } from '../../../../base/common/cancellation.js'; +import { DropdownWithPrimaryActionViewItem } from '../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js'; +import { clamp, rot } from '../../../../base/common/numbers.js'; +import { IHoverService } from '../../../../platform/hover/browser/hover.js'; +import { OpenScmGroupAction } from '../../multiDiffEditor/browser/scmMultiDiffSourceResolver.js'; +import { ContentHoverController } from '../../../../editor/contrib/hover/browser/contentHoverController.js'; +import { GlyphHoverController } from '../../../../editor/contrib/hover/browser/glyphHoverController.js'; +import { ITextModel } from '../../../../editor/common/model.js'; +import { autorun } from '../../../../base/common/observable.js'; +import { PlaceholderTextContribution } from '../../../../editor/contrib/placeholderText/browser/placeholderTextContribution.js'; +import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; + +type TreeElement = ISCMRepository | ISCMInput | ISCMActionButton | ISCMResourceGroup | ISCMResource | IResourceNode; + +function processResourceFilterData(uri: URI, filterData: FuzzyScore | LabelFuzzyScore | undefined): [IMatch[] | undefined, IMatch[] | undefined] { if (!filterData) { return [undefined, undefined]; } if (!(filterData as LabelFuzzyScore).label) { const matches = createMatches(filterData as FuzzyScore); - return [matches, undefined]; } const fileName = basename(uri); - const label = (filterData as LabelFuzzyScore).label; - const pathLength = label.length - fileName.length; - const matches = createMatches((filterData as LabelFuzzyScore).score); // FileName match @@ -310,7 +130,6 @@ function processResourceFilterData( // FilePath match const labelMatches: IMatch[] = []; - const descriptionMatches: IMatch[] = []; for (const match of matches) { @@ -318,7 +137,7 @@ function processResourceFilterData( // Label match labelMatches.push({ start: match.start - pathLength, - end: match.end - pathLength, + end: match.end - pathLength }); } else if (match.end < pathLength) { // Description match @@ -327,11 +146,11 @@ function processResourceFilterData( // Spanning match labelMatches.push({ start: 0, - end: match.end - pathLength, + end: match.end - pathLength }); descriptionMatches.push({ start: match.start, - end: pathLength, + end: pathLength }); } } @@ -351,21 +170,11 @@ interface ActionButtonTemplate { readonly templateDisposable: IDisposable; } -export class ActionButtonRenderer - implements - ICompressibleTreeRenderer< - ISCMActionButton, - FuzzyScore, - ActionButtonTemplate - > -{ +export class ActionButtonRenderer implements ICompressibleTreeRenderer { static readonly DEFAULT_HEIGHT = 30; - static readonly TEMPLATE_ID = "actionButton"; - - get templateId(): string { - return ActionButtonRenderer.TEMPLATE_ID; - } + static readonly TEMPLATE_ID = 'actionButton'; + get templateId(): string { return ActionButtonRenderer.TEMPLATE_ID; } private actionButtons = new Map(); @@ -373,73 +182,44 @@ export class ActionButtonRenderer @ICommandService private commandService: ICommandService, @IContextMenuService private contextMenuService: IContextMenuService, @INotificationService private notificationService: INotificationService, - ) {} + ) { } renderTemplate(container: HTMLElement): ActionButtonTemplate { // hack - ( - container.parentElement!.parentElement!.querySelector( - ".monaco-tl-twistie", - )! as HTMLElement - ).classList.add("force-no-twistie"); + (container.parentElement!.parentElement!.querySelector('.monaco-tl-twistie')! as HTMLElement).classList.add('force-no-twistie'); // Use default cursor & disable hover for list item - container.parentElement!.parentElement!.classList.add( - "cursor-default", - "force-no-hover", - ); + container.parentElement!.parentElement!.classList.add('cursor-default', 'force-no-hover'); - const buttonContainer = append(container, $(".button-container")); + const buttonContainer = append(container, $('.button-container')); + const actionButton = new SCMActionButton(buttonContainer, this.contextMenuService, this.commandService, this.notificationService); - const actionButton = new SCMActionButton( - buttonContainer, - this.contextMenuService, - this.commandService, - this.notificationService, - ); - - return { - actionButton, - disposable: Disposable.None, - templateDisposable: actionButton, - }; + return { actionButton, disposable: Disposable.None, templateDisposable: actionButton }; } - renderElement( - node: ITreeNode, - index: number, - templateData: ActionButtonTemplate, - height: number | undefined, - ): void { + renderElement(node: ITreeNode, index: number, templateData: ActionButtonTemplate, height: number | undefined): void { templateData.disposable.dispose(); const disposables = new DisposableStore(); - const actionButton = node.element; templateData.actionButton.setButton(node.element.button); // Remember action button this.actionButtons.set(actionButton, templateData.actionButton); - disposables.add({ - dispose: () => this.actionButtons.delete(actionButton), - }); + disposables.add({ dispose: () => this.actionButtons.delete(actionButton) }); templateData.disposable = disposables; } renderCompressedElements(): void { - throw new Error("Should never happen since node is incompressible"); + throw new Error('Should never happen since node is incompressible'); } focusActionButton(actionButton: ISCMActionButton): void { this.actionButtons.get(actionButton)?.focus(); } - disposeElement( - node: ITreeNode, - index: number, - template: ActionButtonTemplate, - ): void { + disposeElement(node: ITreeNode, index: number, template: ActionButtonTemplate): void { template.disposable.dispose(); } @@ -449,8 +229,9 @@ export class ActionButtonRenderer } } + class SCMTreeDragAndDrop implements ITreeDragAndDrop { - constructor(private readonly instantiationService: IInstantiationService) {} + constructor(private readonly instantiationService: IInstantiationService) { } getDragURI(element: TreeElement): string | null { if (isSCMResource(element)) { @@ -461,35 +242,20 @@ class SCMTreeDragAndDrop implements ITreeDragAndDrop { } onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void { - const items = SCMTreeDragAndDrop.getResourcesFromDragAndDropData( - data as ElementsDragAndDropData, - ); - + const items = SCMTreeDragAndDrop.getResourcesFromDragAndDropData(data as ElementsDragAndDropData); if (originalEvent.dataTransfer && items?.length) { - this.instantiationService.invokeFunction((accessor) => - fillEditorsDragData(accessor, items, originalEvent), - ); - - const fileResources = items - .filter((s) => s.scheme === Schemas.file) - .map((r) => r.fsPath); + this.instantiationService.invokeFunction(accessor => fillEditorsDragData(accessor, items, originalEvent)); + const fileResources = items.filter(s => s.scheme === Schemas.file).map(r => r.fsPath); if (fileResources.length) { - originalEvent.dataTransfer.setData( - CodeDataTransfers.FILES, - JSON.stringify(fileResources), - ); + originalEvent.dataTransfer.setData(CodeDataTransfers.FILES, JSON.stringify(fileResources)); } } } - getDragLabel( - elements: TreeElement[], - originalEvent: DragEvent, - ): string | undefined { + getDragLabel(elements: TreeElement[], originalEvent: DragEvent): string | undefined { if (elements.length === 1) { const element = elements[0]; - if (isSCMResource(element)) { return basename(element.sourceUri); } @@ -498,30 +264,15 @@ class SCMTreeDragAndDrop implements ITreeDragAndDrop { return String(elements.length); } - onDragOver( - data: IDragAndDropData, - targetElement: TreeElement | undefined, - targetIndex: number | undefined, - targetSector: ListViewTargetSector | undefined, - originalEvent: DragEvent, - ): boolean | ITreeDragOverReaction { + onDragOver(data: IDragAndDropData, targetElement: TreeElement | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction { return true; } - drop( - data: IDragAndDropData, - targetElement: TreeElement | undefined, - targetIndex: number | undefined, - targetSector: ListViewTargetSector | undefined, - originalEvent: DragEvent, - ): void {} + drop(data: IDragAndDropData, targetElement: TreeElement | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): void { } - private static getResourcesFromDragAndDropData( - data: ElementsDragAndDropData, - ): URI[] { + private static getResourcesFromDragAndDropData(data: ElementsDragAndDropData): URI[] { const uris: URI[] = []; - - for (const element of [...(data.context ?? []), ...data.elements]) { + for (const element of [...data.context ?? [], ...data.elements]) { if (isSCMResource(element)) { uris.push(element.sourceUri); } @@ -529,7 +280,7 @@ class SCMTreeDragAndDrop implements ITreeDragAndDrop { return uris; } - dispose(): void {} + dispose(): void { } } interface InputTemplate { @@ -539,16 +290,12 @@ interface InputTemplate { readonly templateDisposable: IDisposable; } -class InputRenderer - implements ICompressibleTreeRenderer -{ - static readonly DEFAULT_HEIGHT = 26; +class InputRenderer implements ICompressibleTreeRenderer { - static readonly TEMPLATE_ID = "input"; + static readonly DEFAULT_HEIGHT = 26; - get templateId(): string { - return InputRenderer.TEMPLATE_ID; - } + static readonly TEMPLATE_ID = 'input'; + get templateId(): string { return InputRenderer.TEMPLATE_ID; } private inputWidgets = new Map(); private contentHeights = new WeakMap(); @@ -558,52 +305,32 @@ class InputRenderer private outerLayout: ISCMLayout, private overflowWidgetsDomNode: HTMLElement, private updateHeight: (input: ISCMInput, height: number) => void, - @IInstantiationService - private instantiationService: IInstantiationService, - ) {} + @IInstantiationService private instantiationService: IInstantiationService + ) { } renderTemplate(container: HTMLElement): InputTemplate { // hack - ( - container.parentElement!.parentElement!.querySelector( - ".monaco-tl-twistie", - )! as HTMLElement - ).classList.add("force-no-twistie"); + (container.parentElement!.parentElement!.querySelector('.monaco-tl-twistie')! as HTMLElement).classList.add('force-no-twistie'); // Disable hover for list item - container.parentElement!.parentElement!.classList.add("force-no-hover"); + container.parentElement!.parentElement!.classList.add('force-no-hover'); const templateDisposable = new DisposableStore(); - - const inputElement = append(container, $(".scm-input")); - - const inputWidget = this.instantiationService.createInstance( - SCMInputWidget, - inputElement, - this.overflowWidgetsDomNode, - ); + const inputElement = append(container, $('.scm-input')); + const inputWidget = this.instantiationService.createInstance(SCMInputWidget, inputElement, this.overflowWidgetsDomNode); templateDisposable.add(inputWidget); - return { - inputWidget, - inputWidgetHeight: InputRenderer.DEFAULT_HEIGHT, - elementDisposables: new DisposableStore(), - templateDisposable, - }; + return { inputWidget, inputWidgetHeight: InputRenderer.DEFAULT_HEIGHT, elementDisposables: new DisposableStore(), templateDisposable }; } - renderElement( - node: ITreeNode, - index: number, - templateData: InputTemplate, - ): void { + renderElement(node: ITreeNode, index: number, templateData: InputTemplate): void { const input = node.element; templateData.inputWidget.input = input; // Remember widget this.inputWidgets.set(input, templateData.inputWidget); templateData.elementDisposables.add({ - dispose: () => this.inputWidgets.delete(input), + dispose: () => this.inputWidgets.delete(input) }); // Widget cursor selections @@ -613,15 +340,13 @@ class InputRenderer templateData.inputWidget.selections = selections; } - templateData.elementDisposables.add( - toDisposable(() => { - const selections = templateData.inputWidget.selections; + templateData.elementDisposables.add(toDisposable(() => { + const selections = templateData.inputWidget.selections; - if (selections) { - this.editorSelections.set(input, selections); - } - }), - ); + if (selections) { + this.editorSelections.set(input, selections); + } + })); // Reset widget height so it's recalculated templateData.inputWidgetHeight = InputRenderer.DEFAULT_HEIGHT; @@ -639,38 +364,24 @@ class InputRenderer }; const startListeningContentHeightChange = () => { - templateData.elementDisposables.add( - templateData.inputWidget.onDidChangeContentHeight( - onDidChangeContentHeight, - ), - ); + templateData.elementDisposables.add(templateData.inputWidget.onDidChangeContentHeight(onDidChangeContentHeight)); onDidChangeContentHeight(); }; // Setup height change listener on next tick - disposableTimeout( - startListeningContentHeightChange, - 0, - templateData.elementDisposables, - ); + disposableTimeout(startListeningContentHeightChange, 0, templateData.elementDisposables); // Layout the editor whenever the outer layout happens const layoutEditor = () => templateData.inputWidget.layout(); - templateData.elementDisposables.add( - this.outerLayout.onDidChange(layoutEditor), - ); + templateData.elementDisposables.add(this.outerLayout.onDidChange(layoutEditor)); layoutEditor(); } renderCompressedElements(): void { - throw new Error("Should never happen since node is incompressible"); + throw new Error('Should never happen since node is incompressible'); } - disposeElement( - group: ITreeNode, - index: number, - template: InputTemplate, - ): void { + disposeElement(group: ITreeNode, index: number, template: InputTemplate): void { template.elementDisposables.clear(); } @@ -679,10 +390,7 @@ class InputRenderer } getHeight(input: ISCMInput): number { - return ( - (this.contentHeights.get(input) ?? InputRenderer.DEFAULT_HEIGHT) + - 10 - ); + return (this.contentHeights.get(input) ?? InputRenderer.DEFAULT_HEIGHT) + 10; } getRenderedInputWidget(input: ISCMInput): SCMInputWidget | undefined { @@ -714,19 +422,10 @@ interface ResourceGroupTemplate { readonly disposables: IDisposable; } -class ResourceGroupRenderer - implements - ICompressibleTreeRenderer< - ISCMResourceGroup, - FuzzyScore, - ResourceGroupTemplate - > -{ - static readonly TEMPLATE_ID = "resource group"; +class ResourceGroupRenderer implements ICompressibleTreeRenderer { - get templateId(): string { - return ResourceGroupRenderer.TEMPLATE_ID; - } + static readonly TEMPLATE_ID = 'resource group'; + get templateId(): string { return ResourceGroupRenderer.TEMPLATE_ID; } constructor( private actionViewItemProvider: IActionViewItemProvider, @@ -736,91 +435,41 @@ class ResourceGroupRenderer @IKeybindingService private keybindingService: IKeybindingService, @IMenuService private menuService: IMenuService, @ISCMViewService private scmViewService: ISCMViewService, - @ITelemetryService private telemetryService: ITelemetryService, - ) {} + @ITelemetryService private telemetryService: ITelemetryService + ) { } renderTemplate(container: HTMLElement): ResourceGroupTemplate { // hack - ( - container.parentElement!.parentElement!.querySelector( - ".monaco-tl-twistie", - )! as HTMLElement - ).classList.add("force-twistie"); - - const element = append(container, $(".resource-group")); - - const name = append(element, $(".name")); - - const actionsContainer = append(element, $(".actions")); - - const actionBar = new WorkbenchToolBar( - actionsContainer, - { actionViewItemProvider: this.actionViewItemProvider }, - this.menuService, - this.contextKeyService, - this.contextMenuService, - this.keybindingService, - this.commandService, - this.telemetryService, - ); - - const countContainer = append(element, $(".count")); - - const count = new CountBadge( - countContainer, - {}, - defaultCountBadgeStyles, - ); - + (container.parentElement!.parentElement!.querySelector('.monaco-tl-twistie')! as HTMLElement).classList.add('force-twistie'); + + const element = append(container, $('.resource-group')); + const name = append(element, $('.name')); + const actionsContainer = append(element, $('.actions')); + const actionBar = new WorkbenchToolBar(actionsContainer, { actionViewItemProvider: this.actionViewItemProvider }, this.menuService, this.contextKeyService, this.contextMenuService, this.keybindingService, this.commandService, this.telemetryService); + const countContainer = append(element, $('.count')); + const count = new CountBadge(countContainer, {}, defaultCountBadgeStyles); const disposables = combinedDisposable(actionBar, count); - return { - name, - count, - actionBar, - elementDisposables: new DisposableStore(), - disposables, - }; + return { name, count, actionBar, elementDisposables: new DisposableStore(), disposables }; } - renderElement( - node: ITreeNode, - index: number, - template: ResourceGroupTemplate, - ): void { + renderElement(node: ITreeNode, index: number, template: ResourceGroupTemplate): void { const group = node.element; template.name.textContent = group.label; template.count.setCount(group.resources.length); - const menus = this.scmViewService.menus.getRepositoryMenus( - group.provider, - ); - template.elementDisposables.add( - connectPrimaryMenu( - menus.getResourceGroupMenu(group), - (primary) => { - template.actionBar.setActions(primary); - }, - "inline", - ), - ); + const menus = this.scmViewService.menus.getRepositoryMenus(group.provider); + template.elementDisposables.add(connectPrimaryMenu(menus.getResourceGroupMenu(group), primary => { + template.actionBar.setActions(primary); + }, 'inline')); template.actionBar.context = group; } - renderCompressedElements( - node: ITreeNode, FuzzyScore>, - index: number, - templateData: ResourceGroupTemplate, - height: number | undefined, - ): void { - throw new Error("Should never happen since node is incompressible"); + renderCompressedElements(node: ITreeNode, FuzzyScore>, index: number, templateData: ResourceGroupTemplate, height: number | undefined): void { + throw new Error('Should never happen since node is incompressible'); } - disposeElement( - group: ITreeNode, - index: number, - template: ResourceGroupTemplate, - ): void { + disposeElement(group: ITreeNode, index: number, template: ResourceGroupTemplate): void { template.elementDisposables.clear(); } @@ -850,57 +499,31 @@ interface RenderedResourceData { } class RepositoryPaneActionRunner extends ActionRunner { - constructor( - private getSelectedResources: () => ( - | ISCMResource - | IResourceNode - )[], - ) { + + constructor(private getSelectedResources: () => (ISCMResource | IResourceNode)[]) { super(); } - protected override async runAction( - action: IAction, - context: ISCMResource | IResourceNode, - ): Promise { + protected override async runAction(action: IAction, context: ISCMResource | IResourceNode): Promise { if (!(action instanceof MenuItemAction)) { return super.runAction(action, context); } const selection = this.getSelectedResources(); - - const contextIsSelected = selection.some((s) => s === context); - + const contextIsSelected = selection.some(s => s === context); const actualContext = contextIsSelected ? selection : [context]; - - const args = actualContext - .map((e) => - ResourceTree.isResourceNode(e) ? ResourceTree.collect(e) : [e], - ) - .flat(); + const args = actualContext.map(e => ResourceTree.isResourceNode(e) ? ResourceTree.collect(e) : [e]).flat(); await action.run(...args); } } -class ResourceRenderer - implements - ICompressibleTreeRenderer< - ISCMResource | IResourceNode, - FuzzyScore | LabelFuzzyScore, - ResourceTemplate - > -{ - static readonly TEMPLATE_ID = "resource"; +class ResourceRenderer implements ICompressibleTreeRenderer, FuzzyScore | LabelFuzzyScore, ResourceTemplate> { - get templateId(): string { - return ResourceRenderer.TEMPLATE_ID; - } + static readonly TEMPLATE_ID = 'resource'; + get templateId(): string { return ResourceRenderer.TEMPLATE_ID; } private readonly disposables = new DisposableStore(); - private renderedResources = new Map< - ResourceTemplate, - RenderedResourceData - >(); + private renderedResources = new Map(); constructor( private viewMode: () => ViewMode, @@ -915,255 +538,106 @@ class ResourceRenderer @IMenuService private menuService: IMenuService, @ISCMViewService private scmViewService: ISCMViewService, @ITelemetryService private telemetryService: ITelemetryService, - @IThemeService private themeService: IThemeService, + @IThemeService private themeService: IThemeService ) { - themeService.onDidColorThemeChange( - this.onDidColorThemeChange, - this, - this.disposables, - ); + themeService.onDidColorThemeChange(this.onDidColorThemeChange, this, this.disposables); } renderTemplate(container: HTMLElement): ResourceTemplate { - const element = append(container, $(".resource")); - - const name = append(element, $(".name")); - - const fileLabel = this.labels.create(name, { - supportDescriptionHighlights: true, - supportHighlights: true, - }); - - const actionsContainer = append(fileLabel.element, $(".actions")); - - const actionBar = new WorkbenchToolBar( - actionsContainer, - { - actionViewItemProvider: this.actionViewItemProvider, - actionRunner: this.actionRunner, - }, - this.menuService, - this.contextKeyService, - this.contextMenuService, - this.keybindingService, - this.commandService, - this.telemetryService, - ); - - const decorationIcon = append(element, $(".decoration-icon")); - + const element = append(container, $('.resource')); + const name = append(element, $('.name')); + const fileLabel = this.labels.create(name, { supportDescriptionHighlights: true, supportHighlights: true }); + const actionsContainer = append(fileLabel.element, $('.actions')); + const actionBar = new WorkbenchToolBar(actionsContainer, { + actionViewItemProvider: this.actionViewItemProvider, + actionRunner: this.actionRunner + }, this.menuService, this.contextKeyService, this.contextMenuService, this.keybindingService, this.commandService, this.telemetryService); + + const decorationIcon = append(element, $('.decoration-icon')); const actionBarMenuListener = new MutableDisposable(); + const disposables = combinedDisposable(actionBar, fileLabel, actionBarMenuListener); - const disposables = combinedDisposable( - actionBar, - fileLabel, - actionBarMenuListener, - ); - - return { - element, - name, - fileLabel, - decorationIcon, - actionBar, - actionBarMenu: undefined, - actionBarMenuListener, - elementDisposables: new DisposableStore(), - disposables, - }; + return { element, name, fileLabel, decorationIcon, actionBar, actionBarMenu: undefined, actionBarMenuListener, elementDisposables: new DisposableStore(), disposables }; } - renderElement( - node: - | ITreeNode - | ITreeNode< - | ISCMResource - | IResourceNode, - FuzzyScore | LabelFuzzyScore - >, - index: number, - template: ResourceTemplate, - ): void { + renderElement(node: ITreeNode | ITreeNode, FuzzyScore | LabelFuzzyScore>, index: number, template: ResourceTemplate): void { const resourceOrFolder = node.element; - - const iconResource = ResourceTree.isResourceNode(resourceOrFolder) - ? resourceOrFolder.element - : resourceOrFolder; - - const uri = ResourceTree.isResourceNode(resourceOrFolder) - ? resourceOrFolder.uri - : resourceOrFolder.sourceUri; - - const fileKind = ResourceTree.isResourceNode(resourceOrFolder) - ? FileKind.FOLDER - : FileKind.FILE; - - const tooltip = - (!ResourceTree.isResourceNode(resourceOrFolder) && - resourceOrFolder.decorations.tooltip) || - ""; - + const iconResource = ResourceTree.isResourceNode(resourceOrFolder) ? resourceOrFolder.element : resourceOrFolder; + const uri = ResourceTree.isResourceNode(resourceOrFolder) ? resourceOrFolder.uri : resourceOrFolder.sourceUri; + const fileKind = ResourceTree.isResourceNode(resourceOrFolder) ? FileKind.FOLDER : FileKind.FILE; + const tooltip = !ResourceTree.isResourceNode(resourceOrFolder) && resourceOrFolder.decorations.tooltip || ''; const hidePath = this.viewMode() === ViewMode.Tree; let matches: IMatch[] | undefined; - let descriptionMatches: IMatch[] | undefined; - let strikethrough: boolean | undefined; if (ResourceTree.isResourceNode(resourceOrFolder)) { if (resourceOrFolder.element) { - const menus = this.scmViewService.menus.getRepositoryMenus( - resourceOrFolder.element.resourceGroup.provider, - ); - this._renderActionBar( - template, - resourceOrFolder, - menus.getResourceMenu(resourceOrFolder.element), - ); - - template.element.classList.toggle( - "faded", - resourceOrFolder.element.decorations.faded, - ); - strikethrough = - resourceOrFolder.element.decorations.strikeThrough; + const menus = this.scmViewService.menus.getRepositoryMenus(resourceOrFolder.element.resourceGroup.provider); + this._renderActionBar(template, resourceOrFolder, menus.getResourceMenu(resourceOrFolder.element)); + + template.element.classList.toggle('faded', resourceOrFolder.element.decorations.faded); + strikethrough = resourceOrFolder.element.decorations.strikeThrough; } else { - const menus = this.scmViewService.menus.getRepositoryMenus( - resourceOrFolder.context.provider, - ); - this._renderActionBar( - template, - resourceOrFolder, - menus.getResourceFolderMenu(resourceOrFolder.context), - ); - - matches = createMatches( - node.filterData as FuzzyScore | undefined, - ); - template.element.classList.remove("faded"); + const menus = this.scmViewService.menus.getRepositoryMenus(resourceOrFolder.context.provider); + this._renderActionBar(template, resourceOrFolder, menus.getResourceFolderMenu(resourceOrFolder.context)); + + matches = createMatches(node.filterData as FuzzyScore | undefined); + template.element.classList.remove('faded'); } } else { - const menus = this.scmViewService.menus.getRepositoryMenus( - resourceOrFolder.resourceGroup.provider, - ); - this._renderActionBar( - template, - resourceOrFolder, - menus.getResourceMenu(resourceOrFolder), - ); - - [matches, descriptionMatches] = processResourceFilterData( - uri, - node.filterData, - ); - template.element.classList.toggle( - "faded", - resourceOrFolder.decorations.faded, - ); + const menus = this.scmViewService.menus.getRepositoryMenus(resourceOrFolder.resourceGroup.provider); + this._renderActionBar(template, resourceOrFolder, menus.getResourceMenu(resourceOrFolder)); + + [matches, descriptionMatches] = processResourceFilterData(uri, node.filterData); + template.element.classList.toggle('faded', resourceOrFolder.decorations.faded); strikethrough = resourceOrFolder.decorations.strikeThrough; } const renderedData: RenderedResourceData = { - tooltip, - uri, - fileLabelOptions: { - hidePath, - fileKind, - matches, - descriptionMatches, - strikethrough, - }, - iconResource, + tooltip, uri, fileLabelOptions: { hidePath, fileKind, matches, descriptionMatches, strikethrough }, iconResource }; this.renderIcon(template, renderedData); this.renderedResources.set(template, renderedData); - template.elementDisposables.add( - toDisposable(() => this.renderedResources.delete(template)), - ); + template.elementDisposables.add(toDisposable(() => this.renderedResources.delete(template))); - template.element.setAttribute("data-tooltip", tooltip); + template.element.setAttribute('data-tooltip', tooltip); } - disposeElement( - resource: - | ITreeNode - | ITreeNode< - IResourceNode, - FuzzyScore | LabelFuzzyScore - >, - index: number, - template: ResourceTemplate, - ): void { + disposeElement(resource: ITreeNode | ITreeNode, FuzzyScore | LabelFuzzyScore>, index: number, template: ResourceTemplate): void { template.elementDisposables.clear(); } - renderCompressedElements( - node: ITreeNode< - | ICompressedTreeNode - | ICompressedTreeNode< - IResourceNode - >, - FuzzyScore | LabelFuzzyScore - >, - index: number, - template: ResourceTemplate, - height: number | undefined, - ): void { - const compressed = node.element as ICompressedTreeNode< - IResourceNode - >; - + renderCompressedElements(node: ITreeNode | ICompressedTreeNode>, FuzzyScore | LabelFuzzyScore>, index: number, template: ResourceTemplate, height: number | undefined): void { + const compressed = node.element as ICompressedTreeNode>; const folder = compressed.elements[compressed.elements.length - 1]; - const label = compressed.elements.map((e) => e.name); - + const label = compressed.elements.map(e => e.name); const fileKind = FileKind.FOLDER; - const matches = createMatches( - node.filterData as FuzzyScore | undefined, - ); - template.fileLabel.setResource( - { resource: folder.uri, name: label }, - { - fileDecorations: { colors: false, badges: true }, - fileKind, - matches, - separator: this.labelService.getSeparator(folder.uri.scheme), - }, - ); + const matches = createMatches(node.filterData as FuzzyScore | undefined); + template.fileLabel.setResource({ resource: folder.uri, name: label }, { + fileDecorations: { colors: false, badges: true }, + fileKind, + matches, + separator: this.labelService.getSeparator(folder.uri.scheme) + }); - const menus = this.scmViewService.menus.getRepositoryMenus( - folder.context.provider, - ); - this._renderActionBar( - template, - folder, - menus.getResourceFolderMenu(folder.context), - ); + const menus = this.scmViewService.menus.getRepositoryMenus(folder.context.provider); + this._renderActionBar(template, folder, menus.getResourceFolderMenu(folder.context)); + + template.name.classList.remove('strike-through'); + template.element.classList.remove('faded'); + template.decorationIcon.style.display = 'none'; + template.decorationIcon.style.backgroundImage = ''; + + template.element.setAttribute('data-tooltip', ''); + } - template.name.classList.remove("strike-through"); - template.element.classList.remove("faded"); - template.decorationIcon.style.display = "none"; - template.decorationIcon.style.backgroundImage = ""; - - template.element.setAttribute("data-tooltip", ""); - } - - disposeCompressedElements( - node: ITreeNode< - | ICompressedTreeNode - | ICompressedTreeNode< - IResourceNode - >, - FuzzyScore | LabelFuzzyScore - >, - index: number, - template: ResourceTemplate, - height: number | undefined, - ): void { + disposeCompressedElements(node: ITreeNode | ICompressedTreeNode>, FuzzyScore | LabelFuzzyScore>, index: number, template: ResourceTemplate, height: number | undefined): void { template.elementDisposables.clear(); } @@ -1172,22 +646,12 @@ class ResourceRenderer template.disposables.dispose(); } - private _renderActionBar( - template: ResourceTemplate, - resourceOrFolder: - | ISCMResource - | IResourceNode, - menu: IMenu, - ): void { + private _renderActionBar(template: ResourceTemplate, resourceOrFolder: ISCMResource | IResourceNode, menu: IMenu): void { if (!template.actionBarMenu || template.actionBarMenu !== menu) { template.actionBarMenu = menu; - template.actionBarMenuListener.value = connectPrimaryMenu( - menu, - (primary) => { - template.actionBar.setActions(primary); - }, - "inline", - ); + template.actionBarMenuListener.value = connectPrimaryMenu(menu, primary => { + template.actionBar.setActions(primary); + }, 'inline'); } template.actionBar.context = resourceOrFolder; @@ -1199,16 +663,9 @@ class ResourceRenderer } } - private renderIcon( - template: ResourceTemplate, - data: RenderedResourceData, - ): void { + private renderIcon(template: ResourceTemplate, data: RenderedResourceData): void { const theme = this.themeService.getColorTheme(); - - const icon = - theme.type === ColorScheme.LIGHT - ? data.iconResource?.decorations.icon - : data.iconResource?.decorations.iconDark; + const icon = theme.type === ColorScheme.LIGHT ? data.iconResource?.decorations.icon : data.iconResource?.decorations.iconDark; template.fileLabel.setFile(data.uri, { ...data.fileLabelOptions, @@ -1218,26 +675,24 @@ class ResourceRenderer if (icon) { if (ThemeIcon.isThemeIcon(icon)) { template.decorationIcon.className = `decoration-icon ${ThemeIcon.asClassName(icon)}`; - if (icon.color) { - template.decorationIcon.style.color = - theme.getColor(icon.color.id)?.toString() ?? ""; + template.decorationIcon.style.color = theme.getColor(icon.color.id)?.toString() ?? ''; } - template.decorationIcon.style.display = ""; - template.decorationIcon.style.backgroundImage = ""; + template.decorationIcon.style.display = ''; + template.decorationIcon.style.backgroundImage = ''; } else { - template.decorationIcon.className = "decoration-icon"; - template.decorationIcon.style.color = ""; - template.decorationIcon.style.display = ""; + template.decorationIcon.className = 'decoration-icon'; + template.decorationIcon.style.color = ''; + template.decorationIcon.style.display = ''; template.decorationIcon.style.backgroundImage = asCSSUrl(icon); } template.decorationIcon.title = data.tooltip; } else { - template.decorationIcon.className = "decoration-icon"; - template.decorationIcon.style.color = ""; - template.decorationIcon.style.display = "none"; - template.decorationIcon.style.backgroundImage = ""; - template.decorationIcon.title = ""; + template.decorationIcon.className = 'decoration-icon'; + template.decorationIcon.style.color = ''; + template.decorationIcon.style.display = 'none'; + template.decorationIcon.style.backgroundImage = ''; + template.decorationIcon.title = ''; } } @@ -1247,7 +702,8 @@ class ResourceRenderer } class ListDelegate implements IListVirtualDelegate { - constructor(private readonly inputRenderer: InputRenderer) {} + + constructor(private readonly inputRenderer: InputRenderer) { } getHeight(element: TreeElement) { if (isSCMInput(element)) { @@ -1271,28 +727,25 @@ class ListDelegate implements IListVirtualDelegate { } else if (isSCMResource(element) || isSCMResourceNode(element)) { return ResourceRenderer.TEMPLATE_ID; } else { - throw new Error("Unknown element"); + throw new Error('Unknown element'); } } } -class SCMTreeCompressionDelegate - implements ITreeCompressionDelegate -{ +class SCMTreeCompressionDelegate implements ITreeCompressionDelegate { + isIncompressible(element: TreeElement): boolean { if (ResourceTree.isResourceNode(element)) { - return ( - element.childrenCount === 0 || - !element.parent || - !element.parent.parent - ); + return element.childrenCount === 0 || !element.parent || !element.parent.parent; } return true; } + } class SCMTreeFilter implements ITreeFilter { + filter(element: TreeElement): boolean { if (isSCMResourceGroup(element)) { return element.resources.length > 0 || !element.hideWhenEmpty; @@ -1303,15 +756,15 @@ class SCMTreeFilter implements ITreeFilter { } export class SCMTreeSorter implements ITreeSorter { + constructor( private readonly viewMode: () => ViewMode, - private readonly viewSortKey: () => ViewSortKey, - ) {} + private readonly viewSortKey: () => ViewSortKey) { } compare(one: TreeElement, other: TreeElement): number { if (isSCMRepository(one)) { if (!isSCMRepository(other)) { - throw new Error("Invalid comparison"); + throw new Error('Invalid comparison'); } return 0; @@ -1338,7 +791,6 @@ export class SCMTreeSorter implements ITreeSorter { // FileName if (this.viewSortKey() === ViewSortKey.Name) { const oneName = basename((one as ISCMResource).sourceUri); - const otherName = basename((other as ISCMResource).sourceUri); return compareFileNames(oneName, otherName); @@ -1346,11 +798,8 @@ export class SCMTreeSorter implements ITreeSorter { // Status if (this.viewSortKey() === ViewSortKey.Status) { - const oneTooltip = - (one as ISCMResource).decorations.tooltip ?? ""; - - const otherTooltip = - (other as ISCMResource).decorations.tooltip ?? ""; + const oneTooltip = (one as ISCMResource).decorations.tooltip ?? ''; + const otherTooltip = (other as ISCMResource).decorations.tooltip ?? ''; if (oneTooltip !== otherTooltip) { return compare(oneTooltip, otherTooltip); @@ -1359,7 +808,6 @@ export class SCMTreeSorter implements ITreeSorter { // Path (default) const onePath = (one as ISCMResource).sourceUri.fsPath; - const otherPath = (other as ISCMResource).sourceUri.fsPath; return comparePaths(onePath, otherPath); @@ -1367,43 +815,30 @@ export class SCMTreeSorter implements ITreeSorter { // Resource (Tree) const oneIsDirectory = ResourceTree.isResourceNode(one); - const otherIsDirectory = ResourceTree.isResourceNode(other); if (oneIsDirectory !== otherIsDirectory) { return oneIsDirectory ? -1 : 1; } - const oneName = ResourceTree.isResourceNode(one) - ? one.name - : basename((one as ISCMResource).sourceUri); - - const otherName = ResourceTree.isResourceNode(other) - ? other.name - : basename((other as ISCMResource).sourceUri); + const oneName = ResourceTree.isResourceNode(one) ? one.name : basename((one as ISCMResource).sourceUri); + const otherName = ResourceTree.isResourceNode(other) ? other.name : basename((other as ISCMResource).sourceUri); return compareFileNames(oneName, otherName); } } -export class SCMTreeKeyboardNavigationLabelProvider - implements ICompressibleKeyboardNavigationLabelProvider -{ +export class SCMTreeKeyboardNavigationLabelProvider implements ICompressibleKeyboardNavigationLabelProvider { + constructor( private viewMode: () => ViewMode, @ILabelService private readonly labelService: ILabelService, - ) {} + ) { } - getKeyboardNavigationLabel( - element: TreeElement, - ): { toString(): string } | { toString(): string }[] | undefined { + getKeyboardNavigationLabel(element: TreeElement): { toString(): string } | { toString(): string }[] | undefined { if (ResourceTree.isResourceNode(element)) { return element.name; - } else if ( - isSCMRepository(element) || - isSCMInput(element) || - isSCMActionButton(element) - ) { + } else if (isSCMRepository(element) || isSCMInput(element) || isSCMActionButton(element)) { return undefined; } else if (isSCMResourceGroup(element)) { return element.label; @@ -1414,11 +849,7 @@ export class SCMTreeKeyboardNavigationLabelProvider // full path we return an array of labels. A match in the // file name takes precedence over a match in the path. const fileName = basename(element.sourceUri); - - const filePath = this.labelService.getUriLabel( - element.sourceUri, - { relative: true }, - ); + const filePath = this.labelService.getUriLabel(element.sourceUri, { relative: true }); return [fileName, filePath]; } else { @@ -1428,79 +859,63 @@ export class SCMTreeKeyboardNavigationLabelProvider } } - getCompressedNodeKeyboardNavigationLabel( - elements: TreeElement[], - ): { toString(): string | undefined } | undefined { - const folders = elements as IResourceNode< - ISCMResource, - ISCMResourceGroup - >[]; - - return folders.map((e) => e.name).join("/"); + getCompressedNodeKeyboardNavigationLabel(elements: TreeElement[]): { toString(): string | undefined } | undefined { + const folders = elements as IResourceNode[]; + return folders.map(e => e.name).join('/'); } } function getSCMResourceId(element: TreeElement): string { if (isSCMRepository(element)) { const provider = element.provider; - return `repo:${provider.id}`; } else if (isSCMInput(element)) { const provider = element.repository.provider; - return `input:${provider.id}`; } else if (isSCMActionButton(element)) { const provider = element.repository.provider; - return `actionButton:${provider.id}`; } else if (isSCMResourceGroup(element)) { const provider = element.provider; - return `resourceGroup:${provider.id}/${element.id}`; } else if (isSCMResource(element)) { const group = element.resourceGroup; - const provider = group.provider; - return `resource:${provider.id}/${group.id}/${element.sourceUri.toString()}`; } else if (isSCMResourceNode(element)) { const group = element.context; - return `folder:${group.provider.id}/${group.id}/$FOLDER/${element.uri.toString()}`; } else { - throw new Error("Invalid tree element"); + throw new Error('Invalid tree element'); } } class SCMResourceIdentityProvider implements IIdentityProvider { + getId(element: TreeElement): string { return getSCMResourceId(element); } } -export class SCMAccessibilityProvider - implements IListAccessibilityProvider -{ - constructor(@ILabelService private readonly labelService: ILabelService) {} +export class SCMAccessibilityProvider implements IListAccessibilityProvider { + + constructor( + @ILabelService private readonly labelService: ILabelService + ) { } getWidgetAriaLabel(): string { - return localize("scm", "Source Control Management"); + return localize('scm', "Source Control Management"); } getAriaLabel(element: TreeElement): string { if (ResourceTree.isResourceNode(element)) { - return ( - this.labelService.getUriLabel(element.uri, { - relative: true, - noPrefix: true, - }) || element.name - ); + return this.labelService.getUriLabel(element.uri, { relative: true, noPrefix: true }) || element.name; } else if (isSCMRepository(element)) { return `${element.provider.name} ${element.provider.label}`; } else if (isSCMInput(element)) { - return localize("input", "Source Control Input"); + return localize('input', "Source Control Input"); } else if (isSCMActionButton(element)) { - return element.button?.command.title ?? ""; + return element.button?.command.title ?? ''; } else if (isSCMResourceGroup(element)) { return element.label; } else { @@ -1512,104 +927,69 @@ export class SCMAccessibilityProvider result.push(element.decorations.tooltip); } - const path = this.labelService.getUriLabel( - dirname(element.sourceUri), - { relative: true, noPrefix: true }, - ); + const path = this.labelService.getUriLabel(dirname(element.sourceUri), { relative: true, noPrefix: true }); if (path) { result.push(path); } - return result.join(", "); + return result.join(', '); } } } const enum ViewMode { - List = "list", - Tree = "tree", + List = 'list', + Tree = 'tree' } const enum ViewSortKey { - Path = "path", - Name = "name", - Status = "status", + Path = 'path', + Name = 'name', + Status = 'status' } const Menus = { - ViewSort: new MenuId("SCMViewSort"), - Repositories: new MenuId("SCMRepositories"), - ChangesSettings: new MenuId("SCMChangesSettings"), + ViewSort: new MenuId('SCMViewSort'), + Repositories: new MenuId('SCMRepositories'), + ChangesSettings: new MenuId('SCMChangesSettings'), }; export const ContextKeys = { - SCMViewMode: new RawContextKey("scmViewMode", ViewMode.List), - SCMViewSortKey: new RawContextKey( - "scmViewSortKey", - ViewSortKey.Path, - ), - SCMViewAreAllRepositoriesCollapsed: new RawContextKey( - "scmViewAreAllRepositoriesCollapsed", - false, - ), - SCMViewIsAnyRepositoryCollapsible: new RawContextKey( - "scmViewIsAnyRepositoryCollapsible", - false, - ), - SCMProvider: new RawContextKey( - "scmProvider", - undefined, - ), - SCMProviderRootUri: new RawContextKey( - "scmProviderRootUri", - undefined, - ), - SCMProviderHasRootUri: new RawContextKey( - "scmProviderHasRootUri", - undefined, - ), - SCMHistoryItemCount: new RawContextKey("scmHistoryItemCount", 0), - SCMCurrentHistoryItemRefHasRemote: new RawContextKey( - "scmCurrentHistoryItemRefHasRemote", - false, - ), - SCMCurrentHistoryItemRefInFilter: new RawContextKey( - "scmCurrentHistoryItemRefInFilter", - false, - ), - RepositoryCount: new RawContextKey("scmRepositoryCount", 0), - RepositoryVisibilityCount: new RawContextKey( - "scmRepositoryVisibleCount", - 0, - ), + SCMViewMode: new RawContextKey('scmViewMode', ViewMode.List), + SCMViewSortKey: new RawContextKey('scmViewSortKey', ViewSortKey.Path), + SCMViewAreAllRepositoriesCollapsed: new RawContextKey('scmViewAreAllRepositoriesCollapsed', false), + SCMViewIsAnyRepositoryCollapsible: new RawContextKey('scmViewIsAnyRepositoryCollapsible', false), + SCMProvider: new RawContextKey('scmProvider', undefined), + SCMProviderRootUri: new RawContextKey('scmProviderRootUri', undefined), + SCMProviderHasRootUri: new RawContextKey('scmProviderHasRootUri', undefined), + SCMHistoryItemCount: new RawContextKey('scmHistoryItemCount', 0), + SCMCurrentHistoryItemRefHasRemote: new RawContextKey('scmCurrentHistoryItemRefHasRemote', false), + SCMCurrentHistoryItemRefInFilter: new RawContextKey('scmCurrentHistoryItemRefInFilter', false), + RepositoryCount: new RawContextKey('scmRepositoryCount', 0), + RepositoryVisibilityCount: new RawContextKey('scmRepositoryVisibleCount', 0), RepositoryVisibility(repository: ISCMRepository) { - return new RawContextKey( - `scmRepositoryVisible:${repository.provider.id}`, - false, - ); - }, + return new RawContextKey(`scmRepositoryVisible:${repository.provider.id}`, false); + } }; MenuRegistry.appendMenuItem(MenuId.SCMTitle, { - title: localize("sortAction", "View & Sort"), + title: localize('sortAction', "View & Sort"), submenu: Menus.ViewSort, - when: ContextKeyExpr.and( - ContextKeyExpr.equals("view", VIEW_PANE_ID), - ContextKeys.RepositoryCount.notEqualsTo(0), - ), - group: "0_view&sort", - order: 1, + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_PANE_ID), ContextKeys.RepositoryCount.notEqualsTo(0)), + group: '0_view&sort', + order: 1 }); MenuRegistry.appendMenuItem(Menus.ViewSort, { - title: localize("repositories", "Repositories"), + title: localize('repositories', "Repositories"), submenu: Menus.Repositories, when: ContextKeyExpr.greater(ContextKeys.RepositoryCount.key, 1), - group: "0_repositories", + group: '0_repositories' }); class RepositoryVisibilityAction extends Action2 { + private repository: ISCMRepository; constructor(repository: ISCMRepository) { @@ -1617,13 +997,9 @@ class RepositoryVisibilityAction extends Action2 { id: `workbench.scm.action.toggleRepositoryVisibility.${repository.provider.id}`, title: repository.provider.name, f1: false, - precondition: ContextKeyExpr.or( - ContextKeys.RepositoryVisibilityCount.notEqualsTo(1), - ContextKeys.RepositoryVisibility(repository).isEqualTo(false), - ), - toggled: - ContextKeys.RepositoryVisibility(repository).isEqualTo(true), - menu: { id: Menus.Repositories, group: "0_repositories" }, + precondition: ContextKeyExpr.or(ContextKeys.RepositoryVisibilityCount.notEqualsTo(1), ContextKeys.RepositoryVisibility(repository).isEqualTo(false)), + toggled: ContextKeys.RepositoryVisibility(repository).isEqualTo(true), + menu: { id: Menus.Repositories, group: '0_repositories' } }); this.repository = repository; } @@ -1640,6 +1016,7 @@ interface RepositoryVisibilityItem { } class RepositoryVisibilityActionController { + private items = new Map(); private repositoryCountContextKey: IContextKey; private repositoryVisibilityCountContextKey: IContextKey; @@ -1648,28 +1025,14 @@ class RepositoryVisibilityActionController { constructor( @IContextKeyService private contextKeyService: IContextKeyService, @ISCMViewService private readonly scmViewService: ISCMViewService, - @ISCMService scmService: ISCMService, + @ISCMService scmService: ISCMService ) { - this.repositoryCountContextKey = - ContextKeys.RepositoryCount.bindTo(contextKeyService); - this.repositoryVisibilityCountContextKey = - ContextKeys.RepositoryVisibilityCount.bindTo(contextKeyService); - - scmViewService.onDidChangeVisibleRepositories( - this.onDidChangeVisibleRepositories, - this, - this.disposables, - ); - scmService.onDidAddRepository( - this.onDidAddRepository, - this, - this.disposables, - ); - scmService.onDidRemoveRepository( - this.onDidRemoveRepository, - this, - this.disposables, - ); + this.repositoryCountContextKey = ContextKeys.RepositoryCount.bindTo(contextKeyService); + this.repositoryVisibilityCountContextKey = ContextKeys.RepositoryVisibilityCount.bindTo(contextKeyService); + + scmViewService.onDidChangeVisibleRepositories(this.onDidChangeVisibleRepositories, this, this.disposables); + scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); + scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables); for (const repository of scmService.repositories) { this.onDidAddRepository(repository); @@ -1677,17 +1040,13 @@ class RepositoryVisibilityActionController { } private onDidAddRepository(repository: ISCMRepository): void { - const action = registerAction2( - class extends RepositoryVisibilityAction { - constructor() { - super(repository); - } - }, - ); + const action = registerAction2(class extends RepositoryVisibilityAction { + constructor() { + super(repository); + } + }); - const contextKey = ContextKeys.RepositoryVisibility(repository).bindTo( - this.contextKeyService, - ); + const contextKey = ContextKeys.RepositoryVisibility(repository).bindTo(this.contextKeyService); contextKey.set(this.scmViewService.isVisible(repository)); this.items.set(repository, { @@ -1695,7 +1054,7 @@ class RepositoryVisibilityActionController { dispose() { contextKey.reset(); action.dispose(); - }, + } }); this.updateRepositoryContextKeys(); @@ -1725,14 +1084,7 @@ class RepositoryVisibilityActionController { private updateRepositoryContextKeys(): void { this.repositoryCountContextKey.set(this.items.size); - this.repositoryVisibilityCountContextKey.set( - Iterable.reduce( - this.items.keys(), - (r, repository) => - r + (this.scmViewService.isVisible(repository) ? 1 : 0), - 0, - ), - ); + this.repositoryVisibilityCountContextKey.set(Iterable.reduce(this.items.keys(), (r, repository) => r + (this.scmViewService.isVisible(repository) ? 1 : 0), 0)); } dispose(): void { @@ -1744,17 +1096,16 @@ class RepositoryVisibilityActionController { class SetListViewModeAction extends ViewAction { constructor( - id = "workbench.scm.action.setListViewMode", - menu: Partial = {}, - ) { + id = 'workbench.scm.action.setListViewMode', + menu: Partial = {}) { super({ id, - title: localize("setListViewMode", "View as List"), + title: localize('setListViewMode', "View as List"), viewId: VIEW_PANE_ID, f1: false, icon: Codicon.listTree, toggled: ContextKeys.SCMViewMode.isEqualTo(ViewMode.List), - menu: { id: Menus.ViewSort, group: "1_viewmode", ...menu }, + menu: { id: Menus.ViewSort, group: '1_viewmode', ...menu } }); } @@ -1765,33 +1116,31 @@ class SetListViewModeAction extends ViewAction { class SetListViewModeNavigationAction extends SetListViewModeAction { constructor() { - super("workbench.scm.action.setListViewModeNavigation", { - id: MenuId.SCMTitle, - when: ContextKeyExpr.and( - ContextKeyExpr.equals("view", VIEW_PANE_ID), - ContextKeys.RepositoryCount.notEqualsTo(0), - ContextKeys.SCMViewMode.isEqualTo(ViewMode.Tree), - ), - group: "navigation", - order: -1000, - }); + super( + 'workbench.scm.action.setListViewModeNavigation', + { + id: MenuId.SCMTitle, + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_PANE_ID), ContextKeys.RepositoryCount.notEqualsTo(0), ContextKeys.SCMViewMode.isEqualTo(ViewMode.Tree)), + group: 'navigation', + order: -1000 + }); } } class SetTreeViewModeAction extends ViewAction { constructor( - id = "workbench.scm.action.setTreeViewMode", - menu: Partial = {}, - ) { - super({ - id, - title: localize("setTreeViewMode", "View as Tree"), - viewId: VIEW_PANE_ID, - f1: false, - icon: Codicon.listFlat, - toggled: ContextKeys.SCMViewMode.isEqualTo(ViewMode.Tree), - menu: { id: Menus.ViewSort, group: "1_viewmode", ...menu }, - }); + id = 'workbench.scm.action.setTreeViewMode', + menu: Partial = {}) { + super( + { + id, + title: localize('setTreeViewMode', "View as Tree"), + viewId: VIEW_PANE_ID, + f1: false, + icon: Codicon.listFlat, + toggled: ContextKeys.SCMViewMode.isEqualTo(ViewMode.Tree), + menu: { id: Menus.ViewSort, group: '1_viewmode', ...menu } + }); } async runInView(_: ServicesAccessor, view: SCMViewPane): Promise { @@ -1801,16 +1150,14 @@ class SetTreeViewModeAction extends ViewAction { class SetTreeViewModeNavigationAction extends SetTreeViewModeAction { constructor() { - super("workbench.scm.action.setTreeViewModeNavigation", { - id: MenuId.SCMTitle, - when: ContextKeyExpr.and( - ContextKeyExpr.equals("view", VIEW_PANE_ID), - ContextKeys.RepositoryCount.notEqualsTo(0), - ContextKeys.SCMViewMode.isEqualTo(ViewMode.List), - ), - group: "navigation", - order: -1000, - }); + super( + 'workbench.scm.action.setTreeViewModeNavigation', + { + id: MenuId.SCMTitle, + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_PANE_ID), ContextKeys.RepositoryCount.notEqualsTo(0), ContextKeys.SCMViewMode.isEqualTo(ViewMode.List)), + group: 'navigation', + order: -1000 + }); } } @@ -1820,10 +1167,7 @@ registerAction2(SetListViewModeNavigationAction); registerAction2(SetTreeViewModeNavigationAction); abstract class RepositorySortAction extends ViewAction { - constructor( - private sortKey: ISCMRepositorySortKey, - title: string, - ) { + constructor(private sortKey: ISCMRepositorySortKey, title: string) { super({ id: `workbench.scm.action.repositories.setSortKey.${sortKey}`, title, @@ -1833,13 +1177,13 @@ abstract class RepositorySortAction extends ViewAction { menu: [ { id: Menus.Repositories, - group: "1_sort", + group: '1_sort' }, { id: MenuId.SCMSourceControlTitle, - group: "1_sort", + group: '1_sort', }, - ], + ] }); } @@ -1848,30 +1192,22 @@ abstract class RepositorySortAction extends ViewAction { } } + class RepositorySortByDiscoveryTimeAction extends RepositorySortAction { constructor() { - super( - ISCMRepositorySortKey.DiscoveryTime, - localize("repositorySortByDiscoveryTime", "Sort by Discovery Time"), - ); + super(ISCMRepositorySortKey.DiscoveryTime, localize('repositorySortByDiscoveryTime', "Sort by Discovery Time")); } } class RepositorySortByNameAction extends RepositorySortAction { constructor() { - super( - ISCMRepositorySortKey.Name, - localize("repositorySortByName", "Sort by Name"), - ); + super(ISCMRepositorySortKey.Name, localize('repositorySortByName', "Sort by Name")); } } class RepositorySortByPathAction extends RepositorySortAction { constructor() { - super( - ISCMRepositorySortKey.Path, - localize("repositorySortByPath", "Sort by Path"), - ); + super(ISCMRepositorySortKey.Path, localize('repositorySortByPath', "Sort by Path")); } } @@ -1880,10 +1216,7 @@ registerAction2(RepositorySortByNameAction); registerAction2(RepositorySortByPathAction); abstract class SetSortKeyAction extends ViewAction { - constructor( - private sortKey: ViewSortKey, - title: string, - ) { + constructor(private sortKey: ViewSortKey, title: string) { super({ id: `workbench.scm.action.setSortKey.${sortKey}`, title, @@ -1891,7 +1224,7 @@ abstract class SetSortKeyAction extends ViewAction { f1: false, toggled: ContextKeys.SCMViewSortKey.isEqualTo(sortKey), precondition: ContextKeys.SCMViewMode.isEqualTo(ViewMode.List), - menu: { id: Menus.ViewSort, group: "2_sort" }, + menu: { id: Menus.ViewSort, group: '2_sort' } }); } @@ -1902,28 +1235,19 @@ abstract class SetSortKeyAction extends ViewAction { class SetSortByNameAction extends SetSortKeyAction { constructor() { - super( - ViewSortKey.Name, - localize("sortChangesByName", "Sort Changes by Name"), - ); + super(ViewSortKey.Name, localize('sortChangesByName', "Sort Changes by Name")); } } class SetSortByPathAction extends SetSortKeyAction { constructor() { - super( - ViewSortKey.Path, - localize("sortChangesByPath", "Sort Changes by Path"), - ); + super(ViewSortKey.Path, localize('sortChangesByPath', "Sort Changes by Path")); } } class SetSortByStatusAction extends SetSortKeyAction { constructor() { - super( - ViewSortKey.Status, - localize("sortChangesByStatus", "Sort Changes by Status"), - ); + super(ViewSortKey.Status, localize('sortChangesByStatus', "Sort Changes by Status")); } } @@ -1932,26 +1256,19 @@ registerAction2(SetSortByPathAction); registerAction2(SetSortByStatusAction); class CollapseAllRepositoriesAction extends ViewAction { + constructor() { super({ id: `workbench.scm.action.collapseAllRepositories`, - title: localize("collapse all", "Collapse All Repositories"), + title: localize('collapse all', "Collapse All Repositories"), viewId: VIEW_PANE_ID, f1: false, icon: Codicon.collapseAll, menu: { id: MenuId.SCMTitle, - group: "navigation", - when: ContextKeyExpr.and( - ContextKeyExpr.equals("view", VIEW_PANE_ID), - ContextKeys.SCMViewIsAnyRepositoryCollapsible.isEqualTo( - true, - ), - ContextKeys.SCMViewAreAllRepositoriesCollapsed.isEqualTo( - false, - ), - ), - }, + group: 'navigation', + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_PANE_ID), ContextKeys.SCMViewIsAnyRepositoryCollapsible.isEqualTo(true), ContextKeys.SCMViewAreAllRepositoriesCollapsed.isEqualTo(false)) + } }); } @@ -1961,26 +1278,19 @@ class CollapseAllRepositoriesAction extends ViewAction { } class ExpandAllRepositoriesAction extends ViewAction { + constructor() { super({ id: `workbench.scm.action.expandAllRepositories`, - title: localize("expand all", "Expand All Repositories"), + title: localize('expand all', "Expand All Repositories"), viewId: VIEW_PANE_ID, f1: false, icon: Codicon.expandAll, menu: { id: MenuId.SCMTitle, - group: "navigation", - when: ContextKeyExpr.and( - ContextKeyExpr.equals("view", VIEW_PANE_ID), - ContextKeys.SCMViewIsAnyRepositoryCollapsible.isEqualTo( - true, - ), - ContextKeys.SCMViewAreAllRepositoriesCollapsed.isEqualTo( - true, - ), - ), - }, + group: 'navigation', + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_PANE_ID), ContextKeys.SCMViewIsAnyRepositoryCollapsible.isEqualTo(true), ContextKeys.SCMViewAreAllRepositoriesCollapsed.isEqualTo(true)) + } }); } @@ -1993,24 +1303,23 @@ registerAction2(CollapseAllRepositoriesAction); registerAction2(ExpandAllRepositoriesAction); const enum SCMInputWidgetCommandId { - CancelAction = "scm.input.cancelAction", + CancelAction = 'scm.input.cancelAction' } const enum SCMInputWidgetStorageKey { - LastActionId = "scm.input.lastActionId", + LastActionId = 'scm.input.lastActionId' } class SCMInputWidgetActionRunner extends ActionRunner { + private readonly _runningActions = new Set(); - public get runningActions(): Set { - return this._runningActions; - } + public get runningActions(): Set { return this._runningActions; } private _cts: CancellationTokenSource | undefined; constructor( private readonly input: ISCMInput, - @IStorageService private readonly storageService: IStorageService, + @IStorageService private readonly storageService: IStorageService ) { super(); } @@ -2028,134 +1337,86 @@ class SCMInputWidgetActionRunner extends ActionRunner { // Create action context const context: ISCMInputValueProviderContext[] = []; - for (const group of this.input.repository.provider.groups) { context.push({ resourceGroupId: group.id, - resources: [...group.resources.map((r) => r.sourceUri)], + resources: [...group.resources.map(r => r.sourceUri)] }); } // Run action this._runningActions.add(action); this._cts = new CancellationTokenSource(); - await action.run( - ...[ - this.input.repository.provider.rootUri, - context, - this._cts.token, - ], - ); + await action.run(...[this.input.repository.provider.rootUri, context, this._cts.token]); } finally { this._runningActions.delete(action); // Save last action if (this._runningActions.size === 0) { - this.storageService.store( - SCMInputWidgetStorageKey.LastActionId, - action.id, - StorageScope.PROFILE, - StorageTarget.USER, - ); + this.storageService.store(SCMInputWidgetStorageKey.LastActionId, action.id, StorageScope.PROFILE, StorageTarget.USER); } } } + } class SCMInputWidgetToolbar extends WorkbenchToolBar { - private _dropdownActions: IAction[] = []; - get dropdownActions(): IAction[] { - return this._dropdownActions; - } + private _dropdownActions: IAction[] = []; + get dropdownActions(): IAction[] { return this._dropdownActions; } private _dropdownAction: IAction; - - get dropdownAction(): IAction { - return this._dropdownAction; - } + get dropdownAction(): IAction { return this._dropdownAction; } private _cancelAction: IAction; private _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; - private readonly _disposables = this._register( - new MutableDisposable(), - ); + private readonly _disposables = this._register(new MutableDisposable()); constructor( container: HTMLElement, options: IMenuWorkbenchToolBarOptions | undefined, @IMenuService private readonly menuService: IMenuService, - @IContextKeyService - private readonly contextKeyService: IContextKeyService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, @IContextMenuService contextMenuService: IContextMenuService, @ICommandService commandService: ICommandService, @IKeybindingService keybindingService: IKeybindingService, @IStorageService private readonly storageService: IStorageService, @ITelemetryService telemetryService: ITelemetryService, ) { - super( - container, - { resetMenu: MenuId.SCMInputBox, ...options }, - menuService, - contextKeyService, - contextMenuService, - keybindingService, - commandService, - telemetryService, - ); + super(container, { resetMenu: MenuId.SCMInputBox, ...options }, menuService, contextKeyService, contextMenuService, keybindingService, commandService, telemetryService); this._dropdownAction = new Action( - "scmInputMoreActions", - localize("scmInputMoreActions", "More Actions..."), - "codicon-chevron-down", - ); + 'scmInputMoreActions', + localize('scmInputMoreActions', "More Actions..."), + 'codicon-chevron-down'); - this._cancelAction = new MenuItemAction( - { - id: SCMInputWidgetCommandId.CancelAction, - title: localize("scmInputCancelAction", "Cancel"), - icon: Codicon.debugStop, - }, - undefined, - undefined, - undefined, - undefined, - contextKeyService, - commandService, - ); + this._cancelAction = new MenuItemAction({ + id: SCMInputWidgetCommandId.CancelAction, + title: localize('scmInputCancelAction', "Cancel"), + icon: Codicon.stopCircle, + }, undefined, undefined, undefined, undefined, contextKeyService, commandService); } public setInput(input: ISCMInput): void { this._disposables.value = new DisposableStore(); const contextKeyService = this.contextKeyService.createOverlay([ - ["scmProvider", input.repository.provider.contextValue], - [ - "scmProviderRootUri", - input.repository.provider.rootUri?.toString(), - ], - ["scmProviderHasRootUri", !!input.repository.provider.rootUri], + ['scmProvider', input.repository.provider.contextValue], + ['scmProviderRootUri', input.repository.provider.rootUri?.toString()], + ['scmProviderHasRootUri', !!input.repository.provider.rootUri] ]); - const menu = this._disposables.value.add( - this.menuService.createMenu(MenuId.SCMInputBox, contextKeyService, { - emitEventsForSubmenuChanges: true, - }), - ); + const menu = this._disposables.value.add(this.menuService.createMenu(MenuId.SCMInputBox, contextKeyService, { emitEventsForSubmenuChanges: true })); const isEnabled = (): boolean => { - return input.repository.provider.groups.some( - (g) => g.resources.length > 0, - ); + return input.repository.provider.groups.some(g => g.resources.length > 0); }; const updateToolbar = () => { - const actions = getFlatActionBarActions( - menu.getActions({ shouldForwardArgs: true }), - ); + const actions = getFlatActionBarActions(menu.getActions({ shouldForwardArgs: true })); for (const action of actions) { action.enabled = isEnabled(); @@ -2167,67 +1428,39 @@ class SCMInputWidgetToolbar extends WorkbenchToolBar { if (actions.length === 1) { primaryAction = actions[0]; } else if (actions.length > 1) { - const lastActionId = this.storageService.get( - SCMInputWidgetStorageKey.LastActionId, - StorageScope.PROFILE, - "", - ); - primaryAction = - actions.find((a) => a.id === lastActionId) ?? actions[0]; + const lastActionId = this.storageService.get(SCMInputWidgetStorageKey.LastActionId, StorageScope.PROFILE, ''); + primaryAction = actions.find(a => a.id === lastActionId) ?? actions[0]; } this._dropdownActions = actions.length === 1 ? [] : actions; - super.setActions(primaryAction ? [primaryAction] : [], []); this._onDidChange.fire(); }; this._disposables.value.add(menu.onDidChange(() => updateToolbar())); - this._disposables.value.add( - input.repository.provider.onDidChangeResources(() => - updateToolbar(), - ), - ); - this._disposables.value.add( - this.storageService.onDidChangeValue( - StorageScope.PROFILE, - SCMInputWidgetStorageKey.LastActionId, - this._disposables.value, - )(() => updateToolbar()), - ); - - this.actionRunner = new SCMInputWidgetActionRunner( - input, - this.storageService, - ); - this._disposables.value.add( - this.actionRunner.onWillRun((e) => { - if ( - (this.actionRunner as SCMInputWidgetActionRunner) - .runningActions.size === 0 - ) { - super.setActions([this._cancelAction], []); - this._onDidChange.fire(); - } - }), - ); - this._disposables.value.add( - this.actionRunner.onDidRun((e) => { - if ( - (this.actionRunner as SCMInputWidgetActionRunner) - .runningActions.size === 0 - ) { - updateToolbar(); - } - }), - ); + this._disposables.value.add(input.repository.provider.onDidChangeResources(() => updateToolbar())); + this._disposables.value.add(this.storageService.onDidChangeValue(StorageScope.PROFILE, SCMInputWidgetStorageKey.LastActionId, this._disposables.value)(() => updateToolbar())); + + this.actionRunner = new SCMInputWidgetActionRunner(input, this.storageService); + this._disposables.value.add(this.actionRunner.onWillRun(e => { + if ((this.actionRunner as SCMInputWidgetActionRunner).runningActions.size === 0) { + super.setActions([this._cancelAction], []); + this._onDidChange.fire(); + } + })); + this._disposables.value.add(this.actionRunner.onDidRun(e => { + if ((this.actionRunner as SCMInputWidgetActionRunner).runningActions.size === 0) { + updateToolbar(); + } + })); updateToolbar(); } } class SCMInputWidgetEditorOptions { + private readonly _onDidChange = new Emitter(); readonly onDidChange = this._onDidChange.event; @@ -2237,34 +1470,28 @@ class SCMInputWidgetEditorOptions { constructor( private readonly overflowWidgetsDomNode: HTMLElement, - private readonly configurationService: IConfigurationService, - ) { + private readonly configurationService: IConfigurationService) { + const onDidChangeConfiguration = Event.filter( this.configurationService.onDidChangeConfiguration, - (e) => { - return ( - e.affectsConfiguration("editor.accessibilitySupport") || - e.affectsConfiguration("editor.cursorBlinking") || - e.affectsConfiguration("editor.fontFamily") || - e.affectsConfiguration("editor.rulers") || - e.affectsConfiguration("editor.wordWrap") || - e.affectsConfiguration("scm.inputFontFamily") || - e.affectsConfiguration("scm.inputFontSize") - ); + e => { + return e.affectsConfiguration('editor.accessibilitySupport') || + e.affectsConfiguration('editor.cursorBlinking') || + e.affectsConfiguration('editor.fontFamily') || + e.affectsConfiguration('editor.rulers') || + e.affectsConfiguration('editor.wordWrap') || + e.affectsConfiguration('scm.inputFontFamily') || + e.affectsConfiguration('scm.inputFontSize'); }, - this._disposables, + this._disposables ); - this._disposables.add( - onDidChangeConfiguration(() => this._onDidChange.fire()), - ); + this._disposables.add(onDidChangeConfiguration(() => this._onDidChange.fire())); } getEditorConstructionOptions(): IEditorConstructionOptions { const fontFamily = this._getEditorFontFamily(); - const fontSize = this._getEditorFontSize(); - const lineHeight = this._getEditorLineHeight(fontSize); return { @@ -2281,56 +1508,34 @@ class SCMInputWidgetEditorOptions { overflowWidgetsDomNode: this.overflowWidgetsDomNode, padding: { top: 2, bottom: 2 }, quickSuggestions: false, - renderWhitespace: "none", + renderWhitespace: 'none', scrollbar: { alwaysConsumeMouseWheel: false, - vertical: "hidden", + vertical: 'hidden' }, - wrappingIndent: "none", - wrappingStrategy: "advanced", + wrappingIndent: 'none', + wrappingStrategy: 'advanced', }; } getEditorOptions(): IEditorOptions { const fontFamily = this._getEditorFontFamily(); - const fontSize = this._getEditorFontSize(); - const lineHeight = this._getEditorLineHeight(fontSize); + const accessibilitySupport = this.configurationService.getValue<'auto' | 'off' | 'on'>('editor.accessibilitySupport'); + const cursorBlinking = this.configurationService.getValue<'blink' | 'smooth' | 'phase' | 'expand' | 'solid'>('editor.cursorBlinking'); - const accessibilitySupport = this.configurationService.getValue< - "auto" | "off" | "on" - >("editor.accessibilitySupport"); - - const cursorBlinking = this.configurationService.getValue< - "blink" | "smooth" | "phase" | "expand" | "solid" - >("editor.cursorBlinking"); - - return { - ...this._getEditorLanguageConfiguration(), - accessibilitySupport, - cursorBlinking, - fontFamily, - fontSize, - lineHeight, - }; + return { ...this._getEditorLanguageConfiguration(), accessibilitySupport, cursorBlinking, fontFamily, fontSize, lineHeight }; } private _getEditorFontFamily(): string { - const inputFontFamily = this.configurationService - .getValue("scm.inputFontFamily") - .trim(); - - if (inputFontFamily.toLowerCase() === "editor") { - return this.configurationService - .getValue("editor.fontFamily") - .trim(); + const inputFontFamily = this.configurationService.getValue('scm.inputFontFamily').trim(); + + if (inputFontFamily.toLowerCase() === 'editor') { + return this.configurationService.getValue('editor.fontFamily').trim(); } - if ( - inputFontFamily.length !== 0 && - inputFontFamily.toLowerCase() !== "default" - ) { + if (inputFontFamily.length !== 0 && inputFontFamily.toLowerCase() !== 'default') { return inputFontFamily; } @@ -2338,31 +1543,17 @@ class SCMInputWidgetEditorOptions { } private _getEditorFontSize(): number { - return this.configurationService.getValue("scm.inputFontSize"); + return this.configurationService.getValue('scm.inputFontSize'); } private _getEditorLanguageConfiguration(): IEditorOptions { // editor.rulers - const rulersConfig = this.configurationService.inspect( - "editor.rulers", - { overrideIdentifier: "scminput" }, - ); - - const rulers = rulersConfig.overrideIdentifiers?.includes("scminput") - ? EditorOptions.rulers.validate(rulersConfig.value) - : []; + const rulersConfig = this.configurationService.inspect('editor.rulers', { overrideIdentifier: 'scminput' }); + const rulers = rulersConfig.overrideIdentifiers?.includes('scminput') ? EditorOptions.rulers.validate(rulersConfig.value) : []; // editor.wordWrap - const wordWrapConfig = this.configurationService.inspect( - "editor.wordWrap", - { overrideIdentifier: "scminput" }, - ); - - const wordWrap = wordWrapConfig.overrideIdentifiers?.includes( - "scminput", - ) - ? EditorOptions.wordWrap.validate(wordWrapConfig.value) - : "on"; + const wordWrapConfig = this.configurationService.inspect('editor.wordWrap', { overrideIdentifier: 'scminput' }); + const wordWrap = wordWrapConfig.overrideIdentifiers?.includes('scminput') ? EditorOptions.wordWrap.validate(wordWrapConfig.value) : 'on'; return { rulers, wordWrap }; } @@ -2374,15 +1565,16 @@ class SCMInputWidgetEditorOptions { dispose(): void { this._disposables.dispose(); } + } class SCMInputWidget { - private static readonly ValidationTimeouts: { [severity: number]: number } = - { - [InputValidationType.Information]: 5000, - [InputValidationType.Warning]: 8000, - [InputValidationType.Error]: 10000, - }; + + private static readonly ValidationTimeouts: { [severity: number]: number } = { + [InputValidationType.Information]: 5000, + [InputValidationType.Warning]: 8000, + [InputValidationType.Error]: 10000 + }; private readonly contextKeyService: IContextKeyService; @@ -2394,9 +1586,7 @@ class SCMInputWidget { private toolbar: SCMInputWidgetToolbar; private readonly disposables = new DisposableStore(); - private model: - | { readonly input: ISCMInput; readonly textModel: ITextModel } - | undefined; + private model: { readonly input: ISCMInput; readonly textModel: ITextModel } | undefined; private repositoryIdContextKey: IContextKey; private readonly repositoryDisposables = new DisposableStore(); @@ -2422,7 +1612,7 @@ class SCMInputWidget { } this.clearValidation(); - this.element.classList.remove("synthetic-focus"); + this.element.classList.remove('synthetic-focus'); this.repositoryDisposables.clear(); this.repositoryIdContextKey.set(input?.repository.id); @@ -2430,36 +1620,21 @@ class SCMInputWidget { if (!input) { this.inputEditor.setModel(undefined); this.model = undefined; - return; } const textModel = input.repository.provider.inputBoxTextModel; this.inputEditor.setModel(textModel); - if ( - this.configurationService.getValue("editor.wordBasedSuggestions", { - resource: textModel.uri, - }) !== "off" - ) { - this.configurationService.updateValue( - "editor.wordBasedSuggestions", - "off", - { resource: textModel.uri }, - ConfigurationTarget.MEMORY, - ); + if (this.configurationService.getValue('editor.wordBasedSuggestions', { resource: textModel.uri }) !== 'off') { + this.configurationService.updateValue('editor.wordBasedSuggestions', 'off', { resource: textModel.uri }, ConfigurationTarget.MEMORY); } // Validation const validationDelayer = new ThrottledDelayer(200); - const validate = async () => { - const position = this.inputEditor - .getSelection() - ?.getStartPosition(); - + const position = this.inputEditor.getSelection()?.getStartPosition(); const offset = position && textModel.getOffsetAt(position); - const value = textModel.getValue(); this.setValidation(await input.validateInput(value, offset || 0)); @@ -2467,135 +1642,75 @@ class SCMInputWidget { const triggerValidation = () => validationDelayer.trigger(validate); this.repositoryDisposables.add(validationDelayer); - this.repositoryDisposables.add( - this.inputEditor.onDidChangeCursorPosition(triggerValidation), - ); + this.repositoryDisposables.add(this.inputEditor.onDidChangeCursorPosition(triggerValidation)); // Adaptive indentation rules - const opts = this.modelService.getCreationOptions( - textModel.getLanguageId(), - textModel.uri, - textModel.isForSimpleWidget, - ); - - const onEnter = Event.filter( - this.inputEditor.onKeyDown, - (e) => e.keyCode === KeyCode.Enter, - this.repositoryDisposables, - ); - this.repositoryDisposables.add( - onEnter(() => - textModel.detectIndentation(opts.insertSpaces, opts.tabSize), - ), - ); + const opts = this.modelService.getCreationOptions(textModel.getLanguageId(), textModel.uri, textModel.isForSimpleWidget); + const onEnter = Event.filter(this.inputEditor.onKeyDown, e => e.keyCode === KeyCode.Enter, this.repositoryDisposables); + this.repositoryDisposables.add(onEnter(() => textModel.detectIndentation(opts.insertSpaces, opts.tabSize))); // Keep model in sync with API textModel.setValue(input.value); - this.repositoryDisposables.add( - input.onDidChange(({ value, reason }) => { - const currentValue = textModel.getValue(); + this.repositoryDisposables.add(input.onDidChange(({ value, reason }) => { + const currentValue = textModel.getValue(); + if (value === currentValue) { // circuit breaker + return; + } - if (value === currentValue) { - // circuit breaker - return; - } + textModel.pushStackElement(); + textModel.pushEditOperations(null, [EditOperation.replaceMove(textModel.getFullModelRange(), value)], () => []); - textModel.pushStackElement(); - textModel.pushEditOperations( - null, - [ - EditOperation.replaceMove( - textModel.getFullModelRange(), - value, - ), - ], - () => [], - ); - - const position = - reason === SCMInputChangeReason.HistoryPrevious - ? textModel.getFullModelRange().getStartPosition() - : textModel.getFullModelRange().getEndPosition(); - this.inputEditor.setPosition(position); - this.inputEditor.revealPositionInCenterIfOutsideViewport( - position, - ); - }), - ); - this.repositoryDisposables.add( - input.onDidChangeFocus(() => this.focus()), - ); - this.repositoryDisposables.add( - input.onDidChangeValidationMessage((e) => - this.setValidation(e, { focus: true, timeout: true }), - ), - ); - this.repositoryDisposables.add( - input.onDidChangeValidateInput((e) => triggerValidation()), - ); + const position = reason === SCMInputChangeReason.HistoryPrevious + ? textModel.getFullModelRange().getStartPosition() + : textModel.getFullModelRange().getEndPosition(); + this.inputEditor.setPosition(position); + this.inputEditor.revealPositionInCenterIfOutsideViewport(position); + })); + this.repositoryDisposables.add(input.onDidChangeFocus(() => this.focus())); + this.repositoryDisposables.add(input.onDidChangeValidationMessage((e) => this.setValidation(e, { focus: true, timeout: true }))); + this.repositoryDisposables.add(input.onDidChangeValidateInput((e) => triggerValidation())); // Keep API in sync with model and validate - this.repositoryDisposables.add( - textModel.onDidChangeContent(() => { - input.setValue(textModel.getValue(), true); - triggerValidation(); - }), - ); + this.repositoryDisposables.add(textModel.onDidChangeContent(() => { + input.setValue(textModel.getValue(), true); + triggerValidation(); + })); // Update placeholder text const updatePlaceholderText = () => { - const binding = - this.keybindingService.lookupKeybinding("scm.acceptInput"); - - const label = binding - ? binding.getLabel() - : platform.isMacintosh - ? "Cmd+Enter" - : "Ctrl+Enter"; - + const binding = this.keybindingService.lookupKeybinding('scm.acceptInput'); + const label = binding ? binding.getLabel() : (platform.isMacintosh ? 'Cmd+Enter' : 'Ctrl+Enter'); const placeholderText = format(input.placeholder, label); this.inputEditor.updateOptions({ placeholder: placeholderText }); }; - this.repositoryDisposables.add( - input.onDidChangePlaceholder(updatePlaceholderText), - ); - this.repositoryDisposables.add( - this.keybindingService.onDidUpdateKeybindings( - updatePlaceholderText, - ), - ); + this.repositoryDisposables.add(input.onDidChangePlaceholder(updatePlaceholderText)); + this.repositoryDisposables.add(this.keybindingService.onDidUpdateKeybindings(updatePlaceholderText)); updatePlaceholderText(); // Update input template - let commitTemplate = ""; - this.repositoryDisposables.add( - autorun((reader) => { - if (!input.visible) { - return; - } - - const oldCommitTemplate = commitTemplate; - commitTemplate = - input.repository.provider.commitTemplate.read(reader); + let commitTemplate = ''; + this.repositoryDisposables.add(autorun(reader => { + if (!input.visible) { + return; + } - const value = textModel.getValue(); + const oldCommitTemplate = commitTemplate; + commitTemplate = input.repository.provider.commitTemplate.read(reader); - if (value && value !== oldCommitTemplate) { - return; - } + const value = textModel.getValue(); + if (value && value !== oldCommitTemplate) { + return; + } - textModel.setValue(commitTemplate); - }), - ); + textModel.setValue(commitTemplate); + })); // Update input enablement const updateEnablement = (enabled: boolean) => { this.inputEditor.updateOptions({ readOnly: !enabled }); }; - this.repositoryDisposables.add( - input.onDidChangeEnablement((enabled) => updateEnablement(enabled)), - ); + this.repositoryDisposables.add(input.onDidChangeEnablement(enabled => updateEnablement(enabled))); updateEnablement(input.enabled); // Toolbar @@ -2615,10 +1730,7 @@ class SCMInputWidget { } } - private setValidation( - validation: IInputValidation | undefined, - options?: { focus?: boolean; timeout?: boolean }, - ) { + private setValidation(validation: IInputValidation | undefined, options?: { focus?: boolean; timeout?: boolean }) { if (this._validationTimer) { clearTimeout(this._validationTimer); this._validationTimer = 0; @@ -2632,10 +1744,7 @@ class SCMInputWidget { } if (validation && options?.timeout) { - this._validationTimer = setTimeout( - () => this.setValidation(undefined), - SCMInputWidget.ValidationTimeouts[validation.type], - ); + this._validationTimer = setTimeout(() => this.setValidation(undefined), SCMInputWidget.ValidationTimeouts[validation.type]); } } @@ -2645,35 +1754,21 @@ class SCMInputWidget { @IContextKeyService contextKeyService: IContextKeyService, @IModelService private modelService: IModelService, @IKeybindingService private keybindingService: IKeybindingService, - @IConfigurationService - private configurationService: IConfigurationService, - @IInstantiationService - private readonly instantiationService: IInstantiationService, + @IConfigurationService private configurationService: IConfigurationService, + @IInstantiationService private readonly instantiationService: IInstantiationService, @ISCMViewService private readonly scmViewService: ISCMViewService, - @IContextViewService - private readonly contextViewService: IContextViewService, + @IContextViewService private readonly contextViewService: IContextViewService, @IOpenerService private readonly openerService: IOpenerService, ) { - this.element = append(container, $(".scm-editor")); - this.editorContainer = append(this.element, $(".scm-editor-container")); - this.toolbarContainer = append(this.element, $(".scm-editor-toolbar")); + this.element = append(container, $('.scm-editor')); + this.editorContainer = append(this.element, $('.scm-editor-container')); + this.toolbarContainer = append(this.element, $('.scm-editor-toolbar')); this.contextKeyService = contextKeyService.createScoped(this.element); - this.repositoryIdContextKey = this.contextKeyService.createKey( - "scmRepository", - undefined, - ); + this.repositoryIdContextKey = this.contextKeyService.createKey('scmRepository', undefined); - this.inputEditorOptions = new SCMInputWidgetEditorOptions( - overflowWidgetsDomNode, - this.configurationService, - ); - this.disposables.add( - this.inputEditorOptions.onDidChange( - this.onDidChangeEditorOptions, - this, - ), - ); + this.inputEditorOptions = new SCMInputWidgetEditorOptions(overflowWidgetsDomNode, this.configurationService); + this.disposables.add(this.inputEditorOptions.onDidChange(this.onDidChangeEditorOptions, this)); this.disposables.add(this.inputEditorOptions); const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { @@ -2695,199 +1790,98 @@ class SCMInputWidget { PlaceholderTextContribution.ID, SelectionClipboardContributionID, SnippetController2.ID, - SuggestController.ID, + SuggestController.ID ]), - isSimpleWidget: true, + isSimpleWidget: true }; - const services = new ServiceCollection([ - IContextKeyService, - this.contextKeyService, - ]); - - const instantiationService2 = instantiationService.createChild( - services, - this.disposables, - ); - - const editorConstructionOptions = - this.inputEditorOptions.getEditorConstructionOptions(); - this.inputEditor = instantiationService2.createInstance( - CodeEditorWidget, - this.editorContainer, - editorConstructionOptions, - codeEditorWidgetOptions, - ); + const services = new ServiceCollection([IContextKeyService, this.contextKeyService]); + const instantiationService2 = instantiationService.createChild(services, this.disposables); + const editorConstructionOptions = this.inputEditorOptions.getEditorConstructionOptions(); + this.inputEditor = instantiationService2.createInstance(CodeEditorWidget, this.editorContainer, editorConstructionOptions, codeEditorWidgetOptions); this.disposables.add(this.inputEditor); - this.disposables.add( - this.inputEditor.onDidFocusEditorText(() => { - if (this.input?.repository) { - this.scmViewService.focus(this.input.repository); - } - - this.element.classList.add("synthetic-focus"); - this.renderValidation(); - }), - ); - this.disposables.add( - this.inputEditor.onDidBlurEditorText(() => { - this.element.classList.remove("synthetic-focus"); + this.disposables.add(this.inputEditor.onDidFocusEditorText(() => { + if (this.input?.repository) { + this.scmViewService.focus(this.input.repository); + } - setTimeout(() => { - if (!this.validation || !this.validationHasFocus) { - this.clearValidation(); - } - }, 0); - }), - ); + this.element.classList.add('synthetic-focus'); + this.renderValidation(); + })); + this.disposables.add(this.inputEditor.onDidBlurEditorText(() => { + this.element.classList.remove('synthetic-focus'); - this.disposables.add( - this.inputEditor.onDidBlurEditorWidget(() => { - CopyPasteController.get(this.inputEditor)?.clearWidgets(); - DropIntoEditorController.get(this.inputEditor)?.clearWidgets(); - }), - ); + setTimeout(() => { + if (!this.validation || !this.validationHasFocus) { + this.clearValidation(); + } + }, 0); + })); - const firstLineKey = this.contextKeyService.createKey( - "scmInputIsInFirstPosition", - false, - ); + this.disposables.add(this.inputEditor.onDidBlurEditorWidget(() => { + CopyPasteController.get(this.inputEditor)?.clearWidgets(); + DropIntoEditorController.get(this.inputEditor)?.clearWidgets(); + })); - const lastLineKey = this.contextKeyService.createKey( - "scmInputIsInLastPosition", - false, - ); + const firstLineKey = this.contextKeyService.createKey('scmInputIsInFirstPosition', false); + const lastLineKey = this.contextKeyService.createKey('scmInputIsInLastPosition', false); - this.disposables.add( - this.inputEditor.onDidChangeCursorPosition(({ position }) => { - const viewModel = this.inputEditor._getViewModel()!; - - const lastLineNumber = viewModel.getLineCount(); - - const lastLineCol = viewModel.getLineLength(lastLineNumber) + 1; - - const viewPosition = - viewModel.coordinatesConverter.convertModelPositionToViewPosition( - position, - ); - firstLineKey.set( - viewPosition.lineNumber === 1 && viewPosition.column === 1, - ); - lastLineKey.set( - viewPosition.lineNumber === lastLineNumber && - viewPosition.column === lastLineCol, - ); - }), - ); - this.disposables.add( - this.inputEditor.onDidScrollChange((e) => { - this.toolbarContainer.classList.toggle( - "scroll-decoration", - e.scrollTop > 0, - ); - }), - ); + this.disposables.add(this.inputEditor.onDidChangeCursorPosition(({ position }) => { + const viewModel = this.inputEditor._getViewModel()!; + const lastLineNumber = viewModel.getLineCount(); + const lastLineCol = viewModel.getLineLength(lastLineNumber) + 1; + const viewPosition = viewModel.coordinatesConverter.convertModelPositionToViewPosition(position); + firstLineKey.set(viewPosition.lineNumber === 1 && viewPosition.column === 1); + lastLineKey.set(viewPosition.lineNumber === lastLineNumber && viewPosition.column === lastLineCol); + })); + this.disposables.add(this.inputEditor.onDidScrollChange(e => { + this.toolbarContainer.classList.toggle('scroll-decoration', e.scrollTop > 0); + })); - Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration("scm.showInputActionButton"), - )(() => this.layout(), this, this.disposables); + Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.showInputActionButton'))(() => this.layout(), this, this.disposables); - this.onDidChangeContentHeight = Event.signal( - Event.filter( - this.inputEditor.onDidContentSizeChange, - (e) => e.contentHeightChanged, - this.disposables, - ), - ); + this.onDidChangeContentHeight = Event.signal(Event.filter(this.inputEditor.onDidContentSizeChange, e => e.contentHeightChanged, this.disposables)); // Toolbar - this.toolbar = instantiationService2.createInstance( - SCMInputWidgetToolbar, - this.toolbarContainer, - { - actionViewItemProvider: (action, options) => { - if ( - action instanceof MenuItemAction && - this.toolbar.dropdownActions.length > 1 - ) { - return instantiationService.createInstance( - DropdownWithPrimaryActionViewItem, - action, - this.toolbar.dropdownAction, - this.toolbar.dropdownActions, - "", - { - actionRunner: this.toolbar.actionRunner, - hoverDelegate: options.hoverDelegate, - }, - ); - } + this.toolbar = instantiationService2.createInstance(SCMInputWidgetToolbar, this.toolbarContainer, { + actionViewItemProvider: (action, options) => { + if (action instanceof MenuItemAction && this.toolbar.dropdownActions.length > 1) { + return instantiationService.createInstance(DropdownWithPrimaryActionViewItem, action, this.toolbar.dropdownAction, this.toolbar.dropdownActions, '', { actionRunner: this.toolbar.actionRunner, hoverDelegate: options.hoverDelegate }); + } - return createActionViewItem( - instantiationService, - action, - options, - ); - }, - menuOptions: { - shouldForwardArgs: true, - }, + return createActionViewItem(instantiationService, action, options); }, - ); + menuOptions: { + shouldForwardArgs: true + } + }); this.disposables.add(this.toolbar.onDidChange(() => this.layout())); this.disposables.add(this.toolbar); } getContentHeight(): number { const lineHeight = this.inputEditor.getOption(EditorOption.lineHeight); + const { top, bottom } = this.inputEditor.getOption(EditorOption.padding); - const { top, bottom } = this.inputEditor.getOption( - EditorOption.padding, - ); - - const inputMinLinesConfig = this.configurationService.getValue( - "scm.inputMinLineCount", - ); - - const inputMinLines = - typeof inputMinLinesConfig === "number" - ? clamp(inputMinLinesConfig, 1, 50) - : 1; - + const inputMinLinesConfig = this.configurationService.getValue('scm.inputMinLineCount'); + const inputMinLines = typeof inputMinLinesConfig === 'number' ? clamp(inputMinLinesConfig, 1, 50) : 1; const editorMinHeight = inputMinLines * lineHeight + top + bottom; - const inputMaxLinesConfig = this.configurationService.getValue( - "scm.inputMaxLineCount", - ); - - const inputMaxLines = - typeof inputMaxLinesConfig === "number" - ? clamp(inputMaxLinesConfig, 1, 50) - : 10; - + const inputMaxLinesConfig = this.configurationService.getValue('scm.inputMaxLineCount'); + const inputMaxLines = typeof inputMaxLinesConfig === 'number' ? clamp(inputMaxLinesConfig, 1, 50) : 10; const editorMaxHeight = inputMaxLines * lineHeight + top + bottom; - return clamp( - this.inputEditor.getContentHeight(), - editorMinHeight, - editorMaxHeight, - ); + return clamp(this.inputEditor.getContentHeight(), editorMinHeight, editorMaxHeight); } layout(): void { const editorHeight = this.getContentHeight(); - const toolbarWidth = this.getToolbarWidth(); - - const dimension = new Dimension( - this.element.clientWidth - toolbarWidth, - editorHeight, - ); + const dimension = new Dimension(this.element.clientWidth - toolbarWidth, editorHeight); if (dimension.width < 0) { this.lastLayoutWasTrash = true; - return; } @@ -2895,14 +1889,8 @@ class SCMInputWidget { this.inputEditor.layout(dimension); this.renderValidation(); - const showInputActionButton = - this.configurationService.getValue( - "scm.showInputActionButton", - ) === true; - this.toolbarContainer.classList.toggle( - "hidden", - !showInputActionButton || this.toolbar?.isEmpty() === true, - ); + const showInputActionButton = this.configurationService.getValue('scm.showInputActionButton') === true; + this.toolbarContainer.classList.toggle('hidden', !showInputActionButton || this.toolbar?.isEmpty() === true); if (this.shouldFocusAfterLayout) { this.shouldFocusAfterLayout = false; @@ -2914,12 +1902,11 @@ class SCMInputWidget { if (this.lastLayoutWasTrash) { this.lastLayoutWasTrash = false; this.shouldFocusAfterLayout = true; - return; } this.inputEditor.focus(); - this.element.classList.add("synthetic-focus"); + this.element.classList.add('synthetic-focus'); } hasFocus(): boolean { @@ -2927,26 +1914,15 @@ class SCMInputWidget { } private onDidChangeEditorOptions(): void { - this.inputEditor.updateOptions( - this.inputEditorOptions.getEditorOptions(), - ); + this.inputEditor.updateOptions(this.inputEditorOptions.getEditorOptions()); } private renderValidation(): void { this.clearValidation(); - this.element.classList.toggle( - "validation-info", - this.validation?.type === InputValidationType.Information, - ); - this.element.classList.toggle( - "validation-warning", - this.validation?.type === InputValidationType.Warning, - ); - this.element.classList.toggle( - "validation-error", - this.validation?.type === InputValidationType.Error, - ); + this.element.classList.toggle('validation-info', this.validation?.type === InputValidationType.Information); + this.element.classList.toggle('validation-warning', this.validation?.type === InputValidationType.Warning); + this.element.classList.toggle('validation-error', this.validation?.type === InputValidationType.Error); if (!this.validation || !this.inputEditor.hasTextFocus()) { return; @@ -2956,99 +1932,53 @@ class SCMInputWidget { this.validationContextView = this.contextViewService.showContextView({ getAnchor: () => this.element, - render: (container) => { - this.element.style.borderBottomLeftRadius = "0"; - this.element.style.borderBottomRightRadius = "0"; - - const validationContainer = append( - container, - $(".scm-editor-validation-container"), - ); - validationContainer.classList.toggle( - "validation-info", - this.validation!.type === InputValidationType.Information, - ); - validationContainer.classList.toggle( - "validation-warning", - this.validation!.type === InputValidationType.Warning, - ); - validationContainer.classList.toggle( - "validation-error", - this.validation!.type === InputValidationType.Error, - ); + render: container => { + this.element.style.borderBottomLeftRadius = '0'; + this.element.style.borderBottomRightRadius = '0'; + + const validationContainer = append(container, $('.scm-editor-validation-container')); + validationContainer.classList.toggle('validation-info', this.validation!.type === InputValidationType.Information); + validationContainer.classList.toggle('validation-warning', this.validation!.type === InputValidationType.Warning); + validationContainer.classList.toggle('validation-error', this.validation!.type === InputValidationType.Error); validationContainer.style.width = `${this.element.clientWidth + 2}px`; - - const element = append( - validationContainer, - $(".scm-editor-validation"), - ); + const element = append(validationContainer, $('.scm-editor-validation')); const message = this.validation!.message; - - if (typeof message === "string") { + if (typeof message === 'string') { element.textContent = message; } else { const tracker = trackFocus(element); disposables.add(tracker); - disposables.add( - tracker.onDidFocus( - () => (this.validationHasFocus = true), - ), - ); - disposables.add( - tracker.onDidBlur(() => { - this.validationHasFocus = false; - this.element.style.borderBottomLeftRadius = "2px"; - this.element.style.borderBottomRightRadius = "2px"; - this.contextViewService.hideContextView(); - }), - ); - - const renderer = disposables.add( - this.instantiationService.createInstance( - MarkdownRenderer, - {}, - ), - ); + disposables.add(tracker.onDidFocus(() => (this.validationHasFocus = true))); + disposables.add(tracker.onDidBlur(() => { + this.validationHasFocus = false; + this.element.style.borderBottomLeftRadius = '2px'; + this.element.style.borderBottomRightRadius = '2px'; + this.contextViewService.hideContextView(); + })); + const renderer = disposables.add(this.instantiationService.createInstance(MarkdownRenderer, {})); const renderedMarkdown = renderer.render(message, { actionHandler: { callback: (link) => { - openLinkFromMarkdown( - this.openerService, - link, - message.isTrusted, - ); - this.element.style.borderBottomLeftRadius = - "2px"; - this.element.style.borderBottomRightRadius = - "2px"; + openLinkFromMarkdown(this.openerService, link, message.isTrusted); + this.element.style.borderBottomLeftRadius = '2px'; + this.element.style.borderBottomRightRadius = '2px'; this.contextViewService.hideContextView(); }, - disposables: disposables, + disposables: disposables }, }); disposables.add(renderedMarkdown); element.appendChild(renderedMarkdown.element); } - const actionsContainer = append( - validationContainer, - $(".scm-editor-validation-actions"), - ); - + const actionsContainer = append(validationContainer, $('.scm-editor-validation-actions')); const actionbar = new ActionBar(actionsContainer); - - const action = new Action( - "scmInputWidget.validationMessage.close", - localize("label.close", "Close"), - ThemeIcon.asClassName(Codicon.close), - true, - () => { - this.contextViewService.hideContextView(); - this.element.style.borderBottomLeftRadius = "2px"; - this.element.style.borderBottomRightRadius = "2px"; - }, - ); + const action = new Action('scmInputWidget.validationMessage.close', localize('label.close', "Close"), ThemeIcon.asClassName(Codicon.close), true, () => { + this.contextViewService.hideContextView(); + this.element.style.borderBottomLeftRadius = '2px'; + this.element.style.borderBottomRightRadius = '2px'; + }); disposables.add(actionbar); actionbar.push(action, { icon: true, label: false }); @@ -3056,31 +1986,23 @@ class SCMInputWidget { }, onHide: () => { this.validationHasFocus = false; - this.element.style.borderBottomLeftRadius = "2px"; - this.element.style.borderBottomRightRadius = "2px"; + this.element.style.borderBottomLeftRadius = '2px'; + this.element.style.borderBottomRightRadius = '2px'; disposables.dispose(); }, - anchorAlignment: AnchorAlignment.LEFT, + anchorAlignment: AnchorAlignment.LEFT }); } private getToolbarWidth(): number { - const showInputActionButton = - this.configurationService.getValue( - "scm.showInputActionButton", - ); - - if ( - !this.toolbar || - !showInputActionButton || - this.toolbar?.isEmpty() === true - ) { + const showInputActionButton = this.configurationService.getValue('scm.showInputActionButton'); + if (!this.toolbar || !showInputActionButton || this.toolbar?.isEmpty() === true) { return 0; } - return this.toolbar.dropdownActions.length === 0 - ? 26 /* 22px action + 4px margin */ - : 39 /* 35px action + 4px margin */; + return this.toolbar.dropdownActions.length === 0 ? + 26 /* 22px action + 4px margin */ : + 39 /* 35px action + 4px margin */; } clearValidation(): void { @@ -3098,26 +2020,20 @@ class SCMInputWidget { } export class SCMViewPane extends ViewPane { + private _onDidLayout: Emitter; private layoutCache: ISCMLayout; private treeScrollTop: number | undefined; private treeContainer!: HTMLElement; - private tree!: WorkbenchCompressibleAsyncDataTree< - ISCMViewService, - TreeElement, - FuzzyScore - >; + private tree!: WorkbenchCompressibleAsyncDataTree; private listLabels!: ResourceLabels; private inputRenderer!: InputRenderer; private actionButtonRenderer!: ActionButtonRenderer; private _viewMode: ViewMode; - - get viewMode(): ViewMode { - return this._viewMode; - } + get viewMode(): ViewMode { return this._viewMode; } set viewMode(mode: ViewMode) { if (this._viewMode === mode) { return; @@ -3134,22 +2050,14 @@ export class SCMViewPane extends ViewPane { this.viewModeContextKey.set(mode); this.updateIndentStyles(this.themeService.getFileIconTheme()); - this.storageService.store( - `scm.viewMode`, - mode, - StorageScope.WORKSPACE, - StorageTarget.USER, - ); + this.storageService.store(`scm.viewMode`, mode, StorageScope.WORKSPACE, StorageTarget.USER); } private readonly _onDidChangeViewMode = new Emitter(); readonly onDidChangeViewMode = this._onDidChangeViewMode.event; private _viewSortKey: ViewSortKey; - - get viewSortKey(): ViewSortKey { - return this._viewSortKey; - } + get viewSortKey(): ViewSortKey { return this._viewSortKey; } set viewSortKey(sortKey: ViewSortKey) { if (this._viewSortKey === sortKey) { return; @@ -3162,12 +2070,7 @@ export class SCMViewPane extends ViewPane { this._onDidChangeViewSortKey.fire(sortKey); if (this._viewMode === ViewMode.List) { - this.storageService.store( - `scm.viewSortKey`, - sortKey, - StorageScope.WORKSPACE, - StorageTarget.USER, - ); + this.storageService.store(`scm.viewSortKey`, sortKey, StorageScope.WORKSPACE, StorageTarget.USER); } } @@ -3200,8 +2103,7 @@ export class SCMViewPane extends ViewPane { @ISCMService private readonly scmService: ISCMService, @ISCMViewService private readonly scmViewService: ISCMViewService, @IStorageService private readonly storageService: IStorageService, - @IUriIdentityService - private readonly uriIdentityService: IUriIdentityService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @IKeybindingService keybindingService: IKeybindingService, @IThemeService themeService: IThemeService, @IContextMenuService contextMenuService: IContextMenuService, @@ -3213,103 +2115,51 @@ export class SCMViewPane extends ViewPane { @ITelemetryService telemetryService: ITelemetryService, @IHoverService hoverService: IHoverService, ) { - super( - { ...options, titleMenuId: MenuId.SCMTitle }, - keybindingService, - contextMenuService, - configurationService, - contextKeyService, - viewDescriptorService, - instantiationService, - openerService, - themeService, - telemetryService, - hoverService, - ); + super({ ...options, titleMenuId: MenuId.SCMTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService); // View mode and sort key this._viewMode = this.getViewMode(); this._viewSortKey = this.getViewSortKey(); // Context Keys - this.viewModeContextKey = - ContextKeys.SCMViewMode.bindTo(contextKeyService); + this.viewModeContextKey = ContextKeys.SCMViewMode.bindTo(contextKeyService); this.viewModeContextKey.set(this._viewMode); - this.viewSortKeyContextKey = - ContextKeys.SCMViewSortKey.bindTo(contextKeyService); + this.viewSortKeyContextKey = ContextKeys.SCMViewSortKey.bindTo(contextKeyService); this.viewSortKeyContextKey.set(this.viewSortKey); - this.areAllRepositoriesCollapsedContextKey = - ContextKeys.SCMViewAreAllRepositoriesCollapsed.bindTo( - contextKeyService, - ); - this.isAnyRepositoryCollapsibleContextKey = - ContextKeys.SCMViewIsAnyRepositoryCollapsible.bindTo( - contextKeyService, - ); - this.scmProviderContextKey = - ContextKeys.SCMProvider.bindTo(contextKeyService); - this.scmProviderRootUriContextKey = - ContextKeys.SCMProviderRootUri.bindTo(contextKeyService); - this.scmProviderHasRootUriContextKey = - ContextKeys.SCMProviderHasRootUri.bindTo(contextKeyService); + this.areAllRepositoriesCollapsedContextKey = ContextKeys.SCMViewAreAllRepositoriesCollapsed.bindTo(contextKeyService); + this.isAnyRepositoryCollapsibleContextKey = ContextKeys.SCMViewIsAnyRepositoryCollapsible.bindTo(contextKeyService); + this.scmProviderContextKey = ContextKeys.SCMProvider.bindTo(contextKeyService); + this.scmProviderRootUriContextKey = ContextKeys.SCMProviderRootUri.bindTo(contextKeyService); + this.scmProviderHasRootUriContextKey = ContextKeys.SCMProviderHasRootUri.bindTo(contextKeyService); this._onDidLayout = new Emitter(); - this.layoutCache = { - height: undefined, - width: undefined, - onDidChange: this._onDidLayout.event, - }; + this.layoutCache = { height: undefined, width: undefined, onDidChange: this._onDidLayout.event }; - this.storageService.onDidChangeValue( - StorageScope.WORKSPACE, - undefined, - this.disposables, - )( - (e) => { - switch (e.key) { - case "scm.viewMode": - this.viewMode = this.getViewMode(); - - break; - - case "scm.viewSortKey": - this.viewSortKey = this.getViewSortKey(); - - break; - } - }, - this, - this.disposables, - ); + this.storageService.onDidChangeValue(StorageScope.WORKSPACE, undefined, this.disposables)(e => { + switch (e.key) { + case 'scm.viewMode': + this.viewMode = this.getViewMode(); + break; + case 'scm.viewSortKey': + this.viewSortKey = this.getViewSortKey(); + break; + } + }, this, this.disposables); - this.storageService.onWillSaveState( - (e) => { - this.viewMode = this.getViewMode(); - this.viewSortKey = this.getViewSortKey(); + this.storageService.onWillSaveState(e => { + this.viewMode = this.getViewMode(); + this.viewSortKey = this.getViewSortKey(); - this.storeTreeViewState(); - }, - this, - this.disposables, - ); + this.storeTreeViewState(); + }, this, this.disposables); - Event.any( - this.scmService.onDidAddRepository, - this.scmService.onDidRemoveRepository, - )( - () => this._onDidChangeViewWelcomeState.fire(), - this, - this.disposables, - ); + Event.any(this.scmService.onDidAddRepository, this.scmService.onDidRemoveRepository)(() => this._onDidChangeViewWelcomeState.fire(), this, this.disposables); this.disposables.add(this.revealResourceThrottler); this.disposables.add(this.updateChildrenThrottler); } - protected override layoutBody( - height: number | undefined = this.layoutCache.height, - width: number | undefined = this.layoutCache.width, - ): void { + protected override layoutBody(height: number | undefined = this.layoutCache.height, width: number | undefined = this.layoutCache.width): void { if (height === undefined) { return; } @@ -3330,219 +2180,113 @@ export class SCMViewPane extends ViewPane { super.renderBody(container); // Tree - this.treeContainer = append(container, $(".scm-view.show-file-icons")); - this.treeContainer.classList.add("file-icon-themable-tree"); - this.treeContainer.classList.add("show-file-icons"); - - const updateActionsVisibility = () => - this.treeContainer.classList.toggle( - "show-actions", - this.configurationService.getValue( - "scm.alwaysShowActions", - ), - ); - Event.filter( - this.configurationService.onDidChangeConfiguration, - (e) => e.affectsConfiguration("scm.alwaysShowActions"), - this.disposables, - )(updateActionsVisibility, this, this.disposables); + this.treeContainer = append(container, $('.scm-view.show-file-icons')); + this.treeContainer.classList.add('file-icon-themable-tree'); + this.treeContainer.classList.add('show-file-icons'); + + const updateActionsVisibility = () => this.treeContainer.classList.toggle('show-actions', this.configurationService.getValue('scm.alwaysShowActions')); + Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.alwaysShowActions'), this.disposables)(updateActionsVisibility, this, this.disposables); updateActionsVisibility(); const updateProviderCountVisibility = () => { - const value = this.configurationService.getValue< - "hidden" | "auto" | "visible" - >("scm.providerCountBadge"); - this.treeContainer.classList.toggle( - "hide-provider-counts", - value === "hidden", - ); - this.treeContainer.classList.toggle( - "auto-provider-counts", - value === "auto", - ); + const value = this.configurationService.getValue<'hidden' | 'auto' | 'visible'>('scm.providerCountBadge'); + this.treeContainer.classList.toggle('hide-provider-counts', value === 'hidden'); + this.treeContainer.classList.toggle('auto-provider-counts', value === 'auto'); }; - Event.filter( - this.configurationService.onDidChangeConfiguration, - (e) => e.affectsConfiguration("scm.providerCountBadge"), - this.disposables, - )(updateProviderCountVisibility, this, this.disposables); + Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.providerCountBadge'), this.disposables)(updateProviderCountVisibility, this, this.disposables); updateProviderCountVisibility(); const viewState = this.loadTreeViewState(); this.createTree(this.treeContainer, viewState); - this.onDidChangeBodyVisibility( - async (visible) => { - if (visible) { - this.treeOperationSequencer.queue(async () => { - await this.tree.setInput( - this.scmViewService, - viewState, - ); - - Event.filter( - this.configurationService.onDidChangeConfiguration, - (e) => - e.affectsConfiguration( - "scm.alwaysShowRepositories", - ), - this.visibilityDisposables, - )( - () => { - this.updateActions(); - this.updateChildren(); - }, - this, - this.visibilityDisposables, - ); - - Event.filter( - this.configurationService.onDidChangeConfiguration, - (e) => - e.affectsConfiguration( - "scm.inputMinLineCount", - ) || - e.affectsConfiguration( - "scm.inputMaxLineCount", - ) || - e.affectsConfiguration("scm.showActionButton"), - this.visibilityDisposables, - )( - () => this.updateChildren(), - this, - this.visibilityDisposables, - ); - - // Add visible repositories - this.editorService.onDidActiveEditorChange( - this.onDidActiveEditorChange, - this, - this.visibilityDisposables, - ); - this.scmViewService.onDidChangeVisibleRepositories( - this.onDidChangeVisibleRepositories, - this, - this.visibilityDisposables, - ); - this.onDidChangeVisibleRepositories({ - added: this.scmViewService.visibleRepositories, - removed: Iterable.empty(), - }); - - // Restore scroll position - if (typeof this.treeScrollTop === "number") { - this.tree.scrollTop = this.treeScrollTop; - this.treeScrollTop = undefined; - } - - this.updateRepositoryCollapseAllContextKeys(); - }); - } else { - this.visibilityDisposables.clear(); - this.onDidChangeVisibleRepositories({ - added: Iterable.empty(), - removed: [...this.items.keys()], - }); - this.treeScrollTop = this.tree.scrollTop; + this.onDidChangeBodyVisibility(async visible => { + if (visible) { + this.treeOperationSequencer.queue(async () => { + await this.tree.setInput(this.scmViewService, viewState); + + Event.filter(this.configurationService.onDidChangeConfiguration, + e => + e.affectsConfiguration('scm.alwaysShowRepositories'), + this.visibilityDisposables) + (() => { + this.updateActions(); + this.updateChildren(); + }, this, this.visibilityDisposables); + + Event.filter(this.configurationService.onDidChangeConfiguration, + e => + e.affectsConfiguration('scm.inputMinLineCount') || + e.affectsConfiguration('scm.inputMaxLineCount') || + e.affectsConfiguration('scm.showActionButton'), + this.visibilityDisposables) + (() => this.updateChildren(), this, this.visibilityDisposables); + + // Add visible repositories + this.editorService.onDidActiveEditorChange(this.onDidActiveEditorChange, this, this.visibilityDisposables); + this.scmViewService.onDidChangeVisibleRepositories(this.onDidChangeVisibleRepositories, this, this.visibilityDisposables); + this.onDidChangeVisibleRepositories({ added: this.scmViewService.visibleRepositories, removed: Iterable.empty() }); + + // Restore scroll position + if (typeof this.treeScrollTop === 'number') { + this.tree.scrollTop = this.treeScrollTop; + this.treeScrollTop = undefined; + } this.updateRepositoryCollapseAllContextKeys(); - } - }, - this, - this.disposables, - ); + }); + } else { + this.visibilityDisposables.clear(); + this.onDidChangeVisibleRepositories({ added: Iterable.empty(), removed: [...this.items.keys()] }); + this.treeScrollTop = this.tree.scrollTop; - this.disposables.add( - this.instantiationService.createInstance( - RepositoryVisibilityActionController, - ), - ); + this.updateRepositoryCollapseAllContextKeys(); + } + }, this, this.disposables); - this.themeService.onDidFileIconThemeChange( - this.updateIndentStyles, - this, - this.disposables, - ); + this.disposables.add(this.instantiationService.createInstance(RepositoryVisibilityActionController)); + + this.themeService.onDidFileIconThemeChange(this.updateIndentStyles, this, this.disposables); this.updateIndentStyles(this.themeService.getFileIconTheme()); } - private createTree( - container: HTMLElement, - viewState?: IAsyncDataTreeViewState, - ): void { - const overflowWidgetsDomNode = $( - ".scm-overflow-widgets-container.monaco-editor", - ); + private createTree(container: HTMLElement, viewState?: IAsyncDataTreeViewState): void { + const overflowWidgetsDomNode = $('.scm-overflow-widgets-container.monaco-editor'); - this.inputRenderer = this.instantiationService.createInstance( - InputRenderer, - this.layoutCache, - overflowWidgetsDomNode, - (input, height) => { - try { - // Attempt to update the input element height. There is an - // edge case where the input has already been disposed and - // updating the height would fail. - this.tree.updateElementHeight(input, height); - } catch {} - }, - ); - this.actionButtonRenderer = - this.instantiationService.createInstance(ActionButtonRenderer); + this.inputRenderer = this.instantiationService.createInstance(InputRenderer, this.layoutCache, overflowWidgetsDomNode, (input, height) => { + try { + // Attempt to update the input element height. There is an + // edge case where the input has already been disposed and + // updating the height would fail. + this.tree.updateElementHeight(input, height); + } + catch { } + }); + this.actionButtonRenderer = this.instantiationService.createInstance(ActionButtonRenderer); - this.listLabels = this.instantiationService.createInstance( - ResourceLabels, - { onDidChangeVisibility: this.onDidChangeBodyVisibility }, - ); + this.listLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); this.disposables.add(this.listLabels); - const resourceActionRunner = new RepositoryPaneActionRunner(() => - this.getSelectedResources(), - ); - resourceActionRunner.onWillRun( - () => this.tree.domFocus(), - this, - this.disposables, - ); + const resourceActionRunner = new RepositoryPaneActionRunner(() => this.getSelectedResources()); + resourceActionRunner.onWillRun(() => this.tree.domFocus(), this, this.disposables); this.disposables.add(resourceActionRunner); - const treeDataSource = this.instantiationService.createInstance( - SCMTreeDataSource, - () => this.viewMode, - ); + const treeDataSource = this.instantiationService.createInstance(SCMTreeDataSource, () => this.viewMode); this.disposables.add(treeDataSource); - const compressionEnabled = observableConfigValue( - "scm.compactFolders", - true, - this.configurationService, - ); + const compressionEnabled = observableConfigValue('scm.compactFolders', true, this.configurationService); this.tree = this.instantiationService.createInstance( WorkbenchCompressibleAsyncDataTree, - "SCM Tree Repo", + 'SCM Tree Repo', container, new ListDelegate(this.inputRenderer), new SCMTreeCompressionDelegate(), [ this.inputRenderer, this.actionButtonRenderer, - this.instantiationService.createInstance( - RepositoryRenderer, - MenuId.SCMTitle, - getActionViewItemProvider(this.instantiationService), - ), - this.instantiationService.createInstance( - ResourceGroupRenderer, - getActionViewItemProvider(this.instantiationService), - ), - this.instantiationService.createInstance( - ResourceRenderer, - () => this.viewMode, - this.listLabels, - getActionViewItemProvider(this.instantiationService), - resourceActionRunner, - ), + this.instantiationService.createInstance(RepositoryRenderer, MenuId.SCMTitle, getActionViewItemProvider(this.instantiationService)), + this.instantiationService.createInstance(ResourceGroupRenderer, getActionViewItemProvider(this.instantiationService)), + this.instantiationService.createInstance(ResourceRenderer, () => this.viewMode, this.listLabels, getActionViewItemProvider(this.instantiationService), resourceActionRunner) ], treeDataSource, { @@ -3552,67 +2296,34 @@ export class SCMViewPane extends ViewPane { filter: new SCMTreeFilter(), dnd: new SCMTreeDragAndDrop(this.instantiationService), identityProvider: new SCMResourceIdentityProvider(), - sorter: new SCMTreeSorter( - () => this.viewMode, - () => this.viewSortKey, - ), - keyboardNavigationLabelProvider: - this.instantiationService.createInstance( - SCMTreeKeyboardNavigationLabelProvider, - () => this.viewMode, - ), - overrideStyles: - this.getLocationBasedColors().listOverrideStyles, + sorter: new SCMTreeSorter(() => this.viewMode, () => this.viewSortKey), + keyboardNavigationLabelProvider: this.instantiationService.createInstance(SCMTreeKeyboardNavigationLabelProvider, () => this.viewMode), + overrideStyles: this.getLocationBasedColors().listOverrideStyles, compressionEnabled: compressionEnabled.get(), collapseByDefault: (e: unknown) => { // Repository, Resource Group, Resource Folder (Tree) - if ( - isSCMRepository(e) || - isSCMResourceGroup(e) || - isSCMResourceNode(e) - ) { + if (isSCMRepository(e) || isSCMResourceGroup(e) || isSCMResourceNode(e)) { return false; } // History Item Group, History Item, or History Item Change - return ( - (viewState?.expanded ?? []).indexOf( - getSCMResourceId(e as TreeElement), - ) === -1 - ); + return (viewState?.expanded ?? []).indexOf(getSCMResourceId(e as TreeElement)) === -1; }, - accessibilityProvider: this.instantiationService.createInstance( - SCMAccessibilityProvider, - ), - }, - ) as WorkbenchCompressibleAsyncDataTree< - ISCMViewService, - TreeElement, - FuzzyScore - >; + accessibilityProvider: this.instantiationService.createInstance(SCMAccessibilityProvider) + }) as WorkbenchCompressibleAsyncDataTree; this.disposables.add(this.tree); this.tree.onDidOpen(this.open, this, this.disposables); this.tree.onContextMenu(this.onListContextMenu, this, this.disposables); - this.tree.onDidScroll( - this.inputRenderer.clearValidation, - this.inputRenderer, - this.disposables, - ); - Event.filter( - this.tree.onDidChangeCollapseState, - (e) => isSCMRepository(e.node.element?.element), - this.disposables, - )(this.updateRepositoryCollapseAllContextKeys, this, this.disposables); - - this.disposables.add( - autorun((reader) => { - this.tree.updateOptions({ - compressionEnabled: compressionEnabled.read(reader), - }); - }), - ); + this.tree.onDidScroll(this.inputRenderer.clearValidation, this.inputRenderer, this.disposables); + Event.filter(this.tree.onDidChangeCollapseState, e => isSCMRepository(e.node.element?.element), this.disposables)(this.updateRepositoryCollapseAllContextKeys, this, this.disposables); + + this.disposables.add(autorun(reader => { + this.tree.updateOptions({ + compressionEnabled: compressionEnabled.read(reader) + }); + })); append(container, overflowWidgetsDomNode); } @@ -3622,7 +2333,6 @@ export class SCMViewPane extends ViewPane { return; } else if (isSCMRepository(e.element)) { this.scmViewService.focus(e.element); - return; } else if (isSCMInput(e.element)) { this.scmViewService.focus(e.element.repository); @@ -3651,86 +2361,50 @@ export class SCMViewPane extends ViewPane { return; } else if (isSCMResourceGroup(e.element)) { const provider = e.element.provider; - - const repository = Iterable.find( - this.scmService.repositories, - (r) => r.provider === provider, - ); - + const repository = Iterable.find(this.scmService.repositories, r => r.provider === provider); if (repository) { this.scmViewService.focus(repository); } return; } else if (isSCMResource(e.element)) { - if ( - e.element.command?.id === API_OPEN_EDITOR_COMMAND_ID || - e.element.command?.id === API_OPEN_DIFF_EDITOR_COMMAND_ID - ) { - if ( - isPointerEvent(e.browserEvent) && - e.browserEvent.button === 1 - ) { + if (e.element.command?.id === API_OPEN_EDITOR_COMMAND_ID || e.element.command?.id === API_OPEN_DIFF_EDITOR_COMMAND_ID) { + if (isPointerEvent(e.browserEvent) && e.browserEvent.button === 1) { const resourceGroup = e.element.resourceGroup; - const title = `${resourceGroup.provider.label}: ${resourceGroup.label}`; - await OpenScmGroupAction.openMultiFileDiffEditor( - this.editorService, - title, - resourceGroup.provider.rootUri, - resourceGroup.id, - { - ...e.editorOptions, - viewState: { - revealData: { - resource: { - original: - e.element - .multiDiffEditorOriginalUri, - modified: - e.element - .multiDiffEditorModifiedUri, - }, - }, - }, - preserveFocus: true, + await OpenScmGroupAction.openMultiFileDiffEditor(this.editorService, title, resourceGroup.provider.rootUri, resourceGroup.id, { + ...e.editorOptions, + viewState: { + revealData: { + resource: { + original: e.element.multiDiffEditorOriginalUri, + modified: e.element.multiDiffEditorModifiedUri, + } + } }, - ); + preserveFocus: true, + }); } else { - await this.commandService.executeCommand( - e.element.command.id, - ...(e.element.command.arguments || []), - e, - ); + await this.commandService.executeCommand(e.element.command.id, ...(e.element.command.arguments || []), e); } } else { await e.element.open(!!e.editorOptions.preserveFocus); if (e.editorOptions.pinned) { - const activeEditorPane = - this.editorService.activeEditorPane; + const activeEditorPane = this.editorService.activeEditorPane; activeEditorPane?.group.pinEditor(activeEditorPane.input); } } const provider = e.element.resourceGroup.provider; - - const repository = Iterable.find( - this.scmService.repositories, - (r) => r.provider === provider, - ); + const repository = Iterable.find(this.scmService.repositories, r => r.provider === provider); if (repository) { this.scmViewService.focus(repository); } } else if (isSCMResourceNode(e.element)) { const provider = e.element.context.provider; - - const repository = Iterable.find( - this.scmService.repositories, - (r) => r.provider === provider, - ); - + const repository = Iterable.find(this.scmService.repositories, r => r.provider === provider); if (repository) { this.scmViewService.focus(repository); } @@ -3739,125 +2413,72 @@ export class SCMViewPane extends ViewPane { } private onDidActiveEditorChange(): void { - if (!this.configurationService.getValue("scm.autoReveal")) { + if (!this.configurationService.getValue('scm.autoReveal')) { return; } - const uri = EditorResourceAccessor.getOriginalUri( - this.editorService.activeEditor, - { supportSideBySide: SideBySideEditor.PRIMARY }, - ); + const uri = EditorResourceAccessor.getOriginalUri(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY }); if (!uri) { return; } // Do not set focus/selection when the resource is already focused and selected - if ( - this.tree - .getFocus() - .some( - (e) => - isSCMResource(e) && - this.uriIdentityService.extUri.isEqual( - e.sourceUri, - uri, - ), - ) && - this.tree - .getSelection() - .some( - (e) => - isSCMResource(e) && - this.uriIdentityService.extUri.isEqual( - e.sourceUri, - uri, - ), - ) - ) { + if (this.tree.getFocus().some(e => isSCMResource(e) && this.uriIdentityService.extUri.isEqual(e.sourceUri, uri)) && + this.tree.getSelection().some(e => isSCMResource(e) && this.uriIdentityService.extUri.isEqual(e.sourceUri, uri))) { return; } - this.revealResourceThrottler.queue(() => - this.treeOperationSequencer.queue(async () => { - for (const repository of this.scmViewService - .visibleRepositories) { - const item = this.items.get(repository); - - if (!item) { - continue; - } + this.revealResourceThrottler.queue( + () => this.treeOperationSequencer.queue( + async () => { + for (const repository of this.scmViewService.visibleRepositories) { + const item = this.items.get(repository); - // go backwards from last group - for ( - let j = repository.provider.groups.length - 1; - j >= 0; - j-- - ) { - const groupItem = repository.provider.groups[j]; + if (!item) { + continue; + } - const resource = - this.viewMode === ViewMode.Tree + // go backwards from last group + for (let j = repository.provider.groups.length - 1; j >= 0; j--) { + const groupItem = repository.provider.groups[j]; + const resource = this.viewMode === ViewMode.Tree ? groupItem.resourceTree.getNode(uri)?.element - : groupItem.resources.find((r) => - this.uriIdentityService.extUri.isEqual( - r.sourceUri, - uri, - ), - ); - - if (resource) { - await this.tree.expandTo(resource); - this.tree.reveal(resource); + : groupItem.resources.find(r => this.uriIdentityService.extUri.isEqual(r.sourceUri, uri)); - this.tree.setSelection([resource]); - this.tree.setFocus([resource]); + if (resource) { + await this.tree.expandTo(resource); + this.tree.reveal(resource); - return; + this.tree.setSelection([resource]); + this.tree.setFocus([resource]); + return; + } } } - } - }), - ); + })); } - private onDidChangeVisibleRepositories({ - added, - removed, - }: ISCMViewVisibleRepositoryChangeEvent): void { + private onDidChangeVisibleRepositories({ added, removed }: ISCMViewVisibleRepositoryChangeEvent): void { // Added repositories for (const repository of added) { const repositoryDisposables = new DisposableStore(); - repositoryDisposables.add( - autorun((reader) => { - /** @description action button */ - repository.provider.actionButton.read(reader); - this.updateChildren(repository); - }), - ); - - repositoryDisposables.add( - repository.input.onDidChangeVisibility(() => - this.updateChildren(repository), - ), - ); - repositoryDisposables.add( - repository.provider.onDidChangeResourceGroups(() => - this.updateChildren(repository), - ), - ); - - const resourceGroupDisposables = repositoryDisposables.add( - new DisposableMap(), - ); + repositoryDisposables.add(autorun(reader => { + /** @description action button */ + repository.provider.actionButton.read(reader); + this.updateChildren(repository); + })); + + repositoryDisposables.add(repository.input.onDidChangeVisibility(() => this.updateChildren(repository))); + repositoryDisposables.add(repository.provider.onDidChangeResourceGroups(() => this.updateChildren(repository))); + + const resourceGroupDisposables = repositoryDisposables.add(new DisposableMap()); const onDidChangeResourceGroups = () => { for (const [resourceGroup] of resourceGroupDisposables) { if (!repository.provider.groups.includes(resourceGroup)) { - resourceGroupDisposables.deleteAndDispose( - resourceGroup, - ); + resourceGroupDisposables.deleteAndDispose(resourceGroup); } } @@ -3865,29 +2486,14 @@ export class SCMViewPane extends ViewPane { if (!resourceGroupDisposables.has(resourceGroup)) { const disposableStore = new DisposableStore(); - disposableStore.add( - resourceGroup.onDidChange(() => - this.updateChildren(repository), - ), - ); - disposableStore.add( - resourceGroup.onDidChangeResources(() => - this.updateChildren(repository), - ), - ); - resourceGroupDisposables.set( - resourceGroup, - disposableStore, - ); + disposableStore.add(resourceGroup.onDidChange(() => this.updateChildren(repository))); + disposableStore.add(resourceGroup.onDidChangeResources(() => this.updateChildren(repository))); + resourceGroupDisposables.set(resourceGroup, disposableStore); } } }; - repositoryDisposables.add( - repository.provider.onDidChangeResourceGroups( - onDidChangeResourceGroups, - ), - ); + repositoryDisposables.add(repository.provider.onDidChangeResourceGroups(onDidChangeResourceGroups)); onDidChangeResourceGroups(); this.items.set(repository, repositoryDisposables); @@ -3902,74 +2508,46 @@ export class SCMViewPane extends ViewPane { this.onDidActiveEditorChange(); } - private onListContextMenu( - e: ITreeContextMenuEvent, - ): void { + private onListContextMenu(e: ITreeContextMenuEvent): void { if (!e.element) { - const menu = this.menuService.getMenuActions( - Menus.ViewSort, - this.contextKeyService, - ); - + const menu = this.menuService.getMenuActions(Menus.ViewSort, this.contextKeyService); const actions = getFlatContextMenuActions(menu); return this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => actions, - onHide: () => {}, + onHide: () => { } }); } const element = e.element; - let context: any = element; - let actions: IAction[] = []; - - let actionRunner: IActionRunner = new RepositoryPaneActionRunner(() => - this.getSelectedResources(), - ); + let actionRunner: IActionRunner = new RepositoryPaneActionRunner(() => this.getSelectedResources()); if (isSCMRepository(element)) { - const menus = this.scmViewService.menus.getRepositoryMenus( - element.provider, - ); - + const menus = this.scmViewService.menus.getRepositoryMenus(element.provider); const menu = menus.repositoryContextMenu; context = element.provider; - actionRunner = new RepositoryActionRunner(() => - this.getSelectedRepositories(), - ); + actionRunner = new RepositoryActionRunner(() => this.getSelectedRepositories()); actions = collectContextMenuActions(menu); } else if (isSCMInput(element) || isSCMActionButton(element)) { // noop } else if (isSCMResourceGroup(element)) { - const menus = this.scmViewService.menus.getRepositoryMenus( - element.provider, - ); - + const menus = this.scmViewService.menus.getRepositoryMenus(element.provider); const menu = menus.getResourceGroupMenu(element); actions = collectContextMenuActions(menu); } else if (isSCMResource(element)) { - const menus = this.scmViewService.menus.getRepositoryMenus( - element.resourceGroup.provider, - ); - + const menus = this.scmViewService.menus.getRepositoryMenus(element.resourceGroup.provider); const menu = menus.getResourceMenu(element); actions = collectContextMenuActions(menu); } else if (isSCMResourceNode(element)) { if (element.element) { - const menus = this.scmViewService.menus.getRepositoryMenus( - element.element.resourceGroup.provider, - ); - + const menus = this.scmViewService.menus.getRepositoryMenus(element.element.resourceGroup.provider); const menu = menus.getResourceMenu(element.element); actions = collectContextMenuActions(menu); } else { - const menus = this.scmViewService.menus.getRepositoryMenus( - element.context.provider, - ); - + const menus = this.scmViewService.menus.getRepositoryMenus(element.context.provider); const menu = menus.getResourceFolderMenu(element.context); actions = collectContextMenuActions(menu); } @@ -3981,50 +2559,26 @@ export class SCMViewPane extends ViewPane { getAnchor: () => e.anchor, getActions: () => actions, getActionsContext: () => context, - actionRunner, + actionRunner }); } private getSelectedRepositories(): ISCMRepository[] { - const focusedRepositories = this.tree - .getFocus() - .filter((r) => !!r && isSCMRepository(r))! as ISCMRepository[]; - - const selectedRepositories = this.tree - .getSelection() - .filter((r) => !!r && isSCMRepository(r))! as ISCMRepository[]; - - return Array.from( - new Set([ - ...focusedRepositories, - ...selectedRepositories, - ]), - ); + const focusedRepositories = this.tree.getFocus().filter(r => !!r && isSCMRepository(r))! as ISCMRepository[]; + const selectedRepositories = this.tree.getSelection().filter(r => !!r && isSCMRepository(r))! as ISCMRepository[]; + + return Array.from(new Set([...focusedRepositories, ...selectedRepositories])); } - private getSelectedResources(): ( - | ISCMResource - | IResourceNode - )[] { - return this.tree - .getSelection() - .filter((r) => !!r && !isSCMResourceGroup(r))! as any; + private getSelectedResources(): (ISCMResource | IResourceNode)[] { + return this.tree.getSelection() + .filter(r => !!r && !isSCMResourceGroup(r))! as any; } private getViewMode(): ViewMode { - let mode = - this.configurationService.getValue<"tree" | "list">( - "scm.defaultViewMode", - ) === "list" - ? ViewMode.List - : ViewMode.Tree; - - const storageMode = this.storageService.get( - `scm.viewMode`, - StorageScope.WORKSPACE, - ) as ViewMode; - - if (typeof storageMode === "string") { + let mode = this.configurationService.getValue<'tree' | 'list'>('scm.defaultViewMode') === 'list' ? ViewMode.List : ViewMode.Tree; + const storageMode = this.storageService.get(`scm.viewMode`, StorageScope.WORKSPACE) as ViewMode; + if (typeof storageMode === 'string') { mode = storageMode; } @@ -4039,34 +2593,21 @@ export class SCMViewPane extends ViewPane { // List let viewSortKey: ViewSortKey; - - const viewSortKeyString = this.configurationService.getValue< - "path" | "name" | "status" - >("scm.defaultViewSortKey"); - + const viewSortKeyString = this.configurationService.getValue<'path' | 'name' | 'status'>('scm.defaultViewSortKey'); switch (viewSortKeyString) { - case "name": + case 'name': viewSortKey = ViewSortKey.Name; - break; - - case "status": + case 'status': viewSortKey = ViewSortKey.Status; - break; - default: viewSortKey = ViewSortKey.Path; - break; } - const storageSortKey = this.storageService.get( - `scm.viewSortKey`, - StorageScope.WORKSPACE, - ) as ViewSortKey; - - if (typeof storageSortKey === "string") { + const storageSortKey = this.storageService.get(`scm.viewSortKey`, StorageScope.WORKSPACE) as ViewSortKey; + if (typeof storageSortKey === 'string') { viewSortKey = storageSortKey; } @@ -4074,18 +2615,13 @@ export class SCMViewPane extends ViewPane { } private loadTreeViewState(): IAsyncDataTreeViewState | undefined { - const storageViewState = this.storageService.get( - "scm.viewState2", - StorageScope.WORKSPACE, - ); - + const storageViewState = this.storageService.get('scm.viewState2', StorageScope.WORKSPACE); if (!storageViewState) { return undefined; } try { const treeViewState = JSON.parse(storageViewState); - return treeViewState; } catch { return undefined; @@ -4094,66 +2630,42 @@ export class SCMViewPane extends ViewPane { private storeTreeViewState() { if (this.tree) { - this.storageService.store( - "scm.viewState2", - JSON.stringify(this.tree.getViewState()), - StorageScope.WORKSPACE, - StorageTarget.MACHINE, - ); + this.storageService.store('scm.viewState2', JSON.stringify(this.tree.getViewState()), StorageScope.WORKSPACE, StorageTarget.MACHINE); } } private updateChildren(element?: ISCMRepository) { - this.updateChildrenThrottler.queue(() => - this.treeOperationSequencer.queue(async () => { - const focusedInput = this.inputRenderer.getFocusedInput(); - - if (element && this.tree.hasNode(element)) { - // Refresh specific repository - await this.tree.updateChildren(element); - } else { - // Refresh the entire tree - await this.tree.updateChildren(undefined); - } + this.updateChildrenThrottler.queue( + () => this.treeOperationSequencer.queue( + async () => { + const focusedInput = this.inputRenderer.getFocusedInput(); + + if (element && this.tree.hasNode(element)) { + // Refresh specific repository + await this.tree.updateChildren(element); + } else { + // Refresh the entire tree + await this.tree.updateChildren(undefined); + } - if (focusedInput) { - this.inputRenderer - .getRenderedInputWidget(focusedInput) - ?.focus(); - } + if (focusedInput) { + this.inputRenderer.getRenderedInputWidget(focusedInput)?.focus(); + } - this.updateScmProviderContextKeys(); - this.updateRepositoryCollapseAllContextKeys(); - }), - ); + this.updateScmProviderContextKeys(); + this.updateRepositoryCollapseAllContextKeys(); + })); } private updateIndentStyles(theme: IFileIconTheme): void { - this.treeContainer.classList.toggle( - "list-view-mode", - this.viewMode === ViewMode.List, - ); - this.treeContainer.classList.toggle( - "tree-view-mode", - this.viewMode === ViewMode.Tree, - ); - this.treeContainer.classList.toggle( - "align-icons-and-twisties", - (this.viewMode === ViewMode.List && theme.hasFileIcons) || - (theme.hasFileIcons && !theme.hasFolderIcons), - ); - this.treeContainer.classList.toggle( - "hide-arrows", - this.viewMode === ViewMode.Tree && - theme.hidesExplorerArrows === true, - ); + this.treeContainer.classList.toggle('list-view-mode', this.viewMode === ViewMode.List); + this.treeContainer.classList.toggle('tree-view-mode', this.viewMode === ViewMode.Tree); + this.treeContainer.classList.toggle('align-icons-and-twisties', (this.viewMode === ViewMode.List && theme.hasFileIcons) || (theme.hasFileIcons && !theme.hasFolderIcons)); + this.treeContainer.classList.toggle('hide-arrows', this.viewMode === ViewMode.Tree && theme.hidesExplorerArrows === true); } private updateScmProviderContextKeys(): void { - const alwaysShowRepositories = - this.configurationService.getValue( - "scm.alwaysShowRepositories", - ); + const alwaysShowRepositories = this.configurationService.getValue('scm.alwaysShowRepositories'); if (!alwaysShowRepositories && this.items.size === 1) { const provider = Iterable.first(this.items.keys())!.provider; @@ -4171,22 +2683,11 @@ export class SCMViewPane extends ViewPane { if (!this.isBodyVisible() || this.items.size === 1) { this.isAnyRepositoryCollapsibleContextKey.set(false); this.areAllRepositoriesCollapsedContextKey.set(false); - return; } - this.isAnyRepositoryCollapsibleContextKey.set( - this.scmViewService.visibleRepositories.some( - (r) => this.tree.hasNode(r) && this.tree.isCollapsible(r), - ), - ); - this.areAllRepositoriesCollapsedContextKey.set( - this.scmViewService.visibleRepositories.every( - (r) => - this.tree.hasNode(r) && - (!this.tree.isCollapsible(r) || this.tree.isCollapsed(r)), - ), - ); + this.isAnyRepositoryCollapsibleContextKey.set(this.scmViewService.visibleRepositories.some(r => this.tree.hasNode(r) && this.tree.isCollapsible(r))); + this.areAllRepositoriesCollapsedContextKey.set(this.scmViewService.visibleRepositories.every(r => this.tree.hasNode(r) && (!this.tree.isCollapsible(r) || this.tree.isCollapsed(r)))); } collapseAllRepositories(): void { @@ -4214,40 +2715,23 @@ export class SCMViewPane extends ViewPane { } private async focusInput(delta: number): Promise { - if ( - !this.scmViewService.focusedRepository || - this.scmViewService.visibleRepositories.length === 0 - ) { + if (!this.scmViewService.focusedRepository || + this.scmViewService.visibleRepositories.length === 0) { return; } let input = this.scmViewService.focusedRepository.input; - const repositories = this.scmViewService.visibleRepositories; // One visible repository and the input is already focused - if ( - repositories.length === 1 && - this.inputRenderer.getRenderedInputWidget(input)?.hasFocus() === - true - ) { + if (repositories.length === 1 && this.inputRenderer.getRenderedInputWidget(input)?.hasFocus() === true) { return; } // Multiple visible repositories and the input already focused - if ( - repositories.length > 1 && - this.inputRenderer.getRenderedInputWidget(input)?.hasFocus() === - true - ) { - const focusedRepositoryIndex = repositories.indexOf( - this.scmViewService.focusedRepository, - ); - - const newFocusedRepositoryIndex = rot( - focusedRepositoryIndex + delta, - repositories.length, - ); + if (repositories.length > 1 && this.inputRenderer.getRenderedInputWidget(input)?.hasFocus() === true) { + const focusedRepositoryIndex = repositories.indexOf(this.scmViewService.focusedRepository); + const newFocusedRepositoryIndex = rot(focusedRepositoryIndex + delta, repositories.length); input = repositories[newFocusedRepositoryIndex].input; } @@ -4266,26 +2750,15 @@ export class SCMViewPane extends ViewPane { } private async focusResourceGroup(delta: number): Promise { - if ( - !this.scmViewService.focusedRepository || - this.scmViewService.visibleRepositories.length === 0 - ) { + if (!this.scmViewService.focusedRepository || + this.scmViewService.visibleRepositories.length === 0) { return; } const treeHasDomFocus = isActiveElement(this.tree.getHTMLElement()); - - const resourceGroups = - this.scmViewService.focusedRepository.provider.groups; - - const focusedResourceGroup = this.tree - .getFocus() - .find((e) => isSCMResourceGroup(e)); - - const focusedResourceGroupIndex = - treeHasDomFocus && focusedResourceGroup - ? resourceGroups.indexOf(focusedResourceGroup) - : -1; + const resourceGroups = this.scmViewService.focusedRepository.provider.groups; + const focusedResourceGroup = this.tree.getFocus().find(e => isSCMResourceGroup(e)); + const focusedResourceGroupIndex = treeHasDomFocus && focusedResourceGroup ? resourceGroups.indexOf(focusedResourceGroup) : -1; let resourceGroupNext: ISCMResourceGroup | undefined; @@ -4294,21 +2767,15 @@ export class SCMViewPane extends ViewPane { for (const resourceGroup of resourceGroups) { if (this.tree.hasNode(resourceGroup)) { resourceGroupNext = resourceGroup; - break; } } } else { // Next/Previous visible resource group - let index = rot( - focusedResourceGroupIndex + delta, - resourceGroups.length, - ); - + let index = rot(focusedResourceGroupIndex + delta, resourceGroups.length); while (index !== focusedResourceGroupIndex) { if (this.tree.hasNode(resourceGroups[index])) { resourceGroupNext = resourceGroups[index]; - break; } index = rot(index + delta, resourceGroups.length); @@ -4330,29 +2797,22 @@ export class SCMViewPane extends ViewPane { } override getActionsContext(): unknown { - return this.scmViewService.visibleRepositories.length === 1 - ? this.scmViewService.visibleRepositories[0].provider - : undefined; + return this.scmViewService.visibleRepositories.length === 1 ? this.scmViewService.visibleRepositories[0].provider : undefined; } override focus(): void { super.focus(); this.treeOperationSequencer.queue(() => { - return new Promise((resolve) => { + return new Promise(resolve => { if (this.isExpanded()) { if (this.tree.getFocus().length === 0) { - for (const repository of this.scmViewService - .visibleRepositories) { - const widget = - this.inputRenderer.getRenderedInputWidget( - repository.input, - ); + for (const repository of this.scmViewService.visibleRepositories) { + const widget = this.inputRenderer.getRenderedInputWidget(repository.input); if (widget) { widget.focus(); resolve(); - return; } } @@ -4369,58 +2829,32 @@ export class SCMViewPane extends ViewPane { this.visibilityDisposables.dispose(); this.disposables.dispose(); this.items.dispose(); - super.dispose(); } } -class SCMTreeDataSource - extends Disposable - implements IAsyncDataSource -{ +class SCMTreeDataSource extends Disposable implements IAsyncDataSource { constructor( private readonly viewMode: () => ViewMode, - @IConfigurationService - private readonly configurationService: IConfigurationService, - @ISCMViewService private readonly scmViewService: ISCMViewService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @ISCMViewService private readonly scmViewService: ISCMViewService ) { super(); } - async getChildren( - inputOrElement: ISCMViewService | TreeElement, - ): Promise> { + async getChildren(inputOrElement: ISCMViewService | TreeElement): Promise> { const repositoryCount = this.scmViewService.visibleRepositories.length; - const showActionButton = - this.configurationService.getValue( - "scm.showActionButton", - ) === true; + const showActionButton = this.configurationService.getValue('scm.showActionButton') === true; + const alwaysShowRepositories = this.configurationService.getValue('scm.alwaysShowRepositories') === true; - const alwaysShowRepositories = - this.configurationService.getValue( - "scm.alwaysShowRepositories", - ) === true; - - if ( - isSCMViewService(inputOrElement) && - (repositoryCount > 1 || alwaysShowRepositories) - ) { + if (isSCMViewService(inputOrElement) && (repositoryCount > 1 || alwaysShowRepositories)) { return this.scmViewService.visibleRepositories; - } else if ( - (isSCMViewService(inputOrElement) && - repositoryCount === 1 && - !alwaysShowRepositories) || - isSCMRepository(inputOrElement) - ) { + } else if ((isSCMViewService(inputOrElement) && repositoryCount === 1 && !alwaysShowRepositories) || isSCMRepository(inputOrElement)) { const children: TreeElement[] = []; - inputOrElement = isSCMRepository(inputOrElement) - ? inputOrElement - : this.scmViewService.visibleRepositories[0]; - + inputOrElement = isSCMRepository(inputOrElement) ? inputOrElement : this.scmViewService.visibleRepositories[0]; const actionButton = inputOrElement.provider.actionButton.get(); - const resourceGroups = inputOrElement.provider.groups; // SCM Input @@ -4431,21 +2865,15 @@ class SCMTreeDataSource // Action Button if (showActionButton && actionButton) { children.push({ - type: "actionButton", + type: 'actionButton', repository: inputOrElement, - button: actionButton, + button: actionButton } satisfies ISCMActionButton); } // ResourceGroups - const hasSomeChanges = resourceGroups.some( - (group) => group.resources.length > 0, - ); - - if ( - hasSomeChanges || - (repositoryCount === 1 && (!showActionButton || !actionButton)) - ) { + const hasSomeChanges = resourceGroups.some(group => group.resources.length > 0); + if (hasSomeChanges || (repositoryCount === 1 && (!showActionButton || !actionButton))) { children.push(...resourceGroups); } @@ -4457,13 +2885,8 @@ class SCMTreeDataSource } else if (this.viewMode() === ViewMode.Tree) { // Resources (Tree) const children: TreeElement[] = []; - for (const node of inputOrElement.resourceTree.root.children) { - children.push( - node.element && node.childrenCount === 0 - ? node.element - : node, - ); + children.push(node.element && node.childrenCount === 0 ? node.element : node); } return children; @@ -4471,13 +2894,8 @@ class SCMTreeDataSource } else if (isSCMResourceNode(inputOrElement)) { // Resources (Tree), History item changes (Tree) const children: TreeElement[] = []; - for (const node of inputOrElement.children) { - children.push( - node.element && node.childrenCount === 0 - ? node.element - : node, - ); + children.push(node.element && node.childrenCount === 0 ? node.element : node); } return children; @@ -4493,21 +2911,18 @@ class SCMTreeDataSource } else if (element.parent) { return element.parent; } else { - throw new Error("Invalid element passed to getParent"); + throw new Error('Invalid element passed to getParent'); } } else if (isSCMResource(element)) { if (this.viewMode() === ViewMode.List) { return element.resourceGroup; } - const node = element.resourceGroup.resourceTree.getNode( - element.sourceUri, - ); - + const node = element.resourceGroup.resourceTree.getNode(element.sourceUri); const result = node?.parent; if (!result) { - throw new Error("Invalid element passed to getParent"); + throw new Error('Invalid element passed to getParent'); } if (result === element.resourceGroup.resourceTree.root) { @@ -4518,17 +2933,14 @@ class SCMTreeDataSource } else if (isSCMInput(element)) { return element.repository; } else if (isSCMResourceGroup(element)) { - const repository = this.scmViewService.visibleRepositories.find( - (r) => r.provider === element.provider, - ); - + const repository = this.scmViewService.visibleRepositories.find(r => r.provider === element.provider); if (!repository) { - throw new Error("Invalid element passed to getParent"); + throw new Error('Invalid element passed to getParent'); } return repository; } else { - throw new Error("Unexpected call to getParent"); + throw new Error('Unexpected call to getParent'); } } @@ -4548,25 +2960,22 @@ class SCMTreeDataSource } else if (ResourceTree.isResourceNode(inputOrElement)) { return inputOrElement.childrenCount > 0; } else { - throw new Error("hasChildren not implemented."); + throw new Error('hasChildren not implemented.'); } } } export class SCMActionButton implements IDisposable { - private button: - | Button - | ButtonWithDescription - | ButtonWithDropdown - | undefined; + private button: Button | ButtonWithDescription | ButtonWithDropdown | undefined; private readonly disposables = new MutableDisposable(); constructor( private readonly container: HTMLElement, private readonly contextMenuService: IContextMenuService, private readonly commandService: ICommandService, - private readonly notificationService: INotificationService, - ) {} + private readonly notificationService: INotificationService + ) { + } dispose(): void { this.disposables?.dispose(); @@ -4575,35 +2984,16 @@ export class SCMActionButton implements IDisposable { setButton(button: ISCMActionButtonDescriptor | undefined): void { // Clear old button this.clear(); - if (!button) { return; } if (button.secondaryCommands?.length) { const actions: IAction[] = []; - - for ( - let index = 0; - index < button.secondaryCommands.length; - index++ - ) { + for (let index = 0; index < button.secondaryCommands.length; index++) { const commands = button.secondaryCommands[index]; - for (const command of commands) { - actions.push( - new Action( - command.id, - command.title, - undefined, - true, - async () => - await this.executeCommand( - command.id, - ...(command.arguments || []), - ), - ), - ); + actions.push(new Action(command.id, command.title, undefined, true, async () => await this.executeCommand(command.id, ...(command.arguments || [])))); } if (commands.length) { actions.push(new Separator()); @@ -4619,33 +3009,19 @@ export class SCMActionButton implements IDisposable { contextMenuProvider: this.contextMenuService, title: button.command.tooltip, supportIcons: true, - ...defaultButtonStyles, + ...defaultButtonStyles }); } else { // Button - this.button = new Button(this.container, { - supportIcons: true, - supportShortLabel: !!button.command.shortTitle, - title: button.command.tooltip, - ...defaultButtonStyles, - }); + this.button = new Button(this.container, { supportIcons: true, supportShortLabel: !!button.command.shortTitle, title: button.command.tooltip, ...defaultButtonStyles }); } this.button.enabled = button.enabled; this.button.label = button.command.title; - if (this.button instanceof Button && button.command.shortTitle) { this.button.labelShort = button.command.shortTitle; } - this.button.onDidClick( - async () => - await this.executeCommand( - button.command.id, - ...(button.command.arguments || []), - ), - null, - this.disposables.value, - ); + this.button.onDidClick(async () => await this.executeCommand(button.command.id, ...(button.command.arguments || [])), null, this.disposables.value); this.disposables.value!.add(this.button); } @@ -4660,10 +3036,7 @@ export class SCMActionButton implements IDisposable { clearNode(this.container); } - private async executeCommand( - commandId: string, - ...args: any[] - ): Promise { + private async executeCommand(commandId: string, ...args: any[]): Promise { try { await this.commandService.executeCommand(commandId, ...args); } catch (ex) { @@ -4672,4 +3045,4 @@ export class SCMActionButton implements IDisposable { } } -setupSimpleEditorSelectionStyling(".scm-view .scm-editor-container"); +setupSimpleEditorSelectionStyling('.scm-view .scm-editor-container'); diff --git a/Source/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts b/Source/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts index 219c784e8305b..57812e50451cc 100644 --- a/Source/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts +++ b/Source/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts @@ -3,66 +3,35 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { ITerminalAddon, Terminal } from "@xterm/xterm"; - -import * as dom from "../../../../../base/browser/dom.js"; -import { - CancellationToken, - CancellationTokenSource, -} from "../../../../../base/common/cancellation.js"; -import { Codicon } from "../../../../../base/common/codicons.js"; -import { Emitter, Event } from "../../../../../base/common/event.js"; -import { - combinedDisposable, - Disposable, - MutableDisposable, -} from "../../../../../base/common/lifecycle.js"; -import { sep } from "../../../../../base/common/path.js"; -import { commonPrefixLength } from "../../../../../base/common/strings.js"; -import { ThemeIcon } from "../../../../../base/common/themables.js"; -import { editorSuggestWidgetSelectedBackground } from "../../../../../editor/contrib/suggest/browser/suggestWidget.js"; -import { IConfigurationService } from "../../../../../platform/configuration/common/configuration.js"; -import { IContextKey } from "../../../../../platform/contextkey/common/contextkey.js"; -import { IInstantiationService } from "../../../../../platform/instantiation/common/instantiation.js"; -import { - IStorageService, - StorageScope, - StorageTarget, -} from "../../../../../platform/storage/common/storage.js"; -import { - TerminalCapability, - type ITerminalCapabilityStore, -} from "../../../../../platform/terminal/common/capabilities/capabilities.js"; -import type { - IPromptInputModel, - IPromptInputModelState, -} from "../../../../../platform/terminal/common/capabilities/commandDetection/promptInputModel.js"; -import { TerminalShellType } from "../../../../../platform/terminal/common/terminal.js"; -import { getListStyles } from "../../../../../platform/theme/browser/defaultStyles.js"; -import { activeContrastBorder } from "../../../../../platform/theme/common/colorRegistry.js"; -import { IExtensionService } from "../../../../services/extensions/common/extensions.js"; -import { SimpleCompletionItem } from "../../../../services/suggest/browser/simpleCompletionItem.js"; -import { - LineContext, - SimpleCompletionModel, -} from "../../../../services/suggest/browser/simpleCompletionModel.js"; -import { - ISimpleSelectedSuggestion, - SimpleSuggestWidget, -} from "../../../../services/suggest/browser/simpleSuggestWidget.js"; -import type { ISimpleSuggestWidgetFontInfo } from "../../../../services/suggest/browser/simpleSuggestWidgetRenderer.js"; -import { ITerminalConfigurationService } from "../../../terminal/browser/terminal.js"; -import type { IXtermCore } from "../../../terminal/browser/xterm-private.js"; -import { TerminalStorageKeys } from "../../../terminal/common/terminalStorageKeys.js"; -import { - terminalSuggestConfigSection, - type ITerminalSuggestConfiguration, -} from "../common/terminalSuggestConfiguration.js"; -import { - ITerminalCompletion, - ITerminalCompletionService, - TerminalCompletionItemKind, -} from "./terminalCompletionService.js"; +import type { ITerminalAddon, Terminal } from '@xterm/xterm'; +import * as dom from '../../../../../base/browser/dom.js'; +import { Codicon } from '../../../../../base/common/codicons.js'; +import { Emitter, Event } from '../../../../../base/common/event.js'; +import { combinedDisposable, Disposable, MutableDisposable } from '../../../../../base/common/lifecycle.js'; +import { sep } from '../../../../../base/common/path.js'; +import { commonPrefixLength } from '../../../../../base/common/strings.js'; +import { editorSuggestWidgetSelectedBackground } from '../../../../../editor/contrib/suggest/browser/suggestWidget.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { IContextKey } from '../../../../../platform/contextkey/common/contextkey.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; +import { TerminalCapability, type ITerminalCapabilityStore } from '../../../../../platform/terminal/common/capabilities/capabilities.js'; +import type { IPromptInputModel, IPromptInputModelState } from '../../../../../platform/terminal/common/capabilities/commandDetection/promptInputModel.js'; +import { getListStyles } from '../../../../../platform/theme/browser/defaultStyles.js'; +import { activeContrastBorder } from '../../../../../platform/theme/common/colorRegistry.js'; +import { ITerminalConfigurationService } from '../../../terminal/browser/terminal.js'; +import type { IXtermCore } from '../../../terminal/browser/xterm-private.js'; +import { TerminalStorageKeys } from '../../../terminal/common/terminalStorageKeys.js'; +import { terminalSuggestConfigSection, type ITerminalSuggestConfiguration } from '../common/terminalSuggestConfiguration.js'; +import { SimpleCompletionItem } from '../../../../services/suggest/browser/simpleCompletionItem.js'; +import { LineContext, SimpleCompletionModel } from '../../../../services/suggest/browser/simpleCompletionModel.js'; +import { ISimpleSelectedSuggestion, SimpleSuggestWidget } from '../../../../services/suggest/browser/simpleSuggestWidget.js'; +import type { ISimpleSuggestWidgetFontInfo } from '../../../../services/suggest/browser/simpleSuggestWidgetRenderer.js'; +import { ITerminalCompletion, ITerminalCompletionService, TerminalCompletionItemKind } from './terminalCompletionService.js'; +import { TerminalShellType } from '../../../../../platform/terminal/common/terminal.js'; +import { CancellationToken, CancellationTokenSource } from '../../../../../base/common/cancellation.js'; +import { IExtensionService } from '../../../../services/extensions/common/extensions.js'; +import { ThemeIcon } from '../../../../../base/common/themables.js'; export interface ISuggestController { isPasting: boolean; @@ -70,22 +39,15 @@ export interface ISuggestController { selectPreviousPageSuggestion(): void; selectNextSuggestion(): void; selectNextPageSuggestion(): void; - acceptSelectedSuggestion( - suggestion?: Pick, - ): void; + acceptSelectedSuggestion(suggestion?: Pick): void; hideSuggestWidget(): void; } -export class SuggestAddon - extends Disposable - implements ITerminalAddon, ISuggestController -{ +export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggestController { private _terminal?: Terminal; private _promptInputModel?: IPromptInputModel; - private readonly _promptInputModelSubscriptions = this._register( - new MutableDisposable(), - ); + private readonly _promptInputModelSubscriptions = this._register(new MutableDisposable()); private _mostRecentPromptInputState?: IPromptInputModelState; private _currentPromptInputState?: IPromptInputModelState; @@ -114,13 +76,9 @@ export class SuggestAddon private readonly _onBell = this._register(new Emitter()); readonly onBell = this._onBell.event; - private readonly _onAcceptedCompletion = this._register( - new Emitter(), - ); + private readonly _onAcceptedCompletion = this._register(new Emitter()); readonly onAcceptedCompletion = this._onAcceptedCompletion.event; - private readonly _onDidReceiveCompletions = this._register( - new Emitter(), - ); + private readonly _onDidReceiveCompletions = this._register(new Emitter()); readonly onDidReceiveCompletions = this._onDidReceiveCompletions.event; private _kindToIconMap = new Map([ @@ -128,81 +86,50 @@ export class SuggestAddon [TerminalCompletionItemKind.Folder, Codicon.folder], [TerminalCompletionItemKind.Flag, Codicon.symbolProperty], [TerminalCompletionItemKind.Method, Codicon.symbolMethod], - [TerminalCompletionItemKind.Argument, Codicon.symbolVariable], + [TerminalCompletionItemKind.Argument, Codicon.symbolVariable] ]); constructor( private readonly _shellType: TerminalShellType | undefined, private readonly _capabilities: ITerminalCapabilityStore, private readonly _terminalSuggestWidgetVisibleContextKey: IContextKey, - @ITerminalCompletionService - private readonly _terminalCompletionService: ITerminalCompletionService, - @IConfigurationService - private readonly _configurationService: IConfigurationService, - @IInstantiationService - private readonly _instantiationService: IInstantiationService, - @ITerminalConfigurationService - private readonly _terminalConfigurationService: ITerminalConfigurationService, - @IExtensionService - private readonly _extensionService: IExtensionService, + @ITerminalCompletionService private readonly _terminalCompletionService: ITerminalCompletionService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @ITerminalConfigurationService private readonly _terminalConfigurationService: ITerminalConfigurationService, + @IExtensionService private readonly _extensionService: IExtensionService ) { super(); - this._register( - Event.runAndSubscribe( - Event.any( - this._capabilities.onDidAddCapabilityType, - this._capabilities.onDidRemoveCapabilityType, - ), - () => { - const commandDetection = this._capabilities.get( - TerminalCapability.CommandDetection, + this._register(Event.runAndSubscribe(Event.any( + this._capabilities.onDidAddCapabilityType, + this._capabilities.onDidRemoveCapabilityType + ), () => { + const commandDetection = this._capabilities.get(TerminalCapability.CommandDetection); + if (commandDetection) { + if (this._promptInputModel !== commandDetection.promptInputModel) { + this._promptInputModel = commandDetection.promptInputModel; + this._promptInputModelSubscriptions.value = combinedDisposable( + this._promptInputModel.onDidChangeInput(e => this._sync(e)), + this._promptInputModel.onDidFinishInput(() => this.hideSuggestWidget()), ); - if (commandDetection) { - if ( - this._promptInputModel !== - commandDetection.promptInputModel - ) { - this._promptInputModel = - commandDetection.promptInputModel; - this._promptInputModelSubscriptions.value = - combinedDisposable( - this._promptInputModel.onDidChangeInput( - (e) => this._sync(e), - ), - this._promptInputModel.onDidFinishInput( - () => this.hideSuggestWidget(), - ), - ); - } - } else { - this._promptInputModel = undefined; - } - }, - ), - ); + } + } else { + this._promptInputModel = undefined; + } + })); } activate(xterm: Terminal): void { this._terminal = xterm; - this._register( - xterm.onData(async (e) => { - this._lastUserData = e; - }), - ); + this._register(xterm.onData(async e => { + this._lastUserData = e; + })); } - private async _handleCompletionProviders( - terminal: Terminal | undefined, - token: CancellationToken, - triggerCharacter?: boolean, - ): Promise { + private async _handleCompletionProviders(terminal: Terminal | undefined, token: CancellationToken, triggerCharacter?: boolean): Promise { // Nothing to handle if the terminal is not attached - if ( - !terminal?.element || - !this._enableWidget || - !this._promptInputModel - ) { + if (!terminal?.element || !this._enableWidget || !this._promptInputModel) { return; } @@ -215,24 +142,12 @@ export class SuggestAddon return; } - const enableExtensionCompletions = - this._configurationService.getValue( - terminalSuggestConfigSection, - ).enableExtensionCompletions; + const enableExtensionCompletions = this._configurationService.getValue(terminalSuggestConfigSection).enableExtensionCompletions; if (enableExtensionCompletions) { - await this._extensionService.activateByEvent( - "onTerminalCompletionsRequested", - ); - } - - const providedCompletions = - await this._terminalCompletionService.provideCompletions( - this._promptInputModel.value, - this._promptInputModel.cursorIndex, - this._shellType, - token, - triggerCharacter, - ); + await this._extensionService.activateByEvent('onTerminalCompletionsRequested'); + } + + const providedCompletions = await this._terminalCompletionService.provideCompletions(this._promptInputModel.value, this._promptInputModel.cursorIndex, this._shellType, token, triggerCharacter); if (!providedCompletions?.length || token.isCancellationRequested) { return; } @@ -240,11 +155,8 @@ export class SuggestAddon // ATM, the two providers calculate the same replacement index / prefix, so we can just take the first one // TODO: figure out if we can add support for multiple replacement indices - const replacementIndices = [ - ...new Set(providedCompletions.map((c) => c.replacementIndex)), - ]; - const replacementIndex = - replacementIndices.length === 1 ? replacementIndices[0] : 0; + const replacementIndices = [...new Set(providedCompletions.map(c => c.replacementIndex))]; + const replacementIndex = replacementIndices.length === 1 ? replacementIndices[0] : 0; this._providerReplacementIndex = replacementIndex; this._requestedCompletionsIndex = this._promptInputModel.cursorIndex; @@ -253,42 +165,28 @@ export class SuggestAddon prefix: this._promptInputModel.prefix, suffix: this._promptInputModel.suffix, cursorIndex: this._promptInputModel.cursorIndex, - ghostTextIndex: this._promptInputModel.ghostTextIndex, + ghostTextIndex: this._promptInputModel.ghostTextIndex }; - this._leadingLineContent = - this._currentPromptInputState.prefix.substring( - replacementIndex, - replacementIndex + - this._promptInputModel.cursorIndex + - this._cursorIndexDelta, - ); + this._leadingLineContent = this._currentPromptInputState.prefix.substring(replacementIndex, replacementIndex + this._promptInputModel.cursorIndex + this._cursorIndexDelta); const completions = providedCompletions.flat(); if (!completions?.length) { return; } - const firstChar = - this._leadingLineContent.length === 0 - ? "" - : this._leadingLineContent[0]; + const firstChar = this._leadingLineContent.length === 0 ? '' : this._leadingLineContent[0]; // This is a TabExpansion2 result - if (this._leadingLineContent.includes(" ") || firstChar === "[") { + if (this._leadingLineContent.includes(' ') || firstChar === '[') { this._leadingLineContent = this._promptInputModel.prefix; } - if ( - this._mostRecentCompletion?.isDirectory && - completions.every((e) => e.isDirectory) - ) { + if (this._mostRecentCompletion?.isDirectory && completions.every(e => e.isDirectory)) { completions.push(this._mostRecentCompletion); } this._mostRecentCompletion = undefined; - this._cursorIndexDelta = - this._currentPromptInputState.cursorIndex - - this._requestedCompletionsIndex; + this._cursorIndexDelta = this._currentPromptInputState.cursorIndex - this._requestedCompletionsIndex; let normalizedLeadingLineContent = this._leadingLineContent; @@ -297,37 +195,27 @@ export class SuggestAddon // - Using `\` or `/` will request new completions. It's important that this only occurs // when a directory is present, if not completions like git branches could be requested // which leads to flickering - this._isFilteringDirectories = completions.some((e) => e.isDirectory); + this._isFilteringDirectories = completions.some(e => e.isDirectory); if (this._isFilteringDirectories) { - const firstDir = completions.find((e) => e.isDirectory); - this._pathSeparator = - firstDir?.label.match(/(?[\\\/])/)?.groups?.sep ?? sep; - normalizedLeadingLineContent = normalizePathSeparator( - normalizedLeadingLineContent, - this._pathSeparator, - ); + const firstDir = completions.find(e => e.isDirectory); + this._pathSeparator = firstDir?.label.match(/(?[\\\/])/)?.groups?.sep ?? sep; + normalizedLeadingLineContent = normalizePathSeparator(normalizedLeadingLineContent, this._pathSeparator); } for (const completion of completions) { if (!completion.icon && completion.kind !== undefined) { completion.icon = this._kindToIconMap.get(completion.kind); } } - const lineContext = new LineContext( - normalizedLeadingLineContent, - this._cursorIndexDelta, - ); - const model = new SimpleCompletionModel( - completions - .filter((c) => !!c.label) - .map((c) => new SimpleCompletionItem(c)), - lineContext, - ); + const lineContext = new LineContext(normalizedLeadingLineContent, this._cursorIndexDelta); + const model = new SimpleCompletionModel(completions.filter(c => !!c.label).map(c => new SimpleCompletionItem(c)), lineContext); if (token.isCancellationRequested) { return; } this._showCompletions(model); } + + setContainerWithOverflow(container: HTMLElement): void { this._container = container; } @@ -350,23 +238,12 @@ export class SuggestAddon } this._cancellationTokenSource = new CancellationTokenSource(); const token = this._cancellationTokenSource.token; - await this._handleCompletionProviders( - this._terminal, - token, - triggerCharacter, - ); + await this._handleCompletionProviders(this._terminal, token, triggerCharacter); } private _sync(promptInputState: IPromptInputModelState): void { - const config = - this._configurationService.getValue( - terminalSuggestConfigSection, - ); - if ( - !this._mostRecentPromptInputState || - promptInputState.cursorIndex > - this._mostRecentPromptInputState.cursorIndex - ) { + const config = this._configurationService.getValue(terminalSuggestConfigSection); + if (!this._mostRecentPromptInputState || promptInputState.cursorIndex > this._mostRecentPromptInputState.cursorIndex) { // If input has been added let sent = false; @@ -376,10 +253,7 @@ export class SuggestAddon // TODO: Make the regex code generic // TODO: Don't use `\[` in bash/zsh // If first character or first character after a space (or `[` in pwsh), request completions - if ( - promptInputState.cursorIndex === 1 || - promptInputState.prefix.match(/([\s\[])[^\s]$/) - ) { + if (promptInputState.cursorIndex === 1 || promptInputState.prefix.match(/([\s\[])[^\s]$/)) { // Never request completions if the last key sequence was up or down as the user was likely // navigating history if (!this._lastUserData?.match(/^\x1b[\[O]?[A-D]$/)) { @@ -399,14 +273,13 @@ export class SuggestAddon prefix?.match(/\s[\-]$/) || // Only trigger on `\` and `/` if it's a directory. Not doing so causes problems // with git branches in particular - (this._isFilteringDirectories && prefix?.match(/[\\\/]$/)) + this._isFilteringDirectories && prefix?.match(/[\\\/]$/) ) { this.requestCompletions(); sent = true; } if (!sent) { - for (const provider of this._terminalCompletionService - .providers) { + for (const provider of this._terminalCompletionService.providers) { if (!provider.triggerCharacters) { continue; } @@ -423,24 +296,14 @@ export class SuggestAddon } this._mostRecentPromptInputState = promptInputState; - if ( - !this._promptInputModel || - !this._terminal || - !this._suggestWidget || - this._leadingLineContent === undefined - ) { + if (!this._promptInputModel || !this._terminal || !this._suggestWidget || this._leadingLineContent === undefined) { return; } this._currentPromptInputState = promptInputState; // Hide the widget if the latest character was a space - if ( - this._currentPromptInputState.cursorIndex > 1 && - this._currentPromptInputState.value.at( - this._currentPromptInputState.cursorIndex - 1, - ) === " " - ) { + if (this._currentPromptInputState.cursorIndex > 1 && this._currentPromptInputState.value.at(this._currentPromptInputState.cursorIndex - 1) === ' ') { this.hideSuggestWidget(); return; } @@ -448,34 +311,18 @@ export class SuggestAddon // Hide the widget if the cursor moves to the left of the initial position as the // completions are no longer valid // to do: get replacement length to be correct, readd this? - if ( - this._currentPromptInputState && - this._currentPromptInputState.cursorIndex <= - this._leadingLineContent.length - ) { + if (this._currentPromptInputState && this._currentPromptInputState.cursorIndex <= this._leadingLineContent.length) { this.hideSuggestWidget(); return; } if (this._terminalSuggestWidgetVisibleContextKey.get()) { - this._cursorIndexDelta = - this._currentPromptInputState.cursorIndex - - this._requestedCompletionsIndex; - let normalizedLeadingLineContent = - this._currentPromptInputState.value.substring( - this._providerReplacementIndex, - this._requestedCompletionsIndex + this._cursorIndexDelta, - ); + this._cursorIndexDelta = this._currentPromptInputState.cursorIndex - (this._requestedCompletionsIndex); + let normalizedLeadingLineContent = this._currentPromptInputState.value.substring(this._providerReplacementIndex, this._requestedCompletionsIndex + this._cursorIndexDelta); if (this._isFilteringDirectories) { - normalizedLeadingLineContent = normalizePathSeparator( - normalizedLeadingLineContent, - this._pathSeparator, - ); + normalizedLeadingLineContent = normalizePathSeparator(normalizedLeadingLineContent, this._pathSeparator); } - const lineContext = new LineContext( - normalizedLeadingLineContent, - this._cursorIndexDelta, - ); + const lineContext = new LineContext(normalizedLeadingLineContent, this._cursorIndexDelta); this._suggestWidget.setLineContext(lineContext); } @@ -491,19 +338,14 @@ export class SuggestAddon } const xtermBox = this._screen!.getBoundingClientRect(); this._suggestWidget.showSuggestions(0, false, false, { - left: - xtermBox.left + - this._terminal.buffer.active.cursorX * dimensions.width, - top: - xtermBox.top + - this._terminal.buffer.active.cursorY * dimensions.height, - height: dimensions.height, + left: xtermBox.left + this._terminal.buffer.active.cursorX * dimensions.width, + top: xtermBox.top + this._terminal.buffer.active.cursorY * dimensions.height, + height: dimensions.height }); } private _getTerminalDimensions(): { width: number; height: number } { - const cssCellDims = (this._terminal as any as { _core: IXtermCore }) - ._core._renderService.dimensions.css.cell; + const cssCellDims = (this._terminal as any as { _core: IXtermCore })._core._renderService.dimensions.css.cell; return { width: cssCellDims.width, height: cssCellDims.height, @@ -527,62 +369,37 @@ export class SuggestAddon const xtermBox = this._screen!.getBoundingClientRect(); suggestWidget.showSuggestions(0, false, false, { - left: - xtermBox.left + - this._terminal.buffer.active.cursorX * dimensions.width, - top: - xtermBox.top + - this._terminal.buffer.active.cursorY * dimensions.height, - height: dimensions.height, + left: xtermBox.left + this._terminal.buffer.active.cursorX * dimensions.width, + top: xtermBox.top + this._terminal.buffer.active.cursorY * dimensions.height, + height: dimensions.height }); } private _ensureSuggestWidget(terminal: Terminal): SimpleSuggestWidget { if (!this._suggestWidget) { const c = this._terminalConfigurationService.config; - const font = this._terminalConfigurationService.getFont( - dom.getActiveWindow(), - ); + const font = this._terminalConfigurationService.getFont(dom.getActiveWindow()); const fontInfo: ISimpleSuggestWidgetFontInfo = { fontFamily: font.fontFamily, fontSize: font.fontSize, lineHeight: Math.ceil(1.5 * font.fontSize), fontWeight: c.fontWeight.toString(), - letterSpacing: font.letterSpacing, + letterSpacing: font.letterSpacing }; - this._suggestWidget = this._register( - this._instantiationService.createInstance( - SimpleSuggestWidget, - this._container!, - this._instantiationService.createInstance( - PersistedWidgetSize, - ), - () => fontInfo, - {}, - ), - ); - this._suggestWidget.list.style( - getListStyles({ - listInactiveFocusBackground: - editorSuggestWidgetSelectedBackground, - listInactiveFocusOutline: activeContrastBorder, - }), - ); - this._register( - this._suggestWidget.onDidSelect(async (e) => - this.acceptSelectedSuggestion(e), - ), - ); - this._register( - this._suggestWidget.onDidHide(() => - this._terminalSuggestWidgetVisibleContextKey.set(false), - ), - ); - this._register( - this._suggestWidget.onDidShow(() => - this._terminalSuggestWidgetVisibleContextKey.set(true), - ), - ); + this._suggestWidget = this._register(this._instantiationService.createInstance( + SimpleSuggestWidget, + this._container!, + this._instantiationService.createInstance(PersistedWidgetSize), + () => fontInfo, + {} + )); + this._suggestWidget.list.style(getListStyles({ + listInactiveFocusBackground: editorSuggestWidgetSelectedBackground, + listInactiveFocusOutline: activeContrastBorder + })); + this._register(this._suggestWidget.onDidSelect(async e => this.acceptSelectedSuggestion(e))); + this._register(this._suggestWidget.onDidHide(() => this._terminalSuggestWidgetVisibleContextKey.set(false))); + this._register(this._suggestWidget.onDidShow(() => this._terminalSuggestWidgetVisibleContextKey.set(true))); this._terminalSuggestWidgetVisibleContextKey.set(false); } return this._suggestWidget; @@ -604,98 +421,60 @@ export class SuggestAddon this._suggestWidget?.selectNextPage(); } - acceptSelectedSuggestion( - suggestion?: Pick, - respectRunOnEnter?: boolean, - ): void { + acceptSelectedSuggestion(suggestion?: Pick, respectRunOnEnter?: boolean): void { if (!suggestion) { suggestion = this._suggestWidget?.getFocusedItem(); } const initialPromptInputState = this._mostRecentPromptInputState; - if ( - !suggestion || - !initialPromptInputState || - this._leadingLineContent === undefined || - !this._model - ) { + if (!suggestion || !initialPromptInputState || this._leadingLineContent === undefined || !this._model) { return; } SuggestAddon.lastAcceptedCompletionTimestamp = Date.now(); this._suggestWidget?.hide(); - const currentPromptInputState = - this._currentPromptInputState ?? initialPromptInputState; + const currentPromptInputState = this._currentPromptInputState ?? initialPromptInputState; // The replacement text is any text after the replacement index for the completions, this // includes any text that was there before the completions were requested and any text added // since to refine the completion. - const replacementText = currentPromptInputState.value.substring( - suggestion.item.completion.replacementIndex ?? - this._providerReplacementIndex, - currentPromptInputState.cursorIndex, - ); + const replacementText = currentPromptInputState.value.substring(suggestion.item.completion.replacementIndex ?? this._providerReplacementIndex, currentPromptInputState.cursorIndex); // Right side of replacement text in the same word - let rightSideReplacementText = ""; + let rightSideReplacementText = ''; if ( // The line didn't end with ghost text - (currentPromptInputState.ghostTextIndex === -1 || - currentPromptInputState.ghostTextIndex > - currentPromptInputState.cursorIndex) && + (currentPromptInputState.ghostTextIndex === -1 || currentPromptInputState.ghostTextIndex > currentPromptInputState.cursorIndex) && // There is more than one charatcer - currentPromptInputState.value.length > - currentPromptInputState.cursorIndex + 1 && + currentPromptInputState.value.length > currentPromptInputState.cursorIndex + 1 && // THe next character is not a space - currentPromptInputState.value.at( - currentPromptInputState.cursorIndex, - ) !== " " + currentPromptInputState.value.at(currentPromptInputState.cursorIndex) !== ' ' ) { - const spaceIndex = currentPromptInputState.value - .substring( - currentPromptInputState.cursorIndex, - currentPromptInputState.ghostTextIndex === -1 - ? undefined - : currentPromptInputState.ghostTextIndex, - ) - .indexOf(" "); - rightSideReplacementText = currentPromptInputState.value.substring( - currentPromptInputState.cursorIndex, - spaceIndex === -1 - ? undefined - : currentPromptInputState.cursorIndex + spaceIndex, - ); + const spaceIndex = currentPromptInputState.value.substring(currentPromptInputState.cursorIndex, currentPromptInputState.ghostTextIndex === -1 ? undefined : currentPromptInputState.ghostTextIndex).indexOf(' '); + rightSideReplacementText = currentPromptInputState.value.substring(currentPromptInputState.cursorIndex, spaceIndex === -1 ? undefined : currentPromptInputState.cursorIndex + spaceIndex); } const completion = suggestion.item.completion; - const completionText = completion.label; - + let completionText = completion.label; + if ((completion.isDirectory || completion.isFile) && completionText.includes(' ')) { + // Escape spaces in files or folders so they're valid paths + completionText = completionText.replaceAll(' ', '\\ '); + } let runOnEnter = false; if (respectRunOnEnter) { - const runOnEnterConfig = - this._configurationService.getValue( - terminalSuggestConfigSection, - ).runOnEnter; + const runOnEnterConfig = this._configurationService.getValue(terminalSuggestConfigSection).runOnEnter; switch (runOnEnterConfig) { - case "always": { + case 'always': { runOnEnter = true; break; } - case "exactMatch": { - runOnEnter = - replacementText.toLowerCase() === - completionText.toLowerCase(); + case 'exactMatch': { + runOnEnter = replacementText.toLowerCase() === completionText.toLowerCase(); break; } - case "exactMatchIgnoreExtension": { - runOnEnter = - replacementText.toLowerCase() === - completionText.toLowerCase(); + case 'exactMatchIgnoreExtension': { + runOnEnter = replacementText.toLowerCase() === completionText.toLowerCase(); if (completion.isFile) { - runOnEnter ||= - replacementText.toLowerCase() === - completionText - .toLowerCase() - .replace(/\.[^\.]+$/, ""); + runOnEnter ||= replacementText.toLowerCase() === completionText.toLowerCase().replace(/\.[^\.]+$/, ''); } break; } @@ -709,36 +488,24 @@ export class SuggestAddon this._mostRecentCompletion = completion; - const commonPrefixLen = commonPrefixLength( - replacementText, - completion.label, - ); - const commonPrefix = replacementText.substring( - replacementText.length - 1 - commonPrefixLen, - replacementText.length - 1, - ); - const completionSuffix = completion.label.substring(commonPrefixLen); + const commonPrefixLen = commonPrefixLength(replacementText, completionText); + const commonPrefix = replacementText.substring(replacementText.length - 1 - commonPrefixLen, replacementText.length - 1); + const completionSuffix = completionText.substring(commonPrefixLen); let resultSequence: string; - if ( - currentPromptInputState.suffix.length > 0 && - currentPromptInputState.prefix.endsWith(commonPrefix) && - currentPromptInputState.suffix.startsWith(completionSuffix) - ) { + if (currentPromptInputState.suffix.length > 0 && currentPromptInputState.prefix.endsWith(commonPrefix) && currentPromptInputState.suffix.startsWith(completionSuffix)) { // Move right to the end of the completion - resultSequence = "\x1bOC".repeat( - completion.label.length - commonPrefixLen, - ); + resultSequence = '\x1bOC'.repeat(completion.label.length - commonPrefixLen); } else { resultSequence = [ // Backspace (left) to remove all additional input - "\x7F".repeat(replacementText.length - commonPrefixLen), + '\x7F'.repeat(replacementText.length - commonPrefixLen), // Delete (right) to remove any additional text in the same word - "\x1b[3~".repeat(rightSideReplacementText.length), + '\x1b[3~'.repeat(rightSideReplacementText.length), // Write the completion completionSuffix, // Run on enter if needed - runOnEnter ? "\r" : "", - ].join(""); + runOnEnter ? '\r' : '' + ].join(''); } // Send the completion @@ -756,15 +523,16 @@ export class SuggestAddon } class PersistedWidgetSize { + private readonly _key = TerminalStorageKeys.TerminalSuggestSize; constructor( - @IStorageService private readonly _storageService: IStorageService, - ) {} + @IStorageService private readonly _storageService: IStorageService + ) { + } restore(): dom.Dimension | undefined { - const raw = - this._storageService.get(this._key, StorageScope.PROFILE) ?? ""; + const raw = this._storageService.get(this._key, StorageScope.PROFILE) ?? ''; try { const obj = JSON.parse(raw); if (dom.Dimension.is(obj)) { @@ -777,12 +545,7 @@ class PersistedWidgetSize { } store(size: dom.Dimension) { - this._storageService.store( - this._key, - JSON.stringify(size), - StorageScope.PROFILE, - StorageTarget.MACHINE, - ); + this._storageService.store(this._key, JSON.stringify(size), StorageScope.PROFILE, StorageTarget.MACHINE); } reset(): void { @@ -790,8 +553,8 @@ class PersistedWidgetSize { } } export function normalizePathSeparator(path: string, sep: string): string { - if (sep === "/") { - return path.replaceAll("\\", "/"); + if (sep === '/') { + return path.replaceAll('\\', '/'); } - return path.replaceAll("/", "\\"); + return path.replaceAll('/', '\\'); } diff --git a/Source/vs/workbench/contrib/testing/browser/testCoverageView.ts b/Source/vs/workbench/contrib/testing/browser/testCoverageView.ts index 21f3972a0f2fc..5ee19fb10f6a8 100644 --- a/Source/vs/workbench/contrib/testing/browser/testCoverageView.ts +++ b/Source/vs/workbench/contrib/testing/browser/testCoverageView.ts @@ -2,230 +2,138 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as dom from "../../../../base/browser/dom.js"; -import { - IIdentityProvider, - IListVirtualDelegate, -} from "../../../../base/browser/ui/list/list.js"; -import { - ICompressedTreeElement, - ICompressedTreeNode, -} from "../../../../base/browser/ui/tree/compressedObjectTreeModel.js"; -import { ICompressibleTreeRenderer } from "../../../../base/browser/ui/tree/objectTree.js"; -import { - ITreeNode, - ITreeSorter, -} from "../../../../base/browser/ui/tree/tree.js"; -import { findLast } from "../../../../base/common/arraysFind.js"; -import { assertNever } from "../../../../base/common/assert.js"; -import { Codicon } from "../../../../base/common/codicons.js"; -import { memoize } from "../../../../base/common/decorators.js"; -import { createMatches, FuzzyScore } from "../../../../base/common/filters.js"; -import { Iterable } from "../../../../base/common/iterator.js"; -import { - Disposable, - DisposableStore, - MutableDisposable, -} from "../../../../base/common/lifecycle.js"; -import { - autorun, - IObservable, - observableValue, -} from "../../../../base/common/observable.js"; -import { IPrefixTreeNode } from "../../../../base/common/prefixTree.js"; -import { basenameOrAuthority } from "../../../../base/common/resources.js"; -import { ThemeIcon } from "../../../../base/common/themables.js"; -import { URI } from "../../../../base/common/uri.js"; -import { Position } from "../../../../editor/common/core/position.js"; -import { Range } from "../../../../editor/common/core/range.js"; -import { localize, localize2 } from "../../../../nls.js"; -import { Categories } from "../../../../platform/action/common/actionCommonCategories.js"; -import { - Action2, - MenuId, - registerAction2, -} from "../../../../platform/actions/common/actions.js"; -import { IConfigurationService } from "../../../../platform/configuration/common/configuration.js"; -import { - ContextKeyExpr, - IContextKeyService, -} from "../../../../platform/contextkey/common/contextkey.js"; -import { IContextMenuService } from "../../../../platform/contextview/browser/contextView.js"; -import { - EditorOpenSource, - TextEditorSelectionRevealType, -} from "../../../../platform/editor/common/editor.js"; -import { FileKind } from "../../../../platform/files/common/files.js"; -import { IHoverService } from "../../../../platform/hover/browser/hover.js"; -import { - IInstantiationService, - ServicesAccessor, -} from "../../../../platform/instantiation/common/instantiation.js"; -import { IKeybindingService } from "../../../../platform/keybinding/common/keybinding.js"; -import { ILabelService } from "../../../../platform/label/common/label.js"; -import { WorkbenchCompressibleObjectTree } from "../../../../platform/list/browser/listService.js"; -import { IOpenerService } from "../../../../platform/opener/common/opener.js"; -import { - IQuickInputService, - IQuickPickItem, - QuickPickInput, -} from "../../../../platform/quickinput/common/quickInput.js"; -import { ITelemetryService } from "../../../../platform/telemetry/common/telemetry.js"; -import { IThemeService } from "../../../../platform/theme/common/themeService.js"; -import { IResourceLabel, ResourceLabels } from "../../../browser/labels.js"; -import { - IViewPaneOptions, - ViewAction, - ViewPane, -} from "../../../browser/parts/views/viewPane.js"; -import { IViewDescriptorService } from "../../../common/views.js"; -import { - ACTIVE_GROUP, - IEditorService, - SIDE_GROUP, -} from "../../../services/editor/common/editorService.js"; -import { TestCommandId, Testing } from "../common/constants.js"; -import { onObservableChange } from "../common/observableUtils.js"; -import { - BypassedFileCoverage, - ComputedFileCoverage, - FileCoverage, - getTotalCoveragePercent, - TestCoverage, -} from "../common/testCoverage.js"; -import { ITestCoverageService } from "../common/testCoverageService.js"; -import { TestId } from "../common/testId.js"; -import { TestingContextKeys } from "../common/testingContextKeys.js"; -import { - CoverageDetails, - DetailType, - ICoverageCount, - IDeclarationCoverage, - TestResultState, -} from "../common/testTypes.js"; -import * as coverUtils from "./codeCoverageDisplayUtils.js"; -import { testingStatesToIcons, testingWasCovered } from "./icons.js"; -import { - CoverageBarSource, - ManagedTestCoverageBars, -} from "./testCoverageBars.js"; + +import * as dom from '../../../../base/browser/dom.js'; +import { IIdentityProvider, IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js'; +import { ICompressedTreeElement, ICompressedTreeNode } from '../../../../base/browser/ui/tree/compressedObjectTreeModel.js'; +import { ICompressibleTreeRenderer } from '../../../../base/browser/ui/tree/objectTree.js'; +import { ITreeNode, ITreeSorter } from '../../../../base/browser/ui/tree/tree.js'; +import { findLast } from '../../../../base/common/arraysFind.js'; +import { assertNever } from '../../../../base/common/assert.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { memoize } from '../../../../base/common/decorators.js'; +import { FuzzyScore, createMatches } from '../../../../base/common/filters.js'; +import { Iterable } from '../../../../base/common/iterator.js'; +import { Disposable, DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js'; +import { IObservable, autorun, observableValue } from '../../../../base/common/observable.js'; +import { IPrefixTreeNode } from '../../../../base/common/prefixTree.js'; +import { basenameOrAuthority } from '../../../../base/common/resources.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { URI } from '../../../../base/common/uri.js'; +import { Position } from '../../../../editor/common/core/position.js'; +import { Range } from '../../../../editor/common/core/range.js'; +import { localize, localize2 } from '../../../../nls.js'; +import { Categories } from '../../../../platform/action/common/actionCommonCategories.js'; +import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; +import { EditorOpenSource, TextEditorSelectionRevealType } from '../../../../platform/editor/common/editor.js'; +import { FileKind } from '../../../../platform/files/common/files.js'; +import { IHoverService } from '../../../../platform/hover/browser/hover.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { ILabelService } from '../../../../platform/label/common/label.js'; +import { WorkbenchCompressibleObjectTree } from '../../../../platform/list/browser/listService.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; +import { IQuickInputService, IQuickPickItem, QuickPickInput } from '../../../../platform/quickinput/common/quickInput.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { IResourceLabel, ResourceLabels } from '../../../browser/labels.js'; +import { IViewPaneOptions, ViewAction, ViewPane } from '../../../browser/parts/views/viewPane.js'; +import { IViewDescriptorService } from '../../../common/views.js'; +import * as coverUtils from './codeCoverageDisplayUtils.js'; +import { testingStatesToIcons, testingWasCovered } from './icons.js'; +import { CoverageBarSource, ManagedTestCoverageBars } from './testCoverageBars.js'; +import { TestCommandId, Testing } from '../common/constants.js'; +import { onObservableChange } from '../common/observableUtils.js'; +import { BypassedFileCoverage, ComputedFileCoverage, FileCoverage, TestCoverage, getTotalCoveragePercent } from '../common/testCoverage.js'; +import { ITestCoverageService } from '../common/testCoverageService.js'; +import { TestId } from '../common/testId.js'; +import { TestingContextKeys } from '../common/testingContextKeys.js'; +import { CoverageDetails, DetailType, ICoverageCount, IDeclarationCoverage, TestResultState } from '../common/testTypes.js'; +import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from '../../../services/editor/common/editorService.js'; const enum CoverageSortOrder { Coverage, Location, Name, } + export class TestCoverageView extends ViewPane { private readonly tree = new MutableDisposable(); - public readonly sortOrder = observableValue( - "sortOrder", - CoverageSortOrder.Location, - ); + public readonly sortOrder = observableValue('sortOrder', CoverageSortOrder.Location); constructor( options: IViewPaneOptions, - @IKeybindingService - keybindingService: IKeybindingService, - @IContextMenuService - contextMenuService: IContextMenuService, - @IConfigurationService - configurationService: IConfigurationService, - @IContextKeyService - contextKeyService: IContextKeyService, - @IViewDescriptorService - viewDescriptorService: IViewDescriptorService, - @IInstantiationService - instantiationService: IInstantiationService, - @IOpenerService - openerService: IOpenerService, - @IThemeService - themeService: IThemeService, - @ITelemetryService - telemetryService: ITelemetryService, - @IHoverService - hoverService: IHoverService, - @ITestCoverageService - private readonly coverageService: ITestCoverageService, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IConfigurationService configurationService: IConfigurationService, + @IContextKeyService contextKeyService: IContextKeyService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IInstantiationService instantiationService: IInstantiationService, + @IOpenerService openerService: IOpenerService, + @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, + @IHoverService hoverService: IHoverService, + @ITestCoverageService private readonly coverageService: ITestCoverageService, ) { - super( - options, - keybindingService, - contextMenuService, - configurationService, - contextKeyService, - viewDescriptorService, - instantiationService, - openerService, - themeService, - telemetryService, - hoverService, - ); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService); } + protected override renderBody(container: HTMLElement): void { super.renderBody(container); - const labels = this._register( - this.instantiationService.createInstance(ResourceLabels, { - onDidChangeVisibility: this.onDidChangeBodyVisibility, - }), - ); - this._register( - autorun((reader) => { - const coverage = this.coverageService.selected.read(reader); - - if (coverage) { - const t = (this.tree.value ??= - this.instantiationService.createInstance( - TestCoverageTree, - container, - labels, - this.sortOrder, - )); - t.setInput( - coverage, - this.coverageService.filterToTest.read(reader), - ); - } else { - this.tree.clear(); - } - }), - ); - this._register( - autorun((reader) => { - this.element.classList.toggle( - "coverage-view-is-filtered", - !!this.coverageService.filterToTest.read(reader), - ); - }), - ); + const labels = this._register(this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility })); + + this._register(autorun(reader => { + const coverage = this.coverageService.selected.read(reader); + if (coverage) { + const t = (this.tree.value ??= this.instantiationService.createInstance(TestCoverageTree, container, labels, this.sortOrder)); + t.setInput(coverage, this.coverageService.filterToTest.read(reader)); + } else { + this.tree.clear(); + } + })); + + this._register(autorun(reader => { + this.element.classList.toggle( + 'coverage-view-is-filtered', + !!this.coverageService.filterToTest.read(reader), + ); + })); } + protected override layoutBody(height: number, width: number): void { super.layoutBody(height, width); this.tree.value?.layout(height, width); } } + let fnNodeId = 0; + class DeclarationCoverageNode { public readonly id = String(fnNodeId++); public readonly containedDetails = new Set(); public readonly children: DeclarationCoverageNode[] = []; + public get hits() { return this.data.count; } + public get label() { return this.data.name; } + public get location() { return this.data.location; } + public get tpc() { const attr = this.attributableCoverage(); - - return ( - attr && - getTotalCoveragePercent(attr.statement, attr.branch, undefined) - ); + return attr && getTotalCoveragePercent(attr.statement, attr.branch, undefined); } + constructor( public readonly uri: URI, private readonly data: IDeclarationCoverage, @@ -239,17 +147,13 @@ class DeclarationCoverageNode { } } } + /** Gets whether this function has a defined range and contains the given range. */ public contains(location: Range | Position) { const own = this.data.location; - - return ( - own instanceof Range && - (location instanceof Range - ? own.containsRange(location) - : own.containsPosition(location)) - ); + return own instanceof Range && (location instanceof Range ? own.containsRange(location) : own.containsPosition(location)); } + /** * If the function defines a range, we can look at statements within the * function to get total coverage for the function, rather than a boolean @@ -258,21 +162,19 @@ class DeclarationCoverageNode { @memoize public attributableCoverage() { const { location, count } = this.data; - if (!(location instanceof Range) || !count) { return; } - const statement: ICoverageCount = { covered: 0, total: 0 }; + const statement: ICoverageCount = { covered: 0, total: 0 }; const branch: ICoverageCount = { covered: 0, total: 0 }; - for (const detail of this.containedDetails) { if (detail.type !== DetailType.Statement) { continue; } + statement.covered += detail.count ? 1 : 0; statement.total++; - if (detail.branches) { for (const { count } of detail.branches) { branch.covered += count ? 1 : 0; @@ -280,94 +182,64 @@ class DeclarationCoverageNode { } } } + return { statement, branch } satisfies CoverageBarSource; } } + class RevealUncoveredDeclarations { public readonly id = String(fnNodeId++); + public get label() { - return localize( - "functionsWithoutCoverage", - "{0} declarations without coverage...", - this.n, - ); + return localize('functionsWithoutCoverage', "{0} declarations without coverage...", this.n); } - constructor(public readonly n: number) {} + + constructor(public readonly n: number) { } } + class LoadingDetails { public readonly id = String(fnNodeId++); - public readonly label = localize( - "loadingCoverageDetails", - "Loading Coverage Details...", - ); + public readonly label = localize('loadingCoverageDetails', "Loading Coverage Details..."); } + /** Type of nodes returned from {@link TestCoverage}. Note: value is *always* defined. */ -type TestCoverageFileNode = IPrefixTreeNode< - ComputedFileCoverage | FileCoverage ->; -type CoverageTreeElement = - | TestCoverageFileNode - | DeclarationCoverageNode - | LoadingDetails - | RevealUncoveredDeclarations; - -const isFileCoverage = (c: CoverageTreeElement): c is TestCoverageFileNode => - typeof c === "object" && "value" in c; - -const isDeclarationCoverage = ( - c: CoverageTreeElement, -): c is DeclarationCoverageNode => c instanceof DeclarationCoverageNode; - -const shouldShowDeclDetailsOnExpand = ( - c: CoverageTreeElement, -): c is IPrefixTreeNode => - isFileCoverage(c) && - c.value instanceof FileCoverage && - !!c.value.declaration?.total; +type TestCoverageFileNode = IPrefixTreeNode; +type CoverageTreeElement = TestCoverageFileNode | DeclarationCoverageNode | LoadingDetails | RevealUncoveredDeclarations; + +const isFileCoverage = (c: CoverageTreeElement): c is TestCoverageFileNode => typeof c === 'object' && 'value' in c; +const isDeclarationCoverage = (c: CoverageTreeElement): c is DeclarationCoverageNode => c instanceof DeclarationCoverageNode; +const shouldShowDeclDetailsOnExpand = (c: CoverageTreeElement): c is IPrefixTreeNode => + isFileCoverage(c) && c.value instanceof FileCoverage && !!c.value.declaration?.total; + class TestCoverageTree extends Disposable { - private readonly tree: WorkbenchCompressibleObjectTree< - CoverageTreeElement, - void - >; + private readonly tree: WorkbenchCompressibleObjectTree; private readonly inputDisposables = this._register(new DisposableStore()); constructor( container: HTMLElement, labels: ResourceLabels, sortOrder: IObservable, - @IInstantiationService - instantiationService: IInstantiationService, - @IEditorService - editorService: IEditorService, + @IInstantiationService instantiationService: IInstantiationService, + @IEditorService editorService: IEditorService, ) { super(); - this.tree = < - WorkbenchCompressibleObjectTree - >instantiationService.createInstance( - WorkbenchCompressibleObjectTree, - "TestCoverageView", + + this.tree = instantiationService.createInstance( + WorkbenchCompressibleObjectTree, + 'TestCoverageView', container, new TestCoverageTreeListDelegate(), [ - instantiationService.createInstance( - FileCoverageRenderer, - labels, - ), - instantiationService.createInstance( - DeclarationCoverageRenderer, - ), + instantiationService.createInstance(FileCoverageRenderer, labels), + instantiationService.createInstance(DeclarationCoverageRenderer), instantiationService.createInstance(BasicRenderer), ], { expandOnlyOnTwistieClick: true, sorter: new Sorter(sortOrder), keyboardNavigationLabelProvider: { - getCompressedNodeKeyboardNavigationLabel( - elements: CoverageTreeElement[], - ) { - return elements - .map((e) => this.getKeyboardNavigationLabel(e)) - .join("/"); + getCompressedNodeKeyboardNavigationLabel(elements: CoverageTreeElement[]) { + return elements.map(e => this.getKeyboardNavigationLabel(e)).join('/'); }, getKeyboardNavigationLabel(e: CoverageTreeElement) { return isFileCoverage(e) @@ -378,210 +250,155 @@ class TestCoverageTree extends Disposable { accessibilityProvider: { getAriaLabel(element: CoverageTreeElement) { if (isFileCoverage(element)) { - const name = basenameOrAuthority( - element.value!.uri, - ); - - return localize( - "testCoverageItemLabel", - "{0} coverage: {0}%", - name, - (element.value!.tpc * 100).toFixed(2), - ); + const name = basenameOrAuthority(element.value!.uri); + return localize('testCoverageItemLabel', "{0} coverage: {0}%", name, (element.value!.tpc * 100).toFixed(2)); } else { return element.label; } }, getWidgetAriaLabel() { - return localize( - "testCoverageTreeLabel", - "Test Coverage Explorer", - ); - }, + return localize('testCoverageTreeLabel', "Test Coverage Explorer"); + } }, identityProvider: new TestCoverageIdentityProvider(), - }, - ); - this._register( - autorun((reader) => { - sortOrder.read(reader); - this.tree.resort(null, true); - }), + } ); + + this._register(autorun(reader => { + sortOrder.read(reader); + this.tree.resort(null, true); + })); + this._register(this.tree); - this._register( - this.tree.onDidChangeCollapseState((e) => { - const el = e.node.element; - - if ( - !e.node.collapsed && - !e.node.children.length && - el && - shouldShowDeclDetailsOnExpand(el) - ) { - if (el.value!.hasSynchronousDetails) { - this.tree.setChildren(el, [ - { - element: new LoadingDetails(), - incompressible: true, - }, - ]); - } - el.value!.details().then((details) => - this.updateWithDetails(el, details), - ); - } - }), - ); - this._register( - this.tree.onDidOpen((e) => { - let resource: URI | undefined; - - let selection: Range | Position | undefined; - - if (e.element) { - if ( - isFileCoverage(e.element) && - !e.element.children?.size - ) { - resource = e.element.value!.uri; - } else if (isDeclarationCoverage(e.element)) { - resource = e.element.uri; - selection = e.element.location; - } + this._register(this.tree.onDidChangeCollapseState(e => { + const el = e.node.element; + if (!e.node.collapsed && !e.node.children.length && el && shouldShowDeclDetailsOnExpand(el)) { + if (el.value!.hasSynchronousDetails) { + this.tree.setChildren(el, [{ element: new LoadingDetails(), incompressible: true }]); } - if (!resource) { - return; + + el.value!.details().then(details => this.updateWithDetails(el, details)); + } + })); + this._register(this.tree.onDidOpen(e => { + let resource: URI | undefined; + let selection: Range | Position | undefined; + if (e.element) { + if (isFileCoverage(e.element) && !e.element.children?.size) { + resource = e.element.value!.uri; + } else if (isDeclarationCoverage(e.element)) { + resource = e.element.uri; + selection = e.element.location; } - editorService.openEditor( - { - resource, - options: { - selection: - selection instanceof Position - ? Range.fromPositions(selection, selection) - : selection, - revealIfOpened: true, - selectionRevealType: - TextEditorSelectionRevealType.NearTopIfOutsideViewport, - preserveFocus: e.editorOptions.preserveFocus, - pinned: e.editorOptions.pinned, - source: EditorOpenSource.USER, - }, - }, - e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP, - ); - }), - ); + } + if (!resource) { + return; + } + + editorService.openEditor({ + resource, + options: { + selection: selection instanceof Position ? Range.fromPositions(selection, selection) : selection, + revealIfOpened: true, + selectionRevealType: TextEditorSelectionRevealType.NearTopIfOutsideViewport, + preserveFocus: e.editorOptions.preserveFocus, + pinned: e.editorOptions.pinned, + source: EditorOpenSource.USER, + }, + }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + })); } + public setInput(coverage: TestCoverage, showOnlyTest?: TestId) { this.inputDisposables.clear(); let tree = coverage.tree; + // Filter to only a test, generate a new tree with only those items selected if (showOnlyTest) { tree = coverage.filterTreeForTest(showOnlyTest); } - const files: TestCoverageFileNode[] = []; + const files: TestCoverageFileNode[] = []; for (let node of tree.nodes) { // when showing initial children, only show from the first file or tee - while ( - !(node.value instanceof FileCoverage) && - node.children?.size === 1 - ) { + while (!(node.value instanceof FileCoverage) && node.children?.size === 1) { node = Iterable.first(node.children.values())!; } files.push(node); } - const toChild = ( - value: TestCoverageFileNode, - ): ICompressedTreeElement => { - const isFile = !value.children?.size; + const toChild = (value: TestCoverageFileNode): ICompressedTreeElement => { + const isFile = !value.children?.size; return { element: value, incompressible: isFile, collapsed: isFile, // directories can be expanded, and items with function info can be expanded collapsible: !isFile || !!value.value?.declaration?.total, - children: - value.children && - Iterable.map(value.children?.values(), toChild), + children: value.children && Iterable.map(value.children?.values(), toChild) }; }; - this.inputDisposables.add( - onObservableChange(coverage.didAddCoverage, (nodes) => { - const toRender = findLast(nodes, (n) => - this.tree.hasElement(n), + + this.inputDisposables.add(onObservableChange(coverage.didAddCoverage, nodes => { + const toRender = findLast(nodes, n => this.tree.hasElement(n)); + if (toRender) { + this.tree.setChildren( + toRender, + Iterable.map(toRender.children?.values() || [], toChild), + { diffIdentityProvider: { getId: el => (el as TestCoverageFileNode).value!.id } } ); + } + })); - if (toRender) { - this.tree.setChildren( - toRender, - Iterable.map( - toRender.children?.values() || [], - toChild, - ), - { - diffIdentityProvider: { - getId: (el) => - (el as TestCoverageFileNode).value!.id, - }, - }, - ); - } - }), - ); this.tree.setChildren(null, Iterable.map(files, toChild)); } + public layout(height: number, width: number) { this.tree.layout(height, width); } - private updateWithDetails( - el: IPrefixTreeNode, - details: readonly CoverageDetails[], - ) { + + private updateWithDetails(el: IPrefixTreeNode, details: readonly CoverageDetails[]) { if (!this.tree.hasElement(el)) { return; // avoid any issues if the tree changes in the meanwhile } - const decl: DeclarationCoverageNode[] = []; + const decl: DeclarationCoverageNode[] = []; for (const fn of details) { if (fn.type !== DetailType.Declaration) { continue; } - let arr = decl; + let arr = decl; while (true) { - const parent = arr.find((p) => p.containedDetails.has(fn)); - + const parent = arr.find(p => p.containedDetails.has(fn)); if (parent) { arr = parent.children; } else { break; } } + arr.push(new DeclarationCoverageNode(el.value!.uri, fn, details)); } - const makeChild = ( - fn: DeclarationCoverageNode, - ): ICompressedTreeElement => ({ + + const makeChild = (fn: DeclarationCoverageNode): ICompressedTreeElement => ({ element: fn, incompressible: true, collapsed: true, collapsible: fn.children.length > 0, - children: fn.children.map(makeChild), + children: fn.children.map(makeChild) }); + this.tree.setChildren(el, decl.map(makeChild)); } } -class TestCoverageTreeListDelegate - implements IListVirtualDelegate -{ + +class TestCoverageTreeListDelegate implements IListVirtualDelegate { getHeight(element: CoverageTreeElement): number { return 22; } + getTemplateId(element: CoverageTreeElement): string { if (isFileCoverage(element)) { return FileCoverageRenderer.ID; @@ -589,28 +406,22 @@ class TestCoverageTreeListDelegate if (isDeclarationCoverage(element)) { return DeclarationCoverageRenderer.ID; } - if ( - element instanceof LoadingDetails || - element instanceof RevealUncoveredDeclarations - ) { + if (element instanceof LoadingDetails || element instanceof RevealUncoveredDeclarations) { return BasicRenderer.ID; } assertNever(element); } } + class Sorter implements ITreeSorter { - constructor(private readonly order: IObservable) {} + constructor(private readonly order: IObservable) { } compare(a: CoverageTreeElement, b: CoverageTreeElement): number { const order = this.order.get(); - if (isFileCoverage(a) && isFileCoverage(b)) { switch (order) { case CoverageSortOrder.Location: case CoverageSortOrder.Name: - return a - .value!.uri.toString() - .localeCompare(b.value!.uri.toString()); - + return a.value!.uri.toString().localeCompare(b.value!.uri.toString()); case CoverageSortOrder.Coverage: return b.value!.tpc - a.value!.tpc; } @@ -618,29 +429,17 @@ class Sorter implements ITreeSorter { switch (order) { case CoverageSortOrder.Location: return Position.compare( - a.location instanceof Range - ? a.location.getStartPosition() - : a.location, - b.location instanceof Range - ? b.location.getStartPosition() - : b.location, + a.location instanceof Range ? a.location.getStartPosition() : a.location, + b.location instanceof Range ? b.location.getStartPosition() : b.location, ); - case CoverageSortOrder.Name: return a.label.localeCompare(b.label); - case CoverageSortOrder.Coverage: { const attrA = a.tpc; - const attrB = b.tpc; - - return ( - (attrA !== undefined && - attrB !== undefined && - attrB - attrA) || - +b.hits - +a.hits || - a.label.localeCompare(b.label) - ); + return (attrA !== undefined && attrB !== undefined && attrB - attrA) + || (+b.hits - +a.hits) + || a.label.localeCompare(b.label); } } } else { @@ -648,6 +447,7 @@ class Sorter implements ITreeSorter { } } } + interface FileTemplateData { container: HTMLElement; bars: ManagedTestCoverageBars; @@ -655,117 +455,74 @@ interface FileTemplateData { elementsDisposables: DisposableStore; label: IResourceLabel; } -class FileCoverageRenderer - implements - ICompressibleTreeRenderer< - CoverageTreeElement, - FuzzyScore, - FileTemplateData - > -{ - public static readonly ID = "F"; + +class FileCoverageRenderer implements ICompressibleTreeRenderer { + public static readonly ID = 'F'; public readonly templateId = FileCoverageRenderer.ID; constructor( private readonly labels: ResourceLabels, - @ILabelService - private readonly labelService: ILabelService, - @IInstantiationService - private readonly instantiationService: IInstantiationService, - ) {} + @ILabelService private readonly labelService: ILabelService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { } + /** @inheritdoc */ public renderTemplate(container: HTMLElement): FileTemplateData { const templateDisposables = new DisposableStore(); - container.classList.add("test-coverage-list-item"); + container.classList.add('test-coverage-list-item'); return { container, - bars: templateDisposables.add( - this.instantiationService.createInstance( - ManagedTestCoverageBars, - { compact: false, container }, - ), - ), - label: templateDisposables.add( - this.labels.create(container, { - supportHighlights: true, - }), - ), + bars: templateDisposables.add(this.instantiationService.createInstance(ManagedTestCoverageBars, { compact: false, container })), + label: templateDisposables.add(this.labels.create(container, { + supportHighlights: true, + })), elementsDisposables: templateDisposables.add(new DisposableStore()), templateDisposables, }; } + /** @inheritdoc */ - public renderElement( - node: ITreeNode, - _index: number, - templateData: FileTemplateData, - ): void { - this.doRender( - node.element as TestCoverageFileNode, - templateData, - node.filterData, - ); + public renderElement(node: ITreeNode, _index: number, templateData: FileTemplateData): void { + this.doRender(node.element as TestCoverageFileNode, templateData, node.filterData); } + /** @inheritdoc */ - public renderCompressedElements( - node: ITreeNode, FuzzyScore>, - _index: number, - templateData: FileTemplateData, - ): void { + public renderCompressedElements(node: ITreeNode, FuzzyScore>, _index: number, templateData: FileTemplateData): void { this.doRender(node.element.elements, templateData, node.filterData); } + public disposeTemplate(templateData: FileTemplateData) { templateData.templateDisposables.dispose(); } + /** @inheritdoc */ - private doRender( - element: CoverageTreeElement | CoverageTreeElement[], - templateData: FileTemplateData, - filterData: FuzzyScore | undefined, - ) { + private doRender(element: CoverageTreeElement | CoverageTreeElement[], templateData: FileTemplateData, filterData: FuzzyScore | undefined) { templateData.elementsDisposables.clear(); - const stat = ( - element instanceof Array ? element[element.length - 1] : element - ) as TestCoverageFileNode; - + const stat = (element instanceof Array ? element[element.length - 1] : element) as TestCoverageFileNode; const file = stat.value!; - - const name = - element instanceof Array - ? element.map((e) => - basenameOrAuthority( - (e as TestCoverageFileNode).value!.uri, - ), - ) - : basenameOrAuthority(file.uri); - + const name = element instanceof Array ? element.map(e => basenameOrAuthority((e as TestCoverageFileNode).value!.uri)) : basenameOrAuthority(file.uri); if (file instanceof BypassedFileCoverage) { templateData.bars.setCoverageInfo(undefined); } else { - templateData.elementsDisposables.add( - autorun((reader) => { - stat.value?.didChange.read(reader); - templateData.bars.setCoverageInfo(file); - }), - ); + templateData.elementsDisposables.add(autorun(reader => { + stat.value?.didChange.read(reader); + templateData.bars.setCoverageInfo(file); + })); + templateData.bars.setCoverageInfo(file); } - templateData.label.setResource( - { resource: file.uri, name }, - { - fileKind: stat.children?.size ? FileKind.FOLDER : FileKind.FILE, - matches: createMatches(filterData), - separator: this.labelService.getSeparator( - file.uri.scheme, - file.uri.authority, - ), - extraClasses: ["test-coverage-list-item-label"], - }, - ); + + templateData.label.setResource({ resource: file.uri, name }, { + fileKind: stat.children?.size ? FileKind.FOLDER : FileKind.FILE, + matches: createMatches(filterData), + separator: this.labelService.getSeparator(file.uri.scheme, file.uri.authority), + extraClasses: ['test-coverage-list-item-label'], + }); } } + interface DeclarationTemplateData { container: HTMLElement; bars: ManagedTestCoverageBars; @@ -773,311 +530,181 @@ interface DeclarationTemplateData { icon: HTMLElement; label: HTMLElement; } -class DeclarationCoverageRenderer - implements - ICompressibleTreeRenderer< - CoverageTreeElement, - FuzzyScore, - DeclarationTemplateData - > -{ - public static readonly ID = "N"; + +class DeclarationCoverageRenderer implements ICompressibleTreeRenderer { + public static readonly ID = 'N'; public readonly templateId = DeclarationCoverageRenderer.ID; constructor( - @IInstantiationService - private readonly instantiationService: IInstantiationService, - ) {} + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { } + /** @inheritdoc */ public renderTemplate(container: HTMLElement): DeclarationTemplateData { const templateDisposables = new DisposableStore(); - container.classList.add("test-coverage-list-item"); - - const icon = dom.append(container, dom.$(".state")); - - const label = dom.append(container, dom.$(".name")); + container.classList.add('test-coverage-list-item'); + const icon = dom.append(container, dom.$('.state')); + const label = dom.append(container, dom.$('.name')); return { container, - bars: templateDisposables.add( - this.instantiationService.createInstance( - ManagedTestCoverageBars, - { compact: false, container }, - ), - ), + bars: templateDisposables.add(this.instantiationService.createInstance(ManagedTestCoverageBars, { compact: false, container })), templateDisposables, icon, label, }; } + /** @inheritdoc */ - public renderElement( - node: ITreeNode, - _index: number, - templateData: DeclarationTemplateData, - ): void { - this.doRender( - node.element as DeclarationCoverageNode, - templateData, - node.filterData, - ); + public renderElement(node: ITreeNode, _index: number, templateData: DeclarationTemplateData): void { + this.doRender(node.element as DeclarationCoverageNode, templateData, node.filterData); } + /** @inheritdoc */ - public renderCompressedElements( - node: ITreeNode, FuzzyScore>, - _index: number, - templateData: DeclarationTemplateData, - ): void { - this.doRender( - node.element.elements[ - node.element.elements.length - 1 - ] as DeclarationCoverageNode, - templateData, - node.filterData, - ); + public renderCompressedElements(node: ITreeNode, FuzzyScore>, _index: number, templateData: DeclarationTemplateData): void { + this.doRender(node.element.elements[node.element.elements.length - 1] as DeclarationCoverageNode, templateData, node.filterData); } + public disposeTemplate(templateData: DeclarationTemplateData) { templateData.templateDisposables.dispose(); } + /** @inheritdoc */ - private doRender( - element: DeclarationCoverageNode, - templateData: DeclarationTemplateData, - _filterData: FuzzyScore | undefined, - ) { + private doRender(element: DeclarationCoverageNode, templateData: DeclarationTemplateData, _filterData: FuzzyScore | undefined) { const covered = !!element.hits; - - const icon = covered - ? testingWasCovered - : testingStatesToIcons.get(TestResultState.Unset); - templateData.container.classList.toggle("not-covered", !covered); + const icon = covered ? testingWasCovered : testingStatesToIcons.get(TestResultState.Unset); + templateData.container.classList.toggle('not-covered', !covered); templateData.icon.className = `computed-state ${ThemeIcon.asClassName(icon!)}`; templateData.label.innerText = element.label; templateData.bars.setCoverageInfo(element.attributableCoverage()); } } -class BasicRenderer - implements - ICompressibleTreeRenderer -{ - public static readonly ID = "B"; + +class BasicRenderer implements ICompressibleTreeRenderer { + public static readonly ID = 'B'; public readonly templateId = BasicRenderer.ID; - renderCompressedElements( - node: ITreeNode, FuzzyScore>, - _index: number, - container: HTMLElement, - ): void { - this.renderInner( - node.element.elements[node.element.elements.length - 1], - container, - ); + + renderCompressedElements(node: ITreeNode, FuzzyScore>, _index: number, container: HTMLElement): void { + this.renderInner(node.element.elements[node.element.elements.length - 1], container); } + renderTemplate(container: HTMLElement): HTMLElement { return container; } - renderElement( - node: ITreeNode, - index: number, - container: HTMLElement, - ): void { + + renderElement(node: ITreeNode, index: number, container: HTMLElement): void { this.renderInner(node.element, container); } + disposeTemplate(): void { // no-op } + private renderInner(element: CoverageTreeElement, container: HTMLElement) { - container.innerText = ( - element as RevealUncoveredDeclarations | LoadingDetails - ).label; + container.innerText = (element as RevealUncoveredDeclarations | LoadingDetails).label; } } -class TestCoverageIdentityProvider - implements IIdentityProvider -{ + +class TestCoverageIdentityProvider implements IIdentityProvider { public getId(element: CoverageTreeElement) { return isFileCoverage(element) ? element.value!.uri.toString() : element.id; } } -registerAction2( - class TestCoverageChangePerTestFilterAction extends Action2 { - constructor() { - super({ - id: TestCommandId.CoverageFilterToTest, - category: Categories.Test, - title: localize2( - "testing.changeCoverageFilter", - "Filter Coverage by Test", - ), - icon: Codicon.filter, - toggled: { - icon: Codicon.filterFilled, - condition: TestingContextKeys.isCoverageFilteredToTest, - }, - menu: [ - { - id: MenuId.CommandPalette, - when: TestingContextKeys.hasPerTestCoverage, - }, - { - id: MenuId.ViewTitle, - when: ContextKeyExpr.and( - TestingContextKeys.hasPerTestCoverage, - ContextKeyExpr.equals( - "view", - Testing.CoverageViewId, - ), - ), - group: "navigation", - }, - ], - }); - } - override run(accessor: ServicesAccessor): void { - const coverageService = accessor.get(ITestCoverageService); - - const quickInputService = accessor.get(IQuickInputService); - - const coverage = coverageService.selected.get(); - - if (!coverage) { - return; - } - const tests = [...coverage.allPerTestIDs()].map(TestId.fromString); - - const commonPrefix = TestId.getLengthOfCommonPrefix( - tests.length, - (i) => tests[i], - ); - - const result = coverage.result; - - const previousSelection = coverageService.filterToTest.get(); - const previousSelectionStr = previousSelection?.toString(); - type TItem = { - label: string; - testId?: TestId; - }; - - const items: QuickPickInput[] = [ - { label: coverUtils.labels.allTests, id: undefined }, - { type: "separator" }, - ...tests.map((testId) => ({ - label: coverUtils.getLabelForItem( - result, - testId, - commonPrefix, - ), - testId, - })), - ]; - quickInputService - .pick(items, { - activeItem: items.find( - (item): item is TItem => - "testId" in item && - item.testId?.toString() === previousSelectionStr, - ), - placeHolder: coverUtils.labels.pickShowCoverage, - onDidFocus: (entry) => { - coverageService.filterToTest.set( - entry.testId, - undefined, - ); - }, - }) - .then((selected) => { - coverageService.filterToTest.set( - selected ? selected.testId : previousSelection, - undefined, - ); - }); - } - }, -); -registerAction2( - class TestCoverageChangeSortingAction extends ViewAction { - constructor() { - super({ - id: TestCommandId.CoverageViewChangeSorting, - viewId: Testing.CoverageViewId, - title: localize2( - "testing.changeCoverageSort", - "Change Sort Order", - ), - icon: Codicon.sortPrecedence, - menu: { +registerAction2(class TestCoverageChangePerTestFilterAction extends Action2 { + constructor() { + super({ + id: TestCommandId.CoverageFilterToTest, + category: Categories.Test, + title: localize2('testing.changeCoverageFilter', 'Filter Coverage by Test'), + icon: Codicon.filter, + toggled: { + icon: Codicon.filterFilled, + condition: TestingContextKeys.isCoverageFilteredToTest, + }, + menu: [ + { id: MenuId.CommandPalette, when: TestingContextKeys.hasPerTestCoverage }, + { id: MenuId.ViewTitle, - when: ContextKeyExpr.equals("view", Testing.CoverageViewId), - group: "navigation", + when: ContextKeyExpr.and(TestingContextKeys.hasPerTestCoverage, ContextKeyExpr.equals('view', Testing.CoverageViewId)), + group: 'navigation', }, - }); - } - override runInView(accessor: ServicesAccessor, view: TestCoverageView) { - type Item = IQuickPickItem & { - value: CoverageSortOrder; - }; + ] + }); + } - const disposables = new DisposableStore(); + override run(accessor: ServicesAccessor): void { + const coverageService = accessor.get(ITestCoverageService); + const quickInputService = accessor.get(IQuickInputService); + const coverage = coverageService.selected.get(); + if (!coverage) { + return; + } - const quickInput = disposables.add( - accessor.get(IQuickInputService).createQuickPick(), - ); + const tests = [...coverage.allPerTestIDs()].map(TestId.fromString); + const commonPrefix = TestId.getLengthOfCommonPrefix(tests.length, i => tests[i]); + const result = coverage.result; + const previousSelection = coverageService.filterToTest.get(); + const previousSelectionStr = previousSelection?.toString(); + + type TItem = { label: string; testId?: TestId }; + + const items: QuickPickInput[] = [ + { label: coverUtils.labels.allTests, id: undefined }, + { type: 'separator' }, + ...tests.map(testId => ({ label: coverUtils.getLabelForItem(result, testId, commonPrefix), testId })), + ]; + + quickInputService.pick(items, { + activeItem: items.find((item): item is TItem => 'testId' in item && item.testId?.toString() === previousSelectionStr), + placeHolder: coverUtils.labels.pickShowCoverage, + onDidFocus: (entry) => { + coverageService.filterToTest.set(entry.testId, undefined); + }, + }).then(selected => { + coverageService.filterToTest.set(selected ? selected.testId : previousSelection, undefined); + }); + } +}); + +registerAction2(class TestCoverageChangeSortingAction extends ViewAction { + constructor() { + super({ + id: TestCommandId.CoverageViewChangeSorting, + viewId: Testing.CoverageViewId, + title: localize2('testing.changeCoverageSort', 'Change Sort Order'), + icon: Codicon.sortPrecedence, + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.equals('view', Testing.CoverageViewId), + group: 'navigation', + } + }); + } - const items: Item[] = [ - { - label: localize( - "testing.coverageSortByLocation", - "Sort by Location", - ), - value: CoverageSortOrder.Location, - description: localize( - "testing.coverageSortByLocationDescription", - "Files are sorted alphabetically, declarations are sorted by position", - ), - }, - { - label: localize( - "testing.coverageSortByCoverage", - "Sort by Coverage", - ), - value: CoverageSortOrder.Coverage, - description: localize( - "testing.coverageSortByCoverageDescription", - "Files and declarations are sorted by total coverage", - ), - }, - { - label: localize( - "testing.coverageSortByName", - "Sort by Name", - ), - value: CoverageSortOrder.Name, - description: localize( - "testing.coverageSortByNameDescription", - "Files and declarations are sorted alphabetically", - ), - }, - ]; - quickInput.placeholder = localize( - "testing.coverageSortPlaceholder", - "Sort the Test Coverage view...", - ); - quickInput.items = items; - quickInput.show(); - disposables.add(quickInput.onDidHide(() => disposables.dispose())); - disposables.add( - quickInput.onDidAccept(() => { - const picked = quickInput.selectedItems[0]?.value; - - if (picked !== undefined) { - view.sortOrder.set(picked, undefined); - quickInput.dispose(); - } - }), - ); - } - }, -); + override runInView(accessor: ServicesAccessor, view: TestCoverageView) { + type Item = IQuickPickItem & { value: CoverageSortOrder }; + + const disposables = new DisposableStore(); + const quickInput = disposables.add(accessor.get(IQuickInputService).createQuickPick()); + const items: Item[] = [ + { label: localize('testing.coverageSortByLocation', 'Sort by Location'), value: CoverageSortOrder.Location, description: localize('testing.coverageSortByLocationDescription', 'Files are sorted alphabetically, declarations are sorted by position') }, + { label: localize('testing.coverageSortByCoverage', 'Sort by Coverage'), value: CoverageSortOrder.Coverage, description: localize('testing.coverageSortByCoverageDescription', 'Files and declarations are sorted by total coverage') }, + { label: localize('testing.coverageSortByName', 'Sort by Name'), value: CoverageSortOrder.Name, description: localize('testing.coverageSortByNameDescription', 'Files and declarations are sorted alphabetically') }, + ]; + + quickInput.placeholder = localize('testing.coverageSortPlaceholder', 'Sort the Test Coverage view...'); + quickInput.items = items; + quickInput.show(); + disposables.add(quickInput.onDidHide(() => disposables.dispose())); + disposables.add(quickInput.onDidAccept(() => { + const picked = quickInput.selectedItems[0]?.value; + if (picked !== undefined) { + view.sortOrder.set(picked, undefined); + quickInput.dispose(); + } + })); + } +}); diff --git a/Source/vs/workbench/contrib/testing/browser/testing.contribution.ts b/Source/vs/workbench/contrib/testing/browser/testing.contribution.ts index 7129a972ffc02..57543747b1394 100644 --- a/Source/vs/workbench/contrib/testing/browser/testing.contribution.ts +++ b/Source/vs/workbench/contrib/testing/browser/testing.contribution.ts @@ -2,252 +2,134 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { - EditorContributionInstantiation, - registerEditorContribution, -} from "../../../../editor/browser/editorExtensions.js"; -import { localize, localize2 } from "../../../../nls.js"; -import { registerAction2 } from "../../../../platform/actions/common/actions.js"; -import { - CommandsRegistry, - ICommandService, -} from "../../../../platform/commands/common/commands.js"; -import { - Extensions as ConfigurationExtensions, - IConfigurationRegistry, -} from "../../../../platform/configuration/common/configurationRegistry.js"; -import { ContextKeyExpr } from "../../../../platform/contextkey/common/contextkey.js"; -import { IFileService } from "../../../../platform/files/common/files.js"; -import { SyncDescriptor } from "../../../../platform/instantiation/common/descriptors.js"; -import { - InstantiationType, - registerSingleton, -} from "../../../../platform/instantiation/common/extensions.js"; -import { ServicesAccessor } from "../../../../platform/instantiation/common/instantiation.js"; -import { IOpenerService } from "../../../../platform/opener/common/opener.js"; -import { IProgressService } from "../../../../platform/progress/common/progress.js"; -import { Registry } from "../../../../platform/registry/common/platform.js"; -import { ViewPaneContainer } from "../../../browser/parts/views/viewPaneContainer.js"; -import { - IWorkbenchContributionsRegistry, - Extensions as WorkbenchExtensions, -} from "../../../common/contributions.js"; -import { - IViewContainersRegistry, - IViewsRegistry, - Extensions as ViewContainerExtensions, - ViewContainerLocation, -} from "../../../common/views.js"; -import { LifecyclePhase } from "../../../services/lifecycle/common/lifecycle.js"; -import { IViewsService } from "../../../services/views/common/viewsService.js"; -import { REVEAL_IN_EXPLORER_COMMAND_ID } from "../../files/browser/fileConstants.js"; -import { testingConfiguration } from "../common/configuration.js"; -import { TestCommandId, Testing } from "../common/constants.js"; -import { - ITestCoverageService, - TestCoverageService, -} from "../common/testCoverageService.js"; -import { - ITestExplorerFilterState, - TestExplorerFilterState, -} from "../common/testExplorerFilterState.js"; -import { TestId, TestPosition } from "../common/testId.js"; -import { TestingContentProvider } from "../common/testingContentProvider.js"; -import { TestingContextKeys } from "../common/testingContextKeys.js"; -import { - ITestingContinuousRunService, - TestingContinuousRunService, -} from "../common/testingContinuousRunService.js"; -import { ITestingDecorationsService } from "../common/testingDecorations.js"; -import { ITestingPeekOpener } from "../common/testingPeekOpener.js"; -import { - ITestProfileService, - TestProfileService, -} from "../common/testProfileService.js"; -import { - ITestResultService, - TestResultService, -} from "../common/testResultService.js"; -import { - ITestResultStorage, - TestResultStorage, -} from "../common/testResultStorage.js"; -import { ITestService } from "../common/testService.js"; -import { TestService } from "../common/testServiceImpl.js"; -import { ITestItem, TestRunProfileBitset } from "../common/testTypes.js"; -import { CodeCoverageDecorations } from "./codeCoverageDecorations.js"; -import { testingResultsIcon, testingViewIcon } from "./icons.js"; -import { TestCoverageView } from "./testCoverageView.js"; -import { allTestActions, discoverAndRunTests } from "./testExplorerActions.js"; -import { - TestingDecorations, - TestingDecorationService, -} from "./testingDecorations.js"; -import { TestingExplorerView } from "./testingExplorerView.js"; -import { - CloseTestPeek, - CollapsePeekStack, - GoToNextMessageAction, - GoToPreviousMessageAction, - OpenMessageInEditorAction, - TestingOutputPeekController, - TestingPeekOpener, - TestResultsView, - ToggleTestingPeekHistory, -} from "./testingOutputPeek.js"; -import { TestingProgressTrigger } from "./testingProgressUiService.js"; -import { TestingViewPaneContainer } from "./testingViewPaneContainer.js"; -import "./testingConfigurationUi.js"; +import { EditorContributionInstantiation, registerEditorContribution } from '../../../../editor/browser/editorExtensions.js'; +import { localize, localize2 } from '../../../../nls.js'; +import { registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { CommandsRegistry, ICommandService } from '../../../../platform/commands/common/commands.js'; +import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js'; +import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; +import { IFileService } from '../../../../platform/files/common/files.js'; +import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; +import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; +import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; +import { IProgressService } from '../../../../platform/progress/common/progress.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { ViewPaneContainer } from '../../../browser/parts/views/viewPaneContainer.js'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from '../../../common/contributions.js'; +import { IViewContainersRegistry, IViewsRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation } from '../../../common/views.js'; +import { REVEAL_IN_EXPLORER_COMMAND_ID } from '../../files/browser/fileConstants.js'; +import { CodeCoverageDecorations } from './codeCoverageDecorations.js'; +import { testingResultsIcon, testingViewIcon } from './icons.js'; +import { TestCoverageView } from './testCoverageView.js'; +import { TestingDecorationService, TestingDecorations } from './testingDecorations.js'; +import { TestingExplorerView } from './testingExplorerView.js'; +import { CloseTestPeek, CollapsePeekStack, GoToNextMessageAction, GoToPreviousMessageAction, OpenMessageInEditorAction, TestResultsView, TestingOutputPeekController, TestingPeekOpener, ToggleTestingPeekHistory } from './testingOutputPeek.js'; +import { TestingProgressTrigger } from './testingProgressUiService.js'; +import { TestingViewPaneContainer } from './testingViewPaneContainer.js'; +import { testingConfiguration } from '../common/configuration.js'; +import { TestCommandId, Testing } from '../common/constants.js'; +import { ITestCoverageService, TestCoverageService } from '../common/testCoverageService.js'; +import { ITestExplorerFilterState, TestExplorerFilterState } from '../common/testExplorerFilterState.js'; +import { TestId, TestPosition } from '../common/testId.js'; +import { canUseProfileWithTest, ITestProfileService, TestProfileService } from '../common/testProfileService.js'; +import { ITestResultService, TestResultService } from '../common/testResultService.js'; +import { ITestResultStorage, TestResultStorage } from '../common/testResultStorage.js'; +import { ITestService } from '../common/testService.js'; +import { TestService } from '../common/testServiceImpl.js'; +import { ITestItem, ITestRunProfileReference, TestRunProfileBitset } from '../common/testTypes.js'; +import { TestingContentProvider } from '../common/testingContentProvider.js'; +import { TestingContextKeys } from '../common/testingContextKeys.js'; +import { ITestingContinuousRunService, TestingContinuousRunService } from '../common/testingContinuousRunService.js'; +import { ITestingDecorationsService } from '../common/testingDecorations.js'; +import { ITestingPeekOpener } from '../common/testingPeekOpener.js'; +import { LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js'; +import { IViewsService } from '../../../services/views/common/viewsService.js'; +import { allTestActions, discoverAndRunTests } from './testExplorerActions.js'; +import './testingConfigurationUi.js'; registerSingleton(ITestService, TestService, InstantiationType.Delayed); -registerSingleton( - ITestResultStorage, - TestResultStorage, - InstantiationType.Delayed, -); -registerSingleton( - ITestProfileService, - TestProfileService, - InstantiationType.Delayed, -); -registerSingleton( - ITestCoverageService, - TestCoverageService, - InstantiationType.Delayed, -); -registerSingleton( - ITestingContinuousRunService, - TestingContinuousRunService, - InstantiationType.Delayed, -); -registerSingleton( - ITestResultService, - TestResultService, - InstantiationType.Delayed, -); -registerSingleton( - ITestExplorerFilterState, - TestExplorerFilterState, - InstantiationType.Delayed, -); -registerSingleton( - ITestingPeekOpener, - TestingPeekOpener, - InstantiationType.Delayed, -); -registerSingleton( - ITestingDecorationsService, - TestingDecorationService, - InstantiationType.Delayed, -); +registerSingleton(ITestResultStorage, TestResultStorage, InstantiationType.Delayed); +registerSingleton(ITestProfileService, TestProfileService, InstantiationType.Delayed); +registerSingleton(ITestCoverageService, TestCoverageService, InstantiationType.Delayed); +registerSingleton(ITestingContinuousRunService, TestingContinuousRunService, InstantiationType.Delayed); +registerSingleton(ITestResultService, TestResultService, InstantiationType.Delayed); +registerSingleton(ITestExplorerFilterState, TestExplorerFilterState, InstantiationType.Delayed); +registerSingleton(ITestingPeekOpener, TestingPeekOpener, InstantiationType.Delayed); +registerSingleton(ITestingDecorationsService, TestingDecorationService, InstantiationType.Delayed); -const viewContainer = Registry.as( - ViewContainerExtensions.ViewContainersRegistry, -).registerViewContainer( - { +const viewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ + id: Testing.ViewletId, + title: localize2('test', 'Testing'), + ctorDescriptor: new SyncDescriptor(TestingViewPaneContainer), + icon: testingViewIcon, + alwaysUseContainerInfo: true, + order: 6, + openCommandActionDescriptor: { id: Testing.ViewletId, - title: localize2("test", "Testing"), - ctorDescriptor: new SyncDescriptor(TestingViewPaneContainer), - icon: testingViewIcon, - alwaysUseContainerInfo: true, - order: 6, - openCommandActionDescriptor: { - id: Testing.ViewletId, - mnemonicTitle: localize( - { key: "miViewTesting", comment: ["&& denotes a mnemonic"] }, - "T&&esting", - ), - // todo: coordinate with joh whether this is available - // keybindings: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_SEMICOLON }, - order: 4, - }, - hideIfEmpty: true, + mnemonicTitle: localize({ key: 'miViewTesting', comment: ['&& denotes a mnemonic'] }, "T&&esting"), + // todo: coordinate with joh whether this is available + // keybindings: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_SEMICOLON }, + order: 4, }, - ViewContainerLocation.Sidebar, -); + hideIfEmpty: true, +}, ViewContainerLocation.Sidebar); -const testResultsViewContainer = Registry.as( - ViewContainerExtensions.ViewContainersRegistry, -).registerViewContainer( - { - id: Testing.ResultsPanelId, - title: localize2("testResultsPanelName", "Test Results"), - icon: testingResultsIcon, - ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [ - Testing.ResultsPanelId, - { mergeViewWithContainerWhenSingleView: true }, - ]), - hideIfEmpty: true, - order: 3, - }, - ViewContainerLocation.Panel, - { doNotRegisterOpenCommand: true }, -); -const viewsRegistry = Registry.as( - ViewContainerExtensions.ViewsRegistry, -); -viewsRegistry.registerViews( - [ - { - id: Testing.ResultsViewId, - name: localize2("testResultsPanelName", "Test Results"), - containerIcon: testingResultsIcon, - canToggleVisibility: false, - canMoveView: true, - when: TestingContextKeys.hasAnyResults.isEqualTo(true), - ctorDescriptor: new SyncDescriptor(TestResultsView), - }, - ], - testResultsViewContainer, -); +const testResultsViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ + id: Testing.ResultsPanelId, + title: localize2('testResultsPanelName', "Test Results"), + icon: testingResultsIcon, + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [Testing.ResultsPanelId, { mergeViewWithContainerWhenSingleView: true }]), + hideIfEmpty: true, + order: 3, +}, ViewContainerLocation.Panel, { doNotRegisterOpenCommand: true }); + +const viewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); + + +viewsRegistry.registerViews([{ + id: Testing.ResultsViewId, + name: localize2('testResultsPanelName', "Test Results"), + containerIcon: testingResultsIcon, + canToggleVisibility: false, + canMoveView: true, + when: TestingContextKeys.hasAnyResults.isEqualTo(true), + ctorDescriptor: new SyncDescriptor(TestResultsView), +}], testResultsViewContainer); + viewsRegistry.registerViewWelcomeContent(Testing.ExplorerViewId, { - content: localize( - "noTestProvidersRegistered", - "No tests have been found in this workspace yet.", - ), + content: localize('noTestProvidersRegistered', "No tests have been found in this workspace yet."), }); + viewsRegistry.registerViewWelcomeContent(Testing.ExplorerViewId, { - content: - "[" + - localize( - "searchForAdditionalTestExtensions", - "Install Additional Test Extensions...", - ) + - `](command:${TestCommandId.SearchForTestExtension})`, - order: 10, + content: '[' + localize('searchForAdditionalTestExtensions', "Install Additional Test Extensions...") + `](command:${TestCommandId.SearchForTestExtension})`, + order: 10 }); -viewsRegistry.registerViews( - [ - { - id: Testing.ExplorerViewId, - name: localize2("testExplorer", "Test Explorer"), - ctorDescriptor: new SyncDescriptor(TestingExplorerView), - canToggleVisibility: true, - canMoveView: true, - weight: 80, - order: -999, - containerIcon: testingViewIcon, - when: ContextKeyExpr.greater( - TestingContextKeys.providerCount.key, - 0, - ), - }, - { - id: Testing.CoverageViewId, - name: localize2("testCoverage", "Test Coverage"), - ctorDescriptor: new SyncDescriptor(TestCoverageView), - canToggleVisibility: true, - canMoveView: true, - weight: 80, - order: -998, - containerIcon: testingViewIcon, - when: TestingContextKeys.isTestCoverageOpen, - }, - ], - viewContainer, -); + +viewsRegistry.registerViews([{ + id: Testing.ExplorerViewId, + name: localize2('testExplorer', "Test Explorer"), + ctorDescriptor: new SyncDescriptor(TestingExplorerView), + canToggleVisibility: true, + canMoveView: true, + weight: 80, + order: -999, + containerIcon: testingViewIcon, + when: ContextKeyExpr.greater(TestingContextKeys.providerCount.key, 0), +}, { + id: Testing.CoverageViewId, + name: localize2('testCoverage', "Test Coverage"), + ctorDescriptor: new SyncDescriptor(TestCoverageView), + canToggleVisibility: true, + canMoveView: true, + weight: 80, + order: -998, + containerIcon: testingViewIcon, + when: TestingContextKeys.isTestCoverageOpen, +}], viewContainer); + allTestActions.forEach(registerAction2); registerAction2(OpenMessageInEditorAction); registerAction2(GoToPreviousMessageAction); @@ -255,117 +137,99 @@ registerAction2(GoToNextMessageAction); registerAction2(CloseTestPeek); registerAction2(ToggleTestingPeekHistory); registerAction2(CollapsePeekStack); -Registry.as( - WorkbenchExtensions.Workbench, -).registerWorkbenchContribution( - TestingContentProvider, - LifecyclePhase.Restored, -); -Registry.as( - WorkbenchExtensions.Workbench, -).registerWorkbenchContribution(TestingPeekOpener, LifecyclePhase.Eventually); -Registry.as( - WorkbenchExtensions.Workbench, -).registerWorkbenchContribution( - TestingProgressTrigger, - LifecyclePhase.Eventually, -); -registerEditorContribution( - Testing.OutputPeekContributionId, - TestingOutputPeekController, - EditorContributionInstantiation.AfterFirstRender, -); -registerEditorContribution( - Testing.DecorationsContributionId, - TestingDecorations, - EditorContributionInstantiation.AfterFirstRender, -); -registerEditorContribution( - Testing.CoverageDecorationsContributionId, - CodeCoverageDecorations, - EditorContributionInstantiation.Eventually, -); + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TestingContentProvider, LifecyclePhase.Restored); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TestingPeekOpener, LifecyclePhase.Eventually); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TestingProgressTrigger, LifecyclePhase.Eventually); + +registerEditorContribution(Testing.OutputPeekContributionId, TestingOutputPeekController, EditorContributionInstantiation.AfterFirstRender); +registerEditorContribution(Testing.DecorationsContributionId, TestingDecorations, EditorContributionInstantiation.AfterFirstRender); +registerEditorContribution(Testing.CoverageDecorationsContributionId, CodeCoverageDecorations, EditorContributionInstantiation.Eventually); + CommandsRegistry.registerCommand({ - id: "_revealTestInExplorer", - handler: async ( - accessor: ServicesAccessor, - testId: string | ITestItem, - focus?: boolean, - ) => { - accessor - .get(ITestExplorerFilterState) - .reveal.set( - typeof testId === "string" ? testId : testId.extId, - undefined, - ); + id: '_revealTestInExplorer', + handler: async (accessor: ServicesAccessor, testId: string | ITestItem, focus?: boolean) => { + accessor.get(ITestExplorerFilterState).reveal.set(typeof testId === 'string' ? testId : testId.extId, undefined); accessor.get(IViewsService).openView(Testing.ExplorerViewId, focus); - }, + } }); CommandsRegistry.registerCommand({ - id: "vscode.peekTestError", + id: TestCommandId.StartContinousRunFromExtension, + handler: async (accessor: ServicesAccessor, profileRef: ITestRunProfileReference, tests: readonly ITestItem[]) => { + const profiles = accessor.get(ITestProfileService); + const collection = accessor.get(ITestService).collection; + const profile = profiles.getControllerProfiles(profileRef.controllerId).find(p => p.profileId === profileRef.profileId); + if (!profile?.supportsContinuousRun) { + return; + } + + const crService = accessor.get(ITestingContinuousRunService); + for (const test of tests) { + const found = collection.getNodeById(test.extId); + if (found && canUseProfileWithTest(profile, found)) { + crService.start([profile], found.item.extId); + } + } + } +}); +CommandsRegistry.registerCommand({ + id: TestCommandId.StopContinousRunFromExtension, + handler: async (accessor: ServicesAccessor, tests: readonly ITestItem[]) => { + const crService = accessor.get(ITestingContinuousRunService); + for (const test of tests) { + crService.stop(test.extId); + } + } +}); + +CommandsRegistry.registerCommand({ + id: 'vscode.peekTestError', handler: async (accessor: ServicesAccessor, extId: string) => { const lookup = accessor.get(ITestResultService).getStateById(extId); - if (!lookup) { return false; } - const [result, ownState] = lookup; + const [result, ownState] = lookup; const opener = accessor.get(ITestingPeekOpener); - - if (opener.tryPeekFirstError(result, ownState)) { - // fast path + if (opener.tryPeekFirstError(result, ownState)) { // fast path return true; } + for (const test of result.tests) { - if ( - TestId.compare(ownState.item.extId, test.item.extId) === - TestPosition.IsChild && - opener.tryPeekFirstError(result, test) - ) { + if (TestId.compare(ownState.item.extId, test.item.extId) === TestPosition.IsChild && opener.tryPeekFirstError(result, test)) { return true; } } + return false; - }, + } }); + CommandsRegistry.registerCommand({ - id: "vscode.revealTest", - handler: async ( - accessor: ServicesAccessor, - extId: string, - opts?: { preserveFocus?: boolean; openToSide?: boolean }, - ) => { + id: 'vscode.revealTest', + handler: async (accessor: ServicesAccessor, extId: string, opts?: { preserveFocus?: boolean; openToSide?: boolean }) => { const test = accessor.get(ITestService).collection.getNodeById(extId); - if (!test) { return; } const commandService = accessor.get(ICommandService); - const fileService = accessor.get(IFileService); - const openerService = accessor.get(IOpenerService); const { range, uri } = test.item; - if (!uri) { return; } // If an editor has the file open, there are decorations. Try to adjust the // revealed range to those decorations (#133441). - const position = - accessor - .get(ITestingDecorationsService) - .getDecoratedTestPosition(uri, extId) || - range?.getStartPosition(); + const position = accessor.get(ITestingDecorationsService).getDecoratedTestPosition(uri, extId) || range?.getStartPosition(); accessor.get(ITestExplorerFilterState).reveal.set(extId, undefined); accessor.get(ITestingPeekOpener).closeAllPeeks(); let isFile = true; - try { if (!(await fileService.stat(uri)).isFile) { isFile = false; @@ -375,55 +239,45 @@ CommandsRegistry.registerCommand({ } if (!isFile) { - await commandService.executeCommand( - REVEAL_IN_EXPLORER_COMMAND_ID, - uri, - ); - + await commandService.executeCommand(REVEAL_IN_EXPLORER_COMMAND_ID, uri); return; } - await openerService.open( - position - ? uri.with({ - fragment: `L${position.lineNumber}:${position.column}`, - }) - : uri, + await openerService.open(position + ? uri.with({ fragment: `L${position.lineNumber}:${position.column}` }) + : uri, { openToSide: opts?.openToSide, editorOptions: { preserveFocus: opts?.preserveFocus, - }, - }, + } + } ); - }, + } }); + CommandsRegistry.registerCommand({ - id: "vscode.runTestsById", - handler: async ( - accessor: ServicesAccessor, - group: TestRunProfileBitset, - ...testIds: string[] - ) => { + id: 'vscode.runTestsById', + handler: async (accessor: ServicesAccessor, group: TestRunProfileBitset, ...testIds: string[]) => { const testService = accessor.get(ITestService); await discoverAndRunTests( accessor.get(ITestService).collection, accessor.get(IProgressService), testIds, - (tests) => testService.runTests({ group, tests }), + tests => testService.runTests({ group, tests }), ); - }, + } }); + CommandsRegistry.registerCommand({ - id: "vscode.testing.getControllersWithTests", + id: 'vscode.testing.getControllersWithTests', handler: async (accessor: ServicesAccessor) => { const testService = accessor.get(ITestService); - return [...testService.collection.rootItems] - .filter((r) => r.children.size > 0) - .map((r) => r.controllerId); - }, + .filter(r => r.children.size > 0) + .map(r => r.controllerId); + } }); -Registry.as( - ConfigurationExtensions.Configuration, -).registerConfiguration(testingConfiguration); + +Registry.as(ConfigurationExtensions.Configuration).registerConfiguration(testingConfiguration); + diff --git a/Source/vs/workbench/contrib/testing/common/constants.ts b/Source/vs/workbench/contrib/testing/common/constants.ts index a9dbd1b19a8f3..36d66ae584a1e 100644 --- a/Source/vs/workbench/contrib/testing/common/constants.ts +++ b/Source/vs/workbench/contrib/testing/common/constants.ts @@ -2,121 +2,120 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { stripIcons } from "../../../../base/common/iconLabels.js"; -import { localize } from "../../../../nls.js"; -import { TestResultState, TestRunProfileBitset } from "./testTypes.js"; + +import { stripIcons } from '../../../../base/common/iconLabels.js'; +import { localize } from '../../../../nls.js'; +import { TestResultState, TestRunProfileBitset } from './testTypes.js'; export const enum Testing { // marked as "extension" so that any existing test extensions are assigned to it. - ViewletId = "workbench.view.extension.test", - ExplorerViewId = "workbench.view.testing", - OutputPeekContributionId = "editor.contrib.testingOutputPeek", - DecorationsContributionId = "editor.contrib.testingDecorations", - CoverageDecorationsContributionId = "editor.contrib.coverageDecorations", - CoverageViewId = "workbench.view.testCoverage", - ResultsPanelId = "workbench.panel.testResults", - ResultsViewId = "workbench.panel.testResults.view", - MessageLanguageId = "vscodeInternalTestMessage", + ViewletId = 'workbench.view.extension.test', + ExplorerViewId = 'workbench.view.testing', + OutputPeekContributionId = 'editor.contrib.testingOutputPeek', + DecorationsContributionId = 'editor.contrib.testingDecorations', + CoverageDecorationsContributionId = 'editor.contrib.coverageDecorations', + CoverageViewId = 'workbench.view.testCoverage', + + ResultsPanelId = 'workbench.panel.testResults', + ResultsViewId = 'workbench.panel.testResults.view', + + MessageLanguageId = 'vscodeInternalTestMessage' } + export const enum TestExplorerViewMode { - List = "list", - Tree = "true", + List = 'list', + Tree = 'true' } + export const enum TestExplorerViewSorting { - ByLocation = "location", - ByStatus = "status", - ByDuration = "duration", + ByLocation = 'location', + ByStatus = 'status', + ByDuration = 'duration', } -const testStateNames: { - [K in TestResultState]: string; -} = { - [TestResultState.Errored]: localize("testState.errored", "Errored"), - [TestResultState.Failed]: localize("testState.failed", "Failed"), - [TestResultState.Passed]: localize("testState.passed", "Passed"), - [TestResultState.Queued]: localize("testState.queued", "Queued"), - [TestResultState.Running]: localize("testState.running", "Running"), - [TestResultState.Skipped]: localize("testState.skipped", "Skipped"), - [TestResultState.Unset]: localize("testState.unset", "Not yet run"), + +const testStateNames: { [K in TestResultState]: string } = { + [TestResultState.Errored]: localize('testState.errored', 'Errored'), + [TestResultState.Failed]: localize('testState.failed', 'Failed'), + [TestResultState.Passed]: localize('testState.passed', 'Passed'), + [TestResultState.Queued]: localize('testState.queued', 'Queued'), + [TestResultState.Running]: localize('testState.running', 'Running'), + [TestResultState.Skipped]: localize('testState.skipped', 'Skipped'), + [TestResultState.Unset]: localize('testState.unset', 'Not yet run'), }; -export const labelForTestInState = (label: string, state: TestResultState) => - localize( - { - key: "testing.treeElementLabel", - comment: [ - 'label then the unit tests state, for example "Addition Tests (Running)"', - ], - }, - "{0} ({1})", - stripIcons(label), - testStateNames[state], - ); -export const testConfigurationGroupNames: Partial< - Record -> = { - [TestRunProfileBitset.Debug]: localize("testGroup.debug", "Debug"), - [TestRunProfileBitset.Run]: localize("testGroup.run", "Run"), - [TestRunProfileBitset.Coverage]: localize("testGroup.coverage", "Coverage"), + +export const labelForTestInState = (label: string, state: TestResultState) => localize({ + key: 'testing.treeElementLabel', + comment: ['label then the unit tests state, for example "Addition Tests (Running)"'], +}, '{0} ({1})', stripIcons(label), testStateNames[state]); + +export const testConfigurationGroupNames: Partial> = { + [TestRunProfileBitset.Debug]: localize('testGroup.debug', 'Debug'), + [TestRunProfileBitset.Run]: localize('testGroup.run', 'Run'), + [TestRunProfileBitset.Coverage]: localize('testGroup.coverage', 'Coverage'), }; + export const enum TestCommandId { - CancelTestRefreshAction = "testing.cancelTestRefresh", - CancelTestRunAction = "testing.cancelRun", - ClearTestResultsAction = "testing.clearTestResults", - CollapseAllAction = "testing.collapseAll", - ConfigureTestProfilesAction = "testing.configureProfile", - ContinousRunUsingForTest = "testing.continuousRunUsingForTest", - CoverageAtCursor = "testing.coverageAtCursor", - CoverageByUri = "testing.coverage.uri", - CoverageClear = "testing.coverage.close", - CoverageCurrentFile = "testing.coverageCurrentFile", - CoverageFilterToTest = "testing.coverageFilterToTest", - CoverageFilterToTestInEditor = "testing.coverageFilterToTestInEditor", - CoverageLastRun = "testing.coverageLastRun", - CoverageSelectedAction = "testing.coverageSelected", - CoverageToggleToolbar = "testing.coverageToggleToolbar", - CoverageViewChangeSorting = "testing.coverageViewChangeSorting", - DebugAction = "testing.debug", - DebugAllAction = "testing.debugAll", - DebugAtCursor = "testing.debugAtCursor", - DebugByUri = "testing.debug.uri", - DebugCurrentFile = "testing.debugCurrentFile", - DebugFailedTests = "testing.debugFailTests", - DebugLastRun = "testing.debugLastRun", - DebugSelectedAction = "testing.debugSelected", - FilterAction = "workbench.actions.treeView.testExplorer.filter", - GetExplorerSelection = "_testing.getExplorerSelection", - GetSelectedProfiles = "testing.getSelectedProfiles", - GoToTest = "testing.editFocusedTest", - GoToRelatedTest = "testing.goToRelatedTest", - PeekRelatedTest = "testing.peekRelatedTest", - GoToRelatedCode = "testing.goToRelatedCode", - PeekRelatedCode = "testing.peekRelatedCode", - HideTestAction = "testing.hideTest", - OpenCoverage = "testing.openCoverage", - OpenOutputPeek = "testing.openOutputPeek", - RefreshTestsAction = "testing.refreshTests", - ReRunFailedTests = "testing.reRunFailTests", - ReRunLastRun = "testing.reRunLastRun", - RunAction = "testing.run", - RunAllAction = "testing.runAll", - RunAllWithCoverageAction = "testing.coverageAll", - RunAtCursor = "testing.runAtCursor", - RunByUri = "testing.run.uri", - RunCurrentFile = "testing.runCurrentFile", - RunSelectedAction = "testing.runSelected", - RunUsingProfileAction = "testing.runUsing", - RunWithCoverageAction = "testing.coverage", - SearchForTestExtension = "testing.searchForTestExtension", - SelectDefaultTestProfiles = "testing.selectDefaultTestProfiles", - ShowMostRecentOutputAction = "testing.showMostRecentOutput", - StartContinousRun = "testing.startContinuousRun", - StopContinousRun = "testing.stopContinuousRun", - TestingSortByDurationAction = "testing.sortByDuration", - TestingSortByLocationAction = "testing.sortByLocation", - TestingSortByStatusAction = "testing.sortByStatus", - TestingViewAsListAction = "testing.viewAsList", - TestingViewAsTreeAction = "testing.viewAsTree", - ToggleContinousRunForTest = "testing.toggleContinuousRunForTest", - ToggleInlineTestOutput = "testing.toggleInlineTestOutput", - UnhideAllTestsAction = "testing.unhideAllTests", - UnhideTestAction = "testing.unhideTest", + CancelTestRefreshAction = 'testing.cancelTestRefresh', + CancelTestRunAction = 'testing.cancelRun', + ClearTestResultsAction = 'testing.clearTestResults', + CollapseAllAction = 'testing.collapseAll', + ConfigureTestProfilesAction = 'testing.configureProfile', + ContinousRunUsingForTest = 'testing.continuousRunUsingForTest', + CoverageAtCursor = 'testing.coverageAtCursor', + CoverageByUri = 'testing.coverage.uri', + CoverageClear = 'testing.coverage.close', + CoverageCurrentFile = 'testing.coverageCurrentFile', + CoverageFilterToTest = 'testing.coverageFilterToTest', + CoverageFilterToTestInEditor = 'testing.coverageFilterToTestInEditor', + CoverageLastRun = 'testing.coverageLastRun', + CoverageSelectedAction = 'testing.coverageSelected', + CoverageToggleToolbar = 'testing.coverageToggleToolbar', + CoverageViewChangeSorting = 'testing.coverageViewChangeSorting', + DebugAction = 'testing.debug', + DebugAllAction = 'testing.debugAll', + DebugAtCursor = 'testing.debugAtCursor', + DebugByUri = 'testing.debug.uri', + DebugCurrentFile = 'testing.debugCurrentFile', + DebugFailedTests = 'testing.debugFailTests', + DebugLastRun = 'testing.debugLastRun', + DebugSelectedAction = 'testing.debugSelected', + FilterAction = 'workbench.actions.treeView.testExplorer.filter', + GetExplorerSelection = '_testing.getExplorerSelection', + GetSelectedProfiles = 'testing.getSelectedProfiles', + GoToTest = 'testing.editFocusedTest', + GoToRelatedTest = 'testing.goToRelatedTest', + PeekRelatedTest = 'testing.peekRelatedTest', + GoToRelatedCode = 'testing.goToRelatedCode', + PeekRelatedCode = 'testing.peekRelatedCode', + HideTestAction = 'testing.hideTest', + OpenCoverage = 'testing.openCoverage', + OpenOutputPeek = 'testing.openOutputPeek', + RefreshTestsAction = 'testing.refreshTests', + ReRunFailedTests = 'testing.reRunFailTests', + ReRunLastRun = 'testing.reRunLastRun', + RunAction = 'testing.run', + RunAllAction = 'testing.runAll', + RunAllWithCoverageAction = 'testing.coverageAll', + RunAtCursor = 'testing.runAtCursor', + RunByUri = 'testing.run.uri', + RunCurrentFile = 'testing.runCurrentFile', + RunSelectedAction = 'testing.runSelected', + RunUsingProfileAction = 'testing.runUsing', + RunWithCoverageAction = 'testing.coverage', + SearchForTestExtension = 'testing.searchForTestExtension', + SelectDefaultTestProfiles = 'testing.selectDefaultTestProfiles', + ShowMostRecentOutputAction = 'testing.showMostRecentOutput', + StartContinousRun = 'testing.startContinuousRun', + StartContinousRunFromExtension = 'testing.startContinuousRunFromExtension', + StopContinousRunFromExtension = 'testing.stopContinuousRunFromExtension', + StopContinousRun = 'testing.stopContinuousRun', + TestingSortByDurationAction = 'testing.sortByDuration', + TestingSortByLocationAction = 'testing.sortByLocation', + TestingSortByStatusAction = 'testing.sortByStatus', + TestingViewAsListAction = 'testing.viewAsList', + TestingViewAsTreeAction = 'testing.viewAsTree', + ToggleContinousRunForTest = 'testing.toggleContinuousRunForTest', + ToggleInlineTestOutput = 'testing.toggleInlineTestOutput', + UnhideAllTestsAction = 'testing.unhideAllTests', + UnhideTestAction = 'testing.unhideTest', } diff --git a/Source/vs/workbench/contrib/testing/common/testTypes.ts b/Source/vs/workbench/contrib/testing/common/testTypes.ts index 37887e9efbfca..ba0aa36308ae1 100644 --- a/Source/vs/workbench/contrib/testing/common/testTypes.ts +++ b/Source/vs/workbench/contrib/testing/common/testTypes.ts @@ -75,6 +75,13 @@ export interface ITestRunProfile { hasConfigurationHandler: boolean; supportsContinuousRun: boolean; } + +export interface ITestRunProfileReference { + controllerId: string; + profileId: number; + group: TestRunProfileBitset; +} + /** * A fully-resolved request to run tests, passsed between the main thread * and extension host. diff --git a/Source/vs/workbench/contrib/timeline/browser/timelinePane.ts b/Source/vs/workbench/contrib/timeline/browser/timelinePane.ts index 31b574d372afd..a62b323be7cc8 100644 --- a/Source/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/Source/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -3,155 +3,78 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import "./media/timelinePane.css"; - -import * as css from "../../../../base/browser/cssValue.js"; -import * as DOM from "../../../../base/browser/dom.js"; -import { renderMarkdownAsPlaintext } from "../../../../base/browser/markdownRenderer.js"; -import { - ActionBar, - IActionViewItemProvider, -} from "../../../../base/browser/ui/actionbar/actionbar.js"; -import { ActionViewItem } from "../../../../base/browser/ui/actionbar/actionViewItems.js"; -import { AriaRole } from "../../../../base/browser/ui/aria/aria.js"; -import { IHoverDelegate } from "../../../../base/browser/ui/hover/hoverDelegate.js"; -import { HoverPosition } from "../../../../base/browser/ui/hover/hoverWidget.js"; -import { IconLabel } from "../../../../base/browser/ui/iconLabel/iconLabel.js"; -import { - IIdentityProvider, - IKeyboardNavigationLabelProvider, - IListVirtualDelegate, -} from "../../../../base/browser/ui/list/list.js"; -import { - ITreeContextMenuEvent, - ITreeElement, - ITreeNode, - ITreeRenderer, -} from "../../../../base/browser/ui/tree/tree.js"; -import { ActionRunner, IAction } from "../../../../base/common/actions.js"; -import { CancellationTokenSource } from "../../../../base/common/cancellation.js"; -import { Codicon } from "../../../../base/common/codicons.js"; -import { fromNow } from "../../../../base/common/date.js"; -import { debounce } from "../../../../base/common/decorators.js"; -import { Emitter, Event } from "../../../../base/common/event.js"; -import { createMatches, FuzzyScore } from "../../../../base/common/filters.js"; -import { Iterable } from "../../../../base/common/iterator.js"; -import { - Disposable, - DisposableStore, - IDisposable, -} from "../../../../base/common/lifecycle.js"; -import { MarshalledId } from "../../../../base/common/marshallingIds.js"; -import { Schemas } from "../../../../base/common/network.js"; -import { escapeRegExpCharacters } from "../../../../base/common/strings.js"; -import { ThemeIcon } from "../../../../base/common/themables.js"; -import { isString } from "../../../../base/common/types.js"; -import { URI } from "../../../../base/common/uri.js"; -import { localize, localize2 } from "../../../../nls.js"; -import { ILocalizedString } from "../../../../platform/action/common/action.js"; -import { - createActionViewItem, - getContextMenuActions, -} from "../../../../platform/actions/browser/menuEntryActionViewItem.js"; -import { - Action2, - IMenuService, - MenuId, - MenuRegistry, - registerAction2, -} from "../../../../platform/actions/common/actions.js"; -import { - CommandsRegistry, - ICommandService, -} from "../../../../platform/commands/common/commands.js"; -import { - IConfigurationChangeEvent, - IConfigurationService, -} from "../../../../platform/configuration/common/configuration.js"; -import { - ContextKeyExpr, - IContextKey, - IContextKeyService, - RawContextKey, -} from "../../../../platform/contextkey/common/contextkey.js"; -import { IContextMenuService } from "../../../../platform/contextview/browser/contextView.js"; -import { - IHoverService, - WorkbenchHoverDelegate, -} from "../../../../platform/hover/browser/hover.js"; -import { - IInstantiationService, - ServicesAccessor, -} from "../../../../platform/instantiation/common/instantiation.js"; -import { IKeybindingService } from "../../../../platform/keybinding/common/keybinding.js"; -import { ILabelService } from "../../../../platform/label/common/label.js"; -import { WorkbenchObjectTree } from "../../../../platform/list/browser/listService.js"; -import { IOpenerService } from "../../../../platform/opener/common/opener.js"; -import { IProgressService } from "../../../../platform/progress/common/progress.js"; -import { - IStorageService, - StorageScope, - StorageTarget, -} from "../../../../platform/storage/common/storage.js"; -import { ITelemetryService } from "../../../../platform/telemetry/common/telemetry.js"; -import { registerIcon } from "../../../../platform/theme/common/iconRegistry.js"; -import { ColorScheme } from "../../../../platform/theme/common/theme.js"; -import { IThemeService } from "../../../../platform/theme/common/themeService.js"; -import { IUriIdentityService } from "../../../../platform/uriIdentity/common/uriIdentity.js"; -import { - API_OPEN_DIFF_EDITOR_COMMAND_ID, - API_OPEN_EDITOR_COMMAND_ID, -} from "../../../browser/parts/editor/editorCommands.js"; -import { - IViewPaneOptions, - ViewPane, -} from "../../../browser/parts/views/viewPane.js"; -import { - EditorResourceAccessor, - SideBySideEditor, -} from "../../../common/editor.js"; -import { IViewDescriptorService } from "../../../common/views.js"; -import { IEditorService } from "../../../services/editor/common/editorService.js"; -import { IExtensionService } from "../../../services/extensions/common/extensions.js"; -import { - ITimelineService, - Timeline, - TimelineChangeEvent, - TimelineItem, - TimelineOptions, - TimelineProvidersChangeEvent, - TimelineRequest, -} from "../common/timeline.js"; +import './media/timelinePane.css'; +import { localize, localize2 } from '../../../../nls.js'; +import * as DOM from '../../../../base/browser/dom.js'; +import * as css from '../../../../base/browser/cssValue.js'; +import { IAction, ActionRunner } from '../../../../base/common/actions.js'; +import { CancellationTokenSource } from '../../../../base/common/cancellation.js'; +import { fromNow } from '../../../../base/common/date.js'; +import { debounce } from '../../../../base/common/decorators.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; +import { FuzzyScore, createMatches } from '../../../../base/common/filters.js'; +import { Iterable } from '../../../../base/common/iterator.js'; +import { DisposableStore, IDisposable, Disposable } from '../../../../base/common/lifecycle.js'; +import { Schemas } from '../../../../base/common/network.js'; +import { ILabelService } from '../../../../platform/label/common/label.js'; +import { escapeRegExpCharacters } from '../../../../base/common/strings.js'; +import { URI } from '../../../../base/common/uri.js'; +import { IconLabel } from '../../../../base/browser/ui/iconLabel/iconLabel.js'; +import { IListVirtualDelegate, IIdentityProvider, IKeyboardNavigationLabelProvider } from '../../../../base/browser/ui/list/list.js'; +import { ITreeNode, ITreeRenderer, ITreeContextMenuEvent, ITreeElement } from '../../../../base/browser/ui/tree/tree.js'; +import { ViewPane, IViewPaneOptions } from '../../../browser/parts/views/viewPane.js'; +import { WorkbenchObjectTree } from '../../../../platform/list/browser/listService.js'; +import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; +import { ContextKeyExpr, IContextKeyService, RawContextKey, IContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { IConfigurationService, IConfigurationChangeEvent } from '../../../../platform/configuration/common/configuration.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { ITimelineService, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvidersChangeEvent, TimelineRequest, Timeline } from '../common/timeline.js'; +import { IEditorService } from '../../../services/editor/common/editorService.js'; +import { SideBySideEditor, EditorResourceAccessor } from '../../../common/editor.js'; +import { ICommandService, CommandsRegistry } from '../../../../platform/commands/common/commands.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { IViewDescriptorService } from '../../../common/views.js'; +import { IProgressService } from '../../../../platform/progress/common/progress.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; +import { ActionBar, IActionViewItemProvider } from '../../../../base/browser/ui/actionbar/actionbar.js'; +import { getContextMenuActions, createActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { IMenuService, MenuId, registerAction2, Action2, MenuRegistry } from '../../../../platform/actions/common/actions.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { ActionViewItem } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; +import { ColorScheme } from '../../../../platform/theme/common/theme.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js'; +import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from '../../../browser/parts/editor/editorCommands.js'; +import { MarshalledId } from '../../../../base/common/marshallingIds.js'; +import { isString } from '../../../../base/common/types.js'; +import { renderMarkdownAsPlaintext } from '../../../../base/browser/markdownRenderer.js'; +import { IHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegate.js'; +import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; +import { IExtensionService } from '../../../services/extensions/common/extensions.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; +import { AriaRole } from '../../../../base/browser/ui/aria/aria.js'; +import { ILocalizedString } from '../../../../platform/action/common/action.js'; +import { IHoverService, WorkbenchHoverDelegate } from '../../../../platform/hover/browser/hover.js'; +import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js'; const ItemHeight = 22; type TreeElement = TimelineItem | LoadMoreCommand; -function isLoadMoreCommand( - item: TreeElement | undefined, -): item is LoadMoreCommand { +function isLoadMoreCommand(item: TreeElement | undefined): item is LoadMoreCommand { return item instanceof LoadMoreCommand; } function isTimelineItem(item: TreeElement | undefined): item is TimelineItem { - return !!item && !item.handle.startsWith("vscode-command:"); + return !!item && !item.handle.startsWith('vscode-command:'); } -function updateRelativeTime( - item: TimelineItem, - lastRelativeTime: string | undefined, -): string | undefined { - item.relativeTime = isTimelineItem(item) - ? fromNow(item.timestamp) - : undefined; - item.relativeTimeFullWord = isTimelineItem(item) - ? fromNow(item.timestamp, false, true) - : undefined; - - if ( - lastRelativeTime === undefined || - item.relativeTime !== lastRelativeTime - ) { +function updateRelativeTime(item: TimelineItem, lastRelativeTime: string | undefined): string | undefined { + item.relativeTime = isTimelineItem(item) ? fromNow(item.timestamp) : undefined; + item.relativeTimeFullWord = isTimelineItem(item) ? fromNow(item.timestamp, false, true) : undefined; + if (lastRelativeTime === undefined || item.relativeTime !== lastRelativeTime) { lastRelativeTime = item.relativeTime; item.hideRelativeTime = false; } else { @@ -180,7 +103,6 @@ class TimelineAggregate { } private _cursor?: string; - get cursor(): string | undefined { return this._cursor; } @@ -204,37 +126,28 @@ class TimelineAggregate { updated = true; const ids = new Set(); - const timestamps = new Set(); for (const item of timeline.items) { if (item.id === undefined) { timestamps.add(item.timestamp); - } else { + } + else { ids.add(item.id); } } // Remove any duplicate items let i = this.items.length; - let item; - while (i--) { item = this.items[i]; - - if ( - (item.id !== undefined && ids.has(item.id)) || - timestamps.has(item.timestamp) - ) { + if ((item.id !== undefined && ids.has(item.id)) || timestamps.has(item.timestamp)) { this.items.splice(i, 1); } } - if ( - (timeline.items[timeline.items.length - 1]?.timestamp ?? 0) >= - (this.newest?.timestamp ?? 0) - ) { + if ((timeline.items[timeline.items.length - 1]?.timestamp ?? 0) >= (this.newest?.timestamp ?? 0)) { this.items.splice(0, 0, ...timeline.items); } else { this.items.push(...timeline.items); @@ -246,24 +159,17 @@ class TimelineAggregate { } // If we are not requesting more recent items than we have, then update the cursor - if (options.cursor !== undefined || typeof options.limit !== "object") { + if (options.cursor !== undefined || typeof options.limit !== 'object') { this._cursor = timeline.paging?.cursor; } if (updated) { this.items.sort( (a, b) => - b.timestamp - a.timestamp || + (b.timestamp - a.timestamp) || (a.source === undefined - ? b.source === undefined - ? 0 - : 1 - : b.source === undefined - ? -1 - : b.source.localeCompare(a.source, undefined, { - numeric: true, - sensitivity: "base", - })), + ? b.source === undefined ? 0 : 1 + : b.source === undefined ? -1 : b.source.localeCompare(a.source, undefined, { numeric: true, sensitivity: 'base' })) ); } @@ -271,13 +177,11 @@ class TimelineAggregate { } private _stale = false; - get stale() { return this._stale; } private _requiresReset = false; - get requiresReset(): boolean { return this._requiresReset; } @@ -289,7 +193,7 @@ class TimelineAggregate { } class LoadMoreCommand { - readonly handle = "vscode-command:loadMore"; + readonly handle = 'vscode-command:loadMore'; readonly timestamp = 0; readonly description = undefined; readonly tooltip = undefined; @@ -307,7 +211,6 @@ class LoadMoreCommand { this._loading = loading; } private _loading: boolean = false; - get loading(): boolean { return this._loading; } @@ -320,9 +223,7 @@ class LoadMoreCommand { } get label() { - return this.loading - ? localize("timeline.loadingMore", "Loading...") - : localize("timeline.loadMore", "Load more"); + return this.loading ? localize('timeline.loadingMore', "Loading...") : localize('timeline.loadMore', "Load more"); } get themeIcon(): ThemeIcon | undefined { @@ -330,19 +231,11 @@ class LoadMoreCommand { } } -export const TimelineFollowActiveEditorContext = new RawContextKey( - "timelineFollowActiveEditor", - true, - true, -); -export const TimelineExcludeSources = new RawContextKey( - "timelineExcludeSources", - "[]", - true, -); +export const TimelineFollowActiveEditorContext = new RawContextKey('timelineFollowActiveEditor', true, true); +export const TimelineExcludeSources = new RawContextKey('timelineExcludeSources', '[]', true); export class TimelinePane extends ViewPane { - static readonly TITLE: ILocalizedString = localize2("timeline", "Timeline"); + static readonly TITLE: ILocalizedString = localize2('timeline', "Timeline"); private $container!: HTMLElement; private $message!: HTMLDivElement; @@ -379,71 +272,28 @@ export class TimelinePane extends ViewPane { @ITelemetryService telemetryService: ITelemetryService, @IHoverService hoverService: IHoverService, @ILabelService private readonly labelService: ILabelService, - @IUriIdentityService - private readonly uriIdentityService: IUriIdentityService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @IExtensionService private readonly extensionService: IExtensionService, ) { - super( - { ...options, titleMenuId: MenuId.TimelineTitle }, - keybindingService, - contextMenuService, - configurationService, - contextKeyService, - viewDescriptorService, - instantiationService, - openerService, - themeService, - telemetryService, - hoverService, - ); + super({ ...options, titleMenuId: MenuId.TimelineTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService); - this.commands = this._register( - this.instantiationService.createInstance( - TimelinePaneCommands, - this, - ), - ); + this.commands = this._register(this.instantiationService.createInstance(TimelinePaneCommands, this)); - this.followActiveEditorContext = - TimelineFollowActiveEditorContext.bindTo(this.contextKeyService); - this.timelineExcludeSourcesContext = TimelineExcludeSources.bindTo( - this.contextKeyService, - ); + this.followActiveEditorContext = TimelineFollowActiveEditorContext.bindTo(this.contextKeyService); + this.timelineExcludeSourcesContext = TimelineExcludeSources.bindTo(this.contextKeyService); - const excludedSourcesString = storageService.get( - "timeline.excludeSources", - StorageScope.PROFILE, - "[]", - ); + const excludedSourcesString = storageService.get('timeline.excludeSources', StorageScope.PROFILE, '[]'); this.timelineExcludeSourcesContext.set(excludedSourcesString); this.excludedSources = new Set(JSON.parse(excludedSourcesString)); - this._register( - storageService.onDidChangeValue( - StorageScope.PROFILE, - "timeline.excludeSources", - this._store, - )(this.onStorageServiceChanged, this), - ); - this._register( - configurationService.onDidChangeConfiguration( - this.onConfigurationChanged, - this, - ), - ); - this._register( - timelineService.onDidChangeProviders(this.onProvidersChanged, this), - ); - this._register( - timelineService.onDidChangeTimeline(this.onTimelineChanged, this), - ); - this._register( - timelineService.onDidChangeUri((uri) => this.setUri(uri), this), - ); + this._register(storageService.onDidChangeValue(StorageScope.PROFILE, 'timeline.excludeSources', this._store)(this.onStorageServiceChanged, this)); + this._register(configurationService.onDidChangeConfiguration(this.onConfigurationChanged, this)); + this._register(timelineService.onDidChangeProviders(this.onProvidersChanged, this)); + this._register(timelineService.onDidChangeTimeline(this.onTimelineChanged, this)); + this._register(timelineService.onDidChangeUri(uri => this.setUri(uri), this)); } private _followActiveEditor: boolean = true; - get followActiveEditor(): boolean { return this._followActiveEditor; } @@ -463,32 +313,19 @@ export class TimelinePane extends ViewPane { } private _pageOnScroll: boolean | undefined; - get pageOnScroll() { if (this._pageOnScroll === undefined) { - this._pageOnScroll = - this.configurationService.getValue( - "timeline.pageOnScroll", - ) ?? false; + this._pageOnScroll = this.configurationService.getValue('timeline.pageOnScroll') ?? false; } return this._pageOnScroll; } get pageSize() { - let pageSize = this.configurationService.getValue< - number | null | undefined - >("timeline.pageSize"); - + let pageSize = this.configurationService.getValue('timeline.pageSize'); if (pageSize === undefined || pageSize === null) { // If we are paging when scrolling, then add an extra item to the end to make sure the "Load more" item is out of view - pageSize = Math.max( - 20, - Math.floor( - (this.tree?.renderHeight ?? 0 / ItemHeight) + - (this.pageOnScroll ? 1 : -1), - ), - ); + pageSize = Math.max(20, Math.floor((this.tree?.renderHeight ?? 0 / ItemHeight) + (this.pageOnScroll ? 1 : -1))); } return pageSize; } @@ -507,42 +344,27 @@ export class TimelinePane extends ViewPane { } this.uri = uri; - this.updateFilename( - uri ? this.labelService.getUriBasenameLabel(uri) : undefined, - ); + this.updateFilename(uri ? this.labelService.getUriBasenameLabel(uri) : undefined); this.treeRenderer?.setUri(uri); this.loadTimeline(true); } private onStorageServiceChanged() { - const excludedSourcesString = this.storageService.get( - "timeline.excludeSources", - StorageScope.PROFILE, - "[]", - ); + const excludedSourcesString = this.storageService.get('timeline.excludeSources', StorageScope.PROFILE, '[]'); this.timelineExcludeSourcesContext.set(excludedSourcesString); this.excludedSources = new Set(JSON.parse(excludedSourcesString)); - const missing = this.timelineService - .getSources() - .filter( - ({ id }) => - !this.excludedSources.has(id) && - !this.timelinesBySource.has(id), - ); - + const missing = this.timelineService.getSources() + .filter(({ id }) => !this.excludedSources.has(id) && !this.timelinesBySource.has(id)); if (missing.length !== 0) { - this.loadTimeline( - true, - missing.map(({ id }) => id), - ); + this.loadTimeline(true, missing.map(({ id }) => id)); } else { this.refresh(); } } private onConfigurationChanged(e: IConfigurationChangeEvent) { - if (e.affectsConfiguration("timeline.pageOnScroll")) { + if (e.affectsConfiguration('timeline.pageOnScroll')) { this._pageOnScroll = undefined; } } @@ -552,20 +374,12 @@ export class TimelinePane extends ViewPane { return; } - const uri = EditorResourceAccessor.getOriginalUri( - this.editorService.activeEditor, - { supportSideBySide: SideBySideEditor.PRIMARY }, - ); + const uri = EditorResourceAccessor.getOriginalUri(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY }); - if ( - (this.uriIdentityService.extUri.isEqual(uri, this.uri) && - uri !== undefined) || + if ((this.uriIdentityService.extUri.isEqual(uri, this.uri) && uri !== undefined) || // Fallback to match on fsPath if we are dealing with files or git schemes - (uri?.fsPath === this.uri?.fsPath && - (uri?.scheme === Schemas.file || uri?.scheme === "git") && - (this.uri?.scheme === Schemas.file || - this.uri?.scheme === "git")) - ) { + (uri?.fsPath === this.uri?.fsPath && (uri?.scheme === Schemas.file || uri?.scheme === 'git') && (this.uri?.scheme === Schemas.file || this.uri?.scheme === 'git'))) { + // If the uri hasn't changed, make sure we have valid caches for (const source of this.timelineService.getSources()) { if (this.excludedSources.has(source.id)) { @@ -573,7 +387,6 @@ export class TimelinePane extends ViewPane { } const timeline = this.timelinesBySource.get(source.id); - if (timeline !== undefined && !timeline.stale) { continue; } @@ -606,12 +419,8 @@ export class TimelinePane extends ViewPane { } private onTimelineChanged(e: TimelineChangeEvent) { - if ( - e?.uri === undefined || - this.uriIdentityService.extUri.isEqual(URI.revive(e.uri), this.uri) - ) { + if (e?.uri === undefined || this.uriIdentityService.extUri.isEqual(URI.revive(e.uri), this.uri)) { const timeline = this.timelinesBySource.get(e.id); - if (timeline === undefined) { return; } @@ -627,7 +436,6 @@ export class TimelinePane extends ViewPane { private _filename: string | undefined; updateFilename(filename: string | undefined) { this._filename = filename; - if (this.followActiveEditor || !filename) { this.updateTitleDescription(filename); } else { @@ -636,7 +444,6 @@ export class TimelinePane extends ViewPane { } private _message: string | undefined; - get message(): string | undefined { return this._message; } @@ -658,7 +465,7 @@ export class TimelinePane extends ViewPane { if (!this.$message) { return; } - this.$message.classList.remove("hide"); + this.$message.classList.remove('hide'); this.resetMessageElement(); this.$message.textContent = message; @@ -666,7 +473,7 @@ export class TimelinePane extends ViewPane { private hideMessage(): void { this.resetMessageElement(); - this.$message.classList.add("hide"); + this.$message.classList.add('hide'); } private resetMessageElement(): void { @@ -708,11 +515,7 @@ export class TimelinePane extends ViewPane { } // TODO@eamodio: Are these the right the list of schemes to exclude? Is there a better way? - if ( - this.uri?.scheme === Schemas.vscodeSettings || - this.uri?.scheme === Schemas.webviewPanel || - this.uri?.scheme === Schemas.walkThrough - ) { + if (this.uri?.scheme === Schemas.vscodeSettings || this.uri?.scheme === Schemas.webviewPanel || this.uri?.scheme === Schemas.walkThrough) { this.uri = undefined; this.clear(false); @@ -739,14 +542,8 @@ export class TimelinePane extends ViewPane { let hasPendingRequests = false; - for (const source of sources ?? - this.timelineService.getSources().map((s) => s.id)) { - const requested = this.loadTimelineForSource( - source, - this.uri, - reset, - ); - + for (const source of sources ?? this.timelineService.getSources().map(s => s.id)) { + const requested = this.loadTimelineForSource(source, this.uri, reset); if (requested) { hasPendingRequests = true; } @@ -759,12 +556,7 @@ export class TimelinePane extends ViewPane { } } - private loadTimelineForSource( - source: string, - uri: URI, - reset: boolean, - options?: TimelineOptions, - ) { + private loadTimelineForSource(source: string, uri: URI, reset: boolean, options?: TimelineOptions) { if (this.excludedSources.has(source)) { return false; } @@ -777,9 +569,7 @@ export class TimelinePane extends ViewPane { !reset && options?.cursor !== undefined && timeline !== undefined && - (!timeline?.more || - timeline.items.length > - timeline.lastRenderedIndex + this.pageSize) + (!timeline?.more || timeline.items.length > timeline.lastRenderedIndex + this.pageSize) ) { return false; } @@ -794,20 +584,16 @@ export class TimelinePane extends ViewPane { // If we are not resetting, have item(s), and already know there are no more to fetch, we're done here return false; } - options = { - cursor: reset ? undefined : timeline?.cursor, - limit: this.pageSize, - }; + options = { cursor: reset ? undefined : timeline?.cursor, limit: this.pageSize }; } let request = this.pendingRequests.get(source); - if (request !== undefined) { options.cursor = request.options.cursor; // TODO@eamodio deal with concurrent requests better - if (typeof options.limit === "number") { - if (typeof request.options.limit === "number") { + if (typeof options.limit === 'number') { + if (typeof request.options.limit === 'number') { options.limit += request.options.limit; } else { options.limit = request.options.limit; @@ -818,10 +604,7 @@ export class TimelinePane extends ViewPane { options.cacheResults = true; options.resetCache = reset; request = this.timelineService.getTimeline( - source, - uri, - options, - new CancellationTokenSource(), + source, uri, options, new CancellationTokenSource() ); if (request === undefined) { @@ -829,9 +612,7 @@ export class TimelinePane extends ViewPane { } this.pendingRequests.set(source, request); - request.tokenSource.token.onCancellationRequested(() => - this.pendingRequests.delete(source), - ); + request.tokenSource.token.onCancellationRequested(() => this.pendingRequests.delete(source)); this.handleRequest(request); @@ -843,25 +624,11 @@ export class TimelinePane extends ViewPane { this.timelinesBySource.delete(timeline.source); // Override the limit, to re-query for all our existing cached (possibly visible) items to keep visual continuity const { oldest } = timeline; - this.loadTimelineForSource( - timeline.source, - this.uri!, - true, - oldest !== undefined - ? { limit: { timestamp: oldest.timestamp, id: oldest.id } } - : undefined, - ); + this.loadTimelineForSource(timeline.source, this.uri!, true, oldest !== undefined ? { limit: { timestamp: oldest.timestamp, id: oldest.id } } : undefined); } else { // Override the limit, to query for any newer items const { newest } = timeline; - this.loadTimelineForSource( - timeline.source, - this.uri!, - false, - newest !== undefined - ? { limit: { timestamp: newest.timestamp, id: newest.id } } - : { limit: this.pageSize }, - ); + this.loadTimelineForSource(timeline.source, this.uri!, false, newest !== undefined ? { limit: { timestamp: newest.timestamp, id: newest.id } } : { limit: this.pageSize }); } } @@ -869,13 +636,10 @@ export class TimelinePane extends ViewPane { private async handleRequest(request: TimelineRequest) { let response: Timeline | undefined; - try { - response = await this.progressService.withProgress( - { location: this.id }, - () => request.result, - ); - } finally { + response = await this.progressService.withProgress({ location: this.id }, () => request.result); + } + finally { this.pendingRequests.delete(request.source); } @@ -894,13 +658,12 @@ export class TimelinePane extends ViewPane { const source = request.source; let updated = false; - const timeline = this.timelinesBySource.get(source); - if (timeline === undefined) { this.timelinesBySource.set(source, new TimelineAggregate(response)); updated = true; - } else { + } + else { updated = timeline.add(response, request.options); } @@ -932,7 +695,6 @@ export class TimelinePane extends ViewPane { } const maxCount = this._maxItemCount; - let count = 0; if (this.timelinesBySource.size === 1) { @@ -954,34 +716,26 @@ export class TimelinePane extends ViewPane { more = timeline.more; let lastRelativeTime: string | undefined; - for (const item of timeline.items) { item.relativeTime = undefined; item.hideRelativeTime = undefined; count++; - if (count > maxCount) { more = true; - break; } lastRelativeTime = updateRelativeTime(item, lastRelativeTime); - yield { element: item }; } timeline.lastRenderedIndex = count - 1; - } else { - const sources: { - timeline: TimelineAggregate; - iterator: IterableIterator; - nextItem: IteratorResult; - }[] = []; + } + else { + const sources: { timeline: TimelineAggregate; iterator: IterableIterator; nextItem: IteratorResult }[] = []; let hasAnyItems = false; - let mostRecentEnd = 0; for (const [source, timeline] of this.timelinesBySource) { @@ -998,11 +752,7 @@ export class TimelinePane extends ViewPane { if (timeline.more) { more = true; - const last = - timeline.items[ - Math.min(maxCount, timeline.items.length - 1) - ]; - + const last = timeline.items[Math.min(maxCount, timeline.items.length - 1)]; if (last.timestamp > mostRecentEnd) { mostRecentEnd = last.timestamp; } @@ -1016,23 +766,13 @@ export class TimelinePane extends ViewPane { function getNextMostRecentSource() { return sources - .filter((source) => !source.nextItem.done) - .reduce( - (previous, current) => - previous === undefined || - current.nextItem.value!.timestamp >= - previous.nextItem.value!.timestamp - ? current - : previous, - undefined!, - ); + .filter(source => !source.nextItem.done) + .reduce((previous, current) => (previous === undefined || current.nextItem.value!.timestamp >= previous.nextItem.value!.timestamp) ? current : previous, undefined!); } let lastRelativeTime: string | undefined; - let nextSource; - - while ((nextSource = getNextMostRecentSource())) { + while (nextSource = getNextMostRecentSource()) { nextSource.timeline.lastRenderedIndex++; const item = nextSource.nextItem.value!; @@ -1041,18 +781,12 @@ export class TimelinePane extends ViewPane { if (item.timestamp >= mostRecentEnd) { count++; - if (count > maxCount) { more = true; - break; } - lastRelativeTime = updateRelativeTime( - item, - lastRelativeTime, - ); - + lastRelativeTime = updateRelativeTime(item, lastRelativeTime); yield { element: item }; } @@ -1061,17 +795,14 @@ export class TimelinePane extends ViewPane { } this._visibleItemCount = count; - if (count > 0) { if (more) { yield { - element: new LoadMoreCommand( - this.pendingRequests.size !== 0, - ), + element: new LoadMoreCommand(this.pendingRequests.size !== 0) }; } else if (this.pendingRequests.size !== 0) { yield { - element: new LoadMoreCommand(true), + element: new LoadMoreCommand(true) }; } } @@ -1087,69 +818,30 @@ export class TimelinePane extends ViewPane { if (this.uri === undefined) { this.updateFilename(undefined); - this.message = localize( - "timeline.editorCannotProvideTimeline", - "The active editor cannot provide timeline information.", - ); + this.message = localize('timeline.editorCannotProvideTimeline', "The active editor cannot provide timeline information."); } else if (this._isEmpty) { if (this.pendingRequests.size !== 0) { this.setLoadingUriMessage(); } else { - this.updateFilename( - this.labelService.getUriBasenameLabel(this.uri), - ); - - const scmProviderCount = - this.contextKeyService.getContextKeyValue( - "scm.providerCount", - ); - - if ( - this.timelineService - .getSources() - .filter(({ id }) => !this.excludedSources.has(id)) - .length === 0 - ) { - this.message = localize( - "timeline.noTimelineSourcesEnabled", - "All timeline sources have been filtered out.", - ); + this.updateFilename(this.labelService.getUriBasenameLabel(this.uri)); + const scmProviderCount = this.contextKeyService.getContextKeyValue('scm.providerCount'); + if (this.timelineService.getSources().filter(({ id }) => !this.excludedSources.has(id)).length === 0) { + this.message = localize('timeline.noTimelineSourcesEnabled', "All timeline sources have been filtered out."); } else { - if ( - this.configurationService.getValue( - "workbench.localHistory.enabled", - ) && - !this.excludedSources.has("timeline.localHistory") - ) { - this.message = localize( - "timeline.noLocalHistoryYet", - "Local History will track recent changes as you save them unless the file has been excluded or is too large.", - ); + if (this.configurationService.getValue('workbench.localHistory.enabled') && !this.excludedSources.has('timeline.localHistory')) { + this.message = localize('timeline.noLocalHistoryYet', "Local History will track recent changes as you save them unless the file has been excluded or is too large."); } else if (this.excludedSources.size > 0) { - this.message = localize( - "timeline.noTimelineInfoFromEnabledSources", - "No filtered timeline information was provided.", - ); + this.message = localize('timeline.noTimelineInfoFromEnabledSources', "No filtered timeline information was provided."); } else { - this.message = localize( - "timeline.noTimelineInfo", - "No timeline information was provided.", - ); + this.message = localize('timeline.noTimelineInfo', "No timeline information was provided."); } } if (!scmProviderCount || scmProviderCount === 0) { - this.message += - " " + - localize( - "timeline.noSCM", - "Source Control has not been configured.", - ); + this.message += ' ' + localize('timeline.noSCM', "Source Control has not been configured."); } } } else { - this.updateFilename( - this.labelService.getUriBasenameLabel(this.uri), - ); + this.updateFilename(this.labelService.getUriBasenameLabel(this.uri)); this.message = undefined; } @@ -1182,20 +874,12 @@ export class TimelinePane extends ViewPane { override setVisible(visible: boolean): void { if (visible) { - this.extensionService.activateByEvent("onView:timeline"); + this.extensionService.activateByEvent('onView:timeline'); this.visibilityDisposables = new DisposableStore(); - this.editorService.onDidActiveEditorChange( - this.onActiveEditorChanged, - this, - this.visibilityDisposables, - ); + this.editorService.onDidActiveEditorChange(this.onActiveEditorChanged, this, this.visibilityDisposables); // Refresh the view on focus to update the relative timestamps - this.onDidFocus( - () => this.refreshDebounced(), - this, - this.visibilityDisposables, - ); + this.onDidFocus(() => this.refreshDebounced(), this, this.visibilityDisposables); super.setVisible(visible); @@ -1215,139 +899,90 @@ export class TimelinePane extends ViewPane { protected override renderHeaderTitle(container: HTMLElement): void { super.renderHeaderTitle(container, this.title); - container.classList.add("timeline-view"); + container.classList.add('timeline-view'); } protected override renderBody(container: HTMLElement): void { super.renderBody(container); this.$container = container; - container.classList.add( - "tree-explorer-viewlet-tree-view", - "timeline-tree-view", - ); + container.classList.add('tree-explorer-viewlet-tree-view', 'timeline-tree-view'); - this.$message = DOM.append(this.$container, DOM.$(".message")); - this.$message.classList.add("timeline-subtle"); + this.$message = DOM.append(this.$container, DOM.$('.message')); + this.$message.classList.add('timeline-subtle'); - this.message = localize( - "timeline.editorCannotProvideTimeline", - "The active editor cannot provide timeline information.", - ); + this.message = localize('timeline.editorCannotProvideTimeline', "The active editor cannot provide timeline information."); - this.$tree = document.createElement("div"); - this.$tree.classList.add( - "customview-tree", - "file-icon-themable-tree", - "hide-arrows", - ); + this.$tree = document.createElement('div'); + this.$tree.classList.add('customview-tree', 'file-icon-themable-tree', 'hide-arrows'); // this.treeElement.classList.add('show-file-icons'); container.appendChild(this.$tree); - this.treeRenderer = this.instantiationService.createInstance( - TimelineTreeRenderer, - this.commands, - ); - this.treeRenderer.onDidScrollToEnd((item) => { + this.treeRenderer = this.instantiationService.createInstance(TimelineTreeRenderer, this.commands); + this.treeRenderer.onDidScrollToEnd(item => { if (this.pageOnScroll) { this.loadMore(item); } }); - this.tree = >( - this.instantiationService.createInstance( - WorkbenchObjectTree, - "TimelinePane", - this.$tree, - new TimelineListVirtualDelegate(), - [this.treeRenderer], - { - identityProvider: new TimelineIdentityProvider(), - accessibilityProvider: { - getAriaLabel(element: TreeElement): string { - if (isLoadMoreCommand(element)) { - return element.ariaLabel; - } - return element.accessibilityInformation - ? element.accessibilityInformation.label - : localize( - "timeline.aria.item", - "{0}: {1}", - element.relativeTimeFullWord ?? "", - element.label, - ); - }, - getRole(element: TreeElement): AriaRole { - if (isLoadMoreCommand(element)) { - return "treeitem"; - } - return element.accessibilityInformation && - element.accessibilityInformation.role - ? element.accessibilityInformation.role - : "treeitem"; - }, - getWidgetAriaLabel(): string { - return localize("timeline", "Timeline"); - }, - }, - keyboardNavigationLabelProvider: - new TimelineKeyboardNavigationLabelProvider(), - multipleSelectionSupport: false, - overrideStyles: - this.getLocationBasedColors().listOverrideStyles, + this.tree = this.instantiationService.createInstance(WorkbenchObjectTree, 'TimelinePane', + this.$tree, new TimelineListVirtualDelegate(), [this.treeRenderer], { + identityProvider: new TimelineIdentityProvider(), + accessibilityProvider: { + getAriaLabel(element: TreeElement): string { + if (isLoadMoreCommand(element)) { + return element.ariaLabel; + } + return element.accessibilityInformation ? element.accessibilityInformation.label : localize('timeline.aria.item', "{0}: {1}", element.relativeTimeFullWord ?? '', element.label); }, - ) - ); - - this._register( - this.tree.onContextMenu((e) => - this.onContextMenu(this.commands, e), - ), - ); - this._register( - this.tree.onDidChangeSelection((e) => this.ensureValidItems()), - ); - this._register( - this.tree.onDidOpen((e) => { - if (!e.browserEvent || !this.ensureValidItems()) { - return; + getRole(element: TreeElement): AriaRole { + if (isLoadMoreCommand(element)) { + return 'treeitem'; + } + return element.accessibilityInformation && element.accessibilityInformation.role ? element.accessibilityInformation.role : 'treeitem'; + }, + getWidgetAriaLabel(): string { + return localize('timeline', "Timeline"); } + }, + keyboardNavigationLabelProvider: new TimelineKeyboardNavigationLabelProvider(), + multipleSelectionSupport: false, + overrideStyles: this.getLocationBasedColors().listOverrideStyles, + }); - const selection = this.tree.getSelection(); - - let item; + this._register(this.tree.onContextMenu(e => this.onContextMenu(this.commands, e))); + this._register(this.tree.onDidChangeSelection(e => this.ensureValidItems())); + this._register(this.tree.onDidOpen(e => { + if (!e.browserEvent || !this.ensureValidItems()) { + return; + } - if (selection.length === 1) { - item = selection[0]; - } + const selection = this.tree.getSelection(); + let item; + if (selection.length === 1) { + item = selection[0]; + } - if (item === null) { - return; - } + if (item === null) { + return; + } - if (isTimelineItem(item)) { - if (item.command) { - let args = item.command.arguments ?? []; - - if ( - item.command.id === API_OPEN_EDITOR_COMMAND_ID || - item.command.id === API_OPEN_DIFF_EDITOR_COMMAND_ID - ) { - // Some commands owned by us should receive the - // `IOpenEvent` as context to open properly - args = [...args, e]; - } - - this.commandService.executeCommand( - item.command.id, - ...args, - ); + if (isTimelineItem(item)) { + if (item.command) { + let args = item.command.arguments ?? []; + if (item.command.id === API_OPEN_EDITOR_COMMAND_ID || item.command.id === API_OPEN_DIFF_EDITOR_COMMAND_ID) { + // Some commands owned by us should receive the + // `IOpenEvent` as context to open properly + args = [...args, e]; } - } else if (isLoadMoreCommand(item)) { - this.loadMore(item); + + this.commandService.executeCommand(item.command.id, ...args); } - }), - ); + } + else if (isLoadMoreCommand(item)) { + this.loadMore(item); + } + })); } private loadMore(item: LoadMoreCommand) { @@ -1368,16 +1003,7 @@ export class TimelinePane extends ViewPane { ensureValidItems() { // If we don't have any non-excluded timelines, clear the tree and show the loading message - if ( - !this.hasVisibleItems || - !this.timelineService - .getSources() - .some( - ({ id }) => - !this.excludedSources.has(id) && - this.timelinesBySource.has(id), - ) - ) { + if (!this.hasVisibleItems || !this.timelineService.getSources().some(({ id }) => !this.excludedSources.has(id) && this.timelinesBySource.has(id))) { this.tree.setChildren(null, undefined); this._isEmpty = true; @@ -1390,20 +1016,13 @@ export class TimelinePane extends ViewPane { } setLoadingUriMessage() { - const file = - this.uri && this.labelService.getUriBasenameLabel(this.uri); + const file = this.uri && this.labelService.getUriBasenameLabel(this.uri); this.updateFilename(file); - this.message = file - ? localize("timeline.loading", "Loading timeline for {0}...", file) - : ""; + this.message = file ? localize('timeline.loading', "Loading timeline for {0}...", file) : ''; } - private onContextMenu( - commands: TimelinePaneCommands, - treeEvent: ITreeContextMenuEvent, - ): void { + private onContextMenu(commands: TimelinePaneCommands, treeEvent: ITreeContextMenuEvent): void { const item = treeEvent.element; - if (item === null) { return; } @@ -1417,9 +1036,7 @@ export class TimelinePane extends ViewPane { } this.tree.setFocus([item]); - const actions = commands.getItemContextActions(item); - if (!actions.length) { return; } @@ -1428,15 +1045,9 @@ export class TimelinePane extends ViewPane { getAnchor: () => treeEvent.anchor, getActions: () => actions, getActionViewItem: (action) => { - const keybinding = this.keybindingService.lookupKeybinding( - action.id, - ); - + const keybinding = this.keybindingService.lookupKeybinding(action.id); if (keybinding) { - return new ActionViewItem(action, action, { - label: true, - keybinding: keybinding.getLabel(), - }); + return new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel() }); } return undefined; }, @@ -1445,17 +1056,14 @@ export class TimelinePane extends ViewPane { this.tree.domFocus(); } }, - getActionsContext: (): TimelineActionContext => ({ - uri: this.uri, - item, - }), - actionRunner: new TimelineActionRunner(), + getActionsContext: (): TimelineActionContext => ({ uri: this.uri, item }), + actionRunner: new TimelineActionRunner() }); } } class TimelineElementTemplate implements IDisposable { - static readonly id = "TimelineElementTemplate"; + static readonly id = 'TimelineElementTemplate'; readonly actionBar: ActionBar; readonly icon: HTMLElement; @@ -1467,34 +1075,16 @@ class TimelineElementTemplate implements IDisposable { actionViewItemProvider: IActionViewItemProvider, hoverDelegate: IHoverDelegate, ) { - container.classList.add("custom-view-tree-node-item"); - this.icon = DOM.append( - container, - DOM.$(".custom-view-tree-node-item-icon"), - ); + container.classList.add('custom-view-tree-node-item'); + this.icon = DOM.append(container, DOM.$('.custom-view-tree-node-item-icon')); - this.iconLabel = new IconLabel(container, { - supportHighlights: true, - supportIcons: true, - hoverDelegate, - }); + this.iconLabel = new IconLabel(container, { supportHighlights: true, supportIcons: true, hoverDelegate }); - const timestampContainer = DOM.append( - this.iconLabel.element, - DOM.$(".timeline-timestamp-container"), - ); - this.timestamp = DOM.append( - timestampContainer, - DOM.$("span.timeline-timestamp"), - ); + const timestampContainer = DOM.append(this.iconLabel.element, DOM.$('.timeline-timestamp-container')); + this.timestamp = DOM.append(timestampContainer, DOM.$('span.timeline-timestamp')); - const actionsContainer = DOM.append( - this.iconLabel.element, - DOM.$(".actions"), - ); - this.actionBar = new ActionBar(actionsContainer, { - actionViewItemProvider, - }); + const actionsContainer = DOM.append(this.iconLabel.element, DOM.$('.actions')); + this.actionBar = new ActionBar(actionsContainer, { actionViewItemProvider }); } dispose() { @@ -1503,29 +1093,24 @@ class TimelineElementTemplate implements IDisposable { } reset() { - this.icon.className = ""; - this.icon.style.backgroundImage = ""; + this.icon.className = ''; + this.icon.style.backgroundImage = ''; this.actionBar.clear(); } } -export class TimelineIdentityProvider - implements IIdentityProvider -{ +export class TimelineIdentityProvider implements IIdentityProvider { getId(item: TreeElement): { toString(): string } { return item.handle; } } class TimelineActionRunner extends ActionRunner { - protected override async runAction( - action: IAction, - { uri, item }: TimelineActionContext, - ): Promise { + + protected override async runAction(action: IAction, { uri, item }: TimelineActionContext): Promise { if (!isTimelineItem(item)) { // TODO@eamodio do we need to do anything else? await action.run(); - return; } @@ -1534,7 +1119,7 @@ class TimelineActionRunner extends ActionRunner { $mid: MarshalledId.TimelineActionContext, handle: item.handle, source: item.source, - uri, + uri }, uri, item.source, @@ -1542,17 +1127,13 @@ class TimelineActionRunner extends ActionRunner { } } -export class TimelineKeyboardNavigationLabelProvider - implements IKeyboardNavigationLabelProvider -{ +export class TimelineKeyboardNavigationLabelProvider implements IKeyboardNavigationLabelProvider { getKeyboardNavigationLabel(element: TreeElement): { toString(): string } { return element.label; } } -export class TimelineListVirtualDelegate - implements IListVirtualDelegate -{ +export class TimelineListVirtualDelegate implements IListVirtualDelegate { getHeight(_element: TreeElement): number { return ItemHeight; } @@ -1562,12 +1143,9 @@ export class TimelineListVirtualDelegate } } -class TimelineTreeRenderer - implements ITreeRenderer -{ +class TimelineTreeRenderer implements ITreeRenderer { private readonly _onDidScrollToEnd = new Emitter(); - readonly onDidScrollToEnd: Event = - this._onDidScrollToEnd.event; + readonly onDidScrollToEnd: Event = this._onDidScrollToEnd.event; readonly templateId: string = TimelineElementTemplate.id; @@ -1577,108 +1155,75 @@ class TimelineTreeRenderer constructor( private readonly commands: TimelinePaneCommands, - @IInstantiationService - protected readonly instantiationService: IInstantiationService, + @IInstantiationService protected readonly instantiationService: IInstantiationService, @IThemeService private themeService: IThemeService, ) { - this.actionViewItemProvider = createActionViewItem.bind( - undefined, - this.instantiationService, - ); - this._hoverDelegate = this.instantiationService.createInstance( - WorkbenchHoverDelegate, - "element", - false, - { - position: { - hoverPosition: HoverPosition.RIGHT, // Will flip when there's no space - }, - }, - ); + this.actionViewItemProvider = createActionViewItem.bind(undefined, this.instantiationService); + this._hoverDelegate = this.instantiationService.createInstance(WorkbenchHoverDelegate, 'element', false, { + position: { + hoverPosition: HoverPosition.RIGHT // Will flip when there's no space + } + }); } private uri: URI | undefined; - setUri(uri: URI | undefined) { this.uri = uri; } renderTemplate(container: HTMLElement): TimelineElementTemplate { - return new TimelineElementTemplate( - container, - this.actionViewItemProvider, - this._hoverDelegate, - ); + return new TimelineElementTemplate(container, this.actionViewItemProvider, this._hoverDelegate); } renderElement( node: ITreeNode, index: number, template: TimelineElementTemplate, - height: number | undefined, + height: number | undefined ): void { template.reset(); const { element: item } = node; const theme = this.themeService.getColorTheme(); - - const icon = - theme.type === ColorScheme.LIGHT ? item.icon : item.iconDark; - + const icon = theme.type === ColorScheme.LIGHT ? item.icon : item.iconDark; const iconUrl = icon ? URI.revive(icon) : null; if (iconUrl) { - template.icon.className = "custom-view-tree-node-item-icon"; + template.icon.className = 'custom-view-tree-node-item-icon'; template.icon.style.backgroundImage = css.asCSSUrl(iconUrl); - template.icon.style.color = ""; + template.icon.style.color = ''; } else if (item.themeIcon) { template.icon.className = `custom-view-tree-node-item-icon ${ThemeIcon.asClassName(item.themeIcon)}`; - if (item.themeIcon.color) { - template.icon.style.color = - theme.getColor(item.themeIcon.color.id)?.toString() ?? ""; + template.icon.style.color = theme.getColor(item.themeIcon.color.id)?.toString() ?? ''; } else { - template.icon.style.color = ""; + template.icon.style.color = ''; } - template.icon.style.backgroundImage = ""; + template.icon.style.backgroundImage = ''; } else { - template.icon.className = "custom-view-tree-node-item-icon"; - template.icon.style.backgroundImage = ""; - template.icon.style.color = ""; + template.icon.className = 'custom-view-tree-node-item-icon'; + template.icon.style.backgroundImage = ''; + template.icon.style.color = ''; } const tooltip = item.tooltip ? isString(item.tooltip) ? item.tooltip - : { - markdown: item.tooltip, - markdownNotSupportedFallback: renderMarkdownAsPlaintext( - item.tooltip, - ), - } + : { markdown: item.tooltip, markdownNotSupportedFallback: renderMarkdownAsPlaintext(item.tooltip) } : undefined; template.iconLabel.setLabel(item.label, item.description, { title: tooltip, - matches: createMatches(node.filterData), + matches: createMatches(node.filterData) }); - template.timestamp.textContent = item.relativeTime ?? ""; - template.timestamp.ariaLabel = item.relativeTimeFullWord ?? ""; - template.timestamp.parentElement!.classList.toggle( - "timeline-timestamp--duplicate", - isTimelineItem(item) && item.hideRelativeTime, - ); + template.timestamp.textContent = item.relativeTime ?? ''; + template.timestamp.ariaLabel = item.relativeTimeFullWord ?? ''; + template.timestamp.parentElement!.classList.toggle('timeline-timestamp--duplicate', isTimelineItem(item) && item.hideRelativeTime); - template.actionBar.context = { - uri: this.uri, - item, - } satisfies TimelineActionContext; + template.actionBar.context = { uri: this.uri, item } satisfies TimelineActionContext; template.actionBar.actionRunner = new TimelineActionRunner(); - template.actionBar.push(this.commands.getItemActions(item), { - icon: true, - label: false, - }); + template.actionBar.push(this.commands.getItemActions(item), { icon: true, label: false }); // If we are rendering the load more item, we've scrolled to the end, so trigger an event if (isLoadMoreCommand(item)) { @@ -1691,23 +1236,10 @@ class TimelineTreeRenderer } } -const timelineRefresh = registerIcon( - "timeline-refresh", - Codicon.refresh, - localize("timelineRefresh", "Icon for the refresh timeline action."), -); - -const timelinePin = registerIcon( - "timeline-pin", - Codicon.pin, - localize("timelinePin", "Icon for the pin timeline action."), -); -const timelineUnpin = registerIcon( - "timeline-unpin", - Codicon.pinned, - localize("timelineUnpin", "Icon for the unpin timeline action."), -); +const timelineRefresh = registerIcon('timeline-refresh', Codicon.refresh, localize('timelineRefresh', 'Icon for the refresh timeline action.')); +const timelinePin = registerIcon('timeline-pin', Codicon.pin, localize('timelinePin', 'Icon for the pin timeline action.')); +const timelineUnpin = registerIcon('timeline-unpin', Codicon.pinned, localize('timelineUnpin', 'Icon for the unpin timeline action.')); class TimelinePaneCommands extends Disposable { private readonly sourceDisposables: DisposableStore; @@ -1716,171 +1248,110 @@ class TimelinePaneCommands extends Disposable { private readonly pane: TimelinePane, @ITimelineService private readonly timelineService: ITimelineService, @IStorageService private readonly storageService: IStorageService, - @IContextKeyService - private readonly contextKeyService: IContextKeyService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, @IMenuService private readonly menuService: IMenuService, ) { super(); - this._register((this.sourceDisposables = new DisposableStore())); - - this._register( - registerAction2( - class extends Action2 { - constructor() { - super({ - id: "timeline.refresh", - title: localize2("refresh", "Refresh"), - icon: timelineRefresh, - category: localize2("timeline", "Timeline"), - menu: { - id: MenuId.TimelineTitle, - group: "navigation", - order: 99, - }, - }); - } - run(accessor: ServicesAccessor, ...args: any[]) { - pane.reset(); + this._register(this.sourceDisposables = new DisposableStore()); + + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: 'timeline.refresh', + title: localize2('refresh', "Refresh"), + icon: timelineRefresh, + category: localize2('timeline', "Timeline"), + menu: { + id: MenuId.TimelineTitle, + group: 'navigation', + order: 99, } - }, - ), - ); - - this._register( - CommandsRegistry.registerCommand( - "timeline.toggleFollowActiveEditor", - (accessor: ServicesAccessor, ...args: any[]) => - (pane.followActiveEditor = !pane.followActiveEditor), - ), - ); - - this._register( - MenuRegistry.appendMenuItem(MenuId.TimelineTitle, { - command: { - id: "timeline.toggleFollowActiveEditor", - title: localize2( - "timeline.toggleFollowActiveEditorCommand.follow", - "Pin the Current Timeline", - ), - icon: timelinePin, - category: localize2("timeline", "Timeline"), - }, - group: "navigation", - order: 98, - when: TimelineFollowActiveEditorContext, - }), - ); - - this._register( - MenuRegistry.appendMenuItem(MenuId.TimelineTitle, { - command: { - id: "timeline.toggleFollowActiveEditor", - title: localize2( - "timeline.toggleFollowActiveEditorCommand.unfollow", - "Unpin the Current Timeline", - ), - icon: timelineUnpin, - category: localize2("timeline", "Timeline"), - }, - group: "navigation", - order: 98, - when: TimelineFollowActiveEditorContext.toNegated(), - }), - ); + }); + } + run(accessor: ServicesAccessor, ...args: any[]) { + pane.reset(); + } + })); + + this._register(CommandsRegistry.registerCommand('timeline.toggleFollowActiveEditor', + (accessor: ServicesAccessor, ...args: any[]) => pane.followActiveEditor = !pane.followActiveEditor + )); + + this._register(MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({ + command: { + id: 'timeline.toggleFollowActiveEditor', + title: localize2('timeline.toggleFollowActiveEditorCommand.follow', 'Pin the Current Timeline'), + icon: timelinePin, + category: localize2('timeline', "Timeline"), + }, + group: 'navigation', + order: 98, + when: TimelineFollowActiveEditorContext + }))); + + this._register(MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({ + command: { + id: 'timeline.toggleFollowActiveEditor', + title: localize2('timeline.toggleFollowActiveEditorCommand.unfollow', 'Unpin the Current Timeline'), + icon: timelineUnpin, + category: localize2('timeline', "Timeline"), + }, + group: 'navigation', + order: 98, + when: TimelineFollowActiveEditorContext.toNegated() + }))); - this._register( - timelineService.onDidChangeProviders(() => - this.updateTimelineSourceFilters(), - ), - ); + this._register(timelineService.onDidChangeProviders(() => this.updateTimelineSourceFilters())); this.updateTimelineSourceFilters(); } getItemActions(element: TreeElement): IAction[] { - return this.getActions(MenuId.TimelineItemContext, { - key: "timelineItem", - value: element.contextValue, - }).primary; + return this.getActions(MenuId.TimelineItemContext, { key: 'timelineItem', value: element.contextValue }).primary; } getItemContextActions(element: TreeElement): IAction[] { - return this.getActions(MenuId.TimelineItemContext, { - key: "timelineItem", - value: element.contextValue, - }).secondary; + return this.getActions(MenuId.TimelineItemContext, { key: 'timelineItem', value: element.contextValue }).secondary; } - private getActions( - menuId: MenuId, - context: { key: string; value?: string }, - ): { primary: IAction[]; secondary: IAction[] } { + private getActions(menuId: MenuId, context: { key: string; value?: string }): { primary: IAction[]; secondary: IAction[] } { const contextKeyService = this.contextKeyService.createOverlay([ - ["view", this.pane.id], + ['view', this.pane.id], [context.key, context.value], ]); - const menu = this.menuService.getMenuActions( - menuId, - contextKeyService, - { shouldForwardArgs: true }, - ); - - return getContextMenuActions(menu, "inline"); + const menu = this.menuService.getMenuActions(menuId, contextKeyService, { shouldForwardArgs: true }); + return getContextMenuActions(menu, 'inline'); } private updateTimelineSourceFilters() { this.sourceDisposables.clear(); - const excluded = new Set( - JSON.parse( - this.storageService.get( - "timeline.excludeSources", - StorageScope.PROFILE, - "[]", - ), - ), - ); - + const excluded = new Set(JSON.parse(this.storageService.get('timeline.excludeSources', StorageScope.PROFILE, '[]'))); for (const source of this.timelineService.getSources()) { - this.sourceDisposables.add( - registerAction2( - class extends Action2 { - constructor() { - super({ - id: `timeline.toggleExcludeSource:${source.id}`, - title: source.label, - menu: { - id: MenuId.TimelineFilterSubMenu, - group: "navigation", - }, - toggled: ContextKeyExpr.regex( - `timelineExcludeSources`, - new RegExp( - `\\b${escapeRegExpCharacters(source.id)}\\b`, - ), - ).negate(), - }); - } - run(accessor: ServicesAccessor, ...args: any[]) { - if (excluded.has(source.id)) { - excluded.delete(source.id); - } else { - excluded.add(source.id); - } - - const storageService = - accessor.get(IStorageService); - storageService.store( - "timeline.excludeSources", - JSON.stringify([...excluded.keys()]), - StorageScope.PROFILE, - StorageTarget.USER, - ); - } - }, - ), - ); + this.sourceDisposables.add(registerAction2(class extends Action2 { + constructor() { + super({ + id: `timeline.toggleExcludeSource:${source.id}`, + title: source.label, + menu: { + id: MenuId.TimelineFilterSubMenu, + group: 'navigation', + }, + toggled: ContextKeyExpr.regex(`timelineExcludeSources`, new RegExp(`\\b${escapeRegExpCharacters(source.id)}\\b`)).negate() + }); + } + run(accessor: ServicesAccessor, ...args: any[]) { + if (excluded.has(source.id)) { + excluded.delete(source.id); + } else { + excluded.add(source.id); + } + + const storageService = accessor.get(IStorageService); + storageService.store('timeline.excludeSources', JSON.stringify([...excluded.keys()]), StorageScope.PROFILE, StorageTarget.USER); + } + })); } } } diff --git a/Source/vs/workbench/services/driver/browser/driver.ts b/Source/vs/workbench/services/driver/browser/driver.ts index c5a59da845c2f..4d2ebf2318dd1 100644 --- a/Source/vs/workbench/services/driver/browser/driver.ts +++ b/Source/vs/workbench/services/driver/browser/driver.ts @@ -3,38 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { Terminal as XtermTerminal } from "@xterm/xterm"; - -import { - getClientArea, - getTopLeftOffset, - isHTMLDivElement, - isHTMLTextAreaElement, -} from "../../../../base/browser/dom.js"; -import { mainWindow } from "../../../../base/browser/window.js"; -import { coalesce } from "../../../../base/common/arrays.js"; -import { language, locale } from "../../../../base/common/platform.js"; -import { IEnvironmentService } from "../../../../platform/environment/common/environment.js"; -import { IFileService } from "../../../../platform/files/common/files.js"; -import { IInstantiationService } from "../../../../platform/instantiation/common/instantiation.js"; -import localizedStrings from "../../../../platform/languagePacks/common/localizedStrings.js"; -import { getLogs, ILogFile } from "../../../../platform/log/browser/log.js"; -import { ILogService } from "../../../../platform/log/common/log.js"; -import { Registry } from "../../../../platform/registry/common/platform.js"; -import { - IWorkbenchContributionsRegistry, - Extensions as WorkbenchExtensions, -} from "../../../common/contributions.js"; -import { - ILifecycleService, - LifecyclePhase, -} from "../../lifecycle/common/lifecycle.js"; -import { - IElement, - ILocaleInfo, - ILocalizedStrings, - IWindowDriver, -} from "../common/driver.js"; +import { getClientArea, getTopLeftOffset } from '../../../../base/browser/dom.js'; +import { mainWindow } from '../../../../base/browser/window.js'; +import { coalesce } from '../../../../base/common/arrays.js'; +import { language, locale } from '../../../../base/common/platform.js'; +import { IEnvironmentService } from '../../../../platform/environment/common/environment.js'; +import { IFileService } from '../../../../platform/files/common/files.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import localizedStrings from '../../../../platform/languagePacks/common/localizedStrings.js'; +import { ILogFile, getLogs } from '../../../../platform/log/browser/log.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from '../../../common/contributions.js'; +import { IWindowDriver, IElement, ILocaleInfo, ILocalizedStrings } from '../common/driver.js'; +import { ILifecycleService, LifecyclePhase } from '../../lifecycle/common/lifecycle.js'; +import type { Terminal as XtermTerminal } from '@xterm/xterm'; export class BrowserWindowDriver implements IWindowDriver { constructor( @@ -179,40 +162,18 @@ export class BrowserWindowDriver implements IWindowDriver { if (!element) { throw new Error(`Editor not found: ${selector}`); } - if (isHTMLDivElement(element)) { - // Edit context is enabled - const editContext = element.editContext; - if (!editContext) { - throw new Error(`Edit context not found: ${selector}`); - } - const selectionStart = editContext.selectionStart; - const selectionEnd = editContext.selectionEnd; - const event = new TextUpdateEvent("textupdate", { - updateRangeStart: selectionStart, - updateRangeEnd: selectionEnd, - text, - selectionStart: selectionStart + text.length, - selectionEnd: selectionStart + text.length, - compositionStart: 0, - compositionEnd: 0, - }); - editContext.dispatchEvent(event); - } else if (isHTMLTextAreaElement(element)) { - const start = element.selectionStart; - const newStart = start + text.length; - const value = element.value; - const newValue = - value.substr(0, start) + text + value.substr(start); - - element.value = newValue; - element.setSelectionRange(newStart, newStart); - - const event = new Event("input", { - "bubbles": true, - "cancelable": true, - }); - element.dispatchEvent(event); - } + + const textarea = element as HTMLTextAreaElement; + const start = textarea.selectionStart; + const newStart = start + text.length; + const value = textarea.value; + const newValue = value.substr(0, start) + text + value.substr(start); + + textarea.value = newValue; + textarea.setSelectionRange(newStart, newStart); + + const event = new Event('input', { 'bubbles': true, 'cancelable': true }); + textarea.dispatchEvent(event); } async getTerminalBuffer(selector: string): Promise { diff --git a/Source/vs/workbench/services/extensionManagement/common/extensionManagement.ts b/Source/vs/workbench/services/extensionManagement/common/extensionManagement.ts index 6e2910a0e6744..149810352bac9 100644 --- a/Source/vs/workbench/services/extensionManagement/common/extensionManagement.ts +++ b/Source/vs/workbench/services/extensionManagement/common/extensionManagement.ts @@ -3,33 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from "../../../../base/common/event.js"; -import { IMarkdownString } from "../../../../base/common/htmlContent.js"; -import { FileAccess } from "../../../../base/common/network.js"; -import { URI } from "../../../../base/common/uri.js"; -import { localize } from "../../../../nls.js"; -import { - DidUninstallExtensionEvent, - DidUpdateExtensionMetadata, - IExtensionManagementService, - IGalleryExtension, - ILocalExtension, - InstallExtensionEvent, - InstallExtensionResult, - InstallOptions, - Metadata, - UninstallExtensionEvent, -} from "../../../../platform/extensionManagement/common/extensionManagement.js"; -import { - ExtensionType, - IExtension, - IExtensionIdentifier, - IExtensionManifest, -} from "../../../../platform/extensions/common/extensions.js"; -import { - createDecorator, - refineServiceDecorator, -} from "../../../../platform/instantiation/common/instantiation.js"; +import { Event } from '../../../../base/common/event.js'; +import { createDecorator, refineServiceDecorator } from '../../../../platform/instantiation/common/instantiation.js'; +import { IExtension, ExtensionType, IExtensionManifest, IExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js'; +import { IExtensionManagementService, IGalleryExtension, ILocalExtension, InstallOptions, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, Metadata, UninstallExtensionEvent, DidUpdateExtensionMetadata } from '../../../../platform/extensionManagement/common/extensionManagement.js'; +import { URI } from '../../../../base/common/uri.js'; +import { FileAccess } from '../../../../base/common/network.js'; +import { IMarkdownString } from '../../../../base/common/htmlContent.js'; export type DidChangeProfileEvent = { readonly added: ILocalExtension[]; @@ -107,12 +87,12 @@ export type DidChangeProfileForServerEvent = DidChangeProfileEvent & { server: IExtensionManagementServer; }; -export const IWorkbenchExtensionManagementService = refineServiceDecorator< - IProfileAwareExtensionManagementService, - IWorkbenchExtensionManagementService ->(IProfileAwareExtensionManagementService); -export interface IWorkbenchExtensionManagementService - extends IProfileAwareExtensionManagementService { +export interface IWorkbenchInstallOptions extends InstallOptions { + readonly installEverywhere?: boolean; +} + +export const IWorkbenchExtensionManagementService = refineServiceDecorator(IProfileAwareExtensionManagementService); +export interface IWorkbenchExtensionManagementService extends IProfileAwareExtensionManagementService { readonly _serviceBrand: undefined; readonly onInstallExtension: Event; @@ -136,15 +116,8 @@ export interface IWorkbenchExtensionManagementService includeInvalid: boolean, ): Promise; - canInstall( - extension: IGalleryExtension | IResourceExtension, - ): Promise; - - installVSIX( - location: URI, - manifest: IExtensionManifest, - installOptions?: InstallOptions, - ): Promise; + installVSIX(location: URI, manifest: IExtensionManifest, installOptions?: InstallOptions): Promise; + installFromGallery(gallery: IGalleryExtension, installOptions?: IWorkbenchInstallOptions): Promise; installFromLocation(location: URI): Promise; installResourceExtension( extension: IResourceExtension, @@ -162,13 +135,6 @@ export interface IWorkbenchExtensionManagementService ): Promise; } -export const extensionsConfigurationNodeBase = { - id: "extensions", - order: 30, - title: localize("extensionsConfigurationTitle", "Extensions"), - type: "object", -}; - export const enum EnablementState { DisabledByTrustRequirement, DisabledByExtensionKind, diff --git a/Source/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/Source/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index 7d31cbeb30a7b..6312b05fe32ab 100644 --- a/Source/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/Source/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -3,157 +3,73 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { coalesce } from "../../../../base/common/arrays.js"; -import { Promises } from "../../../../base/common/async.js"; -import { CancellationToken } from "../../../../base/common/cancellation.js"; +import { Emitter, Event, EventMultiplexer } from '../../../../base/common/event.js'; import { - CancellationError, - getErrorMessage, -} from "../../../../base/common/errors.js"; -import { - Emitter, - Event, - EventMultiplexer, -} from "../../../../base/common/event.js"; -import { - IMarkdownString, - MarkdownString, -} from "../../../../base/common/htmlContent.js"; -import { - Disposable, - DisposableStore, -} from "../../../../base/common/lifecycle.js"; -import { Schemas } from "../../../../base/common/network.js"; -import Severity from "../../../../base/common/severity.js"; -import { isString, isUndefined } from "../../../../base/common/types.js"; -import { URI } from "../../../../base/common/uri.js"; -import { localize } from "../../../../nls.js"; -import { ICommandService } from "../../../../platform/commands/common/commands.js"; -import { IConfigurationService } from "../../../../platform/configuration/common/configuration.js"; -import { - IDialogService, - IPromptButton, -} from "../../../../platform/dialogs/common/dialogs.js"; -import { IDownloadService } from "../../../../platform/download/common/download.js"; -import { - DidUpdateExtensionMetadata, - EXTENSION_INSTALL_SOURCE_CONTEXT, - ExtensionInstallSource, - ExtensionManagementError, - ExtensionManagementErrorCode, - IExtensionGalleryService, - IExtensionIdentifier, - IExtensionsControlManifest, - IGalleryExtension, - ILocalExtension, - InstallExtensionInfo, - InstallExtensionResult, - InstallOperation, - InstallOptions, + ILocalExtension, IGalleryExtension, IExtensionIdentifier, IExtensionsControlManifest, IExtensionGalleryService, InstallOptions, UninstallOptions, InstallExtensionResult, ExtensionManagementError, ExtensionManagementErrorCode, Metadata, InstallOperation, EXTENSION_INSTALL_SOURCE_CONTEXT, InstallExtensionInfo, IProductVersion, - Metadata, - UninstallExtensionInfo, - UninstallOptions, -} from "../../../../platform/extensionManagement/common/extensionManagement.js"; -import { - areSameExtensions, - computeTargetPlatform, -} from "../../../../platform/extensionManagement/common/extensionManagementUtil.js"; -import { - IExtensionsScannerService, - IScannedExtension, -} from "../../../../platform/extensionManagement/common/extensionsScannerService.js"; -import { - ExtensionType, - getWorkspaceSupportTypeMessage, - IExtensionManifest, - isLanguagePackExtension, - TargetPlatform, -} from "../../../../platform/extensions/common/extensions.js"; -import { - FileChangesEvent, - IFileService, -} from "../../../../platform/files/common/files.js"; -import { IInstantiationService } from "../../../../platform/instantiation/common/instantiation.js"; -import { ILogService } from "../../../../platform/log/common/log.js"; -import { IProductService } from "../../../../platform/product/common/productService.js"; -import { - IStorageService, - StorageScope, - StorageTarget, -} from "../../../../platform/storage/common/storage.js"; -import { ITelemetryService } from "../../../../platform/telemetry/common/telemetry.js"; -import { IUriIdentityService } from "../../../../platform/uriIdentity/common/uriIdentity.js"; -import { IUserDataProfilesService } from "../../../../platform/userDataProfile/common/userDataProfile.js"; -import { - IUserDataSyncEnablementService, - SyncResource, -} from "../../../../platform/userDataSync/common/userDataSync.js"; -import { - IWorkspaceContextService, - WorkbenchState, -} from "../../../../platform/workspace/common/workspace.js"; -import { - IWorkspaceTrustRequestService, - WorkspaceTrustRequestButton, -} from "../../../../platform/workspace/common/workspaceTrust.js"; -import { IExtensionManifestPropertiesService } from "../../extensions/common/extensionManifestPropertiesService.js"; -import { IUserDataProfileService } from "../../userDataProfile/common/userDataProfile.js"; -import { - DidChangeProfileForServerEvent, - DidUninstallExtensionOnServerEvent, - IExtensionManagementServer, - IExtensionManagementServerService, - InstallExtensionOnServerEvent, - IResourceExtension, - IWorkbenchExtensionManagementService, - UninstallExtensionOnServerEvent, -} from "./extensionManagement.js"; - -function isGalleryExtension( - extension: IResourceExtension | IGalleryExtension, -): extension is IGalleryExtension { - return extension.type === "gallery"; + ExtensionInstallSource, + DidUpdateExtensionMetadata, + UninstallExtensionInfo +} from '../../../../platform/extensionManagement/common/extensionManagement.js'; +import { DidChangeProfileForServerEvent, DidUninstallExtensionOnServerEvent, IExtensionManagementServer, IExtensionManagementServerService, InstallExtensionOnServerEvent, IResourceExtension, IWorkbenchExtensionManagementService, IWorkbenchInstallOptions, UninstallExtensionOnServerEvent } from './extensionManagement.js'; +import { ExtensionType, isLanguagePackExtension, IExtensionManifest, getWorkspaceSupportTypeMessage, TargetPlatform } from '../../../../platform/extensions/common/extensions.js'; +import { URI } from '../../../../base/common/uri.js'; +import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { areSameExtensions, computeTargetPlatform } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js'; +import { localize } from '../../../../nls.js'; +import { IProductService } from '../../../../platform/product/common/productService.js'; +import { Schemas } from '../../../../base/common/network.js'; +import { IDownloadService } from '../../../../platform/download/common/download.js'; +import { coalesce } from '../../../../base/common/arrays.js'; +import { IDialogService, IPromptButton } from '../../../../platform/dialogs/common/dialogs.js'; +import Severity from '../../../../base/common/severity.js'; +import { IUserDataSyncEnablementService, SyncResource } from '../../../../platform/userDataSync/common/userDataSync.js'; +import { Promises } from '../../../../base/common/async.js'; +import { IWorkspaceTrustRequestService, WorkspaceTrustRequestButton } from '../../../../platform/workspace/common/workspaceTrust.js'; +import { IExtensionManifestPropertiesService } from '../../extensions/common/extensionManifestPropertiesService.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { isString, isUndefined } from '../../../../base/common/types.js'; +import { FileChangesEvent, IFileService } from '../../../../platform/files/common/files.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; +import { CancellationError, getErrorMessage } from '../../../../base/common/errors.js'; +import { IUserDataProfileService } from '../../userDataProfile/common/userDataProfile.js'; +import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js'; +import { IExtensionsScannerService, IScannedExtension } from '../../../../platform/extensionManagement/common/extensionsScannerService.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; +import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js'; +import { IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js'; + +function isGalleryExtension(extension: IResourceExtension | IGalleryExtension): extension is IGalleryExtension { + return extension.type === 'gallery'; } -export class ExtensionManagementService - extends Disposable - implements IWorkbenchExtensionManagementService -{ +export class ExtensionManagementService extends Disposable implements IWorkbenchExtensionManagementService { + declare readonly _serviceBrand: undefined; - private readonly _onInstallExtension = this._register( - new Emitter(), - ); + private readonly _onInstallExtension = this._register(new Emitter()); readonly onInstallExtension: Event; - private readonly _onDidInstallExtensions = this._register( - new Emitter(), - ); + private readonly _onDidInstallExtensions = this._register(new Emitter()); readonly onDidInstallExtensions: Event; - private readonly _onUninstallExtension = this._register( - new Emitter(), - ); + private readonly _onUninstallExtension = this._register(new Emitter()); readonly onUninstallExtension: Event; - private readonly _onDidUninstallExtension = this._register( - new Emitter(), - ); + private readonly _onDidUninstallExtension = this._register(new Emitter()); readonly onDidUninstallExtension: Event; readonly onDidUpdateExtensionMetadata: Event; - private readonly _onDidProfileAwareInstallExtensions = this._register( - new Emitter(), - ); - readonly onProfileAwareDidInstallExtensions: Event< - readonly InstallExtensionResult[] - >; + private readonly _onDidProfileAwareInstallExtensions = this._register(new Emitter()); + readonly onProfileAwareDidInstallExtensions: Event; - private readonly _onDidProfileAwareUninstallExtension = this._register( - new Emitter(), - ); + private readonly _onDidProfileAwareUninstallExtension = this._register(new Emitter()); readonly onProfileAwareDidUninstallExtension: Event; readonly onProfileAwareDidUpdateExtensionMetadata: Event; @@ -167,273 +83,109 @@ export class ExtensionManagementService private readonly workspaceExtensionManagementService: WorkspaceExtensionsManagementService; constructor( - @IExtensionManagementServerService - protected readonly extensionManagementServerService: IExtensionManagementServerService, - @IExtensionGalleryService - private readonly extensionGalleryService: IExtensionGalleryService, - @IUserDataProfileService - private readonly userDataProfileService: IUserDataProfileService, - @IUserDataProfilesService - private readonly userDataProfilesService: IUserDataProfilesService, - @IConfigurationService - protected readonly configurationService: IConfigurationService, + @IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, + @IConfigurationService protected readonly configurationService: IConfigurationService, @IProductService protected readonly productService: IProductService, @IDownloadService protected readonly downloadService: IDownloadService, - @IUserDataSyncEnablementService - private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, + @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @IDialogService private readonly dialogService: IDialogService, - @IWorkspaceTrustRequestService - private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService, - @IExtensionManifestPropertiesService - private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, + @IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService, + @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, @IFileService private readonly fileService: IFileService, @ILogService private readonly logService: ILogService, - @IInstantiationService - private readonly instantiationService: IInstantiationService, - @IExtensionsScannerService - private readonly extensionsScannerService: IExtensionsScannerService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IExtensionsScannerService private readonly extensionsScannerService: IExtensionsScannerService, @ITelemetryService private readonly telemetryService: ITelemetryService, ) { super(); - this.workspaceExtensionManagementService = this._register( - this.instantiationService.createInstance( - WorkspaceExtensionsManagementService, - ), - ); - this.onDidEnableExtensions = - this.workspaceExtensionManagementService.onDidChangeInvalidExtensions; - - if ( - this.extensionManagementServerService.localExtensionManagementServer - ) { - this.servers.push( - this.extensionManagementServerService - .localExtensionManagementServer, - ); - } - if ( - this.extensionManagementServerService - .remoteExtensionManagementServer - ) { - this.servers.push( - this.extensionManagementServerService - .remoteExtensionManagementServer, - ); - } - if ( - this.extensionManagementServerService.webExtensionManagementServer - ) { - this.servers.push( - this.extensionManagementServerService - .webExtensionManagementServer, - ); - } - - const onInstallExtensionEventMultiplexer = this._register( - new EventMultiplexer(), - ); - this._register( - onInstallExtensionEventMultiplexer.add( - this._onInstallExtension.event, - ), - ); + this.workspaceExtensionManagementService = this._register(this.instantiationService.createInstance(WorkspaceExtensionsManagementService)); + this.onDidEnableExtensions = this.workspaceExtensionManagementService.onDidChangeInvalidExtensions; + + if (this.extensionManagementServerService.localExtensionManagementServer) { + this.servers.push(this.extensionManagementServerService.localExtensionManagementServer); + } + if (this.extensionManagementServerService.remoteExtensionManagementServer) { + this.servers.push(this.extensionManagementServerService.remoteExtensionManagementServer); + } + if (this.extensionManagementServerService.webExtensionManagementServer) { + this.servers.push(this.extensionManagementServerService.webExtensionManagementServer); + } + + const onInstallExtensionEventMultiplexer = this._register(new EventMultiplexer()); + this._register(onInstallExtensionEventMultiplexer.add(this._onInstallExtension.event)); this.onInstallExtension = onInstallExtensionEventMultiplexer.event; - const onDidInstallExtensionsEventMultiplexer = this._register( - new EventMultiplexer(), - ); - this._register( - onDidInstallExtensionsEventMultiplexer.add( - this._onDidInstallExtensions.event, - ), - ); - this.onDidInstallExtensions = - onDidInstallExtensionsEventMultiplexer.event; - - const onDidProfileAwareInstallExtensionsEventMultiplexer = - this._register( - new EventMultiplexer(), - ); - this._register( - onDidProfileAwareInstallExtensionsEventMultiplexer.add( - this._onDidProfileAwareInstallExtensions.event, - ), - ); - this.onProfileAwareDidInstallExtensions = - onDidProfileAwareInstallExtensionsEventMultiplexer.event; - - const onUninstallExtensionEventMultiplexer = this._register( - new EventMultiplexer(), - ); - this._register( - onUninstallExtensionEventMultiplexer.add( - this._onUninstallExtension.event, - ), - ); + const onDidInstallExtensionsEventMultiplexer = this._register(new EventMultiplexer()); + this._register(onDidInstallExtensionsEventMultiplexer.add(this._onDidInstallExtensions.event)); + this.onDidInstallExtensions = onDidInstallExtensionsEventMultiplexer.event; + + const onDidProfileAwareInstallExtensionsEventMultiplexer = this._register(new EventMultiplexer()); + this._register(onDidProfileAwareInstallExtensionsEventMultiplexer.add(this._onDidProfileAwareInstallExtensions.event)); + this.onProfileAwareDidInstallExtensions = onDidProfileAwareInstallExtensionsEventMultiplexer.event; + + const onUninstallExtensionEventMultiplexer = this._register(new EventMultiplexer()); + this._register(onUninstallExtensionEventMultiplexer.add(this._onUninstallExtension.event)); this.onUninstallExtension = onUninstallExtensionEventMultiplexer.event; - const onDidUninstallExtensionEventMultiplexer = this._register( - new EventMultiplexer(), - ); - this._register( - onDidUninstallExtensionEventMultiplexer.add( - this._onDidUninstallExtension.event, - ), - ); - this.onDidUninstallExtension = - onDidUninstallExtensionEventMultiplexer.event; - - const onDidProfileAwareUninstallExtensionEventMultiplexer = - this._register( - new EventMultiplexer(), - ); - this._register( - onDidProfileAwareUninstallExtensionEventMultiplexer.add( - this._onDidProfileAwareUninstallExtension.event, - ), - ); - this.onProfileAwareDidUninstallExtension = - onDidProfileAwareUninstallExtensionEventMultiplexer.event; - - const onDidUpdateExtensionMetadaEventMultiplexer = this._register( - new EventMultiplexer(), - ); - this.onDidUpdateExtensionMetadata = - onDidUpdateExtensionMetadaEventMultiplexer.event; - - const onDidProfileAwareUpdateExtensionMetadaEventMultiplexer = - this._register(new EventMultiplexer()); - this.onProfileAwareDidUpdateExtensionMetadata = - onDidProfileAwareUpdateExtensionMetadaEventMultiplexer.event; - - const onDidChangeProfileEventMultiplexer = this._register( - new EventMultiplexer(), - ); + const onDidUninstallExtensionEventMultiplexer = this._register(new EventMultiplexer()); + this._register(onDidUninstallExtensionEventMultiplexer.add(this._onDidUninstallExtension.event)); + this.onDidUninstallExtension = onDidUninstallExtensionEventMultiplexer.event; + + const onDidProfileAwareUninstallExtensionEventMultiplexer = this._register(new EventMultiplexer()); + this._register(onDidProfileAwareUninstallExtensionEventMultiplexer.add(this._onDidProfileAwareUninstallExtension.event)); + this.onProfileAwareDidUninstallExtension = onDidProfileAwareUninstallExtensionEventMultiplexer.event; + + const onDidUpdateExtensionMetadaEventMultiplexer = this._register(new EventMultiplexer()); + this.onDidUpdateExtensionMetadata = onDidUpdateExtensionMetadaEventMultiplexer.event; + + const onDidProfileAwareUpdateExtensionMetadaEventMultiplexer = this._register(new EventMultiplexer()); + this.onProfileAwareDidUpdateExtensionMetadata = onDidProfileAwareUpdateExtensionMetadaEventMultiplexer.event; + + const onDidChangeProfileEventMultiplexer = this._register(new EventMultiplexer()); this.onDidChangeProfile = onDidChangeProfileEventMultiplexer.event; for (const server of this.servers) { - this._register( - onInstallExtensionEventMultiplexer.add( - Event.map( - server.extensionManagementService.onInstallExtension, - (e) => ({ ...e, server }), - ), - ), - ); - this._register( - onDidInstallExtensionsEventMultiplexer.add( - server.extensionManagementService.onDidInstallExtensions, - ), - ); - this._register( - onDidProfileAwareInstallExtensionsEventMultiplexer.add( - server.extensionManagementService - .onProfileAwareDidInstallExtensions, - ), - ); - this._register( - onUninstallExtensionEventMultiplexer.add( - Event.map( - server.extensionManagementService.onUninstallExtension, - (e) => ({ ...e, server }), - ), - ), - ); - this._register( - onDidUninstallExtensionEventMultiplexer.add( - Event.map( - server.extensionManagementService - .onDidUninstallExtension, - (e) => ({ ...e, server }), - ), - ), - ); - this._register( - onDidProfileAwareUninstallExtensionEventMultiplexer.add( - Event.map( - server.extensionManagementService - .onProfileAwareDidUninstallExtension, - (e) => ({ ...e, server }), - ), - ), - ); - this._register( - onDidUpdateExtensionMetadaEventMultiplexer.add( - server.extensionManagementService - .onDidUpdateExtensionMetadata, - ), - ); - this._register( - onDidProfileAwareUpdateExtensionMetadaEventMultiplexer.add( - server.extensionManagementService - .onProfileAwareDidUpdateExtensionMetadata, - ), - ); - this._register( - onDidChangeProfileEventMultiplexer.add( - Event.map( - server.extensionManagementService.onDidChangeProfile, - (e) => ({ ...e, server }), - ), - ), - ); + this._register(onInstallExtensionEventMultiplexer.add(Event.map(server.extensionManagementService.onInstallExtension, e => ({ ...e, server })))); + this._register(onDidInstallExtensionsEventMultiplexer.add(server.extensionManagementService.onDidInstallExtensions)); + this._register(onDidProfileAwareInstallExtensionsEventMultiplexer.add(server.extensionManagementService.onProfileAwareDidInstallExtensions)); + this._register(onUninstallExtensionEventMultiplexer.add(Event.map(server.extensionManagementService.onUninstallExtension, e => ({ ...e, server })))); + this._register(onDidUninstallExtensionEventMultiplexer.add(Event.map(server.extensionManagementService.onDidUninstallExtension, e => ({ ...e, server })))); + this._register(onDidProfileAwareUninstallExtensionEventMultiplexer.add(Event.map(server.extensionManagementService.onProfileAwareDidUninstallExtension, e => ({ ...e, server })))); + this._register(onDidUpdateExtensionMetadaEventMultiplexer.add(server.extensionManagementService.onDidUpdateExtensionMetadata)); + this._register(onDidProfileAwareUpdateExtensionMetadaEventMultiplexer.add(server.extensionManagementService.onProfileAwareDidUpdateExtensionMetadata)); + this._register(onDidChangeProfileEventMultiplexer.add(Event.map(server.extensionManagementService.onDidChangeProfile, e => ({ ...e, server })))); } } - async getInstalled( - type?: ExtensionType, - profileLocation?: URI, - productVersion?: IProductVersion, - ): Promise { + async getInstalled(type?: ExtensionType, profileLocation?: URI, productVersion?: IProductVersion): Promise { const result: ILocalExtension[] = []; - await Promise.all( - this.servers.map(async (server) => { - const installed = - await server.extensionManagementService.getInstalled( - type, - profileLocation, - productVersion, - ); - - if (server === this.getWorkspaceExtensionsServer()) { - const workspaceExtensions = - await this.getInstalledWorkspaceExtensions(true); - installed.push(...workspaceExtensions); - } - result.push(...installed); - }), - ); - + await Promise.all(this.servers.map(async server => { + const installed = await server.extensionManagementService.getInstalled(type, profileLocation, productVersion); + if (server === this.getWorkspaceExtensionsServer()) { + const workspaceExtensions = await this.getInstalledWorkspaceExtensions(true); + installed.push(...workspaceExtensions); + } + result.push(...installed); + })); return result; } - uninstall( - extension: ILocalExtension, - options: UninstallOptions, - ): Promise { + uninstall(extension: ILocalExtension, options: UninstallOptions): Promise { return this.uninstallExtensions([{ extension, options }]); } - async uninstallExtensions( - extensions: UninstallExtensionInfo[], - ): Promise { + async uninstallExtensions(extensions: UninstallExtensionInfo[]): Promise { const workspaceExtensions: ILocalExtension[] = []; + const groupedExtensions = new Map(); - const groupedExtensions = new Map< - IExtensionManagementServer, - UninstallExtensionInfo[] - >(); - - const addExtensionToServer = ( - server: IExtensionManagementServer, - extension: ILocalExtension, - options?: UninstallOptions, - ) => { + const addExtensionToServer = (server: IExtensionManagementServer, extension: ILocalExtension, options?: UninstallOptions) => { let extensions = groupedExtensions.get(server); - if (!extensions) { - groupedExtensions.set(server, (extensions = [])); + groupedExtensions.set(server, extensions = []); } extensions.push({ extension, options }); }; @@ -441,681 +193,315 @@ export class ExtensionManagementService for (const { extension, options } of extensions) { if (extension.isWorkspaceScoped) { workspaceExtensions.push(extension); - continue; } const server = this.getServer(extension); - if (!server) { - throw new Error( - `Invalid location ${extension.location.toString()}`, - ); + throw new Error(`Invalid location ${extension.location.toString()}`); } addExtensionToServer(server, extension, options); - - if ( - this.servers.length > 1 && - isLanguagePackExtension(extension.manifest) - ) { - const otherServers: IExtensionManagementServer[] = - this.servers.filter((s) => s !== server); - + if (this.servers.length > 1 && isLanguagePackExtension(extension.manifest)) { + const otherServers: IExtensionManagementServer[] = this.servers.filter(s => s !== server); for (const otherServer of otherServers) { - const installed = - await otherServer.extensionManagementService.getInstalled(); - - const extensionInOtherServer = installed.find( - (i) => - !i.isBuiltin && - areSameExtensions( - i.identifier, - extension.identifier, - ), - ); - + const installed = await otherServer.extensionManagementService.getInstalled(); + const extensionInOtherServer = installed.find(i => !i.isBuiltin && areSameExtensions(i.identifier, extension.identifier)); if (extensionInOtherServer) { - addExtensionToServer( - otherServer, - extensionInOtherServer, - options, - ); + addExtensionToServer(otherServer, extensionInOtherServer, options); } } } } const promises: Promise[] = []; - for (const workspaceExtension of workspaceExtensions) { - promises.push( - this.uninstallExtensionFromWorkspace(workspaceExtension), - ); + promises.push(this.uninstallExtensionFromWorkspace(workspaceExtension)); } for (const [server, extensions] of groupedExtensions.entries()) { promises.push(this.uninstallInServer(server, extensions)); } const result = await Promise.allSettled(promises); - - const errors = result - .filter((r) => r.status === "rejected") - .map((r) => r.reason); - + const errors = result.filter(r => r.status === 'rejected').map(r => r.reason); if (errors.length) { - throw new Error(errors.map((e) => e.message).join("\n")); + throw new Error(errors.map(e => e.message).join('\n')); } } - private async uninstallInServer( - server: IExtensionManagementServer, - extensions: UninstallExtensionInfo[], - ): Promise { - if ( - server === - this.extensionManagementServerService - .localExtensionManagementServer && - this.extensionManagementServerService - .remoteExtensionManagementServer - ) { + private async uninstallInServer(server: IExtensionManagementServer, extensions: UninstallExtensionInfo[]): Promise { + if (server === this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { for (const { extension } of extensions) { - const installedExtensions = - await this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.getInstalled( - ExtensionType.User, - ); - - const dependentNonUIExtensions = installedExtensions.filter( - (i) => - !this.extensionManifestPropertiesService.prefersExecuteOnUI( - i.manifest, - ) && - i.manifest.extensionDependencies && - i.manifest.extensionDependencies.some((id) => - areSameExtensions({ id }, extension.identifier), - ), - ); - + const installedExtensions = await this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.getInstalled(ExtensionType.User); + const dependentNonUIExtensions = installedExtensions.filter(i => !this.extensionManifestPropertiesService.prefersExecuteOnUI(i.manifest) + && i.manifest.extensionDependencies && i.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.identifier))); if (dependentNonUIExtensions.length) { - throw new Error( - this.getDependentsErrorMessage( - extension, - dependentNonUIExtensions, - ), - ); + throw (new Error(this.getDependentsErrorMessage(extension, dependentNonUIExtensions))); } } } - return server.extensionManagementService.uninstallExtensions( - extensions, - ); + return server.extensionManagementService.uninstallExtensions(extensions); } - private getDependentsErrorMessage( - extension: ILocalExtension, - dependents: ILocalExtension[], - ): string { + private getDependentsErrorMessage(extension: ILocalExtension, dependents: ILocalExtension[]): string { if (dependents.length === 1) { - return localize( - "singleDependentError", - "Cannot uninstall extension '{0}'. Extension '{1}' depends on this.", - extension.manifest.displayName || extension.manifest.name, - dependents[0].manifest.displayName || - dependents[0].manifest.name, - ); + return localize('singleDependentError', "Cannot uninstall extension '{0}'. Extension '{1}' depends on this.", + extension.manifest.displayName || extension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name); } if (dependents.length === 2) { - return localize( - "twoDependentsError", - "Cannot uninstall extension '{0}'. Extensions '{1}' and '{2}' depend on this.", - extension.manifest.displayName || extension.manifest.name, - dependents[0].manifest.displayName || - dependents[0].manifest.name, - dependents[1].manifest.displayName || - dependents[1].manifest.name, - ); - } - return localize( - "multipleDependentsError", - "Cannot uninstall extension '{0}'. Extensions '{1}', '{2}' and others depend on this.", - extension.manifest.displayName || extension.manifest.name, - dependents[0].manifest.displayName || dependents[0].manifest.name, - dependents[1].manifest.displayName || dependents[1].manifest.name, - ); + return localize('twoDependentsError', "Cannot uninstall extension '{0}'. Extensions '{1}' and '{2}' depend on this.", + extension.manifest.displayName || extension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name); + } + return localize('multipleDependentsError', "Cannot uninstall extension '{0}'. Extensions '{1}', '{2}' and others depend on this.", + extension.manifest.displayName || extension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name); + } - async reinstallFromGallery( - extension: ILocalExtension, - ): Promise { + async reinstallFromGallery(extension: ILocalExtension): Promise { const server = this.getServer(extension); - if (server) { await this.checkForWorkspaceTrust(extension.manifest, false); - - return server.extensionManagementService.reinstallFromGallery( - extension, - ); + return server.extensionManagementService.reinstallFromGallery(extension); } - return Promise.reject( - `Invalid location ${extension.location.toString()}`, - ); + return Promise.reject(`Invalid location ${extension.location.toString()}`); } - updateMetadata( - extension: ILocalExtension, - metadata: Partial, - ): Promise { + updateMetadata(extension: ILocalExtension, metadata: Partial): Promise { const server = this.getServer(extension); - if (server) { - const profile = extension.isApplicationScoped - ? this.userDataProfilesService.defaultProfile - : this.userDataProfileService.currentProfile; - - return server.extensionManagementService.updateMetadata( - extension, - metadata, - profile.extensionsResource, - ); - } - return Promise.reject( - `Invalid location ${extension.location.toString()}`, - ); + const profile = extension.isApplicationScoped ? this.userDataProfilesService.defaultProfile : this.userDataProfileService.currentProfile; + return server.extensionManagementService.updateMetadata(extension, metadata, profile.extensionsResource); + } + return Promise.reject(`Invalid location ${extension.location.toString()}`); } async resetPinnedStateForAllUserExtensions(pinned: boolean): Promise { - await Promise.allSettled( - this.servers.map((server) => - server.extensionManagementService.resetPinnedStateForAllUserExtensions( - pinned, - ), - ), - ); + await Promise.allSettled(this.servers.map(server => server.extensionManagementService.resetPinnedStateForAllUserExtensions(pinned))); } zip(extension: ILocalExtension): Promise { const server = this.getServer(extension); - if (server) { return server.extensionManagementService.zip(extension); } - return Promise.reject( - `Invalid location ${extension.location.toString()}`, - ); + return Promise.reject(`Invalid location ${extension.location.toString()}`); } - download( - extension: IGalleryExtension, - operation: InstallOperation, - donotVerifySignature: boolean, - ): Promise { - if ( - this.extensionManagementServerService.localExtensionManagementServer - ) { - return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.download( - extension, - operation, - donotVerifySignature, - ); - } - throw new Error("Cannot download extension"); + download(extension: IGalleryExtension, operation: InstallOperation, donotVerifySignature: boolean): Promise { + if (this.extensionManagementServerService.localExtensionManagementServer) { + return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.download(extension, operation, donotVerifySignature); + } + throw new Error('Cannot download extension'); } - async install( - vsix: URI, - options?: InstallOptions, - ): Promise { + async install(vsix: URI, options?: InstallOptions): Promise { const manifest = await this.getManifest(vsix); - return this.installVSIX(vsix, manifest, options); } - async installVSIX( - vsix: URI, - manifest: IExtensionManifest, - options?: InstallOptions, - ): Promise { + async installVSIX(vsix: URI, manifest: IExtensionManifest, options?: InstallOptions): Promise { const serversToInstall = this.getServersToInstall(manifest); - if (serversToInstall?.length) { await this.checkForWorkspaceTrust(manifest, false); - - const [local] = await Promises.settled( - serversToInstall.map((server) => - this.installVSIXInServer(vsix, server, options), - ), - ); - + const [local] = await Promises.settled(serversToInstall.map(server => this.installVSIXInServer(vsix, server, options))); return local; } - return Promise.reject("No Servers to Install"); + return Promise.reject('No Servers to Install'); } - private getServersToInstall( - manifest: IExtensionManifest, - ): IExtensionManagementServer[] | undefined { - if ( - this.extensionManagementServerService - .localExtensionManagementServer && - this.extensionManagementServerService - .remoteExtensionManagementServer - ) { + private getServersToInstall(manifest: IExtensionManifest): IExtensionManagementServer[] | undefined { + if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { if (isLanguagePackExtension(manifest)) { // Install on both servers - return [ - this.extensionManagementServerService - .localExtensionManagementServer, - this.extensionManagementServerService - .remoteExtensionManagementServer, - ]; + return [this.extensionManagementServerService.localExtensionManagementServer, this.extensionManagementServerService.remoteExtensionManagementServer]; } - if ( - this.extensionManifestPropertiesService.prefersExecuteOnUI( - manifest, - ) - ) { + if (this.extensionManifestPropertiesService.prefersExecuteOnUI(manifest)) { // Install only on local server - return [ - this.extensionManagementServerService - .localExtensionManagementServer, - ]; + return [this.extensionManagementServerService.localExtensionManagementServer]; } // Install only on remote server - return [ - this.extensionManagementServerService - .remoteExtensionManagementServer, - ]; + return [this.extensionManagementServerService.remoteExtensionManagementServer]; } - if ( - this.extensionManagementServerService.localExtensionManagementServer - ) { - return [ - this.extensionManagementServerService - .localExtensionManagementServer, - ]; + if (this.extensionManagementServerService.localExtensionManagementServer) { + return [this.extensionManagementServerService.localExtensionManagementServer]; } - if ( - this.extensionManagementServerService - .remoteExtensionManagementServer - ) { - return [ - this.extensionManagementServerService - .remoteExtensionManagementServer, - ]; + if (this.extensionManagementServerService.remoteExtensionManagementServer) { + return [this.extensionManagementServerService.remoteExtensionManagementServer]; } return undefined; } async installFromLocation(location: URI): Promise { if (location.scheme === Schemas.file) { - if ( - this.extensionManagementServerService - .localExtensionManagementServer - ) { - return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromLocation( - location, - this.userDataProfileService.currentProfile - .extensionsResource, - ); + if (this.extensionManagementServerService.localExtensionManagementServer) { + return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromLocation(location, this.userDataProfileService.currentProfile.extensionsResource); } - throw new Error("Local extension management server is not found"); + throw new Error('Local extension management server is not found'); } if (location.scheme === Schemas.vscodeRemote) { - if ( - this.extensionManagementServerService - .remoteExtensionManagementServer - ) { - return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.installFromLocation( - location, - this.userDataProfileService.currentProfile - .extensionsResource, - ); + if (this.extensionManagementServerService.remoteExtensionManagementServer) { + return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.installFromLocation(location, this.userDataProfileService.currentProfile.extensionsResource); } - throw new Error("Remote extension management server is not found"); + throw new Error('Remote extension management server is not found'); } - if ( - !this.extensionManagementServerService.webExtensionManagementServer - ) { - throw new Error("Web extension management server is not found"); + if (!this.extensionManagementServerService.webExtensionManagementServer) { + throw new Error('Web extension management server is not found'); } - return this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.installFromLocation( - location, - this.userDataProfileService.currentProfile.extensionsResource, - ); + return this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.installFromLocation(location, this.userDataProfileService.currentProfile.extensionsResource); } - protected installVSIXInServer( - vsix: URI, - server: IExtensionManagementServer, - options: InstallOptions | undefined, - ): Promise { + protected installVSIXInServer(vsix: URI, server: IExtensionManagementServer, options: InstallOptions | undefined): Promise { return server.extensionManagementService.install(vsix, options); } getManifest(vsix: URI): Promise { - if ( - vsix.scheme === Schemas.file && - this.extensionManagementServerService.localExtensionManagementServer - ) { - return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getManifest( - vsix, - ); - } - if ( - vsix.scheme === Schemas.file && - this.extensionManagementServerService - .remoteExtensionManagementServer - ) { - return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.getManifest( - vsix, - ); - } - if ( - vsix.scheme === Schemas.vscodeRemote && - this.extensionManagementServerService - .remoteExtensionManagementServer - ) { - return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.getManifest( - vsix, - ); - } - return Promise.reject("No Servers"); + if (vsix.scheme === Schemas.file && this.extensionManagementServerService.localExtensionManagementServer) { + return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getManifest(vsix); + } + if (vsix.scheme === Schemas.file && this.extensionManagementServerService.remoteExtensionManagementServer) { + return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.getManifest(vsix); + } + if (vsix.scheme === Schemas.vscodeRemote && this.extensionManagementServerService.remoteExtensionManagementServer) { + return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.getManifest(vsix); + } + return Promise.reject('No Servers'); } - async canInstall( - extension: IGalleryExtension | IResourceExtension, - ): Promise { + async canInstall(extension: IGalleryExtension | IResourceExtension): Promise { if (isGalleryExtension(extension)) { return this.canInstallGalleryExtension(extension); } return this.canInstallResourceExtension(extension); } - private async canInstallGalleryExtension( - gallery: IGalleryExtension, - ): Promise { - if ( - this.extensionManagementServerService - .localExtensionManagementServer && - (await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.canInstall( - gallery, - )) === true - ) { + private async canInstallGalleryExtension(gallery: IGalleryExtension): Promise { + if (this.extensionManagementServerService.localExtensionManagementServer + && await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.canInstall(gallery) === true) { return true; } - const manifest = await this.extensionGalleryService.getManifest( - gallery, - CancellationToken.None, - ); - + const manifest = await this.extensionGalleryService.getManifest(gallery, CancellationToken.None); if (!manifest) { - return new MarkdownString().appendText( - localize("manifest is not found", "Manifest is not found"), - ); - } - if ( - this.extensionManagementServerService - .remoteExtensionManagementServer && - (await this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.canInstall( - gallery, - )) === true && - this.extensionManifestPropertiesService.canExecuteOnWorkspace( - manifest, - ) - ) { + return new MarkdownString().appendText(localize('manifest is not found', "Manifest is not found")); + } + if (this.extensionManagementServerService.remoteExtensionManagementServer + && await this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.canInstall(gallery) === true + && this.extensionManifestPropertiesService.canExecuteOnWorkspace(manifest)) { return true; } - if ( - this.extensionManagementServerService - .webExtensionManagementServer && - (await this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.canInstall( - gallery, - )) === true && - this.extensionManifestPropertiesService.canExecuteOnWeb(manifest) - ) { + if (this.extensionManagementServerService.webExtensionManagementServer + && await this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.canInstall(gallery) === true + && this.extensionManifestPropertiesService.canExecuteOnWeb(manifest)) { return true; } - return new MarkdownString().appendText( - localize( - "cannot be installed", - "Cannot install the '{0}' extension because it is not available in this setup.", - gallery.displayName || gallery.name, - ), - ); + return new MarkdownString().appendText(localize('cannot be installed', "Cannot install the '{0}' extension because it is not available in this setup.", gallery.displayName || gallery.name)); } - private async canInstallResourceExtension( - extension: IResourceExtension, - ): Promise { - if ( - this.extensionManagementServerService.localExtensionManagementServer - ) { + private async canInstallResourceExtension(extension: IResourceExtension): Promise { + if (this.extensionManagementServerService.localExtensionManagementServer) { return true; } - if ( - this.extensionManagementServerService - .remoteExtensionManagementServer && - this.extensionManifestPropertiesService.canExecuteOnWorkspace( - extension.manifest, - ) - ) { + if (this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManifestPropertiesService.canExecuteOnWorkspace(extension.manifest)) { return true; } - if ( - this.extensionManagementServerService - .webExtensionManagementServer && - this.extensionManifestPropertiesService.canExecuteOnWeb( - extension.manifest, - ) - ) { + if (this.extensionManagementServerService.webExtensionManagementServer && this.extensionManifestPropertiesService.canExecuteOnWeb(extension.manifest)) { return true; } - return new MarkdownString().appendText( - localize( - "cannot be installed", - "Cannot install the '{0}' extension because it is not available in this setup.", - extension.manifest.displayName ?? extension.identifier.id, - ), - ); + return new MarkdownString().appendText(localize('cannot be installed', "Cannot install the '{0}' extension because it is not available in this setup.", extension.manifest.displayName ?? extension.identifier.id)); } - async updateFromGallery( - gallery: IGalleryExtension, - extension: ILocalExtension, - installOptions?: InstallOptions, - ): Promise { + async updateFromGallery(gallery: IGalleryExtension, extension: ILocalExtension, installOptions?: InstallOptions): Promise { const server = this.getServer(extension); - if (!server) { - return Promise.reject( - `Invalid location ${extension.location.toString()}`, - ); + return Promise.reject(`Invalid location ${extension.location.toString()}`); } const servers: IExtensionManagementServer[] = []; // Update Language pack on local and remote servers if (isLanguagePackExtension(extension.manifest)) { - servers.push( - ...this.servers.filter( - (server) => - server !== - this.extensionManagementServerService - .webExtensionManagementServer, - ), - ); + servers.push(...this.servers.filter(server => server !== this.extensionManagementServerService.webExtensionManagementServer)); } else { servers.push(server); } - installOptions = { - ...(installOptions || {}), - isApplicationScoped: extension.isApplicationScoped, - }; - - return Promises.settled( - servers.map((server) => - server.extensionManagementService.installFromGallery( - gallery, - installOptions, - ), - ), - ).then(([local]) => local); + installOptions = { ...(installOptions || {}), isApplicationScoped: extension.isApplicationScoped }; + return Promises.settled(servers.map(server => server.extensionManagementService.installFromGallery(gallery, installOptions))).then(([local]) => local); } - async installGalleryExtensions( - extensions: InstallExtensionInfo[], - ): Promise { + async installGalleryExtensions(extensions: InstallExtensionInfo[]): Promise { const results = new Map(); - const extensionsByServer = new Map< - IExtensionManagementServer, - InstallExtensionInfo[] - >(); - await Promise.all( - extensions.map(async ({ extension, options }) => { - try { - const servers = - await this.validateAndGetExtensionManagementServersToInstall( - extension, - options, - ); - - if ( - !options.isMachineScoped && - this.isExtensionsSyncEnabled() - ) { - if ( - this.extensionManagementServerService - .localExtensionManagementServer && - !servers.includes( - this.extensionManagementServerService - .localExtensionManagementServer, - ) && - (await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.canInstall( - extension, - )) === true - ) { - servers.push( - this.extensionManagementServerService - .localExtensionManagementServer, - ); - } - } - for (const server of servers) { - let exensions = extensionsByServer.get(server); - - if (!exensions) { - extensionsByServer.set(server, (exensions = [])); - } - exensions.push({ extension, options }); + const extensionsByServer = new Map(); + await Promise.all(extensions.map(async ({ extension, options }) => { + try { + const servers = await this.validateAndGetExtensionManagementServersToInstall(extension, options); + if (!options.isMachineScoped && this.isExtensionsSyncEnabled()) { + if (this.extensionManagementServerService.localExtensionManagementServer + && !servers.includes(this.extensionManagementServerService.localExtensionManagementServer) + && await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.canInstall(extension) === true) { + servers.push(this.extensionManagementServerService.localExtensionManagementServer); } - } catch (error) { - results.set(extension.identifier.id.toLowerCase(), { - identifier: extension.identifier, - source: extension, - error, - operation: InstallOperation.Install, - profileLocation: - options.profileLocation ?? - this.userDataProfileService.currentProfile - .extensionsResource, - }); } - }), - ); - - await Promise.all( - [...extensionsByServer.entries()].map( - async ([server, extensions]) => { - const serverResults = - await server.extensionManagementService.installGalleryExtensions( - extensions, - ); - - for (const result of serverResults) { - results.set(result.identifier.id.toLowerCase(), result); + for (const server of servers) { + let exensions = extensionsByServer.get(server); + if (!exensions) { + extensionsByServer.set(server, exensions = []); } - }, - ), - ); + exensions.push({ extension, options }); + } + } catch (error) { + results.set(extension.identifier.id.toLowerCase(), { + identifier: extension.identifier, + source: extension, error, + operation: InstallOperation.Install, + profileLocation: options.profileLocation ?? this.userDataProfileService.currentProfile.extensionsResource + }); + } + })); + + await Promise.all([...extensionsByServer.entries()].map(async ([server, extensions]) => { + const serverResults = await server.extensionManagementService.installGalleryExtensions(extensions); + for (const result of serverResults) { + results.set(result.identifier.id.toLowerCase(), result); + } + })); return [...results.values()]; } - async installFromGallery( - gallery: IGalleryExtension, - installOptions?: InstallOptions, - ): Promise { - const servers = - await this.validateAndGetExtensionManagementServersToInstall( - gallery, - installOptions, - ); - + async installFromGallery(gallery: IGalleryExtension, installOptions?: IWorkbenchInstallOptions): Promise { + const servers = await this.validateAndGetExtensionManagementServersToInstall(gallery, installOptions); if (!installOptions || isUndefined(installOptions.isMachineScoped)) { - const isMachineScoped = await this.hasToFlagExtensionsMachineScoped( - [gallery], - ); + const isMachineScoped = await this.hasToFlagExtensionsMachineScoped([gallery]); installOptions = { ...(installOptions || {}), isMachineScoped }; } - if (!installOptions.isMachineScoped && this.isExtensionsSyncEnabled()) { - if ( - this.extensionManagementServerService - .localExtensionManagementServer && - !servers.includes( - this.extensionManagementServerService - .localExtensionManagementServer, - ) && - (await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.canInstall( - gallery, - )) === true - ) { - servers.push( - this.extensionManagementServerService - .localExtensionManagementServer, - ); + if (installOptions.installEverywhere || (!installOptions.isMachineScoped && this.isExtensionsSyncEnabled())) { + if (this.extensionManagementServerService.localExtensionManagementServer + && !servers.includes(this.extensionManagementServerService.localExtensionManagementServer) + && await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.canInstall(gallery) === true) { + servers.push(this.extensionManagementServerService.localExtensionManagementServer); } } - return Promises.settled( - servers.map((server) => - server.extensionManagementService.installFromGallery( - gallery, - installOptions, - ), - ), - ).then(([local]) => local); + + return Promises.settled(servers.map(server => server.extensionManagementService.installFromGallery(gallery, installOptions))).then(([local]) => local); } async getExtensions(locations: URI[]): Promise { - const scannedExtensions = - await this.extensionsScannerService.scanMultipleExtensions( - locations, - ExtensionType.User, - { includeInvalid: true }, - ); - + const scannedExtensions = await this.extensionsScannerService.scanMultipleExtensions(locations, ExtensionType.User, { includeInvalid: true }); const result: IResourceExtension[] = []; - await Promise.all( - scannedExtensions.map(async (scannedExtension) => { - const workspaceExtension = - await this.workspaceExtensionManagementService.toLocalWorkspaceExtension( - scannedExtension, - ); - - if (workspaceExtension) { - result.push({ - type: "resource", - identifier: workspaceExtension.identifier, - location: workspaceExtension.location, - manifest: workspaceExtension.manifest, - changelogUri: workspaceExtension.changelogUrl, - readmeUri: workspaceExtension.readmeUrl, - }); - } - }), - ); - + await Promise.all(scannedExtensions.map(async scannedExtension => { + const workspaceExtension = await this.workspaceExtensionManagementService.toLocalWorkspaceExtension(scannedExtension); + if (workspaceExtension) { + result.push({ + type: 'resource', + identifier: workspaceExtension.identifier, + location: workspaceExtension.location, + manifest: workspaceExtension.manifest, + changelogUri: workspaceExtension.changelogUrl, + readmeUri: workspaceExtension.readmeUrl, + }); + } + })); return result; } @@ -1123,209 +509,129 @@ export class ExtensionManagementService return this.workspaceExtensionManagementService.getInstalledWorkspaceExtensionsLocations(); } - async getInstalledWorkspaceExtensions( - includeInvalid: boolean, - ): Promise { - return this.workspaceExtensionManagementService.getInstalled( - includeInvalid, - ); + async getInstalledWorkspaceExtensions(includeInvalid: boolean): Promise { + return this.workspaceExtensionManagementService.getInstalled(includeInvalid); } - async installResourceExtension( - extension: IResourceExtension, - installOptions: InstallOptions, - ): Promise { + async installResourceExtension(extension: IResourceExtension, installOptions: InstallOptions): Promise { if (!this.canInstallResourceExtension(extension)) { - throw new Error( - "This extension cannot be installed in the current workspace.", - ); + throw new Error('This extension cannot be installed in the current workspace.'); } if (!installOptions.isWorkspaceScoped) { return this.installFromLocation(extension.location); } - this.logService.info( - `Installing the extension ${extension.identifier.id} from ${extension.location.toString()} in workspace`, - ); - + this.logService.info(`Installing the extension ${extension.identifier.id} from ${extension.location.toString()} in workspace`); const server = this.getWorkspaceExtensionsServer(); this._onInstallExtension.fire({ identifier: extension.identifier, source: extension.location, server, applicationScoped: false, - profileLocation: - this.userDataProfileService.currentProfile.extensionsResource, - workspaceScoped: true, + profileLocation: this.userDataProfileService.currentProfile.extensionsResource, + workspaceScoped: true }); try { await this.checkForWorkspaceTrust(extension.manifest, true); - const workspaceExtension = - await this.workspaceExtensionManagementService.install( - extension, - ); - - this.logService.info( - `Successfully installed the extension ${workspaceExtension.identifier.id} from ${extension.location.toString()} in the workspace`, - ); - this._onDidInstallExtensions.fire([ - { - identifier: workspaceExtension.identifier, - source: extension.location, - operation: InstallOperation.Install, - applicationScoped: false, - profileLocation: - this.userDataProfileService.currentProfile - .extensionsResource, - local: workspaceExtension, - workspaceScoped: true, - }, - ]); + const workspaceExtension = await this.workspaceExtensionManagementService.install(extension); + this.logService.info(`Successfully installed the extension ${workspaceExtension.identifier.id} from ${extension.location.toString()} in the workspace`); + this._onDidInstallExtensions.fire([{ + identifier: workspaceExtension.identifier, + source: extension.location, + operation: InstallOperation.Install, + applicationScoped: false, + profileLocation: this.userDataProfileService.currentProfile.extensionsResource, + local: workspaceExtension, + workspaceScoped: true + }]); return workspaceExtension; } catch (error) { - this.logService.error( - `Failed to install the extension ${extension.identifier.id} from ${extension.location.toString()} in the workspace`, - getErrorMessage(error), - ); - this._onDidInstallExtensions.fire([ - { - identifier: extension.identifier, - source: extension.location, - operation: InstallOperation.Install, - applicationScoped: false, - profileLocation: - this.userDataProfileService.currentProfile - .extensionsResource, - error, - workspaceScoped: true, - }, - ]); - + this.logService.error(`Failed to install the extension ${extension.identifier.id} from ${extension.location.toString()} in the workspace`, getErrorMessage(error)); + this._onDidInstallExtensions.fire([{ + identifier: extension.identifier, + source: extension.location, + operation: InstallOperation.Install, + applicationScoped: false, + profileLocation: this.userDataProfileService.currentProfile.extensionsResource, + error, + workspaceScoped: true + }]); throw error; } } - private async uninstallExtensionFromWorkspace( - extension: ILocalExtension, - ): Promise { + private async uninstallExtensionFromWorkspace(extension: ILocalExtension): Promise { if (!extension.isWorkspaceScoped) { - throw new Error("The extension is not a workspace extension"); + throw new Error('The extension is not a workspace extension'); } - this.logService.info( - `Uninstalling the workspace extension ${extension.identifier.id} from ${extension.location.toString()}`, - ); - + this.logService.info(`Uninstalling the workspace extension ${extension.identifier.id} from ${extension.location.toString()}`); const server = this.getWorkspaceExtensionsServer(); this._onUninstallExtension.fire({ identifier: extension.identifier, server, applicationScoped: false, workspaceScoped: true, - profileLocation: - this.userDataProfileService.currentProfile.extensionsResource, + profileLocation: this.userDataProfileService.currentProfile.extensionsResource }); try { await this.workspaceExtensionManagementService.uninstall(extension); - this.logService.info( - `Successfully uninstalled the workspace extension ${extension.identifier.id} from ${extension.location.toString()}`, - ); - this.telemetryService.publicLog2< - {}, - { - owner: "sandy081"; - comment: "Uninstall workspace extension"; - } - >("workspaceextension:uninstall"); + this.logService.info(`Successfully uninstalled the workspace extension ${extension.identifier.id} from ${extension.location.toString()}`); + this.telemetryService.publicLog2<{}, { + owner: 'sandy081'; + comment: 'Uninstall workspace extension'; + }>('workspaceextension:uninstall'); this._onDidUninstallExtension.fire({ identifier: extension.identifier, server, applicationScoped: false, workspaceScoped: true, - profileLocation: - this.userDataProfileService.currentProfile - .extensionsResource, + profileLocation: this.userDataProfileService.currentProfile.extensionsResource }); } catch (error) { - this.logService.error( - `Failed to uninstall the workspace extension ${extension.identifier.id} from ${extension.location.toString()}`, - getErrorMessage(error), - ); + this.logService.error(`Failed to uninstall the workspace extension ${extension.identifier.id} from ${extension.location.toString()}`, getErrorMessage(error)); this._onDidUninstallExtension.fire({ identifier: extension.identifier, server, error, applicationScoped: false, workspaceScoped: true, - profileLocation: - this.userDataProfileService.currentProfile - .extensionsResource, + profileLocation: this.userDataProfileService.currentProfile.extensionsResource }); - throw error; } } - private async validateAndGetExtensionManagementServersToInstall( - gallery: IGalleryExtension, - installOptions?: InstallOptions, - ): Promise { - const manifest = await this.extensionGalleryService.getManifest( - gallery, - CancellationToken.None, - ); + private async validateAndGetExtensionManagementServersToInstall(gallery: IGalleryExtension, installOptions?: InstallOptions): Promise { + const manifest = await this.extensionGalleryService.getManifest(gallery, CancellationToken.None); if (!manifest) { - return Promise.reject( - localize( - "Manifest is not found", - "Installing Extension {0} failed: Manifest is not found.", - gallery.displayName || gallery.name, - ), - ); + return Promise.reject(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", gallery.displayName || gallery.name)); } const servers: IExtensionManagementServer[] = []; // Install Language pack on local and remote servers if (isLanguagePackExtension(manifest)) { - servers.push( - ...this.servers.filter( - (server) => - server !== - this.extensionManagementServerService - .webExtensionManagementServer, - ), - ); + servers.push(...this.servers.filter(server => server !== this.extensionManagementServerService.webExtensionManagementServer)); } else { const server = this.getExtensionManagementServerToInstall(manifest); - if (server) { servers.push(server); } } if (!servers.length) { - const error = new Error( - localize( - "cannot be installed", - "Cannot install the '{0}' extension because it is not available in this setup.", - gallery.displayName || gallery.name, - ), - ); + const error = new Error(localize('cannot be installed', "Cannot install the '{0}' extension because it is not available in this setup.", gallery.displayName || gallery.name)); error.name = ExtensionManagementErrorCode.Unsupported; - throw error; } - if ( - installOptions?.context?.[EXTENSION_INSTALL_SOURCE_CONTEXT] !== - ExtensionInstallSource.SETTINGS_SYNC - ) { + if (installOptions?.context?.[EXTENSION_INSTALL_SOURCE_CONTEXT] !== ExtensionInstallSource.SETTINGS_SYNC) { await this.checkForWorkspaceTrust(manifest, false); } @@ -1336,110 +642,56 @@ export class ExtensionManagementService return servers; } - private getExtensionManagementServerToInstall( - manifest: IExtensionManifest, - ): IExtensionManagementServer | null { + private getExtensionManagementServerToInstall(manifest: IExtensionManifest): IExtensionManagementServer | null { // Only local server - if ( - this.servers.length === 1 && - this.extensionManagementServerService.localExtensionManagementServer - ) { - return this.extensionManagementServerService - .localExtensionManagementServer; + if (this.servers.length === 1 && this.extensionManagementServerService.localExtensionManagementServer) { + return this.extensionManagementServerService.localExtensionManagementServer; } - const extensionKind = - this.extensionManifestPropertiesService.getExtensionKind(manifest); - + const extensionKind = this.extensionManifestPropertiesService.getExtensionKind(manifest); for (const kind of extensionKind) { - if ( - kind === "ui" && - this.extensionManagementServerService - .localExtensionManagementServer - ) { - return this.extensionManagementServerService - .localExtensionManagementServer; + if (kind === 'ui' && this.extensionManagementServerService.localExtensionManagementServer) { + return this.extensionManagementServerService.localExtensionManagementServer; } - if ( - kind === "workspace" && - this.extensionManagementServerService - .remoteExtensionManagementServer - ) { - return this.extensionManagementServerService - .remoteExtensionManagementServer; + if (kind === 'workspace' && this.extensionManagementServerService.remoteExtensionManagementServer) { + return this.extensionManagementServerService.remoteExtensionManagementServer; } - if ( - kind === "web" && - this.extensionManagementServerService - .webExtensionManagementServer - ) { - return this.extensionManagementServerService - .webExtensionManagementServer; + if (kind === 'web' && this.extensionManagementServerService.webExtensionManagementServer) { + return this.extensionManagementServerService.webExtensionManagementServer; } } // Local server can accept any extension. So return local server if not compatible server found. - return this.extensionManagementServerService - .localExtensionManagementServer; + return this.extensionManagementServerService.localExtensionManagementServer; } private isExtensionsSyncEnabled(): boolean { - return ( - this.userDataSyncEnablementService.isEnabled() && - this.userDataSyncEnablementService.isResourceEnabled( - SyncResource.Extensions, - ) - ); + return this.userDataSyncEnablementService.isEnabled() && this.userDataSyncEnablementService.isResourceEnabled(SyncResource.Extensions); } - private async hasToFlagExtensionsMachineScoped( - extensions: IGalleryExtension[], - ): Promise { + private async hasToFlagExtensionsMachineScoped(extensions: IGalleryExtension[]): Promise { if (this.isExtensionsSyncEnabled()) { const { result } = await this.dialogService.prompt({ type: Severity.Info, - message: - extensions.length === 1 - ? localize("install extension", "Install Extension") - : localize("install extensions", "Install Extensions"), - detail: - extensions.length === 1 - ? localize( - "install single extension", - "Would you like to install and synchronize '{0}' extension across your devices?", - extensions[0].displayName, - ) - : localize( - "install multiple extensions", - "Would you like to install and synchronize extensions across your devices?", - ), + message: extensions.length === 1 ? localize('install extension', "Install Extension") : localize('install extensions', "Install Extensions"), + detail: extensions.length === 1 + ? localize('install single extension', "Would you like to install and synchronize '{0}' extension across your devices?", extensions[0].displayName) + : localize('install multiple extensions', "Would you like to install and synchronize extensions across your devices?"), buttons: [ { - label: localize( - { - key: "install", - comment: ["&& denotes a mnemonic"], - }, - "&&Install", - ), - run: () => false, + label: localize({ key: 'install', comment: ['&& denotes a mnemonic'] }, "&&Install"), + run: () => false }, { - label: localize( - { - key: "install and do no sync", - comment: ["&& denotes a mnemonic"], - }, - "Install (Do &¬ sync)", - ), - run: () => true, - }, + label: localize({ key: 'install and do no sync', comment: ['&& denotes a mnemonic'] }, "Install (Do &¬ sync)"), + run: () => true + } ], cancelButton: { run: () => { throw new CancellationError(); - }, - }, + } + } }); return result; @@ -1448,228 +700,120 @@ export class ExtensionManagementService } getExtensionsControlManifest(): Promise { - if ( - this.extensionManagementServerService.localExtensionManagementServer - ) { + if (this.extensionManagementServerService.localExtensionManagementServer) { return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getExtensionsControlManifest(); } - if ( - this.extensionManagementServerService - .remoteExtensionManagementServer - ) { + if (this.extensionManagementServerService.remoteExtensionManagementServer) { return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.getExtensionsControlManifest(); } - if ( - this.extensionManagementServerService.webExtensionManagementServer - ) { + if (this.extensionManagementServerService.webExtensionManagementServer) { return this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.getExtensionsControlManifest(); } return Promise.resolve({ malicious: [], deprecated: {}, search: [] }); } - private getServer( - extension: ILocalExtension, - ): IExtensionManagementServer | null { + private getServer(extension: ILocalExtension): IExtensionManagementServer | null { if (extension.isWorkspaceScoped) { return this.getWorkspaceExtensionsServer(); } - return this.extensionManagementServerService.getExtensionManagementServer( - extension, - ); + return this.extensionManagementServerService.getExtensionManagementServer(extension); } private getWorkspaceExtensionsServer(): IExtensionManagementServer { - if ( - this.extensionManagementServerService - .remoteExtensionManagementServer - ) { - return this.extensionManagementServerService - .remoteExtensionManagementServer; - } - if ( - this.extensionManagementServerService.localExtensionManagementServer - ) { - return this.extensionManagementServerService - .localExtensionManagementServer; - } - if ( - this.extensionManagementServerService.webExtensionManagementServer - ) { - return this.extensionManagementServerService - .webExtensionManagementServer; - } - throw new Error("No extension server found"); + if (this.extensionManagementServerService.remoteExtensionManagementServer) { + return this.extensionManagementServerService.remoteExtensionManagementServer; + } + if (this.extensionManagementServerService.localExtensionManagementServer) { + return this.extensionManagementServerService.localExtensionManagementServer; + } + if (this.extensionManagementServerService.webExtensionManagementServer) { + return this.extensionManagementServerService.webExtensionManagementServer; + } + throw new Error('No extension server found'); } - protected async checkForWorkspaceTrust( - manifest: IExtensionManifest, - requireTrust: boolean, - ): Promise { - if ( - requireTrust || - this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType( - manifest, - ) === false - ) { + protected async checkForWorkspaceTrust(manifest: IExtensionManifest, requireTrust: boolean): Promise { + if (requireTrust || this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(manifest) === false) { const buttons: WorkspaceTrustRequestButton[] = []; - buttons.push({ - label: localize( - "extensionInstallWorkspaceTrustButton", - "Trust Workspace & Install", - ), - type: "ContinueWithTrust", - }); - + buttons.push({ label: localize('extensionInstallWorkspaceTrustButton', "Trust Workspace & Install"), type: 'ContinueWithTrust' }); if (!requireTrust) { - buttons.push({ - label: localize( - "extensionInstallWorkspaceTrustContinueButton", - "Install", - ), - type: "ContinueWithoutTrust", - }); + buttons.push({ label: localize('extensionInstallWorkspaceTrustContinueButton', "Install"), type: 'ContinueWithoutTrust' }); } - buttons.push({ - label: localize( - "extensionInstallWorkspaceTrustManageButton", - "Learn More", - ), - type: "Manage", + buttons.push({ label: localize('extensionInstallWorkspaceTrustManageButton', "Learn More"), type: 'Manage' }); + const trustState = await this.workspaceTrustRequestService.requestWorkspaceTrust({ + message: localize('extensionInstallWorkspaceTrustMessage', "Enabling this extension requires a trusted workspace."), + buttons }); - const trustState = - await this.workspaceTrustRequestService.requestWorkspaceTrust({ - message: localize( - "extensionInstallWorkspaceTrustMessage", - "Enabling this extension requires a trusted workspace.", - ), - buttons, - }); - if (trustState === undefined) { throw new CancellationError(); } } } - private async checkInstallingExtensionOnWeb( - extension: IGalleryExtension, - manifest: IExtensionManifest, - ): Promise { - if ( - this.servers.length !== 1 || - this.servers[0] !== - this.extensionManagementServerService - .webExtensionManagementServer - ) { + private async checkInstallingExtensionOnWeb(extension: IGalleryExtension, manifest: IExtensionManifest): Promise { + if (this.servers.length !== 1 || this.servers[0] !== this.extensionManagementServerService.webExtensionManagementServer) { return; } const nonWebExtensions = []; - if (manifest.extensionPack?.length) { - const extensions = await this.extensionGalleryService.getExtensions( - manifest.extensionPack.map((id) => ({ id })), - CancellationToken.None, - ); - + const extensions = await this.extensionGalleryService.getExtensions(manifest.extensionPack.map(id => ({ id })), CancellationToken.None); for (const extension of extensions) { - if ( - (await this.servers[0].extensionManagementService.canInstall( - extension, - )) !== true - ) { + if (await this.servers[0].extensionManagementService.canInstall(extension) !== true) { nonWebExtensions.push(extension); } } - if ( - nonWebExtensions.length && - nonWebExtensions.length === extensions.length - ) { - throw new ExtensionManagementError( - "Not supported in Web", - ExtensionManagementErrorCode.Unsupported, - ); + if (nonWebExtensions.length && nonWebExtensions.length === extensions.length) { + throw new ExtensionManagementError('Not supported in Web', ExtensionManagementErrorCode.Unsupported); } } - const productName = localize( - "VS Code for Web", - "{0} for the Web", - this.productService.nameLong, - ); - - const virtualWorkspaceSupport = - this.extensionManifestPropertiesService.getExtensionVirtualWorkspaceSupportType( - manifest, - ); - - const virtualWorkspaceSupportReason = getWorkspaceSupportTypeMessage( - manifest.capabilities?.virtualWorkspaces, - ); - - const hasLimitedSupport = - virtualWorkspaceSupport === "limited" || - !!virtualWorkspaceSupportReason; + const productName = localize('VS Code for Web', "{0} for the Web", this.productService.nameLong); + const virtualWorkspaceSupport = this.extensionManifestPropertiesService.getExtensionVirtualWorkspaceSupportType(manifest); + const virtualWorkspaceSupportReason = getWorkspaceSupportTypeMessage(manifest.capabilities?.virtualWorkspaces); + const hasLimitedSupport = virtualWorkspaceSupport === 'limited' || !!virtualWorkspaceSupportReason; if (!nonWebExtensions.length && !hasLimitedSupport) { return; } - const limitedSupportMessage = localize( - "limited support", - "'{0}' has limited functionality in {1}.", - extension.displayName || extension.identifier.id, - productName, - ); - + const limitedSupportMessage = localize('limited support', "'{0}' has limited functionality in {1}.", extension.displayName || extension.identifier.id, productName); let message: string; - let buttons: IPromptButton[] = []; - let detail: string | undefined; const installAnywayButton: IPromptButton = { - label: localize( - { key: "install anyways", comment: ["&& denotes a mnemonic"] }, - "&&Install Anyway", - ), - run: () => {}, + label: localize({ key: 'install anyways', comment: ['&& denotes a mnemonic'] }, "&&Install Anyway"), + run: () => { } }; const showExtensionsButton: IPromptButton = { - label: localize( - { key: "showExtensions", comment: ["&& denotes a mnemonic"] }, - "&&Show Extensions", - ), - run: () => - this.instantiationService.invokeFunction((accessor) => - accessor - .get(ICommandService) - .executeCommand( - "extension.open", - extension.identifier.id, - "extensionPack", - ), - ), + label: localize({ key: 'showExtensions', comment: ['&& denotes a mnemonic'] }, "&&Show Extensions"), + run: () => this.instantiationService.invokeFunction(accessor => accessor.get(ICommandService).executeCommand('extension.open', extension.identifier.id, 'extensionPack')) }; if (nonWebExtensions.length && hasLimitedSupport) { message = limitedSupportMessage; - detail = `${virtualWorkspaceSupportReason ? `${virtualWorkspaceSupportReason}\n` : ""}${localize("non web extensions detail", "Contains extensions which are not supported.")}`; - buttons = [installAnywayButton, showExtensionsButton]; - } else if (hasLimitedSupport) { + detail = `${virtualWorkspaceSupportReason ? `${virtualWorkspaceSupportReason}\n` : ''}${localize('non web extensions detail', "Contains extensions which are not supported.")}`; + buttons = [ + installAnywayButton, + showExtensionsButton + ]; + } + + else if (hasLimitedSupport) { message = limitedSupportMessage; detail = virtualWorkspaceSupportReason || undefined; buttons = [installAnywayButton]; - } else { - message = localize( - "non web extensions", - "'{0}' contains extensions which are not supported in {1}.", - extension.displayName || extension.identifier.id, - productName, - ); - buttons = [installAnywayButton, showExtensionsButton]; + } + + else { + message = localize('non web extensions', "'{0}' contains extensions which are not supported in {1}.", extension.displayName || extension.identifier.id, productName); + buttons = [ + installAnywayButton, + showExtensionsButton + ]; } await this.dialogService.prompt({ @@ -1678,228 +822,135 @@ export class ExtensionManagementService detail, buttons, cancelButton: { - run: () => { - throw new CancellationError(); - }, - }, + run: () => { throw new CancellationError(); } + } }); } private _targetPlatformPromise: Promise | undefined; - getTargetPlatform(): Promise { if (!this._targetPlatformPromise) { - this._targetPlatformPromise = computeTargetPlatform( - this.fileService, - this.logService, - ); + this._targetPlatformPromise = computeTargetPlatform(this.fileService, this.logService); } return this._targetPlatformPromise; } async cleanUp(): Promise { - await Promise.allSettled( - this.servers.map((server) => - server.extensionManagementService.cleanUp(), - ), - ); + await Promise.allSettled(this.servers.map(server => server.extensionManagementService.cleanUp())); } - toggleAppliationScope( - extension: ILocalExtension, - fromProfileLocation: URI, - ): Promise { + toggleAppliationScope(extension: ILocalExtension, fromProfileLocation: URI): Promise { const server = this.getServer(extension); - if (server) { - return server.extensionManagementService.toggleAppliationScope( - extension, - fromProfileLocation, - ); + return server.extensionManagementService.toggleAppliationScope(extension, fromProfileLocation); } - throw new Error("Not Supported"); + throw new Error('Not Supported'); } copyExtensions(from: URI, to: URI): Promise { - if ( - this.extensionManagementServerService - .remoteExtensionManagementServer - ) { - throw new Error("Not Supported"); - } - if ( - this.extensionManagementServerService.localExtensionManagementServer - ) { - return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.copyExtensions( - from, - to, - ); - } - if ( - this.extensionManagementServerService.webExtensionManagementServer - ) { - return this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.copyExtensions( - from, - to, - ); + if (this.extensionManagementServerService.remoteExtensionManagementServer) { + throw new Error('Not Supported'); + } + if (this.extensionManagementServerService.localExtensionManagementServer) { + return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.copyExtensions(from, to); + } + if (this.extensionManagementServerService.webExtensionManagementServer) { + return this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.copyExtensions(from, to); } return Promise.resolve(); } - registerParticipant() { - throw new Error("Not Supported"); - } - installExtensionsFromProfile( - extensions: IExtensionIdentifier[], - fromProfileLocation: URI, - toProfileLocation: URI, - ): Promise { - throw new Error("Not Supported"); - } + registerParticipant() { throw new Error('Not Supported'); } + installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise { throw new Error('Not Supported'); } } class WorkspaceExtensionsManagementService extends Disposable { - private static readonly WORKSPACE_EXTENSIONS_KEY = - "workspaceExtensions.locations"; - private readonly _onDidChangeInvalidExtensions = this._register( - new Emitter(), - ); - readonly onDidChangeInvalidExtensions = - this._onDidChangeInvalidExtensions.event; + private static readonly WORKSPACE_EXTENSIONS_KEY = 'workspaceExtensions.locations'; + + private readonly _onDidChangeInvalidExtensions = this._register(new Emitter()); + readonly onDidChangeInvalidExtensions = this._onDidChangeInvalidExtensions.event; private readonly extensions: ILocalExtension[] = []; private readonly initializePromise: Promise; - private readonly invalidExtensionWatchers = this._register( - new DisposableStore(), - ); + private readonly invalidExtensionWatchers = this._register(new DisposableStore()); constructor( @IFileService private readonly fileService: IFileService, @ILogService private readonly logService: ILogService, - @IWorkspaceContextService - private readonly workspaceService: IWorkspaceContextService, - @IExtensionsScannerService - private readonly extensionsScannerService: IExtensionsScannerService, + @IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService, + @IExtensionsScannerService private readonly extensionsScannerService: IExtensionsScannerService, @IStorageService private readonly storageService: IStorageService, - @IUriIdentityService - private readonly uriIdentityService: IUriIdentityService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @ITelemetryService private readonly telemetryService: ITelemetryService, ) { super(); - this._register( - Event.debounce( - this.fileService.onDidFilesChange, - (last, e) => { - (last = last ?? []).push(e); - - return last; - }, - 1000, - )((events) => { - const changedInvalidExtensions = this.extensions.filter( - (extension) => - !extension.isValid && - events.some((e) => e.affects(extension.location)), - ); - - if (changedInvalidExtensions.length) { - this.checkExtensionsValidity(changedInvalidExtensions); - } - }), - ); + this._register(Event.debounce(this.fileService.onDidFilesChange, (last, e) => { + (last = last ?? []).push(e); + return last; + }, 1000)(events => { + const changedInvalidExtensions = this.extensions.filter(extension => !extension.isValid && events.some(e => e.affects(extension.location))); + if (changedInvalidExtensions.length) { + this.checkExtensionsValidity(changedInvalidExtensions); + } + })); this.initializePromise = this.initialize(); } private async initialize(): Promise { - const existingLocations = - this.getInstalledWorkspaceExtensionsLocations(); - + const existingLocations = this.getInstalledWorkspaceExtensionsLocations(); if (!existingLocations.length) { return; } - await Promise.allSettled( - existingLocations.map(async (location) => { - if (!this.workspaceService.isInsideWorkspace(location)) { - this.logService.info( - `Removing the workspace extension ${location.toString()} as it is not inside the workspace`, - ); - - return; - } - if (!(await this.fileService.exists(location))) { - this.logService.info( - `Removing the workspace extension ${location.toString()} as it does not exist`, - ); - - return; - } - try { - const extension = - await this.scanWorkspaceExtension(location); - - if (extension) { - this.extensions.push(extension); - } else { - this.logService.info( - `Skipping workspace extension ${location.toString()} as it does not exist`, - ); - } - } catch (error) { - this.logService.error( - "Skipping the workspace extension", - location.toString(), - error, - ); + await Promise.allSettled(existingLocations.map(async location => { + if (!this.workspaceService.isInsideWorkspace(location)) { + this.logService.info(`Removing the workspace extension ${location.toString()} as it is not inside the workspace`); + return; + } + if (!(await this.fileService.exists(location))) { + this.logService.info(`Removing the workspace extension ${location.toString()} as it does not exist`); + return; + } + try { + const extension = await this.scanWorkspaceExtension(location); + if (extension) { + this.extensions.push(extension); + } else { + this.logService.info(`Skipping workspace extension ${location.toString()} as it does not exist`); } - }), - ); + } catch (error) { + this.logService.error('Skipping the workspace extension', location.toString(), error); + } + })); this.saveWorkspaceExtensions(); } private watchInvalidExtensions(): void { this.invalidExtensionWatchers.clear(); - for (const extension of this.extensions) { if (!extension.isValid) { - this.invalidExtensionWatchers.add( - this.fileService.watch(extension.location), - ); + this.invalidExtensionWatchers.add(this.fileService.watch(extension.location)); } } } - private async checkExtensionsValidity( - extensions: ILocalExtension[], - ): Promise { + private async checkExtensionsValidity(extensions: ILocalExtension[]): Promise { const validExtensions: ILocalExtension[] = []; - await Promise.all( - extensions.map(async (extension) => { - const newExtension = await this.scanWorkspaceExtension( - extension.location, - ); - - if (newExtension?.isValid) { - validExtensions.push(newExtension); - } - }), - ); + await Promise.all(extensions.map(async extension => { + const newExtension = await this.scanWorkspaceExtension(extension.location); + if (newExtension?.isValid) { + validExtensions.push(newExtension); + } + })); let changed = false; - for (const extension of validExtensions) { - const index = this.extensions.findIndex((e) => - this.uriIdentityService.extUri.isEqual( - e.location, - extension.location, - ), - ); - + const index = this.extensions.findIndex(e => this.uriIdentityService.extUri.isEqual(e.location, extension.location)); if (index !== -1) { changed = true; this.extensions.splice(index, 1, extension); @@ -1914,45 +965,29 @@ class WorkspaceExtensionsManagementService extends Disposable { async getInstalled(includeInvalid: boolean): Promise { await this.initializePromise; - - return this.extensions.filter((e) => includeInvalid || e.isValid); + return this.extensions.filter(e => includeInvalid || e.isValid); } async install(extension: IResourceExtension): Promise { await this.initializePromise; - const workspaceExtension = await this.scanWorkspaceExtension( - extension.location, - ); - + const workspaceExtension = await this.scanWorkspaceExtension(extension.location); if (!workspaceExtension) { - throw new Error( - "Cannot install the extension as it does not exist.", - ); + throw new Error('Cannot install the extension as it does not exist.'); } - const existingExtensionIndex = this.extensions.findIndex((e) => - areSameExtensions(e.identifier, extension.identifier), - ); - + const existingExtensionIndex = this.extensions.findIndex(e => areSameExtensions(e.identifier, extension.identifier)); if (existingExtensionIndex === -1) { this.extensions.push(workspaceExtension); } else { - this.extensions.splice( - existingExtensionIndex, - 1, - workspaceExtension, - ); + this.extensions.splice(existingExtensionIndex, 1, workspaceExtension); } this.saveWorkspaceExtensions(); - this.telemetryService.publicLog2< - {}, - { - owner: "sandy081"; - comment: "Install workspace extension"; - } - >("workspaceextension:install"); + this.telemetryService.publicLog2<{}, { + owner: 'sandy081'; + comment: 'Install workspace extension'; + }>('workspaceextension:install'); return workspaceExtension; } @@ -1960,159 +995,75 @@ class WorkspaceExtensionsManagementService extends Disposable { async uninstall(extension: ILocalExtension): Promise { await this.initializePromise; - const existingExtensionIndex = this.extensions.findIndex((e) => - areSameExtensions(e.identifier, extension.identifier), - ); - + const existingExtensionIndex = this.extensions.findIndex(e => areSameExtensions(e.identifier, extension.identifier)); if (existingExtensionIndex !== -1) { this.extensions.splice(existingExtensionIndex, 1); this.saveWorkspaceExtensions(); } - this.telemetryService.publicLog2< - {}, - { - owner: "sandy081"; - comment: "Uninstall workspace extension"; - } - >("workspaceextension:uninstall"); + this.telemetryService.publicLog2<{}, { + owner: 'sandy081'; + comment: 'Uninstall workspace extension'; + }>('workspaceextension:uninstall'); } getInstalledWorkspaceExtensionsLocations(): URI[] { const locations: URI[] = []; - try { - const parsed = JSON.parse( - this.storageService.get( - WorkspaceExtensionsManagementService.WORKSPACE_EXTENSIONS_KEY, - StorageScope.WORKSPACE, - "[]", - ), - ); - + const parsed = JSON.parse(this.storageService.get(WorkspaceExtensionsManagementService.WORKSPACE_EXTENSIONS_KEY, StorageScope.WORKSPACE, '[]')); if (Array.isArray(locations)) { for (const location of parsed) { if (isString(location)) { - if ( - this.workspaceService.getWorkbenchState() === - WorkbenchState.FOLDER - ) { - locations.push( - this.workspaceService - .getWorkspace() - .folders[0].toResource(location), - ); + if (this.workspaceService.getWorkbenchState() === WorkbenchState.FOLDER) { + locations.push(this.workspaceService.getWorkspace().folders[0].toResource(location)); } else { - this.logService.warn( - `Invalid value for 'extensions' in workspace storage: ${location}`, - ); + this.logService.warn(`Invalid value for 'extensions' in workspace storage: ${location}`); } } else { locations.push(URI.revive(location)); } } } else { - this.logService.warn( - `Invalid value for 'extensions' in workspace storage: ${locations}`, - ); + this.logService.warn(`Invalid value for 'extensions' in workspace storage: ${locations}`); } } catch (error) { - this.logService.warn( - `Error parsing workspace extensions locations: ${getErrorMessage(error)}`, - ); + this.logService.warn(`Error parsing workspace extensions locations: ${getErrorMessage(error)}`); } return locations; } private saveWorkspaceExtensions(): void { - const locations = this.extensions.map( - (extension) => extension.location, - ); - - if ( - this.workspaceService.getWorkbenchState() === WorkbenchState.FOLDER - ) { - this.storageService.store( - WorkspaceExtensionsManagementService.WORKSPACE_EXTENSIONS_KEY, - JSON.stringify( - coalesce( - locations.map((location) => - this.uriIdentityService.extUri.relativePath( - this.workspaceService.getWorkspace().folders[0] - .uri, - location, - ), - ), - ), - ), - StorageScope.WORKSPACE, - StorageTarget.MACHINE, - ); + const locations = this.extensions.map(extension => extension.location); + if (this.workspaceService.getWorkbenchState() === WorkbenchState.FOLDER) { + this.storageService.store(WorkspaceExtensionsManagementService.WORKSPACE_EXTENSIONS_KEY, + JSON.stringify(coalesce(locations + .map(location => this.uriIdentityService.extUri.relativePath(this.workspaceService.getWorkspace().folders[0].uri, location)))), + StorageScope.WORKSPACE, StorageTarget.MACHINE); } else { - this.storageService.store( - WorkspaceExtensionsManagementService.WORKSPACE_EXTENSIONS_KEY, - JSON.stringify(locations), - StorageScope.WORKSPACE, - StorageTarget.MACHINE, - ); + this.storageService.store(WorkspaceExtensionsManagementService.WORKSPACE_EXTENSIONS_KEY, JSON.stringify(locations), StorageScope.WORKSPACE, StorageTarget.MACHINE); } this.watchInvalidExtensions(); } - async scanWorkspaceExtension( - location: URI, - ): Promise { - const scannedExtension = - await this.extensionsScannerService.scanExistingExtension( - location, - ExtensionType.User, - { includeInvalid: true }, - ); - - return scannedExtension - ? this.toLocalWorkspaceExtension(scannedExtension) - : null; + async scanWorkspaceExtension(location: URI): Promise { + const scannedExtension = await this.extensionsScannerService.scanExistingExtension(location, ExtensionType.User, { includeInvalid: true }); + return scannedExtension ? this.toLocalWorkspaceExtension(scannedExtension) : null; } - async toLocalWorkspaceExtension( - extension: IScannedExtension, - ): Promise { + async toLocalWorkspaceExtension(extension: IScannedExtension): Promise { const stat = await this.fileService.resolve(extension.location); - let readmeUrl: URI | undefined; - let changelogUrl: URI | undefined; - if (stat.children) { - readmeUrl = stat.children.find(({ name }) => - /^readme(\.txt|\.md|)$/i.test(name), - )?.resource; - changelogUrl = stat.children.find(({ name }) => - /^changelog(\.txt|\.md|)$/i.test(name), - )?.resource; + readmeUrl = stat.children.find(({ name }) => /^readme(\.txt|\.md|)$/i.test(name))?.resource; + changelogUrl = stat.children.find(({ name }) => /^changelog(\.txt|\.md|)$/i.test(name))?.resource; } const validations: [Severity, string][] = [...extension.validations]; - let isValid = extension.isValid; - if (extension.manifest.main) { - if ( - !(await this.fileService.exists( - this.uriIdentityService.extUri.joinPath( - extension.location, - extension.manifest.main, - ), - )) - ) { + if (!(await this.fileService.exists(this.uriIdentityService.extUri.joinPath(extension.location, extension.manifest.main)))) { isValid = false; - validations.push([ - Severity.Error, - localize( - "main.notFound", - "Cannot activate because {0} not found", - extension.manifest.main, - ), - ]); + validations.push([Severity.Error, localize('main.notFound', "Cannot activate because {0} not found", extension.manifest.main)]); } } return { @@ -2137,7 +1088,7 @@ class WorkspaceExtensionsManagementService extends Disposable { updated: !!extension.metadata?.updated, pinned: !!extension.metadata?.pinned, isWorkspaceScoped: true, - source: "resource", + source: 'resource', size: extension.metadata?.size ?? 0, }; } diff --git a/Source/vscode-dts/vscode.d.ts b/Source/vscode-dts/vscode.d.ts index 2d8f740d25614..ba759a821d3cb 100644 --- a/Source/vscode-dts/vscode.d.ts +++ b/Source/vscode-dts/vscode.d.ts @@ -18546,6 +18546,29 @@ declare module "vscode" { token: CancellationToken, ) => Thenable; + /** + * An extension-provided function that provides detailed statement and + * function-level coverage for a single test in a file. This is the per-test + * sibling of {@link TestRunProfile.loadDetailedCoverage}, called only if + * a test item is provided in {@link FileCoverage.includesTests} and only + * for files where such data is reported. + * + * Often {@link TestRunProfile.loadDetailedCoverage} will be called first + * when a user opens a file, and then this method will be called if they + * drill down into specific per-test coverage information. This method + * should then return coverage data only for constructs the given test item + * executed during the test run. + * + * The {@link FileCoverage} object passed to this function is the same + * instance emitted on {@link TestRun.addCoverage} calls associated with this profile. + * + * @param testRun The test run that generated the coverage data. + * @param fileCoverage The file coverage object to load detailed coverage for. + * @param fromTestItem The test item to request coverage information for. + * @param token A cancellation token that indicates the operation should be cancelled. + */ + loadDetailedCoverageForTest?: (testRun: TestRun, fileCoverage: FileCoverage, fromTestItem: TestItem, token: CancellationToken) => Thenable; + /** * Deletes the run profile. */ @@ -19184,6 +19207,13 @@ declare module "vscode" { */ declarationCoverage?: TestCoverageCount; + /** + * A list of {@link TestItem test cases} that generated coverage in this + * file. If set, then {@link TestRunProfile.loadDetailedCoverageForTest} + * should also be defined in order to retrieve detailed coverage information. + */ + includesTests?: TestItem[]; + /** * Creates a {@link FileCoverage} instance with counts filled in from * the coverage details. @@ -19202,12 +19232,14 @@ declare module "vscode" { * used to represent line coverage. * @param branchCoverage Branch coverage information * @param declarationCoverage Declaration coverage information + * @param includesTests Test cases included in this coverage report, see {@link includesTests} */ constructor( uri: Uri, statementCoverage: TestCoverageCount, branchCoverage?: TestCoverageCount, declarationCoverage?: TestCoverageCount, + includesTests?: TestItem[], ); } diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index d56ebc1cf1230..39075d822831b 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -790,16 +790,12 @@ extends: name: 1es-ubuntu-22.04-x64 os: linux jobs: - - deployment: ApproveRelease + - job: ApproveRelease displayName: "Approve Release" - environment: "vscode" variables: - skipComponentGovernanceDetection: true - strategy: - runOnce: - deploy: - steps: - - checkout: none + - group: VSCodePeerApproval + - name: skipComponentGovernanceDetection + value: true - ${{ if or(and(parameters.VSCODE_RELEASE, eq(variables['VSCODE_PRIVATE_BUILD'], false)), and(in(parameters.VSCODE_QUALITY, 'insider', 'exploration'), eq(variables['VSCODE_SCHEDULEDBUILD'], true))) }}: - stage: Release diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js index 13f27d6db4774..f05738faa620a 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -52,6 +52,7 @@ const compilations = [ 'extensions/markdown-math/tsconfig.json', 'extensions/media-preview/tsconfig.json', 'extensions/merge-conflict/tsconfig.json', + 'extensions/terminal-suggest/tsconfig.json', 'extensions/microsoft-authentication/tsconfig.json', 'extensions/notebook-renderers/tsconfig.json', 'extensions/npm/tsconfig.json', diff --git a/build/lib/policies.js b/build/lib/policies.js index 466295b8ad543..aaa59a956579a 100644 --- a/build/lib/policies.js +++ b/build/lib/policies.js @@ -37,7 +37,7 @@ function renderADMLString(prefix, moduleName, nlsString, translations) { if (!value) { value = nlsString.value; } - return `${value}`; + return `${value}`; } class BasePolicy { policyType; @@ -59,7 +59,7 @@ class BasePolicy { } renderADMX(regKey) { return [ - ``, + ``, ` `, ` `, ` `, @@ -145,6 +145,24 @@ class StringPolicy extends BasePolicy { return ``; } } +class ObjectPolicy extends BasePolicy { + static from(name, category, minimumVersion, description, moduleName, settingNode) { + const type = getStringProperty(settingNode, 'type'); + if (type !== 'object' && type !== 'array') { + return undefined; + } + return new ObjectPolicy(name, category, minimumVersion, description, moduleName); + } + constructor(name, category, minimumVersion, description, moduleName) { + super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); + } + renderADMXElements() { + return [``]; + } + renderADMLPresentationContents() { + return ``; + } +} class StringEnumPolicy extends BasePolicy { enum_; enumDescriptions; @@ -264,6 +282,7 @@ const PolicyTypes = [ IntPolicy, StringEnumPolicy, StringPolicy, + ObjectPolicy ]; function getPolicy(moduleName, configurationNode, settingNode, policyNode, categories) { const name = getStringProperty(policyNode, 'name'); @@ -319,7 +338,7 @@ function getPolicies(moduleName, node) { arguments: (arguments (object (pair key: [(property_identifier)(string)] @propertiesKey (#eq? @propertiesKey properties) value: (object (pair - key: [(property_identifier)(string)] + key: [(property_identifier)(string)(computed_property_name)] value: (object (pair key: [(property_identifier)(string)] @policyKey (#eq? @policyKey policy) value: (object) @policy diff --git a/build/lib/policies.ts b/build/lib/policies.ts index 63e29ee0c2033..fd0decf56b0ee 100644 --- a/build/lib/policies.ts +++ b/build/lib/policies.ts @@ -61,7 +61,8 @@ function renderADMLString( if (!value) { value = nlsString.value; } - return `${value}`; + + return `${value}`; } abstract class BasePolicy implements Policy { constructor( @@ -85,7 +86,7 @@ abstract class BasePolicy implements Policy { } renderADMX(regKey: string) { return [ - ``, + ``, ` `, ` `, ` `, @@ -257,6 +258,45 @@ class StringPolicy extends BasePolicy { return ``; } } + +class ObjectPolicy extends BasePolicy { + + static from( + name: string, + category: Category, + minimumVersion: string, + description: NlsString, + moduleName: string, + settingNode: Parser.SyntaxNode + ): ObjectPolicy | undefined { + const type = getStringProperty(settingNode, 'type'); + + if (type !== 'object' && type !== 'array') { + return undefined; + } + + return new ObjectPolicy(name, category, minimumVersion, description, moduleName); + } + + private constructor( + name: string, + category: Category, + minimumVersion: string, + description: NlsString, + moduleName: string, + ) { + super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); + } + + protected renderADMXElements(): string[] { + return [``]; + } + + renderADMLPresentationContents() { + return ``; + } +} + class StringEnumPolicy extends BasePolicy { static from( name: string, @@ -437,7 +477,14 @@ function getStringArrayProperty( return getProperty(StringArrayQ, node, key); } // TODO: add more policy types -const PolicyTypes = [BooleanPolicy, IntPolicy, StringEnumPolicy, StringPolicy]; +const PolicyTypes = [ + BooleanPolicy, + IntPolicy, + StringEnumPolicy, + StringPolicy, + ObjectPolicy +]; + function getPolicy( moduleName: string, configurationNode: Parser.SyntaxNode, @@ -515,7 +562,7 @@ function getPolicies(moduleName: string, node: Parser.SyntaxNode): Policy[] { arguments: (arguments (object (pair key: [(property_identifier)(string)] @propertiesKey (#eq? @propertiesKey properties) value: (object (pair - key: [(property_identifier)(string)] + key: [(property_identifier)(string)(computed_property_name)] value: (object (pair key: [(property_identifier)(string)] @policyKey (#eq? @policyKey policy) value: (object) @policy diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index c66764a97611b..a4b270551fff2 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -825,6 +825,7 @@ "--dropdown-padding-bottom", "--dropdown-padding-top", "--inline-chat-frame-progress", + "--inline-chat-hint-progress", "--insert-border-color", "--last-tab-margin-right", "--monaco-monospace-font", diff --git a/extensions/git/Source/api/api1.ts b/extensions/git/Source/api/api1.ts index 139369a46f196..62e3182b51550 100644 --- a/extensions/git/Source/api/api1.ts +++ b/extensions/git/Source/api/api1.ts @@ -2,577 +2,449 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { - CancellationToken, - commands, - Disposable, - Event, - SourceControl, - SourceControlInputBox, - Uri, -} from "vscode"; - -import { GitBaseApi } from "../git-base"; -import { Model } from "../model"; -import { Operation, OperationResult } from "../operation"; -import { Repository as BaseRepository, Resource } from "../repository"; -import { toGitUri } from "../uri"; -import { combinedDisposable, filterEvent, mapEvent } from "../util"; -import { GitExtensionImpl } from "./extension"; -import { - API, - APIState, - Branch, - BranchProtectionProvider, - BranchQuery, - Change, - Commit, - CommitOptions, - CredentialsProvider, - FetchOptions, - ForcePushMode, - Git, - InitOptions, - InputBox, - LogOptions, - PostCommitCommandsProvider, - PublishEvent, - PushErrorHandler, - Ref, - RefQuery, - RefType, - Remote, - RemoteSourceProvider, - RemoteSourcePublisher, - Repository, - RepositoryState, - RepositoryUIState, - Status, - Submodule, -} from "./git"; -import { PickRemoteSourceOptions } from "./git-base"; + +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, BranchProtectionProvider, InitOptions } from './git'; +import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands, CancellationToken } from 'vscode'; +import { combinedDisposable, filterEvent, mapEvent } from '../util'; +import { toGitUri } from '../uri'; +import { GitExtensionImpl } from './extension'; +import { GitBaseApi } from '../git-base'; +import { PickRemoteSourceOptions } from './git-base'; +import { OperationKind, OperationResult } from '../operation'; class ApiInputBox implements InputBox { - set value(value: string) { - this._inputBox.value = value; - } - get value(): string { - return this._inputBox.value; - } - constructor(private _inputBox: SourceControlInputBox) {} + set value(value: string) { this._inputBox.value = value; } + get value(): string { return this._inputBox.value; } + constructor(private _inputBox: SourceControlInputBox) { } } + export class ApiChange implements Change { - get uri(): Uri { - return this.resource.resourceUri; - } - get originalUri(): Uri { - return this.resource.original; - } - get renameUri(): Uri | undefined { - return this.resource.renameResourceUri; - } - get status(): Status { - return this.resource.type; - } - constructor(private readonly resource: Resource) {} + + get uri(): Uri { return this.resource.resourceUri; } + get originalUri(): Uri { return this.resource.original; } + get renameUri(): Uri | undefined { return this.resource.renameResourceUri; } + get status(): Status { return this.resource.type; } + + constructor(private readonly resource: Resource) { } } + export class ApiRepositoryState implements RepositoryState { - get HEAD(): Branch | undefined { - return this._repository.HEAD; - } + + get HEAD(): Branch | undefined { return this._repository.HEAD; } /** * @deprecated Use ApiRepository.getRefs() instead. */ - get refs(): Ref[] { - console.warn("Deprecated. Use ApiRepository.getRefs() instead."); - return []; - } - get remotes(): Remote[] { - return [...this._repository.remotes]; - } - get submodules(): Submodule[] { - return [...this._repository.submodules]; - } - get rebaseCommit(): Commit | undefined { - return this._repository.rebaseCommit; - } - get mergeChanges(): Change[] { - return this._repository.mergeGroup.resourceStates.map( - (r) => new ApiChange(r), - ); - } - get indexChanges(): Change[] { - return this._repository.indexGroup.resourceStates.map( - (r) => new ApiChange(r), - ); - } - get workingTreeChanges(): Change[] { - return this._repository.workingTreeGroup.resourceStates.map( - (r) => new ApiChange(r), - ); - } - get untrackedChanges(): Change[] { - return this._repository.untrackedGroup.resourceStates.map( - (r) => new ApiChange(r), - ); - } + get refs(): Ref[] { console.warn('Deprecated. Use ApiRepository.getRefs() instead.'); return []; } + get remotes(): Remote[] { return [...this._repository.remotes]; } + get submodules(): Submodule[] { return [...this._repository.submodules]; } + get rebaseCommit(): Commit | undefined { return this._repository.rebaseCommit; } + + get mergeChanges(): Change[] { return this._repository.mergeGroup.resourceStates.map(r => new ApiChange(r)); } + get indexChanges(): Change[] { return this._repository.indexGroup.resourceStates.map(r => new ApiChange(r)); } + get workingTreeChanges(): Change[] { return this._repository.workingTreeGroup.resourceStates.map(r => new ApiChange(r)); } + get untrackedChanges(): Change[] { return this._repository.untrackedGroup.resourceStates.map(r => new ApiChange(r)); } + readonly onDidChange: Event = this._repository.onDidRunGitStatus; - constructor(private _repository: BaseRepository) {} + constructor(private _repository: BaseRepository) { } } + export class ApiRepositoryUIState implements RepositoryUIState { - get selected(): boolean { - return this._sourceControl.selected; - } - readonly onDidChange: Event = mapEvent( - this._sourceControl.onDidChangeSelection, - () => null, - ); - constructor(private _sourceControl: SourceControl) {} + get selected(): boolean { return this._sourceControl.selected; } + + readonly onDidChange: Event = mapEvent(this._sourceControl.onDidChangeSelection, () => null); + + constructor(private _sourceControl: SourceControl) { } } + export class ApiRepository implements Repository { + readonly rootUri: Uri = Uri.file(this.repository.root); readonly inputBox: InputBox = new ApiInputBox(this.repository.inputBox); readonly state: RepositoryState = new ApiRepositoryState(this.repository); - readonly ui: RepositoryUIState = new ApiRepositoryUIState( - this.repository.sourceControl, - ); + readonly ui: RepositoryUIState = new ApiRepositoryUIState(this.repository.sourceControl); + readonly onDidCommit: Event = mapEvent( - filterEvent( - this.repository.onDidRunOperation, - (e) => e.operation === Operation.Commit, - ), - () => null, - ); - - constructor(readonly repository: BaseRepository) {} + filterEvent(this.repository.onDidRunOperation, e => e.operation.kind === OperationKind.Commit), () => null); + + readonly onDidCheckout: Event = mapEvent( + filterEvent(this.repository.onDidRunOperation, e => e.operation.kind === OperationKind.Checkout || e.operation.kind === OperationKind.CheckoutTracking), () => null); + + constructor(readonly repository: BaseRepository) { } + apply(patch: string, reverse?: boolean): Promise { return this.repository.apply(patch, reverse); } - getConfigs(): Promise< - { - key: string; - value: string; - }[] - > { + + getConfigs(): Promise<{ key: string; value: string }[]> { return this.repository.getConfigs(); } + getConfig(key: string): Promise { return this.repository.getConfig(key); } + setConfig(key: string, value: string): Promise { return this.repository.setConfig(key, value); } + getGlobalConfig(key: string): Promise { return this.repository.getGlobalConfig(key); } - getObjectDetails( - treeish: string, - path: string, - ): Promise<{ - mode: string; - object: string; - size: number; - }> { + + getObjectDetails(treeish: string, path: string): Promise<{ mode: string; object: string; size: number }> { return this.repository.getObjectDetails(treeish, path); } - detectObjectType(object: string): Promise<{ - mimetype: string; - encoding?: string; - }> { + + detectObjectType(object: string): Promise<{ mimetype: string; encoding?: string }> { return this.repository.detectObjectType(object); } + buffer(ref: string, filePath: string): Promise { return this.repository.buffer(ref, filePath); } + show(ref: string, path: string): Promise { return this.repository.show(ref, path); } + getCommit(ref: string): Promise { return this.repository.getCommit(ref); } + add(paths: string[]) { - return this.repository.add(paths.map((p) => Uri.file(p))); + return this.repository.add(paths.map(p => Uri.file(p))); } + revert(paths: string[]) { - return this.repository.revert(paths.map((p) => Uri.file(p))); + return this.repository.revert(paths.map(p => Uri.file(p))); } + clean(paths: string[]) { - return this.repository.clean(paths.map((p) => Uri.file(p))); + return this.repository.clean(paths.map(p => Uri.file(p))); } + diff(cached?: boolean) { return this.repository.diff(cached); } + diffWithHEAD(): Promise; diffWithHEAD(path: string): Promise; diffWithHEAD(path?: string): Promise { return this.repository.diffWithHEAD(path); } + diffWith(ref: string): Promise; diffWith(ref: string, path: string): Promise; diffWith(ref: string, path?: string): Promise { return this.repository.diffWith(ref, path); } + diffIndexWithHEAD(): Promise; diffIndexWithHEAD(path: string): Promise; diffIndexWithHEAD(path?: string): Promise { return this.repository.diffIndexWithHEAD(path); } + diffIndexWith(ref: string): Promise; diffIndexWith(ref: string, path: string): Promise; diffIndexWith(ref: string, path?: string): Promise { return this.repository.diffIndexWith(ref, path); } + diffBlobs(object1: string, object2: string): Promise { return this.repository.diffBlobs(object1, object2); } + diffBetween(ref1: string, ref2: string): Promise; diffBetween(ref1: string, ref2: string, path: string): Promise; - diffBetween( - ref1: string, - ref2: string, - path?: string, - ): Promise { + diffBetween(ref1: string, ref2: string, path?: string): Promise { return this.repository.diffBetween(ref1, ref2, path); } + hashObject(data: string): Promise { return this.repository.hashObject(data); } - createBranch( - name: string, - checkout: boolean, - ref?: string | undefined, - ): Promise { + + createBranch(name: string, checkout: boolean, ref?: string | undefined): Promise { return this.repository.branch(name, checkout, ref); } + deleteBranch(name: string, force?: boolean): Promise { return this.repository.deleteBranch(name, force); } + getBranch(name: string): Promise { return this.repository.getBranch(name); } - getBranches( - query: BranchQuery, - cancellationToken?: CancellationToken, - ): Promise { + + getBranches(query: BranchQuery, cancellationToken?: CancellationToken): Promise { return this.repository.getBranches(query, cancellationToken); } + getBranchBase(name: string): Promise { return this.repository.getBranchBase(name); } + setBranchUpstream(name: string, upstream: string): Promise { return this.repository.setBranchUpstream(name, upstream); } - getRefs( - query: RefQuery, - cancellationToken?: CancellationToken, - ): Promise { + + getRefs(query: RefQuery, cancellationToken?: CancellationToken): Promise { return this.repository.getRefs(query, cancellationToken); } + checkIgnore(paths: string[]): Promise> { return this.repository.checkIgnore(paths); } + getMergeBase(ref1: string, ref2: string): Promise { return this.repository.getMergeBase(ref1, ref2); } - tag( - name: string, - message: string, - ref?: string | undefined, - ): Promise { + + tag(name: string, message: string, ref?: string | undefined): Promise { return this.repository.tag({ name, message, ref }); } + deleteTag(name: string): Promise { return this.repository.deleteTag(name); } + status(): Promise { return this.repository.status(); } + checkout(treeish: string): Promise { return this.repository.checkout(treeish); } + addRemote(name: string, url: string): Promise { return this.repository.addRemote(name, url); } + removeRemote(name: string): Promise { return this.repository.removeRemote(name); } + renameRemote(name: string, newName: string): Promise { return this.repository.renameRemote(name, newName); } - fetch( - arg0?: FetchOptions | string | undefined, + + fetch(arg0?: FetchOptions | string | undefined, ref?: string | undefined, depth?: number | undefined, - prune?: boolean | undefined, + prune?: boolean | undefined ): Promise { - if (arg0 !== undefined && typeof arg0 !== "string") { + if (arg0 !== undefined && typeof arg0 !== 'string') { return this.repository.fetch(arg0); } + return this.repository.fetch({ remote: arg0, ref, depth, prune }); } + pull(unshallow?: boolean): Promise { return this.repository.pull(undefined, unshallow); } - push( - remoteName?: string, - branchName?: string, - setUpstream: boolean = false, - force?: ForcePushMode, - ): Promise { - return this.repository.pushTo( - remoteName, - branchName, - setUpstream, - force, - ); + + push(remoteName?: string, branchName?: string, setUpstream: boolean = false, force?: ForcePushMode): Promise { + return this.repository.pushTo(remoteName, branchName, setUpstream, force); } + blame(path: string): Promise { return this.repository.blame(path); } + log(options?: LogOptions): Promise { return this.repository.log(options); } + commit(message: string, opts?: CommitOptions): Promise { - return this.repository.commit(message, { - ...opts, - postCommitCommand: null, - }); + return this.repository.commit(message, { ...opts, postCommitCommand: null }); } + merge(ref: string): Promise { return this.repository.merge(ref); } + mergeAbort(): Promise { return this.repository.mergeAbort(); } } + export class ApiGit implements Git { - get path(): string { - return this._model.git.path; - } - constructor(private _model: Model) {} + + get path(): string { return this._model.git.path; } + + constructor(private _model: Model) { } } + export class ApiImpl implements API { + readonly git = new ApiGit(this._model); get state(): APIState { return this._model.state; } + get onDidChangeState(): Event { return this._model.onDidChangeState; } + get onDidPublish(): Event { return this._model.onDidPublish; } + get onDidOpenRepository(): Event { - return mapEvent( - this._model.onDidOpenRepository, - (r) => new ApiRepository(r), - ); + return mapEvent(this._model.onDidOpenRepository, r => new ApiRepository(r)); } + get onDidCloseRepository(): Event { - return mapEvent( - this._model.onDidCloseRepository, - (r) => new ApiRepository(r), - ); + return mapEvent(this._model.onDidCloseRepository, r => new ApiRepository(r)); } + get repositories(): Repository[] { - return this._model.repositories.map((r) => new ApiRepository(r)); + return this._model.repositories.map(r => new ApiRepository(r)); } + toGitUri(uri: Uri, ref: string): Uri { return toGitUri(uri, ref); } + getRepository(uri: Uri): Repository | null { const result = this._model.getRepository(uri); - return result ? new ApiRepository(result) : null; } + async init(root: Uri, options?: InitOptions): Promise { const path = root.fsPath; await this._model.git.init(path, options); await this._model.openRepository(path); - return this.getRepository(root) || null; } + async openRepository(root: Uri): Promise { - if (root.scheme !== "file") { + if (root.scheme !== 'file') { return null; } - await this._model.openRepository(root.fsPath); + await this._model.openRepository(root.fsPath); return this.getRepository(root) || null; } + registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable { const disposables: Disposable[] = []; if (provider.publishRepository) { - disposables.push( - this._model.registerRemoteSourcePublisher( - provider as RemoteSourcePublisher, - ), - ); + disposables.push(this._model.registerRemoteSourcePublisher(provider as RemoteSourcePublisher)); } - disposables.push( - GitBaseApi.getAPI().registerRemoteSourceProvider(provider), - ); + disposables.push(GitBaseApi.getAPI().registerRemoteSourceProvider(provider)); return combinedDisposable(disposables); } - registerRemoteSourcePublisher( - publisher: RemoteSourcePublisher, - ): Disposable { + + registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable { return this._model.registerRemoteSourcePublisher(publisher); } + registerCredentialsProvider(provider: CredentialsProvider): Disposable { return this._model.registerCredentialsProvider(provider); } - registerPostCommitCommandsProvider( - provider: PostCommitCommandsProvider, - ): Disposable { + + registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable { return this._model.registerPostCommitCommandsProvider(provider); } + registerPushErrorHandler(handler: PushErrorHandler): Disposable { return this._model.registerPushErrorHandler(handler); } - registerBranchProtectionProvider( - root: Uri, - provider: BranchProtectionProvider, - ): Disposable { + + registerBranchProtectionProvider(root: Uri, provider: BranchProtectionProvider): Disposable { return this._model.registerBranchProtectionProvider(root, provider); } - constructor(private _model: Model) {} + + constructor(private _model: Model) { } } + function getRefType(type: RefType): string { switch (type) { - case RefType.Head: - return "Head"; - - case RefType.RemoteHead: - return "RemoteHead"; - - case RefType.Tag: - return "Tag"; + case RefType.Head: return 'Head'; + case RefType.RemoteHead: return 'RemoteHead'; + case RefType.Tag: return 'Tag'; } - return "unknown"; + + return 'unknown'; } + function getStatus(status: Status): string { switch (status) { - case Status.INDEX_MODIFIED: - return "INDEX_MODIFIED"; - - case Status.INDEX_ADDED: - return "INDEX_ADDED"; - - case Status.INDEX_DELETED: - return "INDEX_DELETED"; - - case Status.INDEX_RENAMED: - return "INDEX_RENAMED"; - - case Status.INDEX_COPIED: - return "INDEX_COPIED"; - - case Status.MODIFIED: - return "MODIFIED"; - - case Status.DELETED: - return "DELETED"; - - case Status.UNTRACKED: - return "UNTRACKED"; - - case Status.IGNORED: - return "IGNORED"; - - case Status.INTENT_TO_ADD: - return "INTENT_TO_ADD"; - - case Status.INTENT_TO_RENAME: - return "INTENT_TO_RENAME"; - - case Status.TYPE_CHANGED: - return "TYPE_CHANGED"; + case Status.INDEX_MODIFIED: return 'INDEX_MODIFIED'; + case Status.INDEX_ADDED: return 'INDEX_ADDED'; + case Status.INDEX_DELETED: return 'INDEX_DELETED'; + case Status.INDEX_RENAMED: return 'INDEX_RENAMED'; + case Status.INDEX_COPIED: return 'INDEX_COPIED'; + case Status.MODIFIED: return 'MODIFIED'; + case Status.DELETED: return 'DELETED'; + case Status.UNTRACKED: return 'UNTRACKED'; + case Status.IGNORED: return 'IGNORED'; + case Status.INTENT_TO_ADD: return 'INTENT_TO_ADD'; + case Status.INTENT_TO_RENAME: return 'INTENT_TO_RENAME'; + case Status.TYPE_CHANGED: return 'TYPE_CHANGED'; + case Status.ADDED_BY_US: return 'ADDED_BY_US'; + case Status.ADDED_BY_THEM: return 'ADDED_BY_THEM'; + case Status.DELETED_BY_US: return 'DELETED_BY_US'; + case Status.DELETED_BY_THEM: return 'DELETED_BY_THEM'; + case Status.BOTH_ADDED: return 'BOTH_ADDED'; + case Status.BOTH_DELETED: return 'BOTH_DELETED'; + case Status.BOTH_MODIFIED: return 'BOTH_MODIFIED'; + } + + return 'UNKNOWN'; +} - case Status.ADDED_BY_US: - return "ADDED_BY_US"; +export function registerAPICommands(extension: GitExtensionImpl): Disposable { + const disposables: Disposable[] = []; - case Status.ADDED_BY_THEM: - return "ADDED_BY_THEM"; + disposables.push(commands.registerCommand('git.api.getRepositories', () => { + const api = extension.getAPI(1); + return api.repositories.map(r => r.rootUri.toString()); + })); - case Status.DELETED_BY_US: - return "DELETED_BY_US"; + disposables.push(commands.registerCommand('git.api.getRepositoryState', (uri: string) => { + const api = extension.getAPI(1); + const repository = api.getRepository(Uri.parse(uri)); - case Status.DELETED_BY_THEM: - return "DELETED_BY_THEM"; + if (!repository) { + return null; + } - case Status.BOTH_ADDED: - return "BOTH_ADDED"; + const state = repository.state; - case Status.BOTH_DELETED: - return "BOTH_DELETED"; + const ref = (ref: Ref | undefined) => (ref && { ...ref, type: getRefType(ref.type) }); + const change = (change: Change) => ({ + uri: change.uri.toString(), + originalUri: change.originalUri.toString(), + renameUri: change.renameUri?.toString(), + status: getStatus(change.status) + }); - case Status.BOTH_MODIFIED: - return "BOTH_MODIFIED"; - } - return "UNKNOWN"; -} -export function registerAPICommands(extension: GitExtensionImpl): Disposable { - const disposables: Disposable[] = []; - disposables.push( - commands.registerCommand("git.api.getRepositories", () => { - const api = extension.getAPI(1); - - return api.repositories.map((r) => r.rootUri.toString()); - }), - ); - disposables.push( - commands.registerCommand( - "git.api.getRepositoryState", - (uri: string) => { - const api = extension.getAPI(1); - - const repository = api.getRepository(Uri.parse(uri)); - - if (!repository) { - return null; - } - const state = repository.state; - - const ref = (ref: Ref | undefined) => - ref && { ...ref, type: getRefType(ref.type) }; - - const change = (change: Change) => ({ - uri: change.uri.toString(), - originalUri: change.originalUri.toString(), - renameUri: change.renameUri?.toString(), - status: getStatus(change.status), - }); - - return { - HEAD: ref(state.HEAD), - refs: state.refs.map(ref), - remotes: state.remotes, - submodules: state.submodules, - rebaseCommit: state.rebaseCommit, - mergeChanges: state.mergeChanges.map(change), - indexChanges: state.indexChanges.map(change), - workingTreeChanges: state.workingTreeChanges.map(change), - }; - }, - ), - ); - disposables.push( - commands.registerCommand( - "git.api.getRemoteSources", - (opts?: PickRemoteSourceOptions) => { - return commands.executeCommand( - "git-base.api.getRemoteSources", - opts, - ); - }, - ), - ); + return { + HEAD: ref(state.HEAD), + refs: state.refs.map(ref), + remotes: state.remotes, + submodules: state.submodules, + rebaseCommit: state.rebaseCommit, + mergeChanges: state.mergeChanges.map(change), + indexChanges: state.indexChanges.map(change), + workingTreeChanges: state.workingTreeChanges.map(change) + }; + })); + + disposables.push(commands.registerCommand('git.api.getRemoteSources', (opts?: PickRemoteSourceOptions) => { + return commands.executeCommand('git-base.api.getRemoteSources', opts); + })); return Disposable.from(...disposables); } diff --git a/extensions/git/Source/blame.ts b/extensions/git/Source/blame.ts index c71601a8af924..4fa3a9d719c3e 100644 --- a/extensions/git/Source/blame.ts +++ b/extensions/git/Source/blame.ts @@ -9,6 +9,7 @@ import { dispose, fromNow, IDisposable } from './util'; import { Repository } from './repository'; import { throttle } from './decorators'; import { BlameInformation } from './git'; +import { fromGitUri, isGitUri } from './uri'; function lineRangesContainLine(changes: readonly TextEditorChange[], lineNumber: number): boolean { return changes.some(c => c.modified.startLineNumber <= lineNumber && lineNumber < c.modified.endLineNumberExclusive); @@ -52,9 +53,46 @@ function mapModifiedLineNumberToOriginalLineNumber(lineNumber: number, changes: return lineNumber; } +type BlameInformationTemplateTokens = { + readonly hash: string; + readonly hashShort: string; + readonly subject: string; + readonly authorName: string; + readonly authorEmail: string; + readonly authorDate: string; + readonly authorDateAgo: string; +}; + +function formatBlameInformation(template: string, blameInformation: BlameInformation): string { + const templateTokens = { + hash: blameInformation.hash, + hashShort: blameInformation.hash.substring(0, 8), + subject: blameInformation.subject ?? '', + authorName: blameInformation.authorName ?? '', + authorEmail: blameInformation.authorEmail ?? '', + authorDate: new Date(blameInformation.authorDate ?? new Date()).toLocaleString(), + authorDateAgo: fromNow(blameInformation.authorDate ?? new Date(), true, true) + } satisfies BlameInformationTemplateTokens; + + return template.replace(/\$\{(.+?)\}/g, (_, token) => { + return token in templateTokens ? templateTokens[token as keyof BlameInformationTemplateTokens] : `\${${token}}`; + }); +} + interface RepositoryBlameInformation { - readonly commit: string; /* commit used for blame information */ - readonly blameInformation: Map; + /** + * Track the current HEAD of the repository so that we can clear cache entries + */ + HEAD: string; + + /** + * Outer map - maps resource scheme to resource blame information. Using the uri + * scheme as the key so that we can easily delete the cache entries for the "file" + * scheme as those entries are outdated when the HEAD of the repository changes. + * + * Inner map - maps commit + resource to blame information. + */ + readonly blameInformation: Map>; } interface LineBlameInformation { @@ -62,6 +100,64 @@ interface LineBlameInformation { readonly blameInformation: BlameInformation | string; } +class GitBlameInformationCache { + private readonly _cache = new Map(); + + getRepositoryHEAD(repository: Repository): string | undefined { + return this._cache.get(repository)?.HEAD; + } + + setRepositoryHEAD(repository: Repository, commit: string): void { + const repositoryBlameInformation = this._cache.get(repository) ?? { + HEAD: commit, + blameInformation: new Map>() + } satisfies RepositoryBlameInformation; + + this._cache.set(repository, { + ...repositoryBlameInformation, + HEAD: commit + } satisfies RepositoryBlameInformation); + } + + deleteBlameInformation(repository: Repository, scheme?: string): boolean { + if (scheme === undefined) { + return this._cache.delete(repository); + } + + return this._cache.get(repository)?.blameInformation.delete(scheme) === true; + } + + getBlameInformation(repository: Repository, resource: Uri, commit: string): BlameInformation[] | undefined { + const blameInformationKey = this._getBlameInformationKey(resource, commit); + return this._cache.get(repository)?.blameInformation.get(resource.scheme)?.get(blameInformationKey); + } + + setBlameInformation(repository: Repository, resource: Uri, commit: string, blameInformation: BlameInformation[]): void { + if (!repository.HEAD?.commit) { + return; + } + + if (!this._cache.has(repository)) { + this._cache.set(repository, { + HEAD: repository.HEAD.commit, + blameInformation: new Map>() + } satisfies RepositoryBlameInformation); + } + + const repositoryBlameInformation = this._cache.get(repository)!; + if (!repositoryBlameInformation.blameInformation.has(resource.scheme)) { + repositoryBlameInformation.blameInformation.set(resource.scheme, new Map()); + } + + const resourceSchemeBlameInformation = repositoryBlameInformation.blameInformation.get(resource.scheme)!; + resourceSchemeBlameInformation.set(this._getBlameInformationKey(resource, commit), blameInformation); + } + + private _getBlameInformationKey(resource: Uri, commit: string): string { + return `${commit}:${resource.toString()}`; + } +} + export class GitBlameController { private readonly _onDidChangeBlameInformation = new EventEmitter(); @@ -70,7 +166,7 @@ export class GitBlameController { readonly textEditorBlameInformation = new Map(); - private readonly _repositoryBlameInformation = new Map(); + private readonly _repositoryBlameCache = new GitBlameInformationCache(); private _repositoryDisposables = new Map(); private _disposables: IDisposable[] = []; @@ -116,24 +212,24 @@ export class GitBlameController { if (blameInformation.authorName) { markdownString.appendMarkdown(`$(account) **${blameInformation.authorName}**`); - if (blameInformation.date) { - const dateString = new Date(blameInformation.date).toLocaleString(undefined, { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' }); - markdownString.appendMarkdown(`, $(history) ${fromNow(blameInformation.date, true, true)} (${dateString})`); + if (blameInformation.authorDate) { + const dateString = new Date(blameInformation.authorDate).toLocaleString(undefined, { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' }); + markdownString.appendMarkdown(`, $(history) ${fromNow(blameInformation.authorDate, true, true)} (${dateString})`); } markdownString.appendMarkdown('\n\n'); } - markdownString.appendMarkdown(`${blameInformation.message}\n\n`); + markdownString.appendMarkdown(`${blameInformation.subject}\n\n`); markdownString.appendMarkdown(`---\n\n`); - markdownString.appendMarkdown(`[$(eye) View Commit](command:git.blameStatusBarItem.viewCommit?${encodeURIComponent(JSON.stringify([documentUri, blameInformation.id]))})`); + markdownString.appendMarkdown(`[$(eye) View Commit](command:git.blameStatusBarItem.viewCommit?${encodeURIComponent(JSON.stringify([documentUri, blameInformation.hash]))} "${l10n.t('View Commit')}")`); markdownString.appendMarkdown('  |  '); - markdownString.appendMarkdown(`[$(copy) ${blameInformation.id.substring(0, 8)}](command:git.blameStatusBarItem.copyContent?${encodeURIComponent(JSON.stringify(blameInformation.id))})`); + markdownString.appendMarkdown(`[$(copy) ${blameInformation.hash.substring(0, 8)}](command:git.blameStatusBarItem.copyContent?${encodeURIComponent(JSON.stringify(blameInformation.hash))} "${l10n.t('Copy Commit Hash')}")`); - if (blameInformation.message) { + if (blameInformation.subject) { markdownString.appendMarkdown('  '); - markdownString.appendMarkdown(`[$(copy) Message](command:git.blameStatusBarItem.copyContent?${encodeURIComponent(JSON.stringify(blameInformation.message))})`); + markdownString.appendMarkdown(`[$(copy) Subject](command:git.blameStatusBarItem.copyContent?${encodeURIComponent(JSON.stringify(blameInformation.subject))} "${l10n.t('Copy Commit Subject')}")`); } return markdownString; @@ -154,18 +250,20 @@ export class GitBlameController { } this._repositoryDisposables.delete(repository); - this._repositoryBlameInformation.delete(repository); + this._repositoryBlameCache.deleteBlameInformation(repository); } private _onDidRunGitStatus(repository: Repository): void { - const repositoryBlameInformation = this._repositoryBlameInformation.get(repository); - if (!repositoryBlameInformation) { + const repositoryHEAD = this._repositoryBlameCache.getRepositoryHEAD(repository); + if (!repositoryHEAD || !repository.HEAD?.commit) { return; } - // HEAD commit changed (remove blame information for the repository) - if (repositoryBlameInformation.commit !== repository.HEAD?.commit) { - this._repositoryBlameInformation.delete(repository); + // If the HEAD of the repository changed we can remove the cache + // entries for the "file" scheme as those entries are outdated. + if (repositoryHEAD !== repository.HEAD.commit) { + this._repositoryBlameCache.deleteBlameInformation(repository, 'file'); + this._repositoryBlameCache.setRepositoryHEAD(repository, repository.HEAD.commit); for (const textEditor of window.visibleTextEditors) { this._updateTextEditorBlameInformation(textEditor); @@ -173,60 +271,44 @@ export class GitBlameController { } } - private async _getBlameInformation( - resource: Uri, - ): Promise { + private async _getBlameInformation(resource: Uri, commit: string): Promise { const repository = this._model.getRepository(resource); - - if (!repository || !repository.HEAD?.commit) { + if (!repository) { return undefined; } - const repositoryBlameInformation = this._repositoryBlameInformation.get(repository) ?? { - commit: repository.HEAD.commit, - blameInformation: new Map() - } satisfies RepositoryBlameInformation; - - let resourceBlameInformation = repositoryBlameInformation.blameInformation.get(resource); - if (repositoryBlameInformation.commit === repository.HEAD.commit && resourceBlameInformation) { + const resourceBlameInformation = this._repositoryBlameCache.getBlameInformation(repository, resource, commit); + if (resourceBlameInformation) { return resourceBlameInformation; } - // Get blame information for the resource - resourceBlameInformation = await repository.blame2(resource.fsPath, repository.HEAD.commit) ?? []; - - this._repositoryBlameInformation.set(repository, { - ...repositoryBlameInformation, - blameInformation: repositoryBlameInformation.blameInformation.set( - resource, - resourceBlameInformation, - ), - }); + // Get blame information for the resource and cache it + const blameInformation = await repository.blame2(resource.fsPath, commit) ?? []; + this._repositoryBlameCache.setBlameInformation(repository, resource, commit, blameInformation); - return resourceBlameInformation; + return blameInformation; } @throttle private async _updateTextEditorBlameInformation(textEditor: TextEditor | undefined): Promise { - if (!textEditor?.diffInformation) { + if (!textEditor?.diffInformation || textEditor !== window.activeTextEditor) { + return; + } + + const repository = this._model.getRepository(textEditor.document.uri); + if (!repository || !repository.HEAD?.commit) { return; } // Working tree diff information const diffInformationWorkingTree = textEditor.diffInformation - .filter(diff => diff.original?.scheme === 'git') - .find(diff => { - const query = JSON.parse(diff.original!.query) as { ref: string }; - return query.ref !== 'HEAD'; - }); + .filter(diff => diff.original && isGitUri(diff.original)) + .find(diff => fromGitUri(diff.original!).ref !== 'HEAD'); // Working tree + index diff information const diffInformationWorkingTreeAndIndex = textEditor.diffInformation - .filter(diff => diff.original?.scheme === 'git') - .find(diff => { - const query = JSON.parse(diff.original!.query) as { ref: string }; - return query.ref === 'HEAD'; - }); + .filter(diff => diff.original && isGitUri(diff.original)) + .find(diff => fromGitUri(diff.original!).ref === 'HEAD'); // Working tree diff information is not present or it is stale if (!diffInformationWorkingTree || diffInformationWorkingTree.isStale) { @@ -244,7 +326,7 @@ export class GitBlameController { const diffInformation = diffInformationWorkingTreeAndIndex ?? diffInformationWorkingTree; // Git blame information - const resourceBlameInformation = await this._getBlameInformation(textEditor.document.uri); + const resourceBlameInformation = await this._getBlameInformation(textEditor.document.uri, repository.HEAD.commit); if (!resourceBlameInformation) { return; } @@ -315,14 +397,13 @@ class GitBlameEditorDecoration { } private _onDidChangeConfiguration(e: ConfigurationChangeEvent): void { - if (!e.affectsConfiguration("git.blame.editorDecoration.enabled")) { + if (!e.affectsConfiguration('git.blame.editorDecoration.enabled') && + !e.affectsConfiguration('git.blame.editorDecoration.template')) { return; } - const enabled = this._isEnabled(); - for (const textEditor of window.visibleTextEditors) { - if (enabled) { + if (this._getConfiguration().enabled) { this._updateDecorations(textEditor); } else { textEditor.setDecorations(this._decorationType, []); @@ -330,17 +411,30 @@ class GitBlameEditorDecoration { } } - private _isEnabled(): boolean { - const config = workspace.getConfiguration("git"); + private _getConfiguration(): { enabled: boolean; template: string } { + const config = workspace.getConfiguration('git'); + const enabled = config.get('blame.editorDecoration.enabled', false); + const template = config.get('blame.editorDecoration.template', '${subject}, ${authorName} (${authorDateAgo})'); - return config.get("blame.editorDecoration.enabled", false); + return { enabled, template }; } private _updateDecorations(textEditor: TextEditor): void { - if (!this._isEnabled()) { + const { enabled, template } = this._getConfiguration(); + if (!enabled) { return; } + // Clear decorations for the other editors + for (const editor of window.visibleTextEditors) { + if (editor === textEditor) { + continue; + } + + editor.setDecorations(this._decorationType, []); + } + + // Get blame information const blameInformation = this._controller.textEditorBlameInformation.get(textEditor); if (!blameInformation || textEditor.document.uri.scheme !== 'file') { textEditor.setDecorations(this._decorationType, []); @@ -348,10 +442,11 @@ class GitBlameEditorDecoration { return; } + // Set decorations for the editor const decorations = blameInformation.map(blame => { const contentText = typeof blame.blameInformation === 'string' ? blame.blameInformation - : `${blame.blameInformation.message ?? ''}, ${blame.blameInformation.authorName ?? ''} (${fromNow(blame.blameInformation.date ?? Date.now(), true, true)})`; + : formatBlameInformation(template, blame.blameInformation); const hoverMessage = this._controller.getBlameInformationHover(textEditor.document.uri, blame.blameInformation); return this._createDecoration(blame.lineNumber, contentText, hoverMessage); @@ -393,11 +488,12 @@ class GitBlameStatusBarItem { } private _onDidChangeConfiguration(e: ConfigurationChangeEvent): void { - if (!e.affectsConfiguration('git.blame.statusBarItem.enabled')) { + if (!e.affectsConfiguration('git.blame.statusBarItem.enabled') && + !e.affectsConfiguration('git.blame.statusBarItem.template')) { return; } - if (this._isEnabled()) { + if (this._getConfiguration().enabled) { if (window.activeTextEditor) { this._updateStatusBarItem(window.activeTextEditor); } @@ -408,7 +504,7 @@ class GitBlameStatusBarItem { } private _onDidChangeActiveTextEditor(): void { - if (!this._isEnabled()) { + if (!this._getConfiguration().enabled) { return; } @@ -419,13 +515,17 @@ class GitBlameStatusBarItem { } } - private _isEnabled(): boolean { + private _getConfiguration(): { enabled: boolean; template: string } { const config = workspace.getConfiguration('git'); - return config.get('blame.statusBarItem.enabled', false); + const enabled = config.get('blame.statusBarItem.enabled', false); + const template = config.get('blame.statusBarItem.template', '${authorName} (${authorDateAgo})'); + + return { enabled, template }; } private _updateStatusBarItem(textEditor: TextEditor): void { - if (!this._isEnabled() || textEditor !== window.activeTextEditor) { + const { enabled, template } = this._getConfiguration(); + if (!enabled || textEditor !== window.activeTextEditor) { return; } @@ -446,12 +546,12 @@ class GitBlameStatusBarItem { this._statusBarItem.tooltip = this._controller.getBlameInformationHover(textEditor.document.uri, blameInformation[0].blameInformation); this._statusBarItem.command = undefined; } else { - this._statusBarItem.text = `$(git-commit) ${blameInformation[0].blameInformation.authorName ?? ''} (${fromNow(blameInformation[0].blameInformation.date ?? new Date(), true, true)})`; + this._statusBarItem.text = formatBlameInformation(template, blameInformation[0].blameInformation); this._statusBarItem.tooltip = this._controller.getBlameInformationHover(textEditor.document.uri, blameInformation[0].blameInformation); this._statusBarItem.command = { title: l10n.t('View Commit'), command: 'git.blameStatusBarItem.viewCommit', - arguments: [textEditor.document.uri, blameInformation[0].blameInformation.id] + arguments: [textEditor.document.uri, blameInformation[0].blameInformation.hash] } satisfies Command; } diff --git a/extensions/git/Source/git.ts b/extensions/git/Source/git.ts index e8545cbc2a999..20da51246806b 100644 --- a/extensions/git/Source/git.ts +++ b/extensions/git/Source/git.ts @@ -1426,11 +1426,11 @@ function parseGitChanges(repositoryRoot: string, raw: string): Change[] { } export interface BlameInformation { - readonly id: string; - readonly date?: number; - readonly message?: string; + readonly hash: string; + readonly subject?: string; readonly authorName?: string; readonly authorEmail?: string; + readonly authorDate?: number; readonly ranges: { readonly startLineNumber: number; readonly endLineNumber: number; @@ -1498,12 +1498,7 @@ function parseGitBlame(data: string): BlameInformation[] { blameInformation.set(commitHash, existingCommit); } else { blameInformation.set(commitHash, { - id: commitHash, - authorName, - authorEmail, - date: authorTime, - message, - ranges: [{ startLineNumber, endLineNumber }], + hash: commitHash, authorName, authorEmail, authorDate: authorTime, subject: message, ranges: [{ startLineNumber, endLineNumber }] }); } diff --git a/extensions/git/package.json b/extensions/git/package.json index 9d8c0c7929e90..a3055e82dad84 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1072,6 +1072,15 @@ ], "type": "boolean" }, + "git.blame.editorDecoration.template": { + "default": "${subject}, ${authorName} (${authorDateAgo})", + "markdownDescription": "%config.blameEditorDecoration.template%", + "scope": "resource", + "tags": [ + "experimental" + ], + "type": "string" + }, "git.blame.statusBarItem.enabled": { "default": false, "markdownDescription": "%config.blameStatusBarItem.enabled%", @@ -1081,6 +1090,15 @@ ], "type": "boolean" }, + "git.blame.statusBarItem.template": { + "default": "$(git-commit) ${authorName} (${authorDateAgo})", + "markdownDescription": "%config.blameStatusBarItem.template%", + "scope": "resource", + "tags": [ + "experimental" + ], + "type": "string" + }, "git.branchPrefix": { "default": "", "description": "%config.branchPrefix%", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 389c90593467c..1f6775a2da58a 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -276,8 +276,10 @@ "config.publishBeforeContinueOn.never": "Never publish unpublished Git state when using Continue Working On from a Git repository", "config.publishBeforeContinueOn.prompt": "Prompt to publish unpublished Git state when using Continue Working On from a Git repository", "config.similarityThreshold": "Controls the threshold of the similarity index (the amount of additions/deletions compared to the file's size) for changes in a pair of added/deleted files to be considered a rename. **Note:** Requires Git version `2.18.0` or later.", - "config.blameEditorDecoration.enabled": "Controls whether to show git blame information in the editor using editor decorations.", - "config.blameStatusBarItem.enabled": "Controls whether to show git blame information in the status bar.", + "config.blameEditorDecoration.enabled": "Controls whether to show blame information in the editor using editor decorations.", + "config.blameEditorDecoration.template": "Template for the blame information editor decoration. Supported variables:\n\n* `hash`: Commit hash\n\n* `hashShort`: First 8 characters of the commit hash\n\n* `subject`: First line of the commit message\n\n* `authorName`: Author name\n\n* `authorEmail`: Author email\n\n* `authorDate`: Author date\n\n* `authorDateAgo`: Time difference between now and the author date\n\n", + "config.blameStatusBarItem.enabled": "Controls whether to show blame information in the status bar.", + "config.blameStatusBarItem.template": "Template for the blame information status bar item. Supported variables:\n\n* `hash`: Commit hash\n\n* `hashShort`: First 8 characters of the commit hash\n\n* `subject`: First line of the commit message\n\n* `authorName`: Author name\n\n* `authorEmail`: Author email\n\n* `authorDate`: Author date\n\n* `authorDateAgo`: Time difference between now and the author date\n\n", "submenu.explorer": "Git", "submenu.commit": "Commit", "submenu.commit.amend": "Amend", diff --git a/extensions/package.json b/extensions/package.json index fa8e15e6bd3da..1ce33d8070d1f 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -2,7 +2,7 @@ "dependencies": {}, "description": "Dependencies shared by all extensions", "devDependencies": { - "esbuild": "0.24.0" + "esbuild": "0.23.0" }, "name": "vscode-extensions", "overrides": { diff --git a/extensions/terminal-suggest/Source/terminalSuggestMain.ts b/extensions/terminal-suggest/Source/terminalSuggestMain.ts index 1ff88ed9eb6bf..a6e03d65dcd65 100644 --- a/extensions/terminal-suggest/Source/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/Source/terminalSuggestMain.ts @@ -14,6 +14,8 @@ import cdSpec from './completions/cd'; let cachedAvailableCommands: Set | undefined; let cachedBuiltinCommands: Map | undefined; +export const availableSpecs = [codeCompletionSpec, codeInsidersCompletionSpec, cdSpec]; + function getBuiltinCommands(shell: string): string[] | undefined { try { const shellType = path.basename(shell); @@ -89,8 +91,7 @@ export async function activate(context: vscode.ExtensionContext) { const items: vscode.TerminalCompletionItem[] = []; const prefix = getPrefix(terminalContext.commandLine, terminalContext.cursorPosition); - const specs = [codeCompletionSpec, codeInsidersCompletionSpec, cdSpec]; - const specCompletions = await getCompletionItemsFromSpecs(specs, terminalContext, new Set(commands), prefix, token); + const specCompletions = await getCompletionItemsFromSpecs(availableSpecs, terminalContext, commands, prefix, token); items.push(...specCompletions.items); let filesRequested = specCompletions.filesRequested; @@ -98,7 +99,7 @@ export async function activate(context: vscode.ExtensionContext) { if (!specCompletions.specificSuggestionsProvided) { for (const command of commands) { - if (command.startsWith(prefix)) { + if (command.startsWith(prefix) && !items.find(item => item.label === command)) { items.push(createCompletionItem(terminalContext.cursorPosition, prefix, command)); } } @@ -214,7 +215,7 @@ export function asArray(x: T | T[]): T[] { return Array.isArray(x) ? x : [x]; } -function getCompletionItemsFromSpecs(specs: Fig.Spec[], terminalContext: { commandLine: string; cursorPosition: number }, availableCommands: Set, prefix: string, token: vscode.CancellationToken): { items: vscode.TerminalCompletionItem[]; filesRequested: boolean; foldersRequested: boolean; specificSuggestionsProvided: boolean } { +export function getCompletionItemsFromSpecs(specs: Fig.Spec[], terminalContext: { commandLine: string; cursorPosition: number }, availableCommands: string[], prefix: string, token?: vscode.CancellationToken): { items: vscode.TerminalCompletionItem[]; filesRequested: boolean; foldersRequested: boolean; specificSuggestionsProvided: boolean } { const items: vscode.TerminalCompletionItem[] = []; let filesRequested = false; let foldersRequested = false; @@ -224,7 +225,21 @@ function getCompletionItemsFromSpecs(specs: Fig.Spec[], terminalContext: { comma continue; } for (const specLabel of specLabels) { - if (!availableCommands.has(specLabel) || token.isCancellationRequested || !terminalContext.commandLine.startsWith(specLabel)) { + if (!availableCommands.includes(specLabel) || (token && token?.isCancellationRequested)) { + continue; + } + // + if ( + // If the prompt is empty + !terminalContext.commandLine + // or the prefix matches the command and the prefix is not equal to the command + || !!prefix && specLabel.startsWith(prefix) && specLabel !== prefix + ) { + // push it to the completion items + items.push(createCompletionItem(terminalContext.cursorPosition, prefix, specLabel)); + } + if (!terminalContext.commandLine.startsWith(specLabel)) { + // the spec label is not the first word in the command line, so do not provide options or args continue; } const precedingText = terminalContext.commandLine.slice(0, terminalContext.cursorPosition + 1); @@ -235,7 +250,7 @@ function getCompletionItemsFromSpecs(specs: Fig.Spec[], terminalContext: { comma continue; } for (const optionLabel of optionLabels) { - if (optionLabel.startsWith(prefix) || (prefix.length > specLabel.length && prefix.trim() === specLabel)) { + if (!items.find(i => i.label === optionLabel) && optionLabel.startsWith(prefix) || (prefix.length > specLabel.length && prefix.trim() === specLabel)) { items.push(createCompletionItem(terminalContext.cursorPosition, prefix, optionLabel, option.description, false, vscode.TerminalCompletionItemKind.Flag)); } const expectedText = `${specLabel} ${optionLabel} `; @@ -248,13 +263,8 @@ function getCompletionItemsFromSpecs(specs: Fig.Spec[], terminalContext: { comma if (!argsCompletions) { continue; } - if (argsCompletions.specificSuggestionsProvided) { - // prevents the list from containing a bunch of other stuff - return argsCompletions; - } - items.push(...argsCompletions.items); - filesRequested = filesRequested || argsCompletions.filesRequested; - foldersRequested = foldersRequested || argsCompletions.foldersRequested; + // return early so that we don't show the other completions + return argsCompletions; } } } @@ -307,7 +317,10 @@ function getCompletionItemsFromArgs(args: Fig.SingleOrArray | undefined } for (const suggestionLabel of suggestionLabels) { - if (suggestionLabel && suggestionLabel.startsWith(currentPrefix.trim())) { + if (items.find(i => i.label === suggestionLabel)) { + continue; + } + if (suggestionLabel && suggestionLabel.startsWith(currentPrefix.trim()) && suggestionLabel !== currentPrefix.trim()) { const hasSpaceBeforeCursor = terminalContext.commandLine[terminalContext.cursorPosition - 1] === ' '; // prefix will be '' if there is a space before the cursor const description = typeof suggestion !== 'string' ? suggestion.description : ''; diff --git a/extensions/terminal-suggest/package.json b/extensions/terminal-suggest/package.json index 29a78dea937c6..be533d1a4fb08 100644 --- a/extensions/terminal-suggest/package.json +++ b/extensions/terminal-suggest/package.json @@ -11,6 +11,6 @@ "main": "./out/terminalSuggestMain", "name": "terminal-suggest", "scripts": { - "compile": "npx gulp compile-extension:npm" + "compile": "npx gulp compile-extension:terminal-suggest" } } diff --git a/extensions/terminal-suggest/tsconfig.json b/extensions/terminal-suggest/tsconfig.json index 0fc7ab5f53c92..151a29616bb23 100644 --- a/extensions/terminal-suggest/tsconfig.json +++ b/extensions/terminal-suggest/tsconfig.json @@ -14,6 +14,7 @@ }, "include": [ "src/**/*", + "src/completions/index.d.ts", "../../src/vscode-dts/vscode.d.ts", "../../src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts" ] diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index 35b358ae168f3..8900648030899 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -70,6 +70,12 @@ echo npm run test-extension -- -l vscode-colorize-tests kill_app +echo +echo "### Terminal Suggest tests" +echo +npm run test-extension -- -l terminal-suggest --enable-proposed-api=vscode.vscode-api-tests +kill_app + echo echo "### TypeScript tests" echo